From d58304027a2929cf45fc11364e8f8a59c15e983b Mon Sep 17 00:00:00 2001 From: SteveL-MSFT Date: Thu, 15 Jun 2017 10:27:41 -0700 Subject: [PATCH 1/3] Previously powershell.exe treated unknown arguments as a command line to execute. To align with POSIX so that things like shebang scripts work correctly, we are changing powershell.exe so that it treats unknown arguments (aka positional argument) as a file. This means that `powershell foo` will now attempt to use `foo` as a PowerShell script whereas previously `foo` would be treated as a command to execute. This doesn't affect existing usage of either `-File` nor `-Command`. Fixed tests that didn't explicitly use `-Command` parameter. --- .../host/msh/CommandLineParameterParser.cs | 242 +++++++++--------- .../host/msh/ConsoleHost.cs | 12 +- .../resources/ManagedEntranceStrings.resx | 85 +++--- test/powershell/Host/ConsoleHost.Tests.ps1 | 40 ++- .../Invoke-Item.Tests.ps1 | 4 +- .../Start-Transcript.Tests.ps1 | 8 +- test/powershell/SDK/PSDebugging.Tests.ps1 | 16 +- 7 files changed, 221 insertions(+), 186 deletions(-) diff --git a/src/Microsoft.PowerShell.ConsoleHost/host/msh/CommandLineParameterParser.cs b/src/Microsoft.PowerShell.ConsoleHost/host/msh/CommandLineParameterParser.cs index ba4ca508a4c..6779580d485 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/CommandLineParameterParser.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/CommandLineParameterParser.cs @@ -499,10 +499,10 @@ private void ParseHelper(string[] args) if (!SpecialCharacters.IsDash(switchKey[0]) && switchKey[0] != '/') { - // then its a command + // then its a file --i; - ParseCommand(args, ref i, noexitSeen, false); + ParseFile(args, ref i, noexitSeen); break; } @@ -620,126 +620,10 @@ private void ParseHelper(string[] args) #endif else if (MatchSwitch(switchKey, "file", "f")) { - // 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... - - ++i; - if (i >= args.Length) + if (!ParseFile(args, ref i, noexitSeen)) { - WriteCommandLineError( - CommandLineParameterParserStrings.MissingFileArgument, - showHelp: true, - showBanner: true); break; } - - // Don't show the startup banner unless -noexit has been specified. - if (!noexitSeen) - _showBanner = false; - - // Process interactive input... - if (args[i] == "-") - { - // the arg to -file is -, which is secret code for "read the commands from stdin with prompts" - - _explicitReadCommandsFromStdin = true; - _noPrompt = false; - } - else - { - // Exit on script completion unless -noexit was specified... - if (!noexitSeen) - _noExit = false; - - // 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 - { - // Normalize slashes - _file = args[i].Replace(StringLiterals.AlternatePathSeparator, - StringLiterals.DefaultPathSeparator); - _file = Path.GetFullPath(_file); - } - catch (Exception e) - { - // 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), - showBanner: true); - break; - } - - if (!Path.GetExtension(_file).Equals(".ps1", StringComparison.OrdinalIgnoreCase)) - { - WriteCommandLineError( - string.Format(CultureInfo.CurrentCulture, CommandLineParameterParserStrings.InvalidFileArgumentExtension, args[i]), - showBanner: true); - break; - } - - if (!System.IO.File.Exists(_file)) - { - WriteCommandLineError( - string.Format(CultureInfo.CurrentCulture, CommandLineParameterParserStrings.ArgumentFileDoesNotExist, args[i]), - showBanner: true); - break; - } - - i++; - - Regex argPattern = new Regex(@"^.\w+\:", RegexOptions.CultureInvariant); - string pendingParameter = null; - - // Accumulate the arguments to this script... - while (i < args.Length) - { - string arg = args[i]; - - // If there was a pending parameter, add a named parameter - // using the pending parameter and current argument - if (pendingParameter != null) - { - _collectedArgs.Add(new CommandParameter(pendingParameter, arg)); - pendingParameter = null; - } - else if (!string.IsNullOrEmpty(arg) && SpecialCharacters.IsDash(arg[0])) - { - Match m = argPattern.Match(arg); - if (m.Success) - { - 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))); - } - } - else - { - _collectedArgs.Add(new CommandParameter(arg)); - } - } - else - { - _collectedArgs.Add(new CommandParameter(null, arg)); - } - ++i; - } - } - break; } #if DEBUG // this option is useful when debugging ConsoleHost remotely using VS remote debugging, as you can only @@ -858,10 +742,10 @@ private void ParseHelper(string[] args) #endif else { - // The first parameter we fail to recognize marks the beginning of the command string. + // The first parameter we fail to recognize marks the beginning of the file string. --i; - if (!ParseCommand(args, ref i, noexitSeen, false)) + if (!ParseFile(args, ref i, noexitSeen)) { break; } @@ -967,6 +851,122 @@ private void ParseExecutionPolicy(string[] args, ref int i, ref string execution executionPolicy = args[i]; } + 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... + + ++i; + if (i >= args.Length) + { + WriteCommandLineError( + CommandLineParameterParserStrings.MissingFileArgument, + showHelp: true, + showBanner: true); + return false; + } + + // Don't show the startup banner unless -noexit has been specified. + if (!noexitSeen) + _showBanner = false; + + // Process interactive input... + if (args[i] == "-") + { + // the arg to -file is -, which is secret code for "read the commands from stdin with prompts" + + _explicitReadCommandsFromStdin = true; + _noPrompt = false; + } + else + { + // Exit on script completion unless -noexit was specified... + if (!noexitSeen) + _noExit = false; + + // 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 + { + // Normalize slashes + _file = args[i].Replace(StringLiterals.AlternatePathSeparator, + StringLiterals.DefaultPathSeparator); + _file = Path.GetFullPath(_file); + } + catch (Exception e) + { + // 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), + showBanner: true); + return false; + } + + if (!System.IO.File.Exists(_file)) + { + WriteCommandLineError( + string.Format(CultureInfo.CurrentCulture, CommandLineParameterParserStrings.ArgumentFileDoesNotExist, args[i]), + showBanner: true); + return false; + } + + i++; + + Regex argPattern = new Regex(@"^.\w+\:", RegexOptions.CultureInvariant); + string pendingParameter = null; + + // Accumulate the arguments to this script... + while (i < args.Length) + { + string arg = args[i]; + + // If there was a pending parameter, add a named parameter + // using the pending parameter and current argument + if (pendingParameter != null) + { + _collectedArgs.Add(new CommandParameter(pendingParameter, arg)); + pendingParameter = null; + } + else if (!string.IsNullOrEmpty(arg) && SpecialCharacters.IsDash(arg[0])) + { + Match m = argPattern.Match(arg); + if (m.Success) + { + 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))); + } + } + else + { + _collectedArgs.Add(new CommandParameter(arg)); + } + } + else + { + _collectedArgs.Add(new CommandParameter(null, arg)); + } + ++i; + } + } + return true; + } + private bool ParseCommand(string[] args, ref int i, bool noexitSeen, bool isEncoded) { if (_commandLineCommand != null) diff --git a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHost.cs b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHost.cs index 5dc6229832e..55367de4ac7 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHost.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHost.cs @@ -1820,7 +1820,17 @@ private void DoRunspaceInitialization(bool importSystemModules, bool skipProfile s_tracer.WriteLine("running -file '{0}'", filePath); Pipeline tempPipeline = exec.CreatePipeline(); - Command c = new Command(filePath, false, false); + Command c; + // if file doesn't have .ps1 extension, we read the contents and treat it as a script to support shebang with no .ps1 extension usage + if (!Path.GetExtension(filePath).Equals(".ps1", StringComparison.OrdinalIgnoreCase)) + { + string script = File.ReadAllText(filePath); + c = new Command(script, isScript: true, useLocalScope: false); + } + else + { + c = new Command(filePath, false, false); + } tempPipeline.Commands.Add(c); if (initialCommandArgs != null) diff --git a/src/Microsoft.PowerShell.ConsoleHost/resources/ManagedEntranceStrings.resx b/src/Microsoft.PowerShell.ConsoleHost/resources/ManagedEntranceStrings.resx index e8cfe094c5b..99117e20456 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/resources/ManagedEntranceStrings.resx +++ b/src/Microsoft.PowerShell.ConsoleHost/resources/ManagedEntranceStrings.resx @@ -1,17 +1,17 @@  - @@ -131,9 +131,9 @@ Copyright (C) Microsoft Corporation. All rights reserved. [-InputFormat {Text | XML}] [-OutputFormat {Text | XML}] [-WindowStyle <style>] [-EncodedCommand <Base64EncodedCommand>] [-ConfigurationName <string>] - [-File <filePath> <args>] [-ExecutionPolicy <ExecutionPolicy>] [-Command { - | <script-block> [-args <arg-array>] | <string> [<CommandParameters>] } ] + [-File <filePath> <args>] [-ExecutionPolicy <ExecutionPolicy>] PowerShell[.exe] -Help | -? | /? @@ -142,7 +142,7 @@ PowerShell[.exe] -Help | -? | /? file, use Export-Console in Windows PowerShell. -Version - Starts the specified version of Windows PowerShell. + Starts the specified version of Windows PowerShell. Enter a version number with the parameter, such as "-version 2.0". -NoLogo @@ -179,8 +179,8 @@ PowerShell[.exe] -Help | -? | /? Sets the window style to Normal, Minimized, Maximized or Hidden. -EncodedCommand - Accepts a base-64-encoded string version of a command. Use this parameter - to submit commands to Windows PowerShell that require complex quotation + Accepts a base-64-encoded string version of a command. Use this parameter + to submit commands to Windows PowerShell that require complex quotation marks or curly braces. -ConfigurationName @@ -188,24 +188,24 @@ PowerShell[.exe] -Help | -? | /? This can be any endpoint registered on the local machine including the default Windows PowerShell remoting endpoints or a custom endpoint having specific user role capabilities. - + -File - Runs the specified script in the local scope ("dot-sourced"), so that the - functions and variables that the script creates are available in the - current session. Enter the script file path and any parameters. - File must be the last parameter in the command, because all characters - typed after the File parameter name are interpreted + Runs the specified script in the local scope ("dot-sourced"), so that the + functions and variables that the script creates are available in the + current session. Enter the script file path and any parameters. + File must be the last parameter in the command, because all characters + typed after the File parameter name are interpreted as the script file path followed by the script parameters. -ExecutionPolicy - Sets the default execution policy for the current session and saves it - in the $env:PSExecutionPolicyPreference environment variable. - This parameter does not change the Windows PowerShell execution policy + Sets the default execution policy for the current session and saves it + in the $env:PSExecutionPolicyPreference environment variable. + This parameter does not change the Windows PowerShell execution policy that is set in the registry. -Command Executes the specified commands (and any parameters) as though they were - typed at the Windows PowerShell command prompt, and then exits, unless + typed at the Windows PowerShell command prompt, and then exits, unless NoExit is specified. The value of Command can be "-", a string. or a script block. @@ -218,7 +218,7 @@ PowerShell[.exe] -Help | -? | /? parent shell as deserialized XML objects, not live objects. If the value of Command is a string, Command must be the last parameter - in the command , because any characters typed after the command are + in the command , because any characters typed after the command are interpreted as the command arguments. To write a string that runs a Windows PowerShell command, use the format: @@ -237,6 +237,7 @@ EXAMPLES PowerShell -ConfigurationName AdminRoles PowerShell -Command {Get-EventLog -LogName security} PowerShell -Command "& {Get-EventLog -LogName security}" + PowerShell HelloWorld.ps1 # To use the -EncodedCommand parameter: $command = 'dir "c:\program files" ' diff --git a/test/powershell/Host/ConsoleHost.Tests.ps1 b/test/powershell/Host/ConsoleHost.Tests.ps1 index 50dd8fb17df..a00ce883903 100644 --- a/test/powershell/Host/ConsoleHost.Tests.ps1 +++ b/test/powershell/Host/ConsoleHost.Tests.ps1 @@ -176,6 +176,30 @@ Describe "ConsoleHost unit tests" -tags "Feature" { # no extraneous output $observed | should be $currentVersion } + + It "-File should be default parameter" { + Set-Content -Path $testdrive/test -Value "'hello'" + $observed = & $powershell $testdrive/test + $observed | Should Be "hello" + } + + It "-File accepts scripts with and without .ps1 extension" -TestCases @( + @{Filename="test.ps1"}, + @{Filename="test"} + ) { + param($Filename) + Set-Content -Path $testdrive/$Filename -Value "'hello'" + $observed = & $powershell -File $testdrive/$Filename + $observed | Should Be "hello" + } + + It "-File should pass additional arguments to script" { + Set-Content -Path $testdrive/script.ps1 -Value 'foreach($arg in $args){$arg}' + $observed = & $powershell $testdrive/script.ps1 foo bar + $observed.Count | Should Be 2 + $observed[0] | Should Be "foo" + $observed[1] | Should Be "bar" + } } Context "Pipe to/from powershell" { @@ -204,7 +228,7 @@ Describe "ConsoleHost unit tests" -tags "Feature" { Context "Redirected standard output" { It "Simple redirected output" { - $si = NewProcessStartInfo "-noprofile 1+1" + $si = NewProcessStartInfo "-noprofile -c 1+1" $process = RunPowerShell $si $process.StandardOutput.ReadToEnd() | Should Be 2 EnsureChildHasExited $process @@ -217,14 +241,14 @@ Describe "ConsoleHost unit tests" -tags "Feature" { # So none of these tests should close StandardInput It "Redirected input w/ implicit -Command w/ -NonInteractive" { - $si = NewProcessStartInfo "-NonInteractive -noprofile 1+1" -RedirectStdIn + $si = NewProcessStartInfo "-NonInteractive -noprofile -c 1+1" -RedirectStdIn $process = RunPowerShell $si $process.StandardOutput.ReadToEnd() | Should Be 2 EnsureChildHasExited $process } It "Redirected input w/ implicit -Command w/o -NonInteractive" { - $si = NewProcessStartInfo "-noprofile 1+1" -RedirectStdIn + $si = NewProcessStartInfo "-noprofile -c 1+1" -RedirectStdIn $process = RunPowerShell $si $process.StandardOutput.ReadToEnd() | Should Be 2 EnsureChildHasExited $process @@ -295,7 +319,7 @@ Describe "ConsoleHost unit tests" -tags "Feature" { } It "Interactive redirected input w/ initial command" { - $si = NewProcessStartInfo "-noprofile -noexit ""`$function:prompt = { 'PS> ' }""" -RedirectStdIn + $si = NewProcessStartInfo "-noprofile -noexit -c ""`$function:prompt = { 'PS> ' }""" -RedirectStdIn $process = RunPowerShell $si $process.StandardInput.Write("1+1`n") $process.StandardOutput.ReadLine() | Should Be "PS> 1+1" @@ -309,7 +333,7 @@ Describe "ConsoleHost unit tests" -tags "Feature" { } It "Redirected input explicit prompting (-File -)" { - $si = NewProcessStartInfo "-noprofile -File -" -RedirectStdIn + $si = NewProcessStartInfo "-noprofile -" -RedirectStdIn $process = RunPowerShell $si $process.StandardInput.Write("`$function:prompt = { 'PS> ' }`n") $null = $process.StandardOutput.ReadLine() @@ -322,7 +346,7 @@ Describe "ConsoleHost unit tests" -tags "Feature" { } It "Redirected input no prompting (-Command -)" { - $si = NewProcessStartInfo "-noprofile -" -RedirectStdIn + $si = NewProcessStartInfo "-noprofile -Command -" -RedirectStdIn $process = RunPowerShell $si $process.StandardInput.Write("1+1`n") $process.StandardOutput.ReadLine() | Should Be "2" @@ -355,7 +379,7 @@ foo } It "Redirected input w/ nested prompt" { - $si = NewProcessStartInfo "-noprofile -noexit ""`$function:prompt = { 'PS' + ('>'*(`$nestedPromptLevel+1)) + ' ' }""" -RedirectStdIn + $si = NewProcessStartInfo "-noprofile -noexit -c ""`$function:prompt = { 'PS' + ('>'*(`$nestedPromptLevel+1)) + ' ' }""" -RedirectStdIn $process = RunPowerShell $si $process.StandardInput.Write("`$host.EnterNestedPrompt()`n") $process.StandardOutput.ReadLine() | Should Be "PS> `$host.EnterNestedPrompt()" @@ -397,7 +421,7 @@ foo AfterEach { $env:XDG_CACHE_HOME = $XDG_CACHE_HOME $env:XDG_DATA_HOME = $XDG_DATA_HOME - $env:XDG_CONFIG_HOME = $XDG_CONFIG_HOME + $env:XDG_CONFIG_HOME = $XDG_CONFIG_HOME } It "Should start if Data, Config, and Cache location is not accessible" -skip:($IsWindows) { diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/Invoke-Item.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/Invoke-Item.Tests.ps1 index 9954f5d94aa..731d9a085f7 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/Invoke-Item.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/Invoke-Item.Tests.ps1 @@ -52,10 +52,10 @@ Describe "Invoke-Item basic tests" -Tags "CI" { if ($IsWindows) { ## 'ping.exe' on Windows writes out usage to stdout. - & $powershell "-noprofile" "Invoke-Item '$executable'" > $redirectFile + & $powershell -noprofile -c "Invoke-Item '$executable'" > $redirectFile } else { ## 'ping' on Unix write out usage to stderr - & $powershell "-noprofile" "Invoke-Item '$executable'" 2> $redirectFile + & $powershell -noprofile -c "Invoke-Item '$executable'" 2> $redirectFile } Get-Content $redirectFile -Raw | Should Match "usage: ping" } diff --git a/test/powershell/Modules/Microsoft.Powershell.Host/Start-Transcript.Tests.ps1 b/test/powershell/Modules/Microsoft.Powershell.Host/Start-Transcript.Tests.ps1 index cfcb7de91e8..39dbd9f04a8 100644 --- a/test/powershell/Modules/Microsoft.Powershell.Host/Start-Transcript.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.Powershell.Host/Start-Transcript.Tests.ps1 @@ -110,7 +110,7 @@ Describe "Start-Transcript, Stop-Transcript tests" -tags "CI" { try{ $ps = [powershell]::Create() $ps.addscript("Start-Transcript -path $transcriptFilePath").Invoke() - $ps.addscript('$rs = [system.management.automation.runspaces.runspacefactory]::CreateRunspace()').Invoke() + $ps.addscript('$rs = [system.management.automation.runspaces.runspacefactory]::CreateRunspace()').Invoke() $ps.addscript('$rs.open()').Invoke() $ps.addscript('$rs.Dispose()').Invoke() $ps.addscript('Write-Host "After Dispose"').Invoke() @@ -121,16 +121,16 @@ Describe "Start-Transcript, Stop-Transcript tests" -tags "CI" { } } - + Test-Path $transcriptFilePath | Should be $true $transcriptFilePath | Should contain "After Dispose" } It "Transcription should be closed if the only runspace gets closed" { $powerShellPath = [System.Diagnostics.Process]::GetCurrentProcess().Path - $powerShellCommand = $powerShellPath + ' "start-transcript $transcriptFilePath; Write-Host ''Before Dispose'';"' + $powerShellCommand = $powerShellPath + ' -c "start-transcript $transcriptFilePath; Write-Host ''Before Dispose'';"' Invoke-Expression $powerShellCommand - + Test-Path $transcriptFilePath | Should be $true $transcriptFilePath | Should contain "Before Dispose" $transcriptFilePath | Should contain "Windows PowerShell transcript end" diff --git a/test/powershell/SDK/PSDebugging.Tests.ps1 b/test/powershell/SDK/PSDebugging.Tests.ps1 index 8b3818c6678..f161bcadbfc 100644 --- a/test/powershell/SDK/PSDebugging.Tests.ps1 +++ b/test/powershell/SDK/PSDebugging.Tests.ps1 @@ -38,7 +38,7 @@ Describe "PowerShell Command Debugging" -tags "CI" { } It "Should be able to step into debugging" { - $debugfn = NewProcessStartInfo "-noprofile ""`$function:foo = { 'bar' }""" -RedirectStdIn + $debugfn = NewProcessStartInfo "-noprofile -c ""`$function:foo = { 'bar' }""" -RedirectStdIn $process = RunPowerShell $debugfn $process.StandardInput.Write("Set-PsBreakpoint -command foo`n") @@ -57,7 +57,7 @@ Describe "PowerShell Command Debugging" -tags "CI" { } It "Should be able to continue into debugging" { - $debugfn = NewProcessStartInfo "-noprofile ""`$function:foo = { 'bar' }""" -RedirectStdIn + $debugfn = NewProcessStartInfo "-noprofile -c ""`$function:foo = { 'bar' }""" -RedirectStdIn $process = RunPowerShell $debugfn $process.StandardInput.Write("Set-PsBreakpoint -command foo`n") @@ -74,7 +74,7 @@ Describe "PowerShell Command Debugging" -tags "CI" { } It -Pending "Should be able to list help for debugging" { - $debugfn = NewProcessStartInfo "-noprofile ""`$function:foo = { 'bar' }""" -RedirectStdIn + $debugfn = NewProcessStartInfo "-noprofile -c ""`$function:foo = { 'bar' }""" -RedirectStdIn $process = RunPowerShell $debugfn $process.StandardInput.Write("Set-PsBreakpoint -command foo`n") @@ -97,7 +97,7 @@ Describe "PowerShell Command Debugging" -tags "CI" { It "Should be able to step over debugging" { - $debugfn = NewProcessStartInfo "-noprofile ""`$function:foo = { 'bar' }""" -RedirectStdIn + $debugfn = NewProcessStartInfo "-noprofile -c ""`$function:foo = { 'bar' }""" -RedirectStdIn $process = RunPowerShell $debugfn $process.StandardInput.Write("Set-PsBreakpoint -command foo`n") @@ -114,7 +114,7 @@ Describe "PowerShell Command Debugging" -tags "CI" { It "Should be able to step out of debugging" { - $debugfn = NewProcessStartInfo "-noprofile ""`$function:foo = { 'bar' }""" -RedirectStdIn + $debugfn = NewProcessStartInfo "-noprofile -c ""`$function:foo = { 'bar' }""" -RedirectStdIn $process = RunPowerShell $debugfn $process.StandardInput.Write("Set-PsBreakpoint -command foo`n") @@ -128,7 +128,7 @@ Describe "PowerShell Command Debugging" -tags "CI" { } It "Should be able to quit debugging" { - $debugfn = NewProcessStartInfo "-noprofile ""`$function:foo = { 'bar' }""" -RedirectStdIn + $debugfn = NewProcessStartInfo "-noprofile -c ""`$function:foo = { 'bar' }""" -RedirectStdIn $process = RunPowerShell $debugfn $process.StandardInput.Write("Set-PsBreakpoint -command foo`n") @@ -142,7 +142,7 @@ Describe "PowerShell Command Debugging" -tags "CI" { } It -Pending "Should be able to list source code in debugging" { - $debugfn = NewProcessStartInfo "-noprofile ""`$function:foo = { 'bar' }""" -RedirectStdIn + $debugfn = NewProcessStartInfo "-noprofile -c ""`$function:foo = { 'bar' }""" -RedirectStdIn $process = RunPowerShell $debugfn $process.StandardInput.Write("Set-PsBreakpoint -command foo`n") | Write-Host @@ -165,7 +165,7 @@ Describe "PowerShell Command Debugging" -tags "CI" { It -Pending "Should be able to get the call stack in debugging" { - $debugfn = NewProcessStartInfo "-noprofile ""`$function:foo = { 'bar' }""" -RedirectStdIn + $debugfn = NewProcessStartInfo "-noprofile -c ""`$function:foo = { 'bar' }""" -RedirectStdIn $process = RunPowerShell $debugfn $process.StandardInput.Write("Set-PsBreakpoint -command foo`n") | Write-Host From 3d1a5bc89b58edda277f306a15d38e08055666a0 Mon Sep 17 00:00:00 2001 From: SteveL-MSFT Date: Thu, 15 Jun 2017 12:07:57 -0700 Subject: [PATCH 2/3] Fixed a MacOS specific test that needed `-c` added --- .../Modules/Microsoft.PowerShell.Utility/Invoke-Item.Tests.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/Invoke-Item.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/Invoke-Item.Tests.ps1 index 731d9a085f7..52a4198685a 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/Invoke-Item.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/Invoke-Item.Tests.ps1 @@ -33,7 +33,7 @@ Describe "Invoke-Item basic tests" -Tags "CI" { ## Redirect stderr to a file. So if 'open' failed to open the text file, an error ## message from 'open' would be written to the redirection file. - $proc = Start-Process -FilePath $powershell -ArgumentList "-noprofile Invoke-Item '$TestFile'" ` + $proc = Start-Process -FilePath $powershell -ArgumentList "-noprofile -c Invoke-Item '$TestFile'" ` -RedirectStandardError $redirectErr ` -PassThru $proc.WaitForExit(3000) > $null From a7a249733594e8f2f27f98d732eef2ca65018ac2 Mon Sep 17 00:00:00 2001 From: "Steve Lee [MSFT]" Date: Thu, 15 Jun 2017 20:34:56 -0700 Subject: [PATCH 3/3] reverted automatic stripping of trailing whitespace is resx added test to validate exit code from script --- .../resources/ManagedEntranceStrings.resx | 82 +++++++++---------- test/powershell/Host/ConsoleHost.Tests.ps1 | 10 +++ 2 files changed, 51 insertions(+), 41 deletions(-) diff --git a/src/Microsoft.PowerShell.ConsoleHost/resources/ManagedEntranceStrings.resx b/src/Microsoft.PowerShell.ConsoleHost/resources/ManagedEntranceStrings.resx index 99117e20456..9cb65ba4723 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/resources/ManagedEntranceStrings.resx +++ b/src/Microsoft.PowerShell.ConsoleHost/resources/ManagedEntranceStrings.resx @@ -1,17 +1,17 @@  - @@ -142,7 +142,7 @@ PowerShell[.exe] -Help | -? | /? file, use Export-Console in Windows PowerShell. -Version - Starts the specified version of Windows PowerShell. + Starts the specified version of Windows PowerShell. Enter a version number with the parameter, such as "-version 2.0". -NoLogo @@ -179,8 +179,8 @@ PowerShell[.exe] -Help | -? | /? Sets the window style to Normal, Minimized, Maximized or Hidden. -EncodedCommand - Accepts a base-64-encoded string version of a command. Use this parameter - to submit commands to Windows PowerShell that require complex quotation + Accepts a base-64-encoded string version of a command. Use this parameter + to submit commands to Windows PowerShell that require complex quotation marks or curly braces. -ConfigurationName @@ -188,24 +188,24 @@ PowerShell[.exe] -Help | -? | /? This can be any endpoint registered on the local machine including the default Windows PowerShell remoting endpoints or a custom endpoint having specific user role capabilities. - + -File - Runs the specified script in the local scope ("dot-sourced"), so that the - functions and variables that the script creates are available in the - current session. Enter the script file path and any parameters. - File must be the last parameter in the command, because all characters - typed after the File parameter name are interpreted + Runs the specified script in the local scope ("dot-sourced"), so that the + functions and variables that the script creates are available in the + current session. Enter the script file path and any parameters. + File must be the last parameter in the command, because all characters + typed after the File parameter name are interpreted as the script file path followed by the script parameters. -ExecutionPolicy - Sets the default execution policy for the current session and saves it - in the $env:PSExecutionPolicyPreference environment variable. - This parameter does not change the Windows PowerShell execution policy + Sets the default execution policy for the current session and saves it + in the $env:PSExecutionPolicyPreference environment variable. + This parameter does not change the Windows PowerShell execution policy that is set in the registry. -Command Executes the specified commands (and any parameters) as though they were - typed at the Windows PowerShell command prompt, and then exits, unless + typed at the Windows PowerShell command prompt, and then exits, unless NoExit is specified. The value of Command can be "-", a string. or a script block. @@ -218,7 +218,7 @@ PowerShell[.exe] -Help | -? | /? parent shell as deserialized XML objects, not live objects. If the value of Command is a string, Command must be the last parameter - in the command , because any characters typed after the command are + in the command , because any characters typed after the command are interpreted as the command arguments. To write a string that runs a Windows PowerShell command, use the format: diff --git a/test/powershell/Host/ConsoleHost.Tests.ps1 b/test/powershell/Host/ConsoleHost.Tests.ps1 index a00ce883903..0f87e4cf602 100644 --- a/test/powershell/Host/ConsoleHost.Tests.ps1 +++ b/test/powershell/Host/ConsoleHost.Tests.ps1 @@ -200,6 +200,16 @@ Describe "ConsoleHost unit tests" -tags "Feature" { $observed[0] | Should Be "foo" $observed[1] | Should Be "bar" } + + It "-File should return exit code from script" -TestCases @( + @{Filename = "test.ps1"}, + @{Filename = "test"} + ) { + param($Filename) + Set-Content -Path $testdrive/$Filename -Value 'exit 123' + & $powershell $testdrive/$Filename + $LASTEXITCODE | Should Be 123 + } } Context "Pipe to/from powershell" {