Skip to content

Document the need for additional \-escaping of double quotes (") when calling external programs #2361

@mklement0

Description

@mklement0

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.

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

Metadata

Metadata

Labels

Pri2Priority - Mediumarea-native-cmdsArea - native command supportup-for-grabsTag - issue is open for any contributor to resolve

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions