-
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\" '
hi
As 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 # OK
Sadly, ""
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 # OK
bash
:
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 " needed
Note 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