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 Data => + new System.Collections.Generic.List + { + new string[] { "-custompipename", new string('q', CommandLineParameterParser.MaxNameLength() + 1) } + }; + + [SkippableTheory] + [MemberData(nameof(Data))] + public static void TestParameter_CustomPipeName_With_Too_Long_Name(params string[] commandLine) + { + Skip.If(Platform.IsWindows); + + 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( + string.Format( + CommandLineParameterParserStrings.CustomPipeNameTooLong, + CommandLineParameterParser.MaxNameLength(), + commandLine[1], + commandLine[1].Length), + cpp.ErrorMessage); + } + + [Theory] + [InlineData("-command")] + [InlineData("-c")] + public static void TestParameter_Command_No_Value(params string[] commandLine) + { + var cpp = new CommandLineParameterParser(); + + cpp.Parse(commandLine); + + Assert.True(cpp.AbortStartup); + Assert.True(cpp.NoExit); + Assert.True(cpp.ShowShortHelp); + Assert.False(cpp.ShowBanner); + Assert.Equal((uint)ConsoleHost.ExitCodeBadCommandLineParameter, cpp.ExitCode); + Assert.Equal(CommandLineParameterParserStrings.MissingCommandParameter, cpp.ErrorMessage); + } + + [Theory] + [InlineData("-command", "qwerty")] + [InlineData("-c", "qwerty")] + public static void TestParameter_Command_With_Value(params string[] commandLine) + { + var cpp = new CommandLineParameterParser(); + + cpp.Parse(commandLine); + + Assert.False(cpp.AbortStartup); + Assert.False(cpp.NoExit); + Assert.False(cpp.ShowShortHelp); + Assert.False(cpp.ShowBanner); + Assert.Equal("qwerty", cpp.InitialCommand); + Assert.Null(cpp.ErrorMessage); + } + + [Theory] + [InlineData("-command", "-")] + [InlineData("-c", "-")] + public static void TestParameter_Command_With_Dash_And_Not_ConsoleInputRedirected(params string[] commandLine) + { + var cpp = new CommandLineParameterParser(); + + cpp.TestHookConsoleInputRedirected = false; + + cpp.Parse(commandLine); + + Assert.True(cpp.AbortStartup); + Assert.True(cpp.NoExit); + Assert.True(cpp.ShowShortHelp); + Assert.False(cpp.ShowBanner); + Assert.True(cpp.NoPrompt); + Assert.True(cpp.ExplicitReadCommandsFromStdin); + Assert.Equal(CommandLineParameterParserStrings.StdinNotRedirected, cpp.ErrorMessage); + } + + [Theory] + [InlineData("-command", "-", "abbra")] + [InlineData("-c", "-", "abbra")] + public static void TestParameter_Command_With_Dash_And_Tail(params string[] commandLine) + { + var cpp = new CommandLineParameterParser(); + + cpp.Parse(commandLine); + + Assert.True(cpp.AbortStartup); + Assert.True(cpp.NoExit); + Assert.True(cpp.ShowShortHelp); + Assert.False(cpp.ShowBanner); + Assert.Equal((uint)ConsoleHost.ExitCodeBadCommandLineParameter, cpp.ExitCode); + Assert.Equal(CommandLineParameterParserStrings.TooManyParametersToCommand, cpp.ErrorMessage); + } + + [Theory] + [InlineData("-command", "-")] + [InlineData("-c", "-")] + public static void TestParameter_Command_With_Dash_And_ConsoleInputRedirected(params string[] commandLine) + { + var cpp = new CommandLineParameterParser(); + + cpp.TestHookConsoleInputRedirected = true; + + cpp.Parse(commandLine); + + Assert.False(cpp.AbortStartup); + Assert.True(cpp.NoExit); + Assert.False(cpp.ShowShortHelp); + Assert.False(cpp.ShowBanner); + Assert.Equal((uint)ConsoleHost.ExitCodeSuccess, cpp.ExitCode); + Assert.True(cpp.ExplicitReadCommandsFromStdin); + Assert.Null(cpp.ErrorMessage); + } + + [SkippableTheory] + [InlineData("-windowstyle")] + [InlineData("-w")] + public static void TestParameter_WindowsStyle_On_Unix(params string[] commandLine) + { + Skip.If(Platform.IsWindows); + + 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.WindowStyleArgumentNotImplemented, cpp.ErrorMessage); + } + + [SkippableTheory] + [InlineData("-windowstyle")] + [InlineData("-w")] + public static void TestParameter_WindowsStyle_No_Value(params string[] commandLine) + { + Skip.IfNot(Platform.IsWindows); + + 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.MissingWindowStyleArgument, cpp.ErrorMessage); + } + + [SkippableTheory] + [InlineData("-windowstyle", "abbra")] + [InlineData("-w", "abbra")] + public static void TestParameter_WindowsStyle_With_Wrong_Value(params string[] commandLine) + { + Skip.IfNot(Platform.IsWindows); + + string errorMessage = null; + try + { + ProcessWindowStyle style = (ProcessWindowStyle)LanguagePrimitives.ConvertTo( + commandLine[1], typeof(ProcessWindowStyle), System.Globalization.CultureInfo.InvariantCulture); + } + catch (PSInvalidCastException e) + { + errorMessage = e.Message; + } + + 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( + string.Format(CommandLineParameterParserStrings.InvalidWindowStyleArgument, commandLine[1], errorMessage), + cpp.ErrorMessage); + } + + [SkippableTheory] + [InlineData("-windowstyle", "Maximized")] + [InlineData("-w", "Maximized")] + public static void TestParameter_WindowsStyle_With_Right_Value(params string[] commandLine) + { + Skip.IfNot(Platform.IsWindows); + + 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((uint)ConsoleHost.ExitCodeSuccess, cpp.ExitCode); + Assert.Null(cpp.ErrorMessage); + } + + [Theory] + [InlineData("-outputformat")] + [InlineData("-o")] + [InlineData("-of")] + public static void TestParameter_OutputFormat_No_Value(params string[] commandLine) + { + var index = CommandLineParameterParserStrings.MissingOutputFormatParameter.IndexOf('.'); + var errorMessage = CommandLineParameterParserStrings.MissingOutputFormatParameter.Substring(0, index); + + var cpp = new CommandLineParameterParser(); + + cpp.Parse(commandLine); + + Assert.True(cpp.AbortStartup); + Assert.True(cpp.NoExit); + Assert.True(cpp.ShowShortHelp); + Assert.False(cpp.ShowBanner); + Assert.Equal((uint)ConsoleHost.ExitCodeBadCommandLineParameter, cpp.ExitCode); + Assert.Equal(errorMessage, cpp.ErrorMessage.Substring(0, index)); + } + + [Theory] + [InlineData("-outputformat", "abbra")] + [InlineData("-o", "abbra")] + [InlineData("-of", "abbra")] + public static void TestParameter_OutputFormat_With_Wrong_Value(params string[] commandLine) + { + var index = CommandLineParameterParserStrings.BadFormatParameterValue.IndexOf('.'); + var errorMessage = CommandLineParameterParserStrings.BadFormatParameterValue.Substring(0, index); + + var cpp = new CommandLineParameterParser(); + + cpp.Parse(commandLine); + + Assert.True(cpp.AbortStartup); + Assert.True(cpp.NoExit); + Assert.True(cpp.ShowShortHelp); + Assert.False(cpp.ShowBanner); + Assert.Equal((uint)ConsoleHost.ExitCodeBadCommandLineParameter, cpp.ExitCode); + Assert.Equal(errorMessage, cpp.ErrorMessage.Substring(0, index)); + } + + [Theory] + [InlineData("-outputformat", "XML")] + [InlineData("-o", "XML")] + [InlineData("-of", "XML")] + public static void TestParameter_OutputFormat_With_Right_Value(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(Microsoft.PowerShell.Serialization.DataFormat.XML, cpp.OutputFormat); + Assert.Equal((uint)ConsoleHost.ExitCodeSuccess, cpp.ExitCode); + Assert.Null(cpp.ErrorMessage); + } + + [Theory] + [InlineData("-inputformat")] + [InlineData("-inp")] + [InlineData("-if")] + public static void TestParameter_InputFormat_No_Value(params string[] commandLine) + { + var index = CommandLineParameterParserStrings.MissingInputFormatParameter.IndexOf('.'); + var errorMessage = CommandLineParameterParserStrings.MissingInputFormatParameter.Substring(0, index); + + var cpp = new CommandLineParameterParser(); + + cpp.Parse(commandLine); + + Assert.True(cpp.AbortStartup); + Assert.True(cpp.NoExit); + Assert.True(cpp.ShowShortHelp); + Assert.False(cpp.ShowBanner); + Assert.Equal((uint)ConsoleHost.ExitCodeBadCommandLineParameter, cpp.ExitCode); + Assert.Equal(errorMessage, cpp.ErrorMessage.Substring(0, index)); + } + + [Theory] + [InlineData("-inputformat", "abbra")] + [InlineData("-inp", "abbra")] + [InlineData("-if", "abbra")] + public static void TestParameter_InputFormat_With_Wrong_Value(params string[] commandLine) + { + var index = CommandLineParameterParserStrings.BadFormatParameterValue.IndexOf('.'); + var errorMessage = CommandLineParameterParserStrings.BadFormatParameterValue.Substring(0, index); + + var cpp = new CommandLineParameterParser(); + + cpp.Parse(commandLine); + + Assert.True(cpp.AbortStartup); + Assert.True(cpp.NoExit); + Assert.True(cpp.ShowShortHelp); + Assert.False(cpp.ShowBanner); + Assert.Equal((uint)ConsoleHost.ExitCodeBadCommandLineParameter, cpp.ExitCode); + Assert.Equal(errorMessage, cpp.ErrorMessage.Substring(0, index)); + } + + [Theory] + [InlineData("-inputformat", "XML")] + [InlineData("-inp", "XML")] + [InlineData("-if", "XML")] + public static void TestParameter_InputFormat_With_Right_Value(params string[] commandLine) + { + var cpp = new CommandLineParameterParser(); + + cpp.Parse(commandLine); + + Assert.Equal(commandLine[1], cpp.InputFormat.ToString()); + Assert.False(cpp.AbortStartup); + Assert.True(cpp.NoExit); + Assert.False(cpp.ShowShortHelp); + Assert.True(cpp.ShowBanner); + Assert.Equal(Microsoft.PowerShell.Serialization.DataFormat.XML, cpp.InputFormat); + Assert.Equal((uint)ConsoleHost.ExitCodeSuccess, cpp.ExitCode); + Assert.Null(cpp.ErrorMessage); + } + + [Theory] + [InlineData("-executionpolicy")] + [InlineData("-ex")] + [InlineData("-ep")] + public static void TestParameter_ExecutionPolicy_No_Value(params string[] commandLine) + { + var cpp = new CommandLineParameterParser(); + + cpp.Parse(commandLine); + + Assert.True(cpp.AbortStartup); + Assert.True(cpp.NoExit); + Assert.True(cpp.ShowShortHelp); + Assert.False(cpp.ShowBanner); + Assert.Equal((uint)ConsoleHost.ExitCodeBadCommandLineParameter, cpp.ExitCode); + Assert.Equal(CommandLineParameterParserStrings.MissingExecutionPolicyParameter, cpp.ErrorMessage); + } + + [Theory] + [InlineData("-executionpolicy", "XML")] + [InlineData("-ex", "XML")] + [InlineData("-ep", "XML")] + public static void TestParameter_ExecutionPolicy_With_Right_Value(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((uint)ConsoleHost.ExitCodeSuccess, cpp.ExitCode); + Assert.Equal(commandLine[1], cpp.ExecutionPolicy); + Assert.Null(cpp.ErrorMessage); + } + + [Theory] + [InlineData("-encodedcommand")] + [InlineData("-e")] + [InlineData("-ec")] + public static void TestParameter_EncodedCommand_No_Value(params string[] commandLine) + { + var cpp = new CommandLineParameterParser(); + + cpp.Parse(commandLine); + + Assert.True(cpp.AbortStartup); + Assert.True(cpp.NoExit); + Assert.True(cpp.ShowShortHelp); + Assert.False(cpp.ShowBanner); + Assert.Equal((uint)ConsoleHost.ExitCodeBadCommandLineParameter, cpp.ExitCode); + Assert.Equal(CommandLineParameterParserStrings.MissingCommandParameter, cpp.ErrorMessage); + } + + [Theory] + [InlineData("-encodedcommand", "YQBiAGIAcgBhAA==")] // 'abbra' in Base64 format + [InlineData("-e", "YQBiAGIAcgBhAA==")] + [InlineData("-ec", "YQBiAGIAcgBhAA==")] + public static void TestParameter_EncodedCommand_With_Value(params string[] commandLine) + { + var cpp = new CommandLineParameterParser(); + + cpp.Parse(commandLine); + + Assert.False(cpp.AbortStartup); + Assert.False(cpp.NoExit); + Assert.False(cpp.ShowShortHelp); + Assert.False(cpp.ShowBanner); + Assert.Equal("abbra", cpp.InitialCommand); + Assert.Null(cpp.ErrorMessage); + } + + [Theory] + [InlineData("-encodedcommand", "-")] + [InlineData("-e", "-")] + [InlineData("-ec", "-")] + public static void TestParameter_EncodedCommand_With_Dash(params string[] commandLine) + { + var cpp = new CommandLineParameterParser(); + + cpp.Parse(commandLine); + + Assert.True(cpp.AbortStartup); + Assert.True(cpp.NoExit); + Assert.True(cpp.ShowShortHelp); + Assert.False(cpp.ShowBanner); + Assert.False(cpp.NoPrompt); + Assert.False(cpp.ExplicitReadCommandsFromStdin); + Assert.Equal(CommandLineParameterParserStrings.BadCommandValue, cpp.ErrorMessage); + } + + [Theory] + [InlineData("-encodedcommand", "-", "YQBiAGIAcgBhAA==")] + [InlineData("-e", "-", "YQBiAGIAcgBhAA==")] + [InlineData("-ec", "-", "YQBiAGIAcgBhAA==")] + public static void TestParameter_EncodedCommand_With_Dash_And_Tail(params string[] commandLine) + { + var cpp = new CommandLineParameterParser(); + + cpp.Parse(commandLine); + + Assert.True(cpp.AbortStartup); + Assert.True(cpp.NoExit); + Assert.True(cpp.ShowShortHelp); + Assert.False(cpp.ShowBanner); + Assert.Equal((uint)ConsoleHost.ExitCodeBadCommandLineParameter, cpp.ExitCode); + Assert.Equal(CommandLineParameterParserStrings.BadCommandValue, cpp.ErrorMessage); + } + + [Theory] + [InlineData("-encodedcommand", "-")] + [InlineData("-e", "-")] + [InlineData("-ec", "-")] + public static void TestParameter_EncodedCommand_With_Dash_And_ConsoleInputRedirected(params string[] commandLine) + { + var cpp = new CommandLineParameterParser(); + + cpp.TestHookConsoleInputRedirected = true; + + cpp.Parse(commandLine); + + Assert.True(cpp.AbortStartup); + Assert.True(cpp.NoExit); + Assert.True(cpp.ShowShortHelp); + Assert.False(cpp.ShowBanner); + Assert.Equal((uint)ConsoleHost.ExitCodeBadCommandLineParameter, cpp.ExitCode); + Assert.False(cpp.ExplicitReadCommandsFromStdin); + Assert.Equal(CommandLineParameterParserStrings.BadCommandValue, cpp.ErrorMessage); + } + + [Theory] + [InlineData("-encodedarguments")] + [InlineData("-encodeda")] + [InlineData("-ea")] + public static void TestParameter_EncodedArguments_No_Value(params string[] commandLine) + { + var cpp = new CommandLineParameterParser(); + + cpp.Parse(commandLine); + + Assert.True(cpp.AbortStartup); + Assert.True(cpp.NoExit); + Assert.True(cpp.ShowShortHelp); + Assert.False(cpp.ShowBanner); + Assert.Equal((uint)ConsoleHost.ExitCodeBadCommandLineParameter, cpp.ExitCode); + Assert.Equal(CommandLineParameterParserStrings.MissingArgsValue, cpp.ErrorMessage); + } + + [Theory] + [InlineData("-encodedarguments", "abbra")] + [InlineData("-encodeda", "abbra")] + [InlineData("-ea", "abbra")] + public static void TestParameter_EncodedArguments_With_Wrong_Value(params string[] commandLine) + { + var cpp = new CommandLineParameterParser(); + + cpp.Parse(commandLine); + + Assert.True(cpp.AbortStartup); + Assert.True(cpp.NoExit); + Assert.True(cpp.ShowShortHelp); + Assert.False(cpp.ShowBanner); + Assert.Equal((uint)ConsoleHost.ExitCodeBadCommandLineParameter, cpp.ExitCode); + Assert.Equal(CommandLineParameterParserStrings.BadArgsValue, cpp.ErrorMessage); + } + + [Theory] + [InlineData("-encodedarguments", "PABPAGIAagBzACAAVgBlAHIAcwBpAG8AbgA9ACIAMQAuADEALgAwAC4AMQAiACAAeABtAGwAbgBzAD0AIgBoAHQAdABwADoALwAvAHMAYwBoAGUAbQBhAHMALgBtAGkAYwByAG8AcwBvAGYAdAAuAGMAbwBtAC8AcABvAHcAZQByAHMAaABlAGwAbAAvADIAMAAwADQALwAwADQAIgA+AA0ACgAgACAAPABPAGIAagAgAFIAZQBmAEkAZAA9ACIAMAAiAD4ADQAKACAAIAAgACAAPABUAE4AIABSAGUAZgBJAGQAPQAiADAAIgA+AA0ACgAgACAAIAAgACAAIAA8AFQAPgBTAHkAcwB0AGUAbQAuAEMAbwBsAGwAZQBjAHQAaQBvAG4AcwAuAEEAcgByAGEAeQBMAGkAcwB0ADwALwBUAD4ADQAKACAAIAAgACAAIAAgADwAVAA+AFMAeQBzAHQAZQBtAC4ATwBiAGoAZQBjAHQAPAAvAFQAPgANAAoAIAAgACAAIAA8AC8AVABOAD4ADQAKACAAIAAgACAAPABMAFMAVAA+AA0ACgAgACAAIAAgACAAIAA8AFMAPgAtAGEAYgBiAHIAYQA8AC8AUwA+AA0ACgAgACAAIAAgADwALwBMAFMAVAA+AA0ACgAgACAAPAAvAE8AYgBqAD4ADQAKADwALwBPAGIAagBzAD4A")] // '-abbra' in Base64 format + [InlineData("-encodeda", "PABPAGIAagBzACAAVgBlAHIAcwBpAG8AbgA9ACIAMQAuADEALgAwAC4AMQAiACAAeABtAGwAbgBzAD0AIgBoAHQAdABwADoALwAvAHMAYwBoAGUAbQBhAHMALgBtAGkAYwByAG8AcwBvAGYAdAAuAGMAbwBtAC8AcABvAHcAZQByAHMAaABlAGwAbAAvADIAMAAwADQALwAwADQAIgA+AA0ACgAgACAAPABPAGIAagAgAFIAZQBmAEkAZAA9ACIAMAAiAD4ADQAKACAAIAAgACAAPABUAE4AIABSAGUAZgBJAGQAPQAiADAAIgA+AA0ACgAgACAAIAAgACAAIAA8AFQAPgBTAHkAcwB0AGUAbQAuAEMAbwBsAGwAZQBjAHQAaQBvAG4AcwAuAEEAcgByAGEAeQBMAGkAcwB0ADwALwBUAD4ADQAKACAAIAAgACAAIAAgADwAVAA+AFMAeQBzAHQAZQBtAC4ATwBiAGoAZQBjAHQAPAAvAFQAPgANAAoAIAAgACAAIAA8AC8AVABOAD4ADQAKACAAIAAgACAAPABMAFMAVAA+AA0ACgAgACAAIAAgACAAIAA8AFMAPgAtAGEAYgBiAHIAYQA8AC8AUwA+AA0ACgAgACAAIAAgADwALwBMAFMAVAA+AA0ACgAgACAAPAAvAE8AYgBqAD4ADQAKADwALwBPAGIAagBzAD4A")] + [InlineData("-ea", "PABPAGIAagBzACAAVgBlAHIAcwBpAG8AbgA9ACIAMQAuADEALgAwAC4AMQAiACAAeABtAGwAbgBzAD0AIgBoAHQAdABwADoALwAvAHMAYwBoAGUAbQBhAHMALgBtAGkAYwByAG8AcwBvAGYAdAAuAGMAbwBtAC8AcABvAHcAZQByAHMAaABlAGwAbAAvADIAMAAwADQALwAwADQAIgA+AA0ACgAgACAAPABPAGIAagAgAFIAZQBmAEkAZAA9ACIAMAAiAD4ADQAKACAAIAAgACAAPABUAE4AIABSAGUAZgBJAGQAPQAiADAAIgA+AA0ACgAgACAAIAAgACAAIAA8AFQAPgBTAHkAcwB0AGUAbQAuAEMAbwBsAGwAZQBjAHQAaQBvAG4AcwAuAEEAcgByAGEAeQBMAGkAcwB0ADwALwBUAD4ADQAKACAAIAAgACAAIAAgADwAVAA+AFMAeQBzAHQAZQBtAC4ATwBiAGoAZQBjAHQAPAAvAFQAPgANAAoAIAAgACAAIAA8AC8AVABOAD4ADQAKACAAIAAgACAAPABMAFMAVAA+AA0ACgAgACAAIAAgACAAIAA8AFMAPgAtAGEAYgBiAHIAYQA8AC8AUwA+AA0ACgAgACAAIAAgADwALwBMAFMAVAA+AA0ACgAgACAAPAAvAE8AYgBqAD4ADQAKADwALwBPAGIAagBzAD4A")] + public static void TestParameter_EncodedArguments_With_Value(params string[] commandLine) + { + var cpp = new CommandLineParameterParser(); + + cpp.Parse(commandLine); + + Assert.Null(cpp.ErrorMessage); + Assert.False(cpp.AbortStartup); + Assert.True(cpp.NoExit); + Assert.False(cpp.ShowShortHelp); + Assert.True(cpp.ShowBanner); + Assert.Equal("-abbra", cpp.Args[0].Value); + } + + [Theory] + [InlineData("-settingsfile")] + [InlineData("-settings")] + public static void TestParameter_SettingsFile_No_Value(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.MissingSettingsFileArgument, cpp.ErrorMessage); + } + + [Theory] + [InlineData("-settingsfile", "noexistfilename")] + [InlineData("-settings", "noexistfilename")] + public static void TestParameter_SettingsFile_Not_Exists(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( + string.Format(CommandLineParameterParserStrings.SettingsFileNotExists, Path.GetFullPath("noexistfilename")), + cpp.ErrorMessage); + } + + public class TestDataSettingsFile : IEnumerable + { + private string _fileName = Path.GetTempFileName(); + + public IEnumerator GetEnumerator() + { + yield return new object[] { "-settingsfile", _fileName }; + yield return new object[] { "-settings", _fileName }; + } + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + } + + [Theory] + [ClassData(typeof(TestDataSettingsFile))] + public static void TestParameter_SettingsFile_Exists(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((uint)ConsoleHost.ExitCodeSuccess, cpp.ExitCode); + Assert.Null(cpp.ErrorMessage); + Assert.Equal(commandLine[1], cpp.SettingsFile); + } + + [SkippableTheory] + [InlineData("-sta")] + public static void TestParameter_STA_Not_IsWindowsDesktop(params string[] commandLine) + { + Skip.If(Platform.IsWindowsDesktop); + + 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.STANotImplemented, cpp.ErrorMessage); + } + + [SkippableTheory] + [InlineData("-mta", "-sta")] + public static void TestParameter_STA_And_MTA_Mutually_Exclusive(params string[] commandLine) + { + Skip.IfNot(Platform.IsWindows); + + 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.MtaStaMutuallyExclusive, cpp.ErrorMessage); + } + + [SkippableTheory] + [InlineData("-sta")] + public static void TestParameter_STA(params string[] commandLine) + { + Skip.IfNot(Platform.IsWindows); + + 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.StaMode); + Assert.Null(cpp.ErrorMessage); + } + + [SkippableTheory] + [InlineData("-mta")] + public static void TestParameter_MTA_Not_IsWindowsDesktop(params string[] commandLine) + { + Skip.If(Platform.IsWindowsDesktop); + + 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.MTANotImplemented, cpp.ErrorMessage); + } + + [SkippableTheory] + [InlineData("-sta", "-mta")] + public static void TestParameter_MTA_And_STA_Mutually_Exclusive(params string[] commandLine) + { + Skip.IfNot(Platform.IsWindows); + + 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.MtaStaMutuallyExclusive, cpp.ErrorMessage); + } + + [SkippableTheory] + [InlineData("-mta")] + public static void TestParameter_MTA(params string[] commandLine) + { + Skip.IfNot(Platform.IsWindows); + + 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.StaMode); + Assert.Null(cpp.ErrorMessage); + } + + [Theory] + [InlineData("-workingdirectory")] + [InlineData("-wo")] + [InlineData("-wd")] + public static void TestParameter_WorkingDirectory_No_Value(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.MissingWorkingDirectoryArgument, cpp.ErrorMessage); + } + + [Theory] + [InlineData("-workingdirectory", "dirname")] + [InlineData("-wo", "dirname")] + [InlineData("-wd", "dirname")] + public static void TestParameter_WorkingDirectory(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(commandLine[1], cpp.WorkingDirectory); + Assert.Null(cpp.ErrorMessage); + } + + [SkippableTheory] + [InlineData("-workingdirectory", "dirname", "-removeworkingdirectorytrailingcharacter")] + [InlineData("-wo", "dirname", "-removeworkingdirectorytrailingcharacter")] + [InlineData("-wd", "dirname", "-removeworkingdirectorytrailingcharacter")] + public static void TestParameter_WorkingDirectory_RemoveTrailingCharacter(params string[] commandLine) + { + Skip.IfNot(Platform.IsWindows); + + 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(commandLine[1].Remove(commandLine[1].Length - 1), cpp.WorkingDirectory); + Assert.Null(cpp.ErrorMessage); + } + + public class TestDataLastFile : IEnumerable + { + private string _fileName = Path.GetTempFileName(); + + public IEnumerator GetEnumerator() + { + yield return new object[] { "-noprofile", _fileName }; + } + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + } + + [Theory] + [ClassData(typeof(TestDataLastFile))] + public static void TestParameter_LastParameterIsFileName_Exist(params string[] commandLine) + { + var cpp = new CommandLineParameterParser(); + + cpp.Parse(commandLine); + + Assert.False(cpp.AbortStartup); + Assert.False(cpp.NoExit); + Assert.False(cpp.ShowShortHelp); + Assert.False(cpp.ShowBanner); + Assert.True(cpp.StaMode); + Assert.Equal(CommandLineParameterParser.NormalizeFilePath(commandLine[commandLine.Length - 1]), cpp.File); + Assert.Null(cpp.ErrorMessage); + } + } +}