From 34be39a070893f37a31b98dafa639943e133550e Mon Sep 17 00:00:00 2001 From: SteveL-MSFT Date: Tue, 25 Jul 2017 10:55:50 -0700 Subject: [PATCH 1/2] -File accummulates passed parameters as strings which means $true/$false cannot be passed as a parameter/switch value. Fix is to special case this based on discussion with PS-Committee. --- .../host/msh/CommandLineParameterParser.cs | 40 ++++++++++++++++--- test/powershell/Host/ConsoleHost.Tests.ps1 | 34 +++++++++++++++- 2 files changed, 67 insertions(+), 7 deletions(-) diff --git a/src/Microsoft.PowerShell.ConsoleHost/host/msh/CommandLineParameterParser.cs b/src/Microsoft.PowerShell.ConsoleHost/host/msh/CommandLineParameterParser.cs index 6779580d485..c9e302cba24 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/CommandLineParameterParser.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/CommandLineParameterParser.cs @@ -858,6 +858,19 @@ private bool ParseFile(string[] args, ref int i, bool noexitSeen) // of the script to evaluate. If -file comes before -command, it will // treat -command as an argument to the script... + bool? GetBoolValue(string arg) + { + if (arg.Equals("$true", StringComparison.OrdinalIgnoreCase) || arg.Equals("true", StringComparison.OrdinalIgnoreCase)) + { + return true; + } + else if (arg.Equals("$false", StringComparison.OrdinalIgnoreCase) || arg.Equals("false", StringComparison.OrdinalIgnoreCase)) + { + return false; + } + return null; + } + ++i; if (i >= args.Length) { @@ -922,7 +935,6 @@ private bool ParseFile(string[] args, ref int i, bool noexitSeen) i++; - Regex argPattern = new Regex(@"^.\w+\:", RegexOptions.CultureInvariant); string pendingParameter = null; // Accumulate the arguments to this script... @@ -939,17 +951,25 @@ private bool ParseFile(string[] args, ref int i, bool noexitSeen) } else if (!string.IsNullOrEmpty(arg) && SpecialCharacters.IsDash(arg[0])) { - Match m = argPattern.Match(arg); - if (m.Success) + int offset = arg.IndexOf(':'); + if (offset >= 0) { - int offset = arg.IndexOf(':'); if (offset == arg.Length - 1) { pendingParameter = arg.TrimEnd(':'); } else { - _collectedArgs.Add(new CommandParameter(arg.Substring(0, offset), arg.Substring(offset + 1))); + string argValue = arg.Substring(offset + 1); + bool? boolValue = GetBoolValue(argValue); + if (boolValue != null) + { + _collectedArgs.Add(new CommandParameter(arg.Substring(0, offset), boolValue)); + } + else + { + _collectedArgs.Add(new CommandParameter(arg.Substring(0, offset), argValue)); + } } } else @@ -959,7 +979,15 @@ private bool ParseFile(string[] args, ref int i, bool noexitSeen) } else { - _collectedArgs.Add(new CommandParameter(null, arg)); + bool? boolValue = GetBoolValue(arg); + if (boolValue != null) + { + _collectedArgs.Add(new CommandParameter(null, boolValue)); + } + else + { + _collectedArgs.Add(new CommandParameter(null, arg)); + } } ++i; } diff --git a/test/powershell/Host/ConsoleHost.Tests.ps1 b/test/powershell/Host/ConsoleHost.Tests.ps1 index 1182d4a03e8..7eb12ca9098 100644 --- a/test/powershell/Host/ConsoleHost.Tests.ps1 +++ b/test/powershell/Host/ConsoleHost.Tests.ps1 @@ -201,7 +201,39 @@ Describe "ConsoleHost unit tests" -tags "Feature" { $observed[1] | Should Be "bar" } - It "-File should return exit code from script: " -TestCases @( + It "-File should be able to pass bool values as strings to parameters: " -TestCases @( + @{BoolString = '$truE';BoolValue = 'True'}, + @{BoolString = '$falsE';BoolValue = 'False'}, + @{BoolString = 'truE';BoolValue = 'True'}, + @{BoolString = 'falsE';BoolValue = 'False'} + ) { + param([string]$BoolString, [string]$BoolValue) + Set-Content -Path $testdrive/test.ps1 -Value 'param([bool]$bool) $bool' + $observed = & $powershell -NoProfile -Nologo -File $testdrive/test.ps1 -Bool $BoolString + $observed | Should Be $BoolValue + } + + It "-File should be able to pass bool values as strings to positional parameters: " -TestCases @( + @{BoolString = '$true'; BoolValue = 'True'}, + @{BoolString = '$false'; BoolValue = 'False'} + ) { + param([string]$BoolString, [string]$BoolValue) + Set-Content -Path $testdrive/test.ps1 -Value 'param([bool]$bool) $bool' + $observed = & $powershell -NoProfile -Nologo -File $testdrive/test.ps1 $BoolString + $observed | Should Be $BoolValue + } + + It "-File should be able to pass bool values as strings to switches: " -TestCases @( + @{BoolString = '$true'; BoolValue = 'True'}, + @{BoolString = '$false'; BoolValue = 'False'} + ) { + param([string]$BoolString, [string]$BoolValue) + Set-Content -Path $testdrive/test.ps1 -Value 'param([switch]$switch) $switch.IsPresent' + $observed = & $powershell -NoProfile -Nologo -File $testdrive/test.ps1 -switch:$BoolString + $observed | Should Be $BoolValue + } + + It "-File should return exit code from script" -TestCases @( @{Filename = "test.ps1"}, @{Filename = "test"} ) { From 4ae724ff30b654ed35d436259cb598ff98f6d6c4 Mon Sep 17 00:00:00 2001 From: SteveL-MSFT Date: Fri, 28 Jul 2017 09:45:07 -0700 Subject: [PATCH 2/2] only support $true/$false strings for switches, added more tests, changed to use Tester-Doer pattern --- .../host/msh/CommandLineParameterParser.cs | 27 +++++------- test/powershell/Host/ConsoleHost.Tests.ps1 | 42 +++++++++++-------- 2 files changed, 35 insertions(+), 34 deletions(-) diff --git a/src/Microsoft.PowerShell.ConsoleHost/host/msh/CommandLineParameterParser.cs b/src/Microsoft.PowerShell.ConsoleHost/host/msh/CommandLineParameterParser.cs index c9e302cba24..fd6823d6b2a 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/CommandLineParameterParser.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/CommandLineParameterParser.cs @@ -858,17 +858,20 @@ private bool ParseFile(string[] args, ref int i, bool noexitSeen) // of the script to evaluate. If -file comes before -command, it will // treat -command as an argument to the script... - bool? GetBoolValue(string arg) + bool TryGetBoolValue(string arg, out bool boolValue) { 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)) { - return false; + boolValue = false; + return true; } - return null; + boolValue = false; + return false; } ++i; @@ -961,14 +964,14 @@ private bool ParseFile(string[] args, ref int i, bool noexitSeen) else { string argValue = arg.Substring(offset + 1); - bool? boolValue = GetBoolValue(argValue); - if (boolValue != null) + string argName = arg.Substring(0, offset); + if (TryGetBoolValue(argValue, out bool boolValue)) { - _collectedArgs.Add(new CommandParameter(arg.Substring(0, offset), boolValue)); + _collectedArgs.Add(new CommandParameter(argName, boolValue)); } else { - _collectedArgs.Add(new CommandParameter(arg.Substring(0, offset), argValue)); + _collectedArgs.Add(new CommandParameter(argName, argValue)); } } } @@ -979,15 +982,7 @@ private bool ParseFile(string[] args, ref int i, bool noexitSeen) } else { - bool? boolValue = GetBoolValue(arg); - if (boolValue != null) - { - _collectedArgs.Add(new CommandParameter(null, boolValue)); - } - else - { - _collectedArgs.Add(new CommandParameter(null, arg)); - } + _collectedArgs.Add(new CommandParameter(null, arg)); } ++i; } diff --git a/test/powershell/Host/ConsoleHost.Tests.ps1 b/test/powershell/Host/ConsoleHost.Tests.ps1 index 7eb12ca9098..2c3b8ccaaaf 100644 --- a/test/powershell/Host/ConsoleHost.Tests.ps1 +++ b/test/powershell/Host/ConsoleHost.Tests.ps1 @@ -201,31 +201,37 @@ Describe "ConsoleHost unit tests" -tags "Feature" { $observed[1] | Should Be "bar" } - It "-File should be able to pass bool values as strings to parameters: " -TestCases @( - @{BoolString = '$truE';BoolValue = 'True'}, - @{BoolString = '$falsE';BoolValue = 'False'}, - @{BoolString = 'truE';BoolValue = 'True'}, - @{BoolString = 'falsE';BoolValue = 'False'} - ) { - param([string]$BoolString, [string]$BoolValue) - Set-Content -Path $testdrive/test.ps1 -Value 'param([bool]$bool) $bool' + It "-File should be able to pass bool string values as string to parameters: " -TestCases @( + # validates case is preserved + @{BoolString = '$truE'}, + @{BoolString = '$falSe'}, + @{BoolString = 'trUe'}, + @{BoolString = 'faLse'} + ) { + param([string]$BoolString) + Set-Content -Path $testdrive/test.ps1 -Value 'param([string]$bool) $bool' $observed = & $powershell -NoProfile -Nologo -File $testdrive/test.ps1 -Bool $BoolString - $observed | Should Be $BoolValue + $observed | Should Be $BoolString } - It "-File should be able to pass bool values as strings to positional parameters: " -TestCases @( - @{BoolString = '$true'; BoolValue = 'True'}, - @{BoolString = '$false'; BoolValue = 'False'} + It "-File should be able to pass bool string values as string to positional parameters: " -TestCases @( + # validates case is preserved + @{BoolString = '$tRue'}, + @{BoolString = '$falSe'}, + @{BoolString = 'tRUe'}, + @{BoolString = 'fALse'} ) { - param([string]$BoolString, [string]$BoolValue) - Set-Content -Path $testdrive/test.ps1 -Value 'param([bool]$bool) $bool' + param([string]$BoolString) + Set-Content -Path $testdrive/test.ps1 -Value 'param([string]$bool) $bool' $observed = & $powershell -NoProfile -Nologo -File $testdrive/test.ps1 $BoolString - $observed | Should Be $BoolValue + $observed | Should BeExactly $BoolString } - It "-File should be able to pass bool values as strings to switches: " -TestCases @( - @{BoolString = '$true'; BoolValue = 'True'}, - @{BoolString = '$false'; BoolValue = 'False'} + It "-File should be able to pass bool string values as bool to switches: " -TestCases @( + @{BoolString = '$tRue'; BoolValue = 'True'}, + @{BoolString = '$faLse'; BoolValue = 'False'}, + @{BoolString = 'tRue'; BoolValue = 'True'}, + @{BoolString = 'fAlse'; BoolValue = 'False'} ) { param([string]$BoolString, [string]$BoolValue) Set-Content -Path $testdrive/test.ps1 -Value 'param([switch]$switch) $switch.IsPresent'