The problem is that the standard Windows C environment prevents double quotation marks from arguments when parsing the command line. Powershell passes arguments to its own commands by placing double quotes around the arguments, but does not escape the double quotes that are contained in the arguments.
Here's a test program that prints the arguments provided with C stdlib, a raw Windows command line and Windows command line processing (which seems to behave the same with stdlib):
C:\Temp>type tc #include <stdio.h> #include <windows.h> #include <ShellAPI.h> int main(int argc,char **argv){ int i; for(i=0; i < argc; i++) { printf("Arg[%d]: %s\n", i, argv[i]); } LPWSTR *szArglist; LPWSTR cmdLine = GetCommandLineW(); wprintf(L"Command Line: %s\n", cmdLine); int nArgs; szArglist = CommandLineToArgvW(GetCommandLineW(), &nArgs); if( NULL == szArglist ) { wprintf(L"CommandLineToArgvW failed\n"); return 0; } else for( i=0; i<nArgs; i++) printf("%d: %ws\n", i, szArglist[i]); // Free memory allocated for CommandLineToArgvW arguments. LocalFree(szArglist); return 0; } C:\Temp>cl tc "C:\Program Files (x86)\Windows Kits\8.1\lib\winv6.3\um\x86\shell32.lib" Microsoft (R) C/C++ Optimizing Compiler Version 18.00.21005.1 for x86 Copyright (C) Microsoft Corporation. All rights reserved. tc Microsoft (R) Incremental Linker Version 12.00.21005.1 Copyright (C) Microsoft Corporation. All rights reserved. /out:t.exe t.obj "C:\Program Files (x86)\Windows Kits\8.1\lib\winv6.3\um\x86\shell32.lib"
Running this in cmd will see that all unshielded quotes are stripped and spaces only separate arguments when there was an even number of non-exclusive quotes:
C:\Temp>t "a"b" "\"escaped\"" Arg[0]: t Arg[1]: ab "escaped" Command Line: t "a"b" "\"escaped\"" 0: t 1: ab "escaped" C:\Temp>t "a"bc"de" Arg[0]: t Arg[1]: ab Arg[2]: cd e Command Line: t "a"bc"de" 0: t 1: ab 2: cd e
Powershell behaves differently:
C:\Temp>powershell Windows PowerShell Copyright (C) 2012 Microsoft Corporation. All rights reserved. C:\Temp> .\t 'a"b' Arg[0]: C:\Temp\t.exe Arg[1]: ab Command Line: "C:\Temp\t.exe" a"b 0: C:\Temp\t.exe 1: ab C:\Temp> $a = "string with `"double quotes`"" C:\Temp> $a string with "double quotes" C:\Temp> .\t $a nospaces Arg[0]: C:\Temp\t.exe Arg[1]: string with double Arg[2]: quotes Arg[3]: nospaces Command Line: "C:\Temp\t.exe" "string with "double quotes"" nospaces 0: C:\Temp\t.exe 1: string with double 2: quotes 3: nospaces
In Powershell, any argument containing spaces is enclosed in double quotes. Also, the command itself gets quotes, even if there are no spaces. Other arguments are not quoted, even if they include punctuation marks such as double quotes, and, and I think this is a mistake. Powershell does not escape double quotes that appear inside arguments.
If you are interested (I was), Powershell did not even bother to give arguments containing new lines, but argument processing does not consider new lines as whitespace:
C:\Temp> $a = @" >> a >> b >> "@ >> C:\Temp> .\t $a Arg[0]: C:\Temp\t.exe Arg[1]: a b Command Line: "C:\Temp\t.exe" a b 0: C:\Temp\t.exe 1: a b
The only option, since Powershell does not escape quotes, for you, it seems you need to do this yourself:
C:\Temp> .\t 'BEGIN {print "hello"}'.replace('"','\"') Arg[0]: C:\Temp\t.exe Arg[1]: BEGIN {print "hello"} Command Line: "C:\Temp\t.exe" "BEGIN {print \"hello\"}" 0: C:\Temp\t.exe 1: BEGIN {print "hello"}
To avoid this every time, you can define a simple function:
C:\Temp> function run-native($command) { & $command $args.replace('\','\\').replace('"','\"') } C:\Temp> run-native .\t 'BEGIN {print "hello"}' 'And "another"' Arg[0]: C:\Temp\t.exe Arg[1]: BEGIN {print "hello"} Arg[2]: And "another" Command Line: "C:\Temp\t.exe" "BEGIN {print \"hello\"}" "And \"another\"" 0: C:\Temp\t.exe 1: BEGIN {print "hello"} 2: And "another"
NB You must avoid backslashes as well as double quotes, otherwise this will not work ( this does not work, see further editing below ):
C:\Temp> run-native .\t 'BEGIN {print "hello"}' 'And \"another\"' Arg[0]: C:\Temp\t.exe Arg[1]: BEGIN {print "hello"} Arg[2]: And \"another\" Command Line: "C:\Temp\t.exe" "B EGIN {print \"hello\"}" "And \\\"another\\\"" 0: C:\Temp\t.exe 1: BEGIN {print "hello"} 2: And \"another\"
Other editing: Backslash and quote processing in a Microsoft universe is even weirder than I understood. In the end, I had to go and read the C stdlib sources to find out how they interpret backslashes and quotes:
/* Rules: 2N backslashes + " ==> N backslashes and begin/end quote 2N+1 backslashes + " ==> N backslashes + literal " N backslashes ==> N backslashes */
So run-native should be:
function run-native($command) { & $command ($args -replace'(\\*)"','$1$1\"') }
and all backslashes and quotes will withstand command line processing. Or if you want to run a specific command:
filter awk() { $_ | awk.exe ($args -replace'(\\*)"','$1$1\"') }
(updated after the comment by @jhclark: it should be a filter that allows you to create pipelines in stdin).