diff --git a/.gitignore b/.gitignore index 1d151847f6d..61dd6cb62f7 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ project.lock.json /debug/ /staging/ /Packages/ +*.nuget.props # dotnet cli install/uninstall scripts dotnet-install.ps1 diff --git a/CHANGELOG.md b/CHANGELOG.md index d17e37647e4..81bb2128e56 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ Changelog Unreleased ---------- +- Fix passing escaped double quoted spaces to native executables + v6.0.0-alpha.9 - 2016-08-15 --------------------------- diff --git a/build.psm1 b/build.psm1 index ad3891a66dc..0bee2ed8a4d 100644 --- a/build.psm1 +++ b/build.psm1 @@ -547,6 +547,21 @@ function Get-PesterTag { $o } +function Publish-PSTestTools { + [CmdletBinding()] + param() + + Find-Dotnet + + # Publish EchoArgs so it can be run by tests + Push-Location "$PSScriptRoot/test/tools/EchoArgs" + try { + dotnet publish --output bin + } finally { + Pop-Location + } +} + function Start-PSPester { [CmdletBinding()] param( @@ -563,6 +578,7 @@ function Start-PSPester { ) Write-Verbose "Running pester tests at '$path' with tag '$($Tag -join ''', ''')' and ExcludeTag '$($ExcludeTag -join ''', ''')'" -Verbose + Publish-PSTestTools # All concatenated commands/arguments are suffixed with the delimiter (space) $Command = "" diff --git a/src/System.Management.Automation/engine/NativeCommandParameterBinder.cs b/src/System.Management.Automation/engine/NativeCommandParameterBinder.cs index 14083cb80ac..62e282ca377 100644 --- a/src/System.Management.Automation/engine/NativeCommandParameterBinder.cs +++ b/src/System.Management.Automation/engine/NativeCommandParameterBinder.cs @@ -194,11 +194,11 @@ private void appendOneNativeArgument(ExecutionContext context, object obj, char // just the normal double quotes, no other special quotes. Also note that mismatched // quotes are supported. - bool needQuotes = false; + bool needQuotes = false, followingBackslash = false; int quoteCount = 0; for (int i = 0; i < arg.Length; i++) { - if (arg[i] == '"') + if (arg[i] == '"' && !followingBackslash) { quoteCount += 1; } @@ -206,6 +206,8 @@ private void appendOneNativeArgument(ExecutionContext context, object obj, char { needQuotes = true; } + + followingBackslash = arg[i] == '\\'; } if (needQuotes) diff --git a/test/powershell/Scripting/NativeExecution/NativeCommandArguments.Tests.ps1 b/test/powershell/Scripting/NativeExecution/NativeCommandArguments.Tests.ps1 new file mode 100644 index 00000000000..a55d56ed44d --- /dev/null +++ b/test/powershell/Scripting/NativeExecution/NativeCommandArguments.Tests.ps1 @@ -0,0 +1,41 @@ +Describe "Native Command Arguments" -tags "CI" { + # Find where test/powershell is so we can find the echoargs command relative to it + $powershellTestDir = $PSScriptRoot + while ($powershellTestDir -notmatch 'test[\\/]powershell$') { + $powershellTestDir = Split-Path $powershellTestDir + } + $echoArgs = Join-Path (Split-Path $powershellTestDir) tools/EchoArgs/bin/echoargs + + # When passing arguments to native commands, quoted segments that contain + # spaces need to be quoted with '"' characters when they are passed to the + # native command (or to bash or sh on Linux). + # + # This test checks that the proper quoting is occuring by passing arguments + # to the echoargs native command and looking at how it got the arguments. + It "Should handle quoted spaces correctly" { + $a = 'a"b c"d' + $lines = & $echoArgs $a 'a"b c"d' a"b c"d + ($lines | measure).Count | Should Be 3 + $lines[0] | Should Be 'Arg 0 is ' + $lines[1] | Should Be 'Arg 1 is ' + $lines[2] | Should Be 'Arg 2 is ' + } + + # In order to pass '"' characters so they are actually part of command line + # arguments for native commands, they need to be escaped with a '\' (this + # is in addition to the '`' escaping needed inside '"' quoted strings in + # PowerShell). + # + # This functionality was broken in PowerShell 5.0 and 5.1, so this test + # will fail on those versions unless the fix is backported to them. + # + # This test checks that the proper quoting and escaping is occurring by + # passing arguments with escaped quotes to the echoargs native command and + # looking at how it got the arguments. + It "Should handle spaces between escaped quotes" { + $lines = & $echoArgs 'a\"b c\"d' "a\`"b c\`"d" + ($lines | measure).Count | Should Be 2 + $lines[0] | Should Be 'Arg 0 is ' + $lines[1] | Should Be 'Arg 1 is ' + } +} diff --git a/test/tools/EchoArgs/EchoArgs.cs b/test/tools/EchoArgs/EchoArgs.cs new file mode 100644 index 00000000000..209d5d86155 --- /dev/null +++ b/test/tools/EchoArgs/EchoArgs.cs @@ -0,0 +1,25 @@ +//--------------------------------------------------------------------- +// Author: Keith Hill +// Source: https://github.com/Pscx/Pscx/blob/master/Src/EchoArgs/EchoArgs.cs +// +// Description: Very simple little console class that you can use to see +// how PowerShell is passing parameters to legacy console +// apps. +// +// Creation Date: March 06, 2006 +//--------------------------------------------------------------------- +using System; + +namespace Pscx.Applications +{ + class EchoArgs + { + static void Main(string[] args) + { + for (int i = 0; i < args.Length; i++) + { + Console.WriteLine("Arg {0} is <{1}>", i, args[i]); + } + } + } +} diff --git a/test/tools/EchoArgs/project.json b/test/tools/EchoArgs/project.json new file mode 100644 index 00000000000..106dcc46a71 --- /dev/null +++ b/test/tools/EchoArgs/project.json @@ -0,0 +1,28 @@ +{ + "name": "echoargs", + "version": "1.0.0-*", + "description": "Very simple little console class that you can use to see how PowerShell is passing parameters to legacy console apps.", + + "buildOptions": { + "emitEntryPoint": true + }, + + "frameworks": { + "netcoreapp1.0": { + "dependencies": { + "Microsoft.NETCore.App": "1.0.0" + } + } + }, + + "runtimes": { + "ubuntu.16.04-x64": { }, + "ubuntu.14.04-x64": { }, + "debian.8-x64": { }, + "centos.7-x64": { }, + "win7-x64": { }, + "win81-x64": { }, + "win10-x64": { }, + "osx.10.11-x64": { } + } +}