mirror of
https://github.com/PowerShell/PowerShell
synced 2026-06-08 12:12:50 +00:00
Merge pull request #858 from PowerShell/options
Re-write host code command parser engine to support both Linux and Windows style options. Also, add support for -noninteractive and -encodedcommand.
This commit is contained in:
@@ -16,7 +16,7 @@ namespace Microsoft.PowerShell.CoreConsoleHost
|
||||
public MyHost(Listener Listener)
|
||||
{
|
||||
this.Listener = Listener;
|
||||
this.myHostUserInterface = new MyHostUserInterface(Listener.HasUI);
|
||||
this.myHostUserInterface = new MyHostUserInterface(Listener.HasUI, Listener.Interactive);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -14,6 +14,30 @@ namespace Microsoft.PowerShell.CoreConsoleHost
|
||||
|
||||
public static class Program
|
||||
{
|
||||
private enum Options {Help, File, Command, NoProfile, NonInteractive, EncodedCommand, Invalid};
|
||||
private class CommandOptions
|
||||
{
|
||||
public Options opt;
|
||||
public string optString;
|
||||
|
||||
public CommandOptions(Options o, string s)
|
||||
{
|
||||
opt = o;
|
||||
optString = s;
|
||||
}
|
||||
}
|
||||
|
||||
private static CommandOptions[] commandOptions = new CommandOptions[]
|
||||
{
|
||||
new CommandOptions(Options.Help, "help"),
|
||||
new CommandOptions(Options.Help, "?"),
|
||||
new CommandOptions(Options.File, "file"),
|
||||
new CommandOptions(Options.Command, "command"),
|
||||
new CommandOptions(Options.NoProfile, "noprofile"),
|
||||
new CommandOptions(Options.NonInteractive, "noninteractive"),
|
||||
new CommandOptions(Options.EncodedCommand, "encodedcommand")
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Creates and initiates the listener instance.
|
||||
/// </summary>
|
||||
@@ -25,9 +49,11 @@ namespace Microsoft.PowerShell.CoreConsoleHost
|
||||
// here eliminates the need for a custom native host.
|
||||
PowerShellAssemblyLoadContextInitializer.SetPowerShellAssemblyLoadContext(string.Empty);
|
||||
|
||||
|
||||
// Argument parsing
|
||||
string initialScript = null;
|
||||
bool loadProfiles = true;
|
||||
bool interactive = true;
|
||||
|
||||
if (args.Length > 0)
|
||||
{
|
||||
@@ -37,15 +63,33 @@ namespace Microsoft.PowerShell.CoreConsoleHost
|
||||
bool hasNext = (i+1) < args.Length;
|
||||
string nextArg = hasNext ? args[i+1] : string.Empty;
|
||||
|
||||
// --help was specified
|
||||
if (arg == "--help" || arg == "-h")
|
||||
// options start with "-" or "--"
|
||||
|
||||
string option;
|
||||
if (arg.StartsWith("-"))
|
||||
{
|
||||
Console.WriteLine(@"
|
||||
usage: powershell[.exe] [ (--help | -h) ]
|
||||
if (arg.StartsWith("--"))
|
||||
{
|
||||
option = arg.Remove(0, 2);
|
||||
}
|
||||
else
|
||||
{
|
||||
option = arg.Remove(0, 1);
|
||||
}
|
||||
|
||||
Options bestOption = FindOption(option);
|
||||
|
||||
switch (bestOption)
|
||||
{
|
||||
case Options.Help:
|
||||
Console.WriteLine(@"
|
||||
usage: powershell[.exe] [ (--help | -h | -?) ]
|
||||
[ (--file | -f) <filePath> ]
|
||||
[ <script>.ps1 ]
|
||||
[ (--command | -c) <string> ]
|
||||
[ --noprofile ]
|
||||
[ --noprofile | -nop ]
|
||||
[ --noninteractive | -non ]
|
||||
[ (--encodedcommand | -e) <base64Command> ]
|
||||
|
||||
SYNOPSIS
|
||||
|
||||
@@ -65,46 +109,63 @@ OPTIONS
|
||||
(--command | -c) <string>
|
||||
Will execute given string as a PowerShell script.
|
||||
|
||||
--noprofile
|
||||
(--noprofile | -nop)
|
||||
Disables parsing of PowerShell profiles.
|
||||
|
||||
(--help | -h)
|
||||
(--help | -h | -?)
|
||||
Prints this text.
|
||||
|
||||
(--noninteractive | -non)
|
||||
Does not present an interactive prompt to the user.
|
||||
|
||||
(--encodedcommand | -e) <base64Command>
|
||||
Accepts a base-64-encoded string version of a command.
|
||||
");
|
||||
return;
|
||||
}
|
||||
else if (arg == "--noprofile")
|
||||
{
|
||||
loadProfiles = false;
|
||||
}
|
||||
// lone argument is a script
|
||||
else if (!hasNext && arg.EndsWith(".ps1"))
|
||||
{
|
||||
initialScript = Path.GetFullPath(arg);
|
||||
}
|
||||
// lone argument is an inline script
|
||||
else if (!hasNext)
|
||||
{
|
||||
initialScript = arg;
|
||||
}
|
||||
// --file <filePath> was specified
|
||||
else if (hasNext && (arg == "--file" || arg == "-f"))
|
||||
{
|
||||
initialScript = Path.GetFullPath(nextArg);
|
||||
++i;
|
||||
}
|
||||
// --command <string> was specified
|
||||
else if (hasNext && (arg == "--command" || arg == "-c"))
|
||||
{
|
||||
if (nextArg == "-")
|
||||
{
|
||||
initialScript = "\"TODO: read stdin using Console.OpenStandardInput\"";
|
||||
return;
|
||||
|
||||
case Options.NoProfile:
|
||||
loadProfiles = false;
|
||||
break;
|
||||
case Options.File:
|
||||
initialScript = Path.GetFullPath(nextArg);
|
||||
++i;
|
||||
break;
|
||||
case Options.Command:
|
||||
if (nextArg == "-")
|
||||
{
|
||||
initialScript = "\"TODO: read stdin using Console.OpenStandardInput\"";
|
||||
}
|
||||
else
|
||||
{
|
||||
initialScript = nextArg;
|
||||
}
|
||||
++i;
|
||||
break;
|
||||
case Options.NonInteractive:
|
||||
interactive = false;
|
||||
break;
|
||||
case Options.EncodedCommand:
|
||||
byte[] data = Convert.FromBase64String(nextArg);
|
||||
initialScript = Encoding.UTF8.GetString(data);
|
||||
++i;
|
||||
break;
|
||||
default:
|
||||
Console.WriteLine("Invalid command-line option: {0}. Use '-help' option to get a list of valid options.", arg);
|
||||
return;
|
||||
}
|
||||
else
|
||||
}
|
||||
else // not an option parameter
|
||||
{
|
||||
// lone argument is a script
|
||||
if (!hasNext && arg.EndsWith(".ps1"))
|
||||
{
|
||||
initialScript = nextArg;
|
||||
initialScript = Path.GetFullPath(arg);
|
||||
}
|
||||
// lone argument is an inline script
|
||||
else if (!hasNext)
|
||||
{
|
||||
initialScript = arg;
|
||||
}
|
||||
++i;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -114,7 +175,7 @@ OPTIONS
|
||||
ConsoleColor InitialBackgroundColor = Console.BackgroundColor;
|
||||
|
||||
// Create the listener and run it
|
||||
Listener listener = new Listener(initialScript, loadProfiles);
|
||||
Listener listener = new Listener(initialScript, loadProfiles, interactive);
|
||||
|
||||
// only run if there was no script file passed in
|
||||
if (initialScript == null)
|
||||
@@ -129,6 +190,23 @@ OPTIONS
|
||||
// The exit code is set in the host by the MyHost.SetShouldExit() method.
|
||||
System.Environment.Exit(listener.ExitCode);
|
||||
}
|
||||
|
||||
private static Options FindOption(string option)
|
||||
{
|
||||
int matches = 0;
|
||||
int index = -1;
|
||||
|
||||
for (int i=0; i<commandOptions.Length; ++i)
|
||||
{
|
||||
if (commandOptions[i].optString.StartsWith(option, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
matches++;
|
||||
index = i;
|
||||
}
|
||||
}
|
||||
|
||||
return (matches == 1) ? commandOptions[index].opt : Options.Invalid;
|
||||
}
|
||||
}
|
||||
|
||||
internal class Listener
|
||||
@@ -205,13 +283,19 @@ OPTIONS
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether UI should exit.
|
||||
/// Gets or sets a value indicating whether UI exists (display prompt)
|
||||
/// </summary>
|
||||
public bool HasUI;
|
||||
|
||||
public Listener(string initialScript, bool loadProfiles)
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether session is interactive (allow popup)
|
||||
/// </summary>
|
||||
public bool Interactive;
|
||||
|
||||
public Listener(string initialScript, bool loadProfiles, bool interactive)
|
||||
{
|
||||
HasUI = (initialScript == null) ? true : false;
|
||||
this.HasUI = (initialScript == null) ? true : false;
|
||||
this.Interactive = interactive;
|
||||
|
||||
// Create the host and runspace instances for this interpreter.
|
||||
// Note that this application does not support console files so
|
||||
|
||||
@@ -20,7 +20,7 @@ namespace Microsoft.PowerShell.CoreConsoleHost
|
||||
/// <summary>
|
||||
/// Public constructor
|
||||
/// </summary>
|
||||
public MyHostUserInterface(bool hasUI)
|
||||
public MyHostUserInterface(bool hasUI, bool interactive)
|
||||
{
|
||||
myRawUi = (hasUI) ? new MyRawUserInterface() : null;
|
||||
}
|
||||
@@ -39,6 +39,11 @@ namespace Microsoft.PowerShell.CoreConsoleHost
|
||||
get { return this.myRawUi; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether user pop-ups are allowed
|
||||
/// </summary>
|
||||
public bool Interactive;
|
||||
|
||||
/// <summary>
|
||||
/// Prompts the user for input.
|
||||
/// <param name="caption">The caption or title of the prompt.</param>
|
||||
@@ -52,26 +57,33 @@ namespace Microsoft.PowerShell.CoreConsoleHost
|
||||
string message,
|
||||
Collection<FieldDescription> descriptions)
|
||||
{
|
||||
this.Write(
|
||||
ConsoleColor.White,
|
||||
Console.BackgroundColor,
|
||||
caption + System.Environment.NewLine + message + " ");
|
||||
Dictionary<string, PSObject> results =
|
||||
new Dictionary<string, PSObject>();
|
||||
foreach (FieldDescription fd in descriptions)
|
||||
if (Interactive)
|
||||
{
|
||||
string[] label = GetHotkeyAndLabel(fd.Label);
|
||||
this.WriteLine(label[1]);
|
||||
string userData = Console.ReadLine();
|
||||
if (userData == null)
|
||||
this.Write(
|
||||
ConsoleColor.White,
|
||||
Console.BackgroundColor,
|
||||
caption + System.Environment.NewLine + message + " ");
|
||||
Dictionary<string, PSObject> results =
|
||||
new Dictionary<string, PSObject>();
|
||||
foreach (FieldDescription fd in descriptions)
|
||||
{
|
||||
return null;
|
||||
string[] label = GetHotkeyAndLabel(fd.Label);
|
||||
this.WriteLine(label[1]);
|
||||
string userData = Console.ReadLine();
|
||||
if (userData == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
results[fd.Name] = PSObject.AsPSObject(userData);
|
||||
}
|
||||
|
||||
results[fd.Name] = PSObject.AsPSObject(userData);
|
||||
return results;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new PSInvalidOperationException("Cannot prompt user when invoked with --noninteractive option.");
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -93,57 +105,64 @@ namespace Microsoft.PowerShell.CoreConsoleHost
|
||||
Collection<ChoiceDescription> choices,
|
||||
int defaultChoice)
|
||||
{
|
||||
// Write the caption and message strings in Blue.
|
||||
this.WriteLine(
|
||||
ConsoleColor.Blue,
|
||||
Console.BackgroundColor,
|
||||
caption + System.Environment.NewLine + message + System.Environment.NewLine);
|
||||
|
||||
// Convert the choice collection into something that is
|
||||
// easier to work with. See the BuildHotkeysAndPlainLabels
|
||||
// method for details.
|
||||
string[,] promptData = BuildHotkeysAndPlainLabels(choices);
|
||||
|
||||
// Format the overall choice prompt string to display.
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int element = 0; element < choices.Count; element++)
|
||||
if (Interactive)
|
||||
{
|
||||
// Write the caption and message strings in Blue.
|
||||
this.WriteLine(
|
||||
ConsoleColor.Blue,
|
||||
Console.BackgroundColor,
|
||||
caption + System.Environment.NewLine + message + System.Environment.NewLine);
|
||||
|
||||
// Convert the choice collection into something that is
|
||||
// easier to work with. See the BuildHotkeysAndPlainLabels
|
||||
// method for details.
|
||||
string[,] promptData = BuildHotkeysAndPlainLabels(choices);
|
||||
|
||||
// Format the overall choice prompt string to display.
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int element = 0; element < choices.Count; element++)
|
||||
{
|
||||
sb.Append(String.Format(
|
||||
CultureInfo.CurrentCulture,
|
||||
"|{0}> {1} ",
|
||||
promptData[0, element],
|
||||
promptData[1, element]));
|
||||
}
|
||||
|
||||
sb.Append(String.Format(
|
||||
CultureInfo.CurrentCulture,
|
||||
"|{0}> {1} ",
|
||||
promptData[0, element],
|
||||
promptData[1, element]));
|
||||
}
|
||||
CultureInfo.CurrentCulture,
|
||||
"[Default is ({0}]",
|
||||
promptData[0, defaultChoice]));
|
||||
|
||||
sb.Append(String.Format(
|
||||
CultureInfo.CurrentCulture,
|
||||
"[Default is ({0}]",
|
||||
promptData[0, defaultChoice]));
|
||||
|
||||
// Read prompts until a match is made, the default is
|
||||
// chosen, or the loop is interrupted with ctrl-C.
|
||||
while (true)
|
||||
{
|
||||
this.WriteLine(ConsoleColor.Cyan, Console.BackgroundColor, sb.ToString());
|
||||
string data = Console.ReadLine().Trim().ToUpper();
|
||||
|
||||
// If the choice string was empty, use the default selection.
|
||||
if (data.Length == 0)
|
||||
// Read prompts until a match is made, the default is
|
||||
// chosen, or the loop is interrupted with ctrl-C.
|
||||
while (true)
|
||||
{
|
||||
return defaultChoice;
|
||||
}
|
||||
this.WriteLine(ConsoleColor.Cyan, Console.BackgroundColor, sb.ToString());
|
||||
string data = Console.ReadLine().Trim().ToUpper();
|
||||
|
||||
// See if the selection matched and return the
|
||||
// corresponding index if it did.
|
||||
for (int i = 0; i < choices.Count; i++)
|
||||
{
|
||||
if (promptData[0, i] == data)
|
||||
// If the choice string was empty, use the default selection.
|
||||
if (data.Length == 0)
|
||||
{
|
||||
return i;
|
||||
return defaultChoice;
|
||||
}
|
||||
}
|
||||
|
||||
this.WriteErrorLine("Invalid choice: " + data);
|
||||
// See if the selection matched and return the
|
||||
// corresponding index if it did.
|
||||
for (int i = 0; i < choices.Count; i++)
|
||||
{
|
||||
if (promptData[0, i] == data)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
this.WriteErrorLine("Invalid choice: " + data);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new PSInvalidOperationException("Cannot prompt user when invoked with --noninteractive option.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,87 +186,94 @@ namespace Microsoft.PowerShell.CoreConsoleHost
|
||||
Collection<ChoiceDescription> choices,
|
||||
IEnumerable<int> defaultChoices)
|
||||
{
|
||||
// Write the caption and message strings in Blue.
|
||||
this.WriteLine(
|
||||
ConsoleColor.Blue,
|
||||
Console.BackgroundColor,
|
||||
caption + System.Environment.NewLine + message + System.Environment.NewLine);
|
||||
|
||||
// Convert the choice collection into something that is
|
||||
// easier to work with. See the BuildHotkeysAndPlainLabels
|
||||
// method for details.
|
||||
string[,] promptData = BuildHotkeysAndPlainLabels(choices);
|
||||
|
||||
// Format the overall choice prompt string to display.
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int element = 0; element < choices.Count; element++)
|
||||
if (Interactive)
|
||||
{
|
||||
sb.Append(String.Format(
|
||||
CultureInfo.CurrentCulture,
|
||||
"|{0}> {1} ",
|
||||
promptData[0, element],
|
||||
promptData[1, element]));
|
||||
}
|
||||
// Write the caption and message strings in Blue.
|
||||
this.WriteLine(
|
||||
ConsoleColor.Blue,
|
||||
Console.BackgroundColor,
|
||||
caption + System.Environment.NewLine + message + System.Environment.NewLine);
|
||||
|
||||
Collection<int> defaultResults = new Collection<int>();
|
||||
if (defaultChoices != null)
|
||||
{
|
||||
int countDefaults = 0;
|
||||
foreach (int defaultChoice in defaultChoices)
|
||||
// Convert the choice collection into something that is
|
||||
// easier to work with. See the BuildHotkeysAndPlainLabels
|
||||
// method for details.
|
||||
string[,] promptData = BuildHotkeysAndPlainLabels(choices);
|
||||
|
||||
// Format the overall choice prompt string to display.
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int element = 0; element < choices.Count; element++)
|
||||
{
|
||||
++countDefaults;
|
||||
defaultResults.Add(defaultChoice);
|
||||
sb.Append(String.Format(
|
||||
CultureInfo.CurrentCulture,
|
||||
"|{0}> {1} ",
|
||||
promptData[0, element],
|
||||
promptData[1, element]));
|
||||
}
|
||||
|
||||
if (countDefaults != 0)
|
||||
Collection<int> defaultResults = new Collection<int>();
|
||||
if (defaultChoices != null)
|
||||
{
|
||||
sb.Append(countDefaults == 1 ? "[Default choice is " : "[Default choices are ");
|
||||
int countDefaults = 0;
|
||||
foreach (int defaultChoice in defaultChoices)
|
||||
{
|
||||
sb.AppendFormat(
|
||||
CultureInfo.CurrentCulture,
|
||||
"\"{0}\",",
|
||||
promptData[0, defaultChoice]);
|
||||
++countDefaults;
|
||||
defaultResults.Add(defaultChoice);
|
||||
}
|
||||
|
||||
sb.Remove(sb.Length - 1, 1);
|
||||
sb.Append("]");
|
||||
if (countDefaults != 0)
|
||||
{
|
||||
sb.Append(countDefaults == 1 ? "[Default choice is " : "[Default choices are ");
|
||||
foreach (int defaultChoice in defaultChoices)
|
||||
{
|
||||
sb.AppendFormat(
|
||||
CultureInfo.CurrentCulture,
|
||||
"\"{0}\",",
|
||||
promptData[0, defaultChoice]);
|
||||
}
|
||||
|
||||
sb.Remove(sb.Length - 1, 1);
|
||||
sb.Append("]");
|
||||
}
|
||||
}
|
||||
|
||||
this.WriteLine(
|
||||
ConsoleColor.Cyan,
|
||||
Console.BackgroundColor,
|
||||
sb.ToString());
|
||||
// Read prompts until a match is made, the default is
|
||||
// chosen, or the loop is interrupted with ctrl-C.
|
||||
Collection<int> results = new Collection<int>();
|
||||
while (true)
|
||||
{
|
||||
ReadNext:
|
||||
string prompt = string.Format(CultureInfo.CurrentCulture, "Choice[{0}]:", results.Count);
|
||||
this.Write(ConsoleColor.Cyan, Console.BackgroundColor, prompt);
|
||||
string data = Console.ReadLine().Trim().ToUpper();
|
||||
|
||||
// If the choice string was empty, no more choices have been made.
|
||||
// If there were no choices made, return the defaults
|
||||
if (data.Length == 0)
|
||||
{
|
||||
return (results.Count == 0) ? defaultResults : results;
|
||||
}
|
||||
|
||||
// See if the selection matched and return the
|
||||
// corresponding index if it did.
|
||||
for (int i = 0; i < choices.Count; i++)
|
||||
{
|
||||
if (promptData[0, i] == data)
|
||||
{
|
||||
results.Add(i);
|
||||
goto ReadNext;
|
||||
}
|
||||
}
|
||||
|
||||
this.WriteErrorLine("Invalid choice: " + data);
|
||||
}
|
||||
}
|
||||
|
||||
this.WriteLine(
|
||||
ConsoleColor.Cyan,
|
||||
Console.BackgroundColor,
|
||||
sb.ToString());
|
||||
// Read prompts until a match is made, the default is
|
||||
// chosen, or the loop is interrupted with ctrl-C.
|
||||
Collection<int> results = new Collection<int>();
|
||||
while (true)
|
||||
else
|
||||
{
|
||||
ReadNext:
|
||||
string prompt = string.Format(CultureInfo.CurrentCulture, "Choice[{0}]:", results.Count);
|
||||
this.Write(ConsoleColor.Cyan, Console.BackgroundColor, prompt);
|
||||
string data = Console.ReadLine().Trim().ToUpper();
|
||||
|
||||
// If the choice string was empty, no more choices have been made.
|
||||
// If there were no choices made, return the defaults
|
||||
if (data.Length == 0)
|
||||
{
|
||||
return (results.Count == 0) ? defaultResults : results;
|
||||
}
|
||||
|
||||
// See if the selection matched and return the
|
||||
// corresponding index if it did.
|
||||
for (int i = 0; i < choices.Count; i++)
|
||||
{
|
||||
if (promptData[0, i] == data)
|
||||
{
|
||||
results.Add(i);
|
||||
goto ReadNext;
|
||||
}
|
||||
}
|
||||
|
||||
this.WriteErrorLine("Invalid choice: " + data);
|
||||
throw new PSInvalidOperationException("Cannot prompt user when invoked with --noninteractive option.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user