-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Description
Related: #5152; once PowerShell/PowerShell#1995 gets fixed, this topic will become obsolete.
An additional layer of escaping of " chars. is unexpectedly needed when calling external programs from PowerShell to make sure that the external program sees " embedded in "..." as \", which is what PowerShell's own CLI expects too.
This is currently not documented, as far I can tell, neither in about_Parsing nor in about_Quoting_Rules nor in about_Special_Characters
Update: As an alternative to the manual \-escaping detailed below, you can use the PSv3+ ie helper function from the Native module (in PSv5+, install with Install-Module Native from the PowerShell Gallery), which internally compensates for all broken behavior and allows passing arguments as expected; to use it, simply prepend ie to your invocations; e.g.:
ie pwsh -noprofile -command ' "hi" ' works as expected.
Calling an external program from PowerShell (the external program just happens to be another instance of PowerShell (pwsh) in the following examples):
In order to pass string literal "hi" as a command to an external program from PowerShell, PowerShell's own escaping of embedded " chars. must unexpectedly be supplemented with \-escaping (the following commands are equivalent):
# !! The \-escaping of the " chars. should NOT be necessary; without it, these commands would fail.
PS> pwsh -noprofile -command " \""hi\"" "
PS> pwsh -noprofile -command " \`"hi\`" "
PS> pwsh -noprofile -command ' \"hi\" '
hiAs an aside: If you're really calling another PowerShell instance from inside PowerShell, using a script block ({ ... }) to pass the command avoids all quoting headaches and additionally enables support for typed results (not just strings), albeit with the same limitations on type fidelity as with background jobs / remoting.
Note: There are also a number of bugs:
-
Empty-string arguments are quietly removed from the invocation.
-
Values with embedded
"are passed incorrectly: in addition to not automatically escaping them (the problem shown above), the situationally necessary enclosure in"..."behind the scenes is not reliably triggered, such as with
3" of snow. -
In Windows PowerShell only, values with spaces that end in a
\char. are passed incorrectly.
For details and workarounds, see PowerShell/PowerShell#1995 (comment)
Calling the PowerShell CLI from another shell - cmd.exe or bash:
Note: The difficulties discussed next stem primarily from cmd.exe limitations and how commands are invoked by a single command-line string on Window.
Calling from Bash / POSIX-like shells works robustly and as expected.
cmd.exe:
On Windows, PowerShell Core now properly recognizes as "" as escaped ", which enables robust escaping, given that "" is also recognized by cmd.exe itself:
# PowerShell *Core* only, on Windows only: use "", which works robustly.
C:\> pwsh.exe -noprofile -command " ""hi & dry"" "
hi & dry # OKSadly, "" doesn't work in Windows PowerShell, where \-escaping for PowerShell's sake is also required, which causes problems:
Using \"" (sic) doesn't require escaping of cmd.exe metachars., but doesn't preserve whitespace as-is:
# No extra escaping needed, but whitespace is normalized.
C:\> powershell.exe -noprofile -command " \""hi & dry\"" "
hi & dry # !! Two spaces were collapsed into one.Only using \" preserves whitespace as-is, but it additionally requires individual ^-escaping of the following cmd.exe metacharacters inside \"...\" runs: & | < > ^
# Whitespace is faithfully preserved, but cmd.exe metachars. must be ^-escaped
C:\> powershell.exe -noprofile -command " \"hi ^& dry\" "
hi & dry # OKbash:
Use \" inside "...." strings, and no escaping at all inside '...' strings:
$ pwsh -noprofile -command " \"hi\" " # \-escaping needed for Bash's own sake (nesting ")
$ pwsh -noprofile -command ' "hi" ' # !! NO escaping of " neededNote that on Unix-like platforms PowerShell's own command-line parsing does not come into play (arguments are invariably passed as an array of literal tokens), and the above commands solely use Bash [non]-escaping to pass an argument with literal contents "hi" .
Thus:
\"is Bash's native way to escape"inside a"..."string.- Inside
'...',"needs no escaping- Caveat: if you tried
\"inside'...', the\chars. would be passed as literals, and the PowerShell command would break.
- Caveat: if you tried
This asymmetry with how things work on Windows is unfortunate, but unavoidable.
Note that calling from PowerShell on Unix-like platforms still requires the extra \-escaping:
PS> bash -c 'echo "one two" ' # !! only prints 'one', because the " are *ignored*
PS> bash -c 'echo \"one two\" ' # OK, but the \-escaping shouldn't be necessary.While this requirement at least makes for consistent behavior across platforms from the PowerShell side, it is certainly unexpected - and an artificial requirement - for anyone familiar with Unix shell scripting.
The bottom line is:
-
In an ideal world, PowerShell would transparently handle any additional, platform-specific escaping needs for interfacing with external programs behind the scenes, so that all that users need to take care of - on any supported platform - is to follow PowerShell's escaping rules.
-
Sadly, this is not an option if backward compatibility must be preserved.
Version(s) of document impacted
- Impacts 6.1 document
- Impacts 6.0 document
- Impacts 5.1 document
- Impacts 5.0 document
- Impacts 4.0 document
- Impacts 3.0 document