diff --git a/src/Microsoft.PowerShell.ConsoleHost/host/msh/CommandLineParameterParser.cs b/src/Microsoft.PowerShell.ConsoleHost/host/msh/CommandLineParameterParser.cs
index 92190c2d173..3e8d629d6ef 100644
--- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/CommandLineParameterParser.cs
+++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/CommandLineParameterParser.cs
@@ -1,6 +1,8 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
+#nullable enable
+
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
@@ -29,10 +31,8 @@ internal class NullHostUserInterface : PSHostUserInterface
///
/// RawUI.
///
- public override PSHostRawUserInterface RawUI
- {
- get { return null; }
- }
+ public override PSHostRawUserInterface? RawUI
+ => null;
///
/// Prompt.
@@ -42,9 +42,7 @@ public override PSHostRawUserInterface RawUI
///
///
public override Dictionary Prompt(string caption, string message, Collection descriptions)
- {
- throw new PSNotImplementedException();
- }
+ => throw new PSNotImplementedException();
///
/// PromptForChoice.
@@ -55,9 +53,7 @@ public override Dictionary Prompt(string caption, string messa
///
///
public override int PromptForChoice(string caption, string message, Collection choices, int defaultChoice)
- {
- throw new PSNotImplementedException();
- }
+ => throw new PSNotImplementedException();
///
/// PromptForCredential.
@@ -68,9 +64,7 @@ public override int PromptForChoice(string caption, string message, Collection
///
public override PSCredential PromptForCredential(string caption, string message, string userName, string targetName)
- {
- throw new PSNotImplementedException();
- }
+ => throw new PSNotImplementedException();
///
/// PromptForCredential.
@@ -83,27 +77,21 @@ public override PSCredential PromptForCredential(string caption, string message,
///
///
public override PSCredential PromptForCredential(string caption, string message, string userName, string targetName, PSCredentialTypes allowedCredentialTypes, PSCredentialUIOptions options)
- {
- throw new PSNotImplementedException();
- }
+ => throw new PSNotImplementedException();
///
/// ReadLine.
///
///
public override string ReadLine()
- {
- throw new PSNotImplementedException();
- }
+ => throw new PSNotImplementedException();
///
/// ReadLineAsSecureString.
///
///
public override SecureString ReadLineAsSecureString()
- {
- throw new PSNotImplementedException();
- }
+ => throw new PSNotImplementedException();
///
/// Write.
@@ -133,9 +121,7 @@ public override void WriteDebugLine(string message)
///
///
public override void WriteErrorLine(string value)
- {
- Console.Out.WriteLine(value);
- }
+ => Console.Out.WriteLine(value);
///
/// WriteLine.
@@ -172,7 +158,20 @@ internal class CommandLineParameterParser
private const int MaxPipePathLengthLinux = 108;
private const int MaxPipePathLengthMacOS = 104;
- internal static readonly string[] validParameters = {
+ internal static int MaxNameLength()
+ {
+ if (Platform.IsWindows)
+ {
+ return ushort.MaxValue;
+ }
+
+ int maxLength = Platform.IsLinux ? MaxPipePathLengthLinux : MaxPipePathLengthMacOS;
+ return maxLength - Path.GetTempPath().Length;
+ }
+
+ internal bool? TestHookConsoleInputRedirected;
+
+ private static readonly string[] s_validParameters = {
"sta",
"mta",
"command",
@@ -196,17 +195,17 @@ internal class CommandLineParameterParser
"workingdirectory"
};
- internal CommandLineParameterParser(PSHostUserInterface hostUI, string bannerText, string helpText)
+ [Conditional("DEBUG")]
+ private void AssertArgumentsParsed()
{
- if (hostUI == null)
+ if (!_dirty)
{
- throw new PSArgumentNullException(nameof(hostUI));
+ throw new InvalidOperationException("Parse has not been called yet");
}
+ }
- _hostUI = hostUI;
-
- _bannerText = bannerText;
- _helpText = helpText;
+ internal CommandLineParameterParser()
+ {
}
#region Internal properties
@@ -214,18 +213,25 @@ internal bool AbortStartup
{
get
{
- Dbg.Assert(_dirty, "Parse has not been called yet");
-
+ AssertArgumentsParsed();
return _abortStartup;
}
}
- internal string InitialCommand
+ internal string? SettingsFile
{
get
{
- Dbg.Assert(_dirty, "Parse has not been called yet");
+ AssertArgumentsParsed();
+ return _settingsFile;
+ }
+ }
+ internal string? InitialCommand
+ {
+ get
+ {
+ AssertArgumentsParsed();
return _commandLineCommand;
}
}
@@ -234,8 +240,7 @@ internal bool WasInitialCommandEncoded
{
get
{
- Dbg.Assert(_dirty, "Parse has not been called yet");
-
+ AssertArgumentsParsed();
return _wasCommandEncoded;
}
}
@@ -244,7 +249,7 @@ internal bool ShowBanner
{
get
{
- Dbg.Assert(_dirty, "Parse has not been called yet");
+ AssertArgumentsParsed();
return _showBanner;
}
}
@@ -253,8 +258,7 @@ internal bool NoExit
{
get
{
- Dbg.Assert(_dirty, "Parse has not been called yet");
-
+ AssertArgumentsParsed();
return _noExit;
}
}
@@ -263,8 +267,7 @@ internal bool SkipProfiles
{
get
{
- Dbg.Assert(_dirty, "Parse has not been called yet");
-
+ AssertArgumentsParsed();
return _skipUserInit;
}
}
@@ -273,8 +276,7 @@ internal uint ExitCode
{
get
{
- Dbg.Assert(_dirty, "Parse has not been called yet");
-
+ AssertArgumentsParsed();
return _exitCode;
}
}
@@ -283,8 +285,7 @@ internal bool ExplicitReadCommandsFromStdin
{
get
{
- Dbg.Assert(_dirty, "Parse has not been called yet");
-
+ AssertArgumentsParsed();
return _explicitReadCommandsFromStdin;
}
}
@@ -293,8 +294,7 @@ internal bool NoPrompt
{
get
{
- Dbg.Assert(_dirty, "Parse has not been called yet");
-
+ AssertArgumentsParsed();
return _noPrompt;
}
}
@@ -303,55 +303,107 @@ internal Collection Args
{
get
{
- Dbg.Assert(_dirty, "Parse has not been called yet");
-
+ AssertArgumentsParsed();
return _collectedArgs;
}
}
- internal string ConfigurationName
+ internal string? ConfigurationName
{
- get { return _configurationName; }
+ get
+ {
+ AssertArgumentsParsed();
+ return _configurationName;
+ }
+ set
+ {
+ if (!string.IsNullOrEmpty(value))
+ {
+ _configurationName = value;
+ }
+ }
}
internal bool SocketServerMode
{
get
{
+ AssertArgumentsParsed();
return _socketServerMode;
}
}
internal bool NamedPipeServerMode
{
- get { return _namedPipeServerMode; }
+ get
+ {
+ AssertArgumentsParsed();
+ return _namedPipeServerMode;
+ }
}
internal bool SSHServerMode
{
- get { return _sshServerMode; }
+ get
+ {
+ AssertArgumentsParsed();
+ return _sshServerMode;
+ }
}
internal bool ServerMode
{
get
{
+ AssertArgumentsParsed();
return _serverMode;
}
}
+ // Added for using in xUnit tests
+ internal string? ErrorMessage
+ {
+ get
+ {
+ AssertArgumentsParsed();
+ return _error;
+ }
+ }
+
+ // Added for using in xUnit tests
+ internal bool ShowShortHelp
+ {
+ get
+ {
+ AssertArgumentsParsed();
+ return _showHelp;
+ }
+ }
+
+ // Added for using in xUnit tests
+ internal bool ShowExtendedHelp
+ {
+ get
+ {
+ AssertArgumentsParsed();
+ return _showExtendedHelp;
+ }
+ }
+
internal bool ShowVersion
{
get
{
+ AssertArgumentsParsed();
return _showVersion;
}
}
- internal string CustomPipeName
+ internal string? CustomPipeName
{
get
{
+ AssertArgumentsParsed();
return _customPipeName;
}
}
@@ -360,8 +412,7 @@ internal Serialization.DataFormat OutputFormat
{
get
{
- Dbg.Assert(_dirty, "Parse has not been called yet");
-
+ AssertArgumentsParsed();
return _outFormat;
}
}
@@ -370,8 +421,7 @@ internal bool OutputFormatSpecified
{
get
{
- Dbg.Assert(_dirty, "Parse has not been called yet");
-
+ AssertArgumentsParsed();
return _outputFormatSpecified;
}
}
@@ -380,48 +430,70 @@ internal Serialization.DataFormat InputFormat
{
get
{
- Dbg.Assert(_dirty, "Parse has not been called yet");
+ AssertArgumentsParsed();
return _inFormat;
}
}
- internal string File
+ internal string? File
{
get
{
- Dbg.Assert(_dirty, "Parse has not been called yet");
+ AssertArgumentsParsed();
return _file;
}
}
- internal string ExecutionPolicy
+ internal string? ExecutionPolicy
{
get
{
- Dbg.Assert(_dirty, "Parse has not been called yet");
+ AssertArgumentsParsed();
return _executionPolicy;
}
}
+ internal bool StaMode
+ {
+ get
+ {
+ AssertArgumentsParsed();
+ if (_staMode.HasValue)
+ {
+ return _staMode.Value;
+ }
+ else
+ {
+ return true;
+ }
+ }
+ }
+
internal bool ThrowOnReadAndPrompt
{
get
{
+ AssertArgumentsParsed();
return _noInteractive;
}
}
internal bool NonInteractive
{
- get { return _noInteractive; }
+ get
+ {
+ AssertArgumentsParsed();
+ return _noInteractive;
+ }
}
- internal string WorkingDirectory
+ internal string? WorkingDirectory
{
get
{
+ AssertArgumentsParsed();
#if !UNIX
- if (_removeWorkingDirectoryTrailingCharacter && _workingDirectory.Length > 0)
+ if (_removeWorkingDirectoryTrailingCharacter && _workingDirectory?.Length > 0)
{
return _workingDirectory.Remove(_workingDirectory.Length - 1);
}
@@ -433,7 +505,11 @@ internal string WorkingDirectory
#if !UNIX
internal bool RemoveWorkingDirectoryTrailingCharacter
{
- get { return _removeWorkingDirectoryTrailingCharacter; }
+ get
+ {
+ AssertArgumentsParsed();
+ return _removeWorkingDirectoryTrailingCharacter;
+ }
}
#endif
@@ -449,57 +525,42 @@ internal bool RemoveWorkingDirectoryTrailingCharacter
///
/// The index in args to the argument following '-SettingFile'.
///
- ///
- /// Used to allow the helper to write errors to the console. If not supplied, no errors will be written.
- ///
///
/// Returns true if the argument was parsed successfully and false if not.
///
- private static bool TryParseSettingFileHelper(string[] args, int settingFileArgIndex, CommandLineParameterParser parser)
+ private bool TryParseSettingFileHelper(string[] args, int settingFileArgIndex)
{
if (settingFileArgIndex >= args.Length)
{
- if (parser != null)
- {
- parser.WriteCommandLineError(
- CommandLineParameterParserStrings.MissingSettingsFileArgument);
- }
-
+ SetCommandLineError(CommandLineParameterParserStrings.MissingSettingsFileArgument);
return false;
}
- string configFile = null;
+ string configFile;
try
{
configFile = NormalizeFilePath(args[settingFileArgIndex]);
}
catch (Exception ex)
{
- if (parser != null)
- {
- string error = string.Format(CultureInfo.CurrentCulture, CommandLineParameterParserStrings.InvalidSettingsFileArgument, args[settingFileArgIndex], ex.Message);
- parser.WriteCommandLineError(error);
- }
-
+ string error = string.Format(CultureInfo.CurrentCulture, CommandLineParameterParserStrings.InvalidSettingsFileArgument, args[settingFileArgIndex], ex.Message);
+ SetCommandLineError(error);
return false;
}
if (!System.IO.File.Exists(configFile))
{
- if (parser != null)
- {
- string error = string.Format(CultureInfo.CurrentCulture, CommandLineParameterParserStrings.SettingsFileNotExists, configFile);
- parser.WriteCommandLineError(error);
- }
-
+ string error = string.Format(CultureInfo.CurrentCulture, CommandLineParameterParserStrings.SettingsFileNotExists, configFile);
+ SetCommandLineError(error);
return false;
}
- PowerShellConfig.Instance.SetSystemConfigFilePath(configFile);
+ _settingsFile = configFile;
+
return true;
}
- private static string GetConfigurationNameFromGroupPolicy()
+ internal static string GetConfigurationNameFromGroupPolicy()
{
// Current user policy takes precedence.
var consoleSessionSetting = Utils.GetPolicySetting(Utils.CurrentUserThenSystemWideConfig);
@@ -508,43 +569,6 @@ private static string GetConfigurationNameFromGroupPolicy()
consoleSessionSetting.ConsoleSessionConfigurationName : string.Empty;
}
- ///
- /// Processes the command line parameters to ConsoleHost which must be parsed before the Host is created.
- /// Success to indicate that the program should continue running.
- ///
- ///
- /// The command line parameters to be processed.
- ///
- internal static void EarlyParse(string[] args)
- {
- if (args == null)
- {
- Dbg.Assert(args != null, "Argument 'args' to EarlyParseHelper should never be null");
- return;
- }
-
- bool noexitSeen = false;
- for (int i = 0; i < args.Length; ++i)
- {
- (string SwitchKey, bool ShouldBreak) switchKeyResults = GetSwitchKey(args, ref i, parser: null, ref noexitSeen);
- if (switchKeyResults.ShouldBreak)
- {
- break;
- }
-
- string switchKey = switchKeyResults.SwitchKey;
-
- if (!string.IsNullOrEmpty(switchKey) && MatchSwitch(switchKey, match: "settingsfile", smallestUnambiguousMatch: "settings"))
- {
- // parse setting file arg and don't write error as there is no host yet.
- if (!TryParseSettingFileHelper(args, ++i, parser: null))
- {
- break;
- }
- }
- }
- }
-
///
/// Gets the word in a switch from the current argument or parses a file.
/// For example -foo, /foo, or --foo would return 'foo'.
@@ -555,35 +579,29 @@ internal static void EarlyParse(string[] args)
///
/// The index in args to the argument to process.
///
- ///
- /// Used to parse files in the args. If not supplied, Files will not be parsed.
- ///
///
/// Used during parsing files.
///
///
/// Returns a Tuple:
- /// The first value is a String called SwitchKey with the word in a switch from the current argument or null.
- /// The second value is a bool called ShouldBreak, indicating if the parsing look should break.
+ /// The first value is a String called 'switchKey' with the word in a switch from the current argument or null.
+ /// The second value is a bool called 'shouldBreak', indicating if the parsing look should break.
///
- private static (string SwitchKey, bool ShouldBreak) GetSwitchKey(string[] args, ref int argIndex, CommandLineParameterParser parser, ref bool noexitSeen)
+ private (string switchKey, bool shouldBreak) GetSwitchKey(string[] args, ref int argIndex, ref bool noexitSeen)
{
string switchKey = args[argIndex].Trim().ToLowerInvariant();
if (string.IsNullOrEmpty(switchKey))
{
- return (SwitchKey: null, ShouldBreak: false);
+ return (switchKey: string.Empty, shouldBreak: false);
}
if (!CharExtensions.IsDash(switchKey[0]) && switchKey[0] != '/')
{
- // then its a file
- if (parser != null)
- {
- --argIndex;
- parser.ParseFile(args, ref argIndex, noexitSeen);
- }
+ // then it's a file
+ --argIndex;
+ ParseFile(args, ref argIndex, noexitSeen);
- return (SwitchKey: null, ShouldBreak: true);
+ return (switchKey: string.Empty, shouldBreak: true);
}
// chop off the first character so that we're agnostic wrt specifying / or -
@@ -596,66 +614,74 @@ private static (string SwitchKey, bool ShouldBreak) GetSwitchKey(string[] args,
switchKey = switchKey.Substring(1);
}
- return (SwitchKey: switchKey, ShouldBreak: false);
+ return (switchKey: switchKey, shouldBreak: false);
}
- private static string NormalizeFilePath(string path)
+ internal static string NormalizeFilePath(string path)
{
// Normalize slashes
- path = path.Replace(StringLiterals.AlternatePathSeparator,
- StringLiterals.DefaultPathSeparator);
+ path = path.Replace(
+ StringLiterals.AlternatePathSeparator,
+ StringLiterals.DefaultPathSeparator);
return Path.GetFullPath(path);
}
private static bool MatchSwitch(string switchKey, string match, string smallestUnambiguousMatch)
{
- Dbg.Assert(switchKey != null, "need a value");
Dbg.Assert(!string.IsNullOrEmpty(match), "need a value");
Dbg.Assert(match.Trim().ToLowerInvariant() == match, "match should be normalized to lowercase w/ no outside whitespace");
Dbg.Assert(smallestUnambiguousMatch.Trim().ToLowerInvariant() == smallestUnambiguousMatch, "match should be normalized to lowercase w/ no outside whitespace");
Dbg.Assert(match.Contains(smallestUnambiguousMatch), "sUM should be a substring of match");
- return (match.Trim().ToLowerInvariant().IndexOf(switchKey, StringComparison.Ordinal) == 0 &&
- switchKey.Length >= smallestUnambiguousMatch.Length);
+ return (switchKey.Length >= smallestUnambiguousMatch.Length
+ && match.IndexOf(switchKey, StringComparison.Ordinal) == 0);
}
#endregion
- private void ShowHelp()
+ private void ShowError(PSHostUserInterface hostUI)
{
- Dbg.Assert(_helpText != null, "_helpText should not be null");
- _hostUI.WriteLine(string.Empty);
- _hostUI.Write(_helpText);
- if (_showExtendedHelp)
+ if (_error != null)
{
- _hostUI.Write(ManagedEntranceStrings.ExtendedHelp);
+ hostUI.WriteErrorLine(_error);
}
-
- _hostUI.WriteLine(string.Empty);
}
- private void DisplayBanner()
+ private void ShowHelp(PSHostUserInterface hostUI, string? helpText)
{
- // If banner text is not supplied do nothing.
- if (!string.IsNullOrEmpty(_bannerText))
+ if (helpText is null)
{
- _hostUI.WriteLine(_bannerText);
- _hostUI.WriteLine();
+ return;
+ }
+
+ if (_showHelp)
+ {
+ hostUI.WriteLine();
+ hostUI.Write(helpText);
+ if (_showExtendedHelp)
+ {
+ hostUI.Write(ManagedEntranceStrings.ExtendedHelp);
+ }
+
+ hostUI.WriteLine();
}
}
- internal bool StaMode
+ private void DisplayBanner(PSHostUserInterface hostUI, string? bannerText)
{
- get
+ if (_showBanner && !_showHelp)
{
- if (_staMode.HasValue)
+ // If banner text is not supplied do nothing.
+ if (!string.IsNullOrEmpty(bannerText))
{
- return _staMode.Value;
+ hostUI.WriteLine(bannerText);
+ hostUI.WriteLine();
}
- else
+
+ if (UpdatesNotification.CanNotifyUpdates)
{
- return true;
+ UpdatesNotification.ShowUpdateNotification(hostUI);
}
}
}
@@ -667,41 +693,38 @@ internal bool StaMode
///
/// The command line parameters to be processed.
///
-
internal void Parse(string[] args)
{
- Dbg.Assert(!_dirty, "This instance has already been used. Create a new instance.");
+ if (_dirty)
+ {
+ throw new InvalidOperationException("This instance has already been used. Create a new instance.");
+ }
- // indicates that we've called this method on this instance, and that when it's done, the state variables
+ // Indicates that we've called this method on this instance, and that when it's done, the state variables
// will reflect the parse.
-
_dirty = true;
ParseHelper(args);
-
- // Check registry setting for a Group Policy ConfigurationName entry and
- // use it to override anything set by the user.
- var configurationName = GetConfigurationNameFromGroupPolicy();
- if (!string.IsNullOrEmpty(configurationName))
- {
- _configurationName = configurationName;
- }
}
private void ParseHelper(string[] args)
{
- Dbg.Assert(args != null, "Argument 'args' to ParseHelper should never be null");
+ if (args.Length == 0)
+ {
+ return;
+ }
+
bool noexitSeen = false;
for (int i = 0; i < args.Length; ++i)
{
- (string SwitchKey, bool ShouldBreak) switchKeyResults = GetSwitchKey(args, ref i, this, ref noexitSeen);
- if (switchKeyResults.ShouldBreak)
+ (string switchKey, bool shouldBreak) switchKeyResults = GetSwitchKey(args, ref i, ref noexitSeen);
+ if (switchKeyResults.shouldBreak)
{
break;
}
- string switchKey = switchKeyResults.SwitchKey;
+ string switchKey = switchKeyResults.switchKey;
// If version is in the commandline, don't continue to look at any other parameters
if (MatchSwitch(switchKey, "version", "v"))
@@ -767,7 +790,7 @@ private void ParseHelper(string[] args)
++i;
if (i >= args.Length)
{
- WriteCommandLineError(
+ SetCommandLineError(
CommandLineParameterParserStrings.MissingConfigurationNameArgument);
break;
}
@@ -779,26 +802,24 @@ private void ParseHelper(string[] args)
++i;
if (i >= args.Length)
{
- WriteCommandLineError(
+ SetCommandLineError(
CommandLineParameterParserStrings.MissingCustomPipeNameArgument);
break;
}
- if (!Platform.IsWindows)
+#if UNIX
+ int maxNameLength = MaxNameLength();
+ if (args[i].Length > maxNameLength)
{
- int maxNameLength = (Platform.IsLinux ? MaxPipePathLengthLinux : MaxPipePathLengthMacOS) - Path.GetTempPath().Length;
- if (args[i].Length > maxNameLength)
- {
- WriteCommandLineError(
- string.Format(
- CommandLineParameterParserStrings.CustomPipeNameTooLong,
- maxNameLength,
- args[i],
- args[i].Length));
- break;
- }
+ SetCommandLineError(
+ string.Format(
+ CommandLineParameterParserStrings.CustomPipeNameTooLong,
+ maxNameLength,
+ args[i],
+ args[i].Length));
+ break;
}
-
+#endif
_customPipeName = args[i];
}
else if (MatchSwitch(switchKey, "command", "c"))
@@ -811,14 +832,14 @@ private void ParseHelper(string[] args)
else if (MatchSwitch(switchKey, "windowstyle", "w"))
{
#if UNIX
- WriteCommandLineError(
+ SetCommandLineError(
CommandLineParameterParserStrings.WindowStyleArgumentNotImplemented);
break;
#else
++i;
if (i >= args.Length)
{
- WriteCommandLineError(
+ SetCommandLineError(
CommandLineParameterParserStrings.MissingWindowStyleArgument);
break;
}
@@ -831,7 +852,7 @@ private void ParseHelper(string[] args)
}
catch (PSInvalidCastException e)
{
- WriteCommandLineError(
+ SetCommandLineError(
string.Format(CultureInfo.CurrentCulture, CommandLineParameterParserStrings.InvalidWindowStyleArgument, args[i], e.Message));
break;
}
@@ -845,64 +866,9 @@ private void ParseHelper(string[] args)
}
}
#if DEBUG
- // this option is useful when debugging ConsoleHost remotely using VS remote debugging, as you can only
- // attach to an already running process with that debugger.
- else if (MatchSwitch(switchKey, "wait", "wa"))
- {
- // This does not need to be localized: its chk only
-
- ((ConsoleHostUserInterface)_hostUI).WriteToConsole("Waiting - type enter to continue:", false);
- _hostUI.ReadLine();
- }
-
- // this option is useful for testing the initial InitialSessionState experience
- else if (MatchSwitch(switchKey, "iss", "iss"))
- {
- // Just toss this option, it was processed earlier...
- }
-
- // this option is useful for testing the initial InitialSessionState experience
- // this is independent of the normal wait switch because configuration processing
- // happens so early in the cycle...
else if (MatchSwitch(switchKey, "isswait", "isswait"))
{
- // Just toss this option, it was processed earlier...
- }
- else if (MatchSwitch(switchKey, "modules", "mod"))
- {
- if (ConsoleHost.DefaultInitialSessionState == null)
- {
- WriteCommandLineError(
- "The -module option can only be specified with the -iss option.",
- showHelp: true,
- showBanner: false);
- break;
- }
-
- ++i;
- int moduleCount = 0;
- // Accumulate the arguments to this script...
- while (i < args.Length)
- {
- string arg = args[i];
-
- if (!string.IsNullOrEmpty(arg) && CharExtensions.IsDash(arg[0]))
- {
- break;
- }
- else
- {
- ConsoleHost.DefaultInitialSessionState.ImportPSModule(new string[] { arg });
- moduleCount++;
- }
-
- ++i;
- }
-
- if (moduleCount < 1)
- {
- _hostUI.WriteErrorLine("No modules specified for -module option");
- }
+ // Just toss this option, it was processed earlier in 'ManagedEntrance.Start()'.
}
#endif
else if (MatchSwitch(switchKey, "outputformat", "o") || MatchSwitch(switchKey, "of", "o"))
@@ -936,7 +902,7 @@ private void ParseHelper(string[] args)
else if (MatchSwitch(switchKey, "settingsfile", "settings"))
{
// Parse setting file arg and write error
- if (!TryParseSettingFileHelper(args, ++i, this))
+ if (!TryParseSettingFileHelper(args, ++i))
{
break;
}
@@ -945,7 +911,7 @@ private void ParseHelper(string[] args)
{
if (!Platform.IsWindowsDesktop)
{
- WriteCommandLineError(
+ SetCommandLineError(
CommandLineParameterParserStrings.STANotImplemented);
break;
}
@@ -953,7 +919,7 @@ private void ParseHelper(string[] args)
if (_staMode.HasValue)
{
// -sta and -mta are mutually exclusive.
- WriteCommandLineError(
+ SetCommandLineError(
CommandLineParameterParserStrings.MtaStaMutuallyExclusive);
break;
}
@@ -964,7 +930,7 @@ private void ParseHelper(string[] args)
{
if (!Platform.IsWindowsDesktop)
{
- WriteCommandLineError(
+ SetCommandLineError(
CommandLineParameterParserStrings.MTANotImplemented);
break;
}
@@ -972,7 +938,7 @@ private void ParseHelper(string[] args)
if (_staMode.HasValue)
{
// -sta and -mta are mutually exclusive.
- WriteCommandLineError(
+ SetCommandLineError(
CommandLineParameterParserStrings.MtaStaMutuallyExclusive);
break;
}
@@ -984,7 +950,7 @@ private void ParseHelper(string[] args)
++i;
if (i >= args.Length)
{
- WriteCommandLineError(
+ SetCommandLineError(
CommandLineParameterParserStrings.MissingWorkingDirectoryArgument);
break;
}
@@ -1000,7 +966,6 @@ private void ParseHelper(string[] args)
else
{
// The first parameter we fail to recognize marks the beginning of the file string.
-
--i;
if (!ParseFile(args, ref i, noexitSeen))
{
@@ -1009,30 +974,27 @@ private void ParseHelper(string[] args)
}
}
- if (_showHelp)
- {
- ShowHelp();
- }
-
- if (_showBanner && !_showHelp)
- {
- DisplayBanner();
-
- if (UpdatesNotification.CanNotifyUpdates)
- {
- UpdatesNotification.ShowUpdateNotification(_hostUI);
- }
- }
-
Dbg.Assert(
((_exitCode == ConsoleHost.ExitCodeBadCommandLineParameter) && _abortStartup)
|| (_exitCode == ConsoleHost.ExitCodeSuccess),
"if exit code is failure, then abortstartup should be true");
}
- private void WriteCommandLineError(string msg, bool showHelp = false, bool showBanner = false)
+ internal void ShowErrorHelpBanner(PSHostUserInterface hostUI, string? bannerText, string? helpText)
+ {
+ ShowError(hostUI);
+ ShowHelp(hostUI, helpText);
+ DisplayBanner(hostUI, bannerText);
+ }
+
+ private void SetCommandLineError(string msg, bool showHelp = false, bool showBanner = false)
{
- _hostUI.WriteErrorLine(msg);
+ if (_error != null)
+ {
+ throw new ArgumentException(nameof(SetCommandLineError), nameof(_error));
+ }
+
+ _error = msg;
_showHelp = showHelp;
_showBanner = showBanner;
_abortStartup = true;
@@ -1051,13 +1013,11 @@ private void ParseFormat(string[] args, ref int i, ref Serialization.DataFormat
++i;
if (i >= args.Length)
{
- _hostUI.WriteErrorLine(
+ SetCommandLineError(
StringUtil.Format(
resourceStr,
- sb.ToString()));
- _showHelp = true;
- _abortStartup = true;
- _exitCode = ConsoleHost.ExitCodeBadCommandLineParameter;
+ sb.ToString()),
+ showHelp: true);
return;
}
@@ -1067,61 +1027,46 @@ private void ParseFormat(string[] args, ref int i, ref Serialization.DataFormat
}
catch (ArgumentException)
{
- _hostUI.WriteErrorLine(
+ SetCommandLineError(
StringUtil.Format(
CommandLineParameterParserStrings.BadFormatParameterValue,
args[i],
- sb.ToString()));
- _showHelp = true;
- _abortStartup = true;
- _exitCode = ConsoleHost.ExitCodeBadCommandLineParameter;
+ sb.ToString()),
+ showHelp: true);
}
}
- private void ParseExecutionPolicy(string[] args, ref int i, ref string executionPolicy, string resourceStr)
+ private void ParseExecutionPolicy(string[] args, ref int i, ref string? executionPolicy, string resourceStr)
{
++i;
if (i >= args.Length)
{
- _hostUI.WriteErrorLine(resourceStr);
-
- _showHelp = true;
- _abortStartup = true;
- _exitCode = ConsoleHost.ExitCodeBadCommandLineParameter;
+ SetCommandLineError(resourceStr, showHelp: true);
return;
}
executionPolicy = args[i];
}
+ // Process file execution. We don't need to worry about checking -command
+ // since if -command comes before -file, -file will be treated as part
+ // of the script to evaluate. If -file comes before -command, it will
+ // treat -command as an argument to the script...
private bool ParseFile(string[] args, ref int i, bool noexitSeen)
{
- // Process file execution. We don't need to worry about checking -command
- // since if -command comes before -file, -file will be treated as part
- // of the script to evaluate. If -file comes before -command, it will
- // treat -command as an argument to the script...
-
- bool TryGetBoolValue(string arg, out bool boolValue)
+ // Try parse '$true', 'true', '$false' and 'false' values.
+ object ConvertToBoolIfPossible(string arg)
{
- if (arg.Equals("$true", StringComparison.OrdinalIgnoreCase) || arg.Equals("true", StringComparison.OrdinalIgnoreCase))
- {
- boolValue = true;
- return true;
- }
- else if (arg.Equals("$false", StringComparison.OrdinalIgnoreCase) || arg.Equals("false", StringComparison.OrdinalIgnoreCase))
- {
- boolValue = false;
- return true;
- }
-
- boolValue = false;
- return false;
+ // Before parsing we skip '$' if present.
+ return arg.Length > 0 && bool.TryParse(arg.AsSpan().Slice(arg[0] == '$' ? 1 : 0), out bool boolValue)
+ ? (object)boolValue
+ : (object)arg;
}
++i;
if (i >= args.Length)
{
- WriteCommandLineError(
+ SetCommandLineError(
CommandLineParameterParserStrings.MissingFileArgument,
showHelp: true,
showBanner: false);
@@ -1149,7 +1094,6 @@ bool TryGetBoolValue(string arg, out bool boolValue)
// We need to get the full path to the script because it will be
// executed after the profiles are run and they may change the current
// directory.
- string exceptionMessage = null;
try
{
_file = NormalizeFilePath(args[i]);
@@ -1158,13 +1102,8 @@ bool TryGetBoolValue(string arg, out bool boolValue)
{
// Catch all exceptions - we're just going to exit anyway so there's
// no issue of the system being destabilized.
- exceptionMessage = e.Message;
- }
-
- if (exceptionMessage != null)
- {
- WriteCommandLineError(
- string.Format(CultureInfo.CurrentCulture, CommandLineParameterParserStrings.InvalidFileArgument, args[i], exceptionMessage),
+ SetCommandLineError(
+ string.Format(CultureInfo.CurrentCulture, CommandLineParameterParserStrings.InvalidFileArgument, args[i], e.Message),
showBanner: false);
return false;
}
@@ -1175,7 +1114,7 @@ bool TryGetBoolValue(string arg, out bool boolValue)
{
string param = args[i].Substring(1, args[i].Length - 1).ToLower();
StringBuilder possibleParameters = new StringBuilder();
- foreach (string validParameter in validParameters)
+ foreach (string validParameter in s_validParameters)
{
if (validParameter.Contains(param))
{
@@ -1186,15 +1125,16 @@ bool TryGetBoolValue(string arg, out bool boolValue)
if (possibleParameters.Length > 0)
{
- WriteCommandLineError(
- string.Format(CultureInfo.CurrentCulture, CommandLineParameterParserStrings.InvalidArgument, args[i]),
+ SetCommandLineError(
+ string.Format(CultureInfo.CurrentCulture, CommandLineParameterParserStrings.InvalidArgument, args[i])
+ + Environment.NewLine
+ + possibleParameters.ToString(),
showBanner: false);
- WriteCommandLineError(possibleParameters.ToString(), showBanner: false);
return false;
}
}
- WriteCommandLineError(
+ SetCommandLineError(
string.Format(CultureInfo.CurrentCulture, CommandLineParameterParserStrings.ArgumentFileDoesNotExist, args[i]),
showHelp: true);
return false;
@@ -1202,7 +1142,7 @@ bool TryGetBoolValue(string arg, out bool boolValue)
i++;
- string pendingParameter = null;
+ string? pendingParameter = null;
// Accumulate the arguments to this script...
while (i < args.Length)
@@ -1229,14 +1169,7 @@ bool TryGetBoolValue(string arg, out bool boolValue)
{
string argValue = arg.Substring(offset + 1);
string argName = arg.Substring(0, offset);
- if (TryGetBoolValue(argValue, out bool boolValue))
- {
- _collectedArgs.Add(new CommandParameter(argName, boolValue));
- }
- else
- {
- _collectedArgs.Add(new CommandParameter(argName, argValue));
- }
+ _collectedArgs.Add(new CommandParameter(argName, ConvertToBoolIfPossible(argValue)));
}
}
else
@@ -1261,21 +1194,14 @@ private bool ParseCommand(string[] args, ref int i, bool noexitSeen, bool isEnco
if (_commandLineCommand != null)
{
// we've already set the command, so squawk
-
- _hostUI.WriteErrorLine(CommandLineParameterParserStrings.CommandAlreadySpecified);
- _showHelp = true;
- _abortStartup = true;
- _exitCode = ConsoleHost.ExitCodeBadCommandLineParameter;
+ SetCommandLineError(CommandLineParameterParserStrings.CommandAlreadySpecified, showHelp: true);
return false;
}
++i;
if (i >= args.Length)
{
- _hostUI.WriteErrorLine(CommandLineParameterParserStrings.MissingCommandParameter);
- _showHelp = true;
- _abortStartup = true;
- _exitCode = ConsoleHost.ExitCodeBadCommandLineParameter;
+ SetCommandLineError(CommandLineParameterParserStrings.MissingCommandParameter, showHelp: true);
return false;
}
@@ -1288,10 +1214,7 @@ private bool ParseCommand(string[] args, ref int i, bool noexitSeen, bool isEnco
// decoding failed
catch
{
- _hostUI.WriteErrorLine(CommandLineParameterParserStrings.BadCommandValue);
- _showHelp = true;
- _abortStartup = true;
- _exitCode = ConsoleHost.ExitCodeBadCommandLineParameter;
+ SetCommandLineError(CommandLineParameterParserStrings.BadCommandValue, showHelp: true);
return false;
}
}
@@ -1307,19 +1230,13 @@ private bool ParseCommand(string[] args, ref int i, bool noexitSeen, bool isEnco
{
// there are more parameters to -command than -, which is an error.
- _hostUI.WriteErrorLine(CommandLineParameterParserStrings.TooManyParametersToCommand);
- _showHelp = true;
- _abortStartup = true;
- _exitCode = ConsoleHost.ExitCodeBadCommandLineParameter;
+ SetCommandLineError(CommandLineParameterParserStrings.TooManyParametersToCommand, showHelp: true);
return false;
}
- if (!Console.IsInputRedirected)
+ if (TestHookConsoleInputRedirected.HasValue ? !TestHookConsoleInputRedirected.Value : !Console.IsInputRedirected)
{
- _hostUI.WriteErrorLine(CommandLineParameterParserStrings.StdinNotRedirected);
- _showHelp = true;
- _abortStartup = true;
- _exitCode = ConsoleHost.ExitCodeBadCommandLineParameter;
+ SetCommandLineError(CommandLineParameterParserStrings.StdinNotRedirected, showHelp: true);
return false;
}
}
@@ -1359,20 +1276,14 @@ private bool CollectArgs(string[] args, ref int i)
{
if (_collectedArgs.Count != 0)
{
- _hostUI.WriteErrorLine(CommandLineParameterParserStrings.ArgsAlreadySpecified);
- _showHelp = true;
- _abortStartup = true;
- _exitCode = ConsoleHost.ExitCodeBadCommandLineParameter;
+ SetCommandLineError(CommandLineParameterParserStrings.ArgsAlreadySpecified, showHelp: true);
return false;
}
++i;
if (i >= args.Length)
{
- _hostUI.WriteErrorLine(CommandLineParameterParserStrings.MissingArgsValue);
- _showHelp = true;
- _abortStartup = true;
- _exitCode = ConsoleHost.ExitCodeBadCommandLineParameter;
+ SetCommandLineError(CommandLineParameterParserStrings.MissingArgsValue, showHelp: true);
return false;
}
@@ -1390,11 +1301,7 @@ private bool CollectArgs(string[] args, ref int i)
catch
{
// decoding failed
-
- _hostUI.WriteErrorLine(CommandLineParameterParserStrings.BadArgsValue);
- _showHelp = true;
- _abortStartup = true;
- _exitCode = ConsoleHost.ExitCodeBadCommandLineParameter;
+ SetCommandLineError(CommandLineParameterParserStrings.BadArgsValue, showHelp: true);
return false;
}
@@ -1406,22 +1313,20 @@ private bool CollectArgs(string[] args, ref int i)
private bool _namedPipeServerMode;
private bool _sshServerMode;
private bool _showVersion;
- private string _configurationName;
- private PSHostUserInterface _hostUI;
+ private string? _configurationName;
+ private string? _error;
private bool _showHelp;
private bool _showExtendedHelp;
private bool _showBanner = true;
private bool _noInteractive;
- private string _bannerText;
- private string _helpText;
private bool _abortStartup;
private bool _skipUserInit;
- private string _customPipeName;
+ private string? _customPipeName;
private bool? _staMode = null;
private bool _noExit = true;
private bool _explicitReadCommandsFromStdin;
private bool _noPrompt;
- private string _commandLineCommand;
+ private string? _commandLineCommand;
private bool _wasCommandEncoded;
private uint _exitCode = ConsoleHost.ExitCodeSuccess;
private bool _dirty;
@@ -1429,13 +1334,13 @@ private bool CollectArgs(string[] args, ref int i)
private bool _outputFormatSpecified = false;
private Serialization.DataFormat _inFormat = Serialization.DataFormat.Text;
private Collection _collectedArgs = new Collection();
- private string _file;
- private string _executionPolicy;
- private string _workingDirectory;
+ private string? _file;
+ private string? _executionPolicy;
+ private string? _settingsFile;
+ private string? _workingDirectory;
#if !UNIX
private bool _removeWorkingDirectoryTrailingCharacter = false;
#endif
}
} // namespace
-
diff --git a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHost.cs b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHost.cs
index 39a32d77c22..54a60cbead5 100644
--- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHost.cs
+++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHost.cs
@@ -4,7 +4,6 @@
#pragma warning disable 1634, 1691
using System;
-using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
@@ -12,6 +11,7 @@
using System.Globalization;
using System.IO;
using System.Management.Automation;
+using System.Management.Automation.Configuration;
using System.Management.Automation.Host;
using System.Management.Automation.Internal;
using System.Management.Automation.Language;
@@ -70,9 +70,6 @@ internal sealed partial class ConsoleHost
///
/// Help text for minishell. This is displayed on 'minishell -?'.
///
- ///
- /// Command line parameters to pwsh.exe
- ///
///
/// The exit code for the shell.
///
@@ -99,7 +96,7 @@ internal sealed partial class ConsoleHost
/// Anyone checking the exit code of the shell or monitor can mask off the high word to determine the exit code passed
/// by the script that the shell last executed.
///
- internal static int Start(string bannerText, string helpText, string[] args)
+ internal static int Start(string bannerText, string helpText)
{
#if DEBUG
if (Environment.GetEnvironmentVariable("POWERSHELL_DEBUG_STARTUP") != null)
@@ -161,15 +158,8 @@ internal static int Start(string bannerText, string helpText, string[] args)
hostException = e;
}
- PSHostUserInterface hostUi = s_theConsoleHost?.UI ?? new NullHostUserInterface();
- s_cpp = new CommandLineParameterParser(hostUi, bannerText, helpText);
- s_cpp.Parse(args);
-
-#if UNIX
- // On Unix, logging has to be deferred until after command-line parsing
- // completes to allow overriding logging options.
- PSEtwLog.LogConsoleStartup();
-#endif
+ PSHostUserInterface hostUI = s_theConsoleHost?.UI ?? new NullHostUserInterface();
+ s_cpp.ShowErrorHelpBanner(hostUI, bannerText, helpText);
if (s_cpp.ShowVersion)
{
@@ -277,7 +267,22 @@ internal static int Start(string bannerText, string helpText, string[] args)
}
}
- private static CommandLineParameterParser s_cpp;
+ internal static void ParseCommandLine(string[] args)
+ {
+ s_cpp.Parse(args);
+
+ if (s_cpp.SettingsFile is not null)
+ {
+ PowerShellConfig.Instance.SetSystemConfigFilePath(s_cpp.SettingsFile);
+ }
+
+ // Check registry setting for a Group Policy ConfigurationName entry and
+ // use it to override anything set by the user.
+ // It depends on setting file so 'SetSystemConfigFilePath()' should be called before.
+ s_cpp.ConfigurationName = CommandLineParameterParser.GetConfigurationNameFromGroupPolicy();
+ }
+
+ private static readonly CommandLineParameterParser s_cpp = new CommandLineParameterParser();
#if UNIX
///
diff --git a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleShell.cs b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleShell.cs
index cd8364fc7ce..707f164f983 100644
--- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleShell.cs
+++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleShell.cs
@@ -1,9 +1,10 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
+#nullable enable
+
using System.Management.Automation;
using System.Management.Automation.Runspaces;
-using System.Runtime.CompilerServices;
namespace Microsoft.PowerShell
{
@@ -19,7 +20,7 @@ public static class ConsoleShell
/// Help text for minishell. This is displayed on 'minishell -?'.
/// Commandline parameters specified by user.
/// An integer value which should be used as exit code for the process.
- public static int Start(string bannerText, string helpText, string[] args)
+ public static int Start(string? bannerText, string? helpText, string?[] args)
{
return Start(InitialSessionState.CreateDefault2(), bannerText, helpText, args);
}
@@ -30,7 +31,7 @@ public static int Start(string bannerText, string helpText, string[] args)
/// Help text for the shell.
/// Commandline parameters specified by user.
/// An integer value which should be used as exit code for the process.
- public static int Start(InitialSessionState initialSessionState, string bannerText, string helpText, string[] args)
+ public static int Start(InitialSessionState initialSessionState, string? bannerText, string? helpText, string?[] args)
{
if (initialSessionState == null)
{
@@ -42,9 +43,10 @@ public static int Start(InitialSessionState initialSessionState, string bannerTe
throw PSTraceSource.NewArgumentNullException(nameof(args));
}
+ ConsoleHost.ParseCommandLine(args);
ConsoleHost.DefaultInitialSessionState = initialSessionState;
- return ConsoleHost.Start(bannerText, helpText, args);
+ return ConsoleHost.Start(bannerText, helpText);
}
}
}
diff --git a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ManagedEntrance.cs b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ManagedEntrance.cs
index cfe7f0c16e1..4cc190b369d 100644
--- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ManagedEntrance.cs
+++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ManagedEntrance.cs
@@ -1,6 +1,8 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
+#nullable enable
+
using System;
using System.ComponentModel;
using System.Globalization;
@@ -30,19 +32,42 @@ public sealed class UnmanagedPSEntry
///
/// Length of the passed in argument array.
///
- public static int Start(string consoleFilePath, [MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.LPWStr, SizeParamIndex = 2)]string[] args, int argc)
+ [Obsolete("Callers should now use UnmanagedPSEntry.Start(string[], int)", error: true)]
+ public static int Start(string consoleFilePath, [MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.LPWStr, SizeParamIndex = 2)]string?[] args, int argc)
{
- // Warm up some components concurrently on background threads.
- EarlyStartup.Init();
+ return Start(args, argc);
+ }
- // We need to read the settings file before we create the console host
- CommandLineParameterParser.EarlyParse(args);
+ ///
+ /// Starts managed MSH.
+ ///
+ ///
+ /// Command line arguments to the managed MSH
+ ///
+ ///
+ /// Length of the passed in argument array.
+ ///
+ public static int Start([MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.LPWStr, SizeParamIndex = 1)]string?[] args, int argc)
+ {
+ if (args == null)
+ {
+ throw new ArgumentNullException(nameof(args));
+ }
-#if !UNIX
- // NOTE: On Unix, logging has to be deferred until after command-line parsing
- // complete. On Windows, deferring the call is not needed.
- PSEtwLog.LogConsoleStartup();
+#if DEBUG
+ if (args.Length > 0 && !string.IsNullOrEmpty(args[0]) && args[0]!.Equals("-isswait", StringComparison.OrdinalIgnoreCase))
+ {
+ Console.WriteLine("Attach the debugger to continue...");
+ while (!System.Diagnostics.Debugger.IsAttached)
+ {
+ Thread.Sleep(100);
+ }
+
+ System.Diagnostics.Debugger.Break();
+ }
#endif
+ // Warm up some components concurrently on background threads.
+ EarlyStartup.Init();
// Windows Vista and later support non-traditional UI fallback ie., a
// user on an Arabic machine can choose either French or English(US) as
@@ -54,18 +79,13 @@ public static int Start(string consoleFilePath, [MarshalAs(UnmanagedType.LPArray
Thread.CurrentThread.CurrentUICulture = NativeCultureResolver.UICulture;
Thread.CurrentThread.CurrentCulture = NativeCultureResolver.Culture;
-#if DEBUG
- if (args.Length > 0 && !string.IsNullOrEmpty(args[0]) && args[0].Equals("-isswait", StringComparison.OrdinalIgnoreCase))
- {
- Console.WriteLine("Attach the debugger to continue...");
- while (!System.Diagnostics.Debugger.IsAttached)
- {
- Thread.Sleep(100);
- }
+ ConsoleHost.ParseCommandLine(args);
+
+ // NOTE: On Unix, logging depends on a command line parsing
+ // and must be just after ConsoleHost.ParseCommandLine(args)
+ // to allow overriding logging options.
+ PSEtwLog.LogConsoleStartup();
- System.Diagnostics.Debugger.Break();
- }
-#endif
int exitCode = 0;
try
{
@@ -74,14 +94,14 @@ public static int Start(string consoleFilePath, [MarshalAs(UnmanagedType.LPArray
ManagedEntranceStrings.ShellBannerNonWindowsPowerShell,
PSVersionInfo.GitCommitId);
- exitCode = ConsoleShell.Start(banner, ManagedEntranceStrings.UsageHelp, args);
+ ConsoleHost.DefaultInitialSessionState = InitialSessionState.CreateDefault2();
+
+ exitCode = ConsoleHost.Start(banner, ManagedEntranceStrings.UsageHelp);
}
catch (HostException e)
{
- if (e.InnerException != null && e.InnerException.GetType() == typeof(Win32Exception))
+ if (e.InnerException is Win32Exception win32e)
{
- Win32Exception win32e = e.InnerException as Win32Exception;
-
// These exceptions are caused by killing conhost.exe
// 1236, network connection aborted by local system
// 0x6, invalid console handle
@@ -102,4 +122,3 @@ public static int Start(string consoleFilePath, [MarshalAs(UnmanagedType.LPArray
}
}
}
-
diff --git a/src/powershell/Program.cs b/src/powershell/Program.cs
index e4d3b51c5d6..a2c6a7852e0 100644
--- a/src/powershell/Program.cs
+++ b/src/powershell/Program.cs
@@ -1,6 +1,8 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
+#nullable enable
+
using System;
using System.IO;
using System.Reflection;
@@ -66,7 +68,7 @@ public static int Main(string[] args)
#if UNIX
AttemptExecPwshLogin(args);
#endif
- return UnmanagedPSEntry.Start(string.Empty, args, args.Length);
+ return UnmanagedPSEntry.Start(args, args.Length);
}
#if UNIX
@@ -93,7 +95,7 @@ private static void AttemptExecPwshLogin(string[] args)
byte procNameFirstByte;
// The path to the executable this process was started from
- string pwshPath;
+ string? pwshPath;
// On Linux, we can simply use the /proc filesystem
if (isLinux)
@@ -116,6 +118,11 @@ private static void AttemptExecPwshLogin(string[] args)
pwshPath = Marshal.PtrToStringAnsi(linkPathPtr, (int)bufSize);
Marshal.FreeHGlobal(linkPathPtr);
+ if (pwshPath == null)
+ {
+ throw new ArgumentNullException(nameof(pwshPath));
+ }
+
// exec pwsh
ThrowOnFailure("exec", ExecPwshLogin(args, pwshPath, isMacOS: false));
return;
@@ -200,6 +207,11 @@ private static void AttemptExecPwshLogin(string[] args)
// Get the pwshPath from exec_path
pwshPath = Marshal.PtrToStringAnsi(executablePathPtr);
+ if (pwshPath == null)
+ {
+ throw new ArgumentNullException(nameof(pwshPath));
+ }
+
// exec pwsh
ThrowOnFailure("exec", ExecPwshLogin(args, pwshPath, isMacOS: true));
}
@@ -281,20 +293,20 @@ private static int ExecPwshLogin(string[] args, string pwshPath, bool isMacOS)
int quotedPwshPathLength = GetQuotedPathLength(pwshPath);
string pwshInvocation = string.Create(
- quotedPwshPathLength + 10, // exec '{pwshPath}' "$@"
+ quotedPwshPathLength + 10, // exec '{pwshPath}' "$@"
(pwshPath, quotedPwshPathLength),
CreatePwshInvocation);
// Set up the arguments for '/bin/sh'.
// We need to add 5 slots for the '/bin/sh' invocation parts, plus 1 slot for the null terminator at the end
- var execArgs = new string[args.Length + 6];
+ var execArgs = new string?[args.Length + 6];
// The command arguments
// First argument is the command name.
// Even when executing 'zsh', we want to set this to '/bin/sh'
// because this tells 'zsh' to run in sh emulation mode (it examines $0)
- execArgs[0] = "/bin/sh";
+ execArgs[0] = "/bin/sh";
execArgs[1] = "-l"; // Login flag
execArgs[2] = "-c"; // Command parameter
@@ -433,7 +445,7 @@ private static void ThrowOnFailure(string call, int code)
CallingConvention = CallingConvention.Cdecl,
CharSet = CharSet.Ansi,
SetLastError = true)]
- private static extern int Exec(string path, string[] args);
+ private static extern int Exec(string path, string?[] args);
///
/// The `readlink` POSIX syscall we use to read the symlink from /proc/self/exe
diff --git a/test/xUnit/csharp/test_CommandLineParser.cs b/test/xUnit/csharp/test_CommandLineParser.cs
new file mode 100644
index 00000000000..d4b722b7f4a
--- /dev/null
+++ b/test/xUnit/csharp/test_CommandLineParser.cs
@@ -0,0 +1,1183 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System.Collections;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Management.Automation;
+
+using Microsoft.PowerShell;
+using Xunit;
+
+namespace PSTests.Parallel
+{
+ public static class PSCommandLineParserTests
+ {
+ [Fact]
+ public static void TestDefaults()
+ {
+ var cpp = new CommandLineParameterParser();
+
+ cpp.Parse(new string[0]);
+
+ Assert.False(cpp.AbortStartup);
+ Assert.Empty(cpp.Args);
+ Assert.Null(cpp.ConfigurationName);
+ Assert.Null(cpp.CustomPipeName);
+ Assert.Null(cpp.ErrorMessage);
+ Assert.Null(cpp.ExecutionPolicy);
+ Assert.Equal((uint)ConsoleHost.ExitCodeSuccess, cpp.ExitCode);
+ Assert.False(cpp.ExplicitReadCommandsFromStdin);
+ Assert.Null(cpp.File);
+ Assert.Null(cpp.InitialCommand);
+ Assert.Equal(Microsoft.PowerShell.Serialization.DataFormat.Text, cpp.InputFormat);
+ Assert.False(cpp.NamedPipeServerMode);
+ Assert.True(cpp.NoExit);
+ Assert.False(cpp.NonInteractive);
+ Assert.False(cpp.NoPrompt);
+ Assert.Equal(Microsoft.PowerShell.Serialization.DataFormat.Text, cpp.OutputFormat);
+ Assert.False(cpp.OutputFormatSpecified);
+#if !UNIX
+ Assert.False(cpp.RemoveWorkingDirectoryTrailingCharacter);
+#endif
+ Assert.False(cpp.ServerMode);
+ Assert.True(cpp.ShowBanner);
+ Assert.False(cpp.ShowShortHelp);
+ Assert.False(cpp.ShowVersion);
+ Assert.False(cpp.SkipProfiles);
+ Assert.False(cpp.SocketServerMode);
+ Assert.False(cpp.SSHServerMode);
+ Assert.True(cpp.StaMode);
+ Assert.False(cpp.ThrowOnReadAndPrompt);
+ Assert.False(cpp.WasInitialCommandEncoded);
+ Assert.Null(cpp.WorkingDirectory);
+ Assert.False(cpp.NonInteractive);
+ }
+
+ [Fact]
+ public static void Test_Throws_On_Reuse()
+ {
+ var cpp = new CommandLineParameterParser();
+
+ cpp.Parse(new string[0]);
+
+ Assert.Throws(() => cpp.Parse(new string[0]));
+ }
+
+ [Theory]
+ [InlineData("noexistfilename")]
+ public static void TestDefaultParameterIsFileName_Not_Exist(params string[] commandLine)
+ {
+ var cpp = new CommandLineParameterParser();
+
+ cpp.Parse(commandLine);
+
+ Assert.True(cpp.AbortStartup);
+ Assert.False(cpp.NoExit);
+ Assert.True(cpp.ShowShortHelp);
+ Assert.False(cpp.ShowBanner);
+ Assert.Equal(CommandLineParameterParser.NormalizeFilePath("noexistfilename"), cpp.File);
+ Assert.Equal(
+ string.Format(CommandLineParameterParserStrings.ArgumentFileDoesNotExist, "noexistfilename"),
+ cpp.ErrorMessage);
+ }
+
+ [Fact]
+ public static void TestDefaultParameterIsFileName_Exist()
+ {
+ var fileName = System.IO.Path.GetTempFileName();
+
+ var cpp = new CommandLineParameterParser();
+
+ cpp.Parse(new string[] { fileName });
+
+ Assert.False(cpp.AbortStartup);
+ Assert.False(cpp.NoExit);
+ Assert.False(cpp.ShowShortHelp);
+ Assert.False(cpp.ShowBanner);
+ Assert.Equal(CommandLineParameterParser.NormalizeFilePath(fileName), cpp.File);
+ Assert.Null(cpp.ErrorMessage);
+ }
+
+ [Theory]
+ [InlineData("-file", "-")]
+ public static void TestParameterIsFileName_Dash(params string[] commandLine)
+ {
+ var cpp = new CommandLineParameterParser();
+
+ cpp.Parse(commandLine);
+
+ Assert.False(cpp.AbortStartup);
+ Assert.True(cpp.NoExit);
+ Assert.False(cpp.ShowShortHelp);
+ Assert.False(cpp.ShowBanner);
+ Assert.False(cpp.NoPrompt);
+ Assert.True(cpp.ExplicitReadCommandsFromStdin);
+ Assert.Null(cpp.ErrorMessage);
+ }
+
+ [Theory]
+ [InlineData("-prof")]
+ public static void TestParameterIs_Wrong_Value(params string[] commandLine)
+ {
+ var cpp = new CommandLineParameterParser();
+
+ cpp.Parse(commandLine);
+
+ Assert.True(cpp.AbortStartup);
+ Assert.False(cpp.NoExit);
+ Assert.False(cpp.ShowShortHelp);
+ Assert.False(cpp.ShowBanner);
+ Assert.False(cpp.NoPrompt);
+ Assert.Contains(commandLine[0], cpp.ErrorMessage);
+ Assert.Contains("-noprofile", cpp.ErrorMessage);
+ }
+
+ [Theory]
+ [InlineData("-Version")]
+ [InlineData("-V")]
+ [InlineData("-Version", "abbra")] // Ignore all after the parameter
+ public static void TestParameter_Version(params string[] commandLine)
+ {
+ var cpp = new CommandLineParameterParser();
+
+ cpp.Parse(commandLine);
+
+ Assert.False(cpp.AbortStartup);
+ Assert.False(cpp.NoExit);
+ Assert.True(cpp.NonInteractive);
+ Assert.False(cpp.ShowBanner);
+ Assert.True(cpp.ShowVersion);
+ Assert.True(cpp.SkipProfiles);
+ Assert.Null(cpp.ErrorMessage);
+ }
+
+ [Theory]
+ [InlineData("-Help")]
+ [InlineData("-h")]
+ public static void TestParameter_Help(params string[] commandLine)
+ {
+ var cpp = new CommandLineParameterParser();
+
+ cpp.Parse(commandLine);
+
+ Assert.True(cpp.AbortStartup);
+ Assert.True(cpp.ShowShortHelp);
+ Assert.True(cpp.ShowExtendedHelp);
+ Assert.Null(cpp.ErrorMessage);
+ }
+
+ [Theory]
+ [InlineData("-Login")]
+ [InlineData("-l")]
+ public static void TestParameter_Login(params string[] commandLine)
+ {
+ var cpp = new CommandLineParameterParser();
+
+ cpp.Parse(commandLine);
+
+ // Parser does not change any internal properties for the parameter.
+ Assert.False(cpp.AbortStartup);
+ Assert.True(cpp.NoExit);
+ Assert.False(cpp.ShowShortHelp);
+ Assert.True(cpp.ShowBanner);
+ Assert.Null(cpp.ErrorMessage);
+ }
+
+ [Theory]
+ [InlineData("-noexit")]
+ [InlineData("-noe")]
+ public static void TestParameter_NoExit(params string[] commandLine)
+ {
+ var cpp = new CommandLineParameterParser();
+
+ cpp.Parse(commandLine);
+
+ Assert.False(cpp.AbortStartup);
+ Assert.True(cpp.NoExit);
+ Assert.False(cpp.ShowShortHelp);
+ Assert.True(cpp.ShowBanner);
+ Assert.Null(cpp.ErrorMessage);
+ }
+
+ [Theory]
+ [InlineData("-noprofile")]
+ [InlineData("-nop")]
+ public static void TestParameter_NoProfile(params string[] commandLine)
+ {
+ var cpp = new CommandLineParameterParser();
+
+ cpp.Parse(commandLine);
+
+ Assert.False(cpp.AbortStartup);
+ Assert.True(cpp.NoExit);
+ Assert.False(cpp.ShowShortHelp);
+ Assert.True(cpp.ShowBanner);
+ Assert.True(cpp.SkipProfiles);
+ Assert.Null(cpp.ErrorMessage);
+ }
+
+ [Theory]
+ [InlineData("-nologo")]
+ [InlineData("-nol")]
+ public static void TestParameter_NoLogo(params string[] commandLine)
+ {
+ var cpp = new CommandLineParameterParser();
+
+ cpp.Parse(commandLine);
+
+ Assert.False(cpp.AbortStartup);
+ Assert.True(cpp.NoExit);
+ Assert.False(cpp.ShowShortHelp);
+ Assert.False(cpp.ShowBanner);
+ Assert.Null(cpp.ErrorMessage);
+ }
+
+ [Theory]
+ [InlineData("-noninteractive")]
+ [InlineData("-noni")]
+ public static void TestParameter_NoInteractive(params string[] commandLine)
+ {
+ var cpp = new CommandLineParameterParser();
+
+ cpp.Parse(commandLine);
+
+ Assert.False(cpp.AbortStartup);
+ Assert.True(cpp.NoExit);
+ Assert.False(cpp.ShowShortHelp);
+ Assert.True(cpp.ShowBanner);
+ Assert.True(cpp.NonInteractive);
+ Assert.Null(cpp.ErrorMessage);
+ }
+
+ [Theory]
+ [InlineData("-socketservermode")]
+ [InlineData("-so")]
+ public static void TestParameter_SocketServerMode(params string[] commandLine)
+ {
+ var cpp = new CommandLineParameterParser();
+
+ cpp.Parse(commandLine);
+
+ Assert.False(cpp.AbortStartup);
+ Assert.True(cpp.NoExit);
+ Assert.False(cpp.ShowShortHelp);
+ Assert.True(cpp.ShowBanner);
+ Assert.True(cpp.SocketServerMode);
+ Assert.Null(cpp.ErrorMessage);
+ }
+
+ [Theory]
+ [InlineData("-servermode")]
+ [InlineData("-s")]
+ public static void TestParameter_ServerMode(params string[] commandLine)
+ {
+ var cpp = new CommandLineParameterParser();
+
+ cpp.Parse(commandLine);
+
+ Assert.False(cpp.AbortStartup);
+ Assert.True(cpp.NoExit);
+ Assert.False(cpp.ShowShortHelp);
+ Assert.True(cpp.ShowBanner);
+ Assert.True(cpp.ServerMode);
+ Assert.Null(cpp.ErrorMessage);
+ }
+
+ [Theory]
+ [InlineData("-namedpipeservermode")]
+ [InlineData("-nam")]
+ public static void TestParameter_NamedPipeServerMode(params string[] commandLine)
+ {
+ var cpp = new CommandLineParameterParser();
+
+ cpp.Parse(commandLine);
+
+ Assert.False(cpp.AbortStartup);
+ Assert.True(cpp.NoExit);
+ Assert.False(cpp.ShowShortHelp);
+ Assert.True(cpp.ShowBanner);
+ Assert.True(cpp.NamedPipeServerMode);
+ Assert.Null(cpp.ErrorMessage);
+ }
+
+ [Theory]
+ [InlineData("-sshservermode")]
+ [InlineData("-sshs")]
+ public static void TestParameter_SSHServerMode(params string[] commandLine)
+ {
+ var cpp = new CommandLineParameterParser();
+
+ cpp.Parse(commandLine);
+
+ Assert.False(cpp.AbortStartup);
+ Assert.True(cpp.NoExit);
+ Assert.False(cpp.ShowShortHelp);
+ Assert.True(cpp.ShowBanner);
+ Assert.True(cpp.SSHServerMode);
+ Assert.Null(cpp.ErrorMessage);
+ }
+
+ [Theory]
+ [InlineData("-interactive")]
+ [InlineData("-i")]
+ public static void TestParameter_Interactive(params string[] commandLine)
+ {
+ var cpp = new CommandLineParameterParser();
+
+ cpp.Parse(commandLine);
+
+ Assert.False(cpp.AbortStartup);
+ Assert.True(cpp.NoExit);
+ Assert.False(cpp.ShowShortHelp);
+ Assert.True(cpp.ShowBanner);
+ Assert.False(cpp.NonInteractive);
+ Assert.Null(cpp.ErrorMessage);
+ }
+
+ [Theory]
+ [InlineData("-configurationname")]
+ [InlineData("-config")]
+ public static void TestParameter_ConfigurationName_No_Name(params string[] commandLine)
+ {
+ var cpp = new CommandLineParameterParser();
+
+ cpp.Parse(commandLine);
+
+ Assert.True(cpp.AbortStartup);
+ Assert.True(cpp.NoExit);
+ Assert.False(cpp.ShowShortHelp);
+ Assert.False(cpp.ShowBanner);
+ Assert.Equal((uint)ConsoleHost.ExitCodeBadCommandLineParameter, cpp.ExitCode);
+ Assert.Equal(CommandLineParameterParserStrings.MissingConfigurationNameArgument, cpp.ErrorMessage);
+ }
+
+ [Theory]
+ [InlineData("-configurationname", "qwerty")]
+ [InlineData("-config", "qwerty")]
+ public static void TestParameter_ConfigurationName_With_Name(params string[] commandLine)
+ {
+ var cpp = new CommandLineParameterParser();
+
+ cpp.Parse(commandLine);
+
+ Assert.False(cpp.AbortStartup);
+ Assert.True(cpp.NoExit);
+ Assert.False(cpp.ShowShortHelp);
+ Assert.True(cpp.ShowBanner);
+ Assert.Equal("qwerty", cpp.ConfigurationName);
+ Assert.Null(cpp.ErrorMessage);
+ }
+
+ [Theory]
+ [InlineData("-custompipename")]
+ [InlineData("-cus")]
+ public static void TestParameter_CustomPipeName_No_Name(params string[] commandLine)
+ {
+ var cpp = new CommandLineParameterParser();
+
+ cpp.Parse(commandLine);
+
+ Assert.True(cpp.AbortStartup);
+ Assert.True(cpp.NoExit);
+ Assert.False(cpp.ShowShortHelp);
+ Assert.False(cpp.ShowBanner);
+ Assert.Equal((uint)ConsoleHost.ExitCodeBadCommandLineParameter, cpp.ExitCode);
+ Assert.Equal(CommandLineParameterParserStrings.MissingCustomPipeNameArgument, cpp.ErrorMessage);
+ }
+
+ [Theory]
+ [InlineData("-custompipename", "qwerty")]
+ [InlineData("-cus", "qwerty")]
+ public static void TestParameter_CustomPipeName_With_Name(params string[] commandLine)
+ {
+ var cpp = new CommandLineParameterParser();
+
+ cpp.Parse(commandLine);
+
+ Assert.False(cpp.AbortStartup);
+ Assert.True(cpp.NoExit);
+ Assert.False(cpp.ShowShortHelp);
+ Assert.True(cpp.ShowBanner);
+ Assert.Equal("qwerty", cpp.CustomPipeName);
+ Assert.Null(cpp.ErrorMessage);
+ }
+
+ public static System.Collections.Generic.IEnumerable