tl;dr
Your overall "..."
-enclosed JSON string has embedded "
, which must be escaped as \"
(sic; command simplified):
powershell.exe -File "C:\...\customscript.PS1" ... -jsonContent "{ \"c\": \"some setting\", \"d\": \"unknown\", \"b\": \"some thing\", \"a\": 1 }"
Read on for when additional escaping is needed, how -File
invocation differs from -Command
invocation, and in what way the calling shell (where you call powershell.exe
from) matters.
Note:
This answer primarily discusses use of the Windows PowerShell executable, powershell.exe
, but it applies analogously to the PowerShell Core executable, pwsh
, and there's a section on calling from bash
at the bottom.
The section Calling from PowerShell itself below, specifically the syntax required with -File
, applies to passing JSON to other programs such as curl.exe
as well.
The required syntax for the PowerShell CLI - that is, invoking powershell.exe
with arguments - depends on:
whether you're calling from cmd.exe
(Command Prompt / batch file) or from PowerShell itself (or, in PowerShell Core from a POSIX-like shell such as bash
).
whether you pass arguments to powershell -Command
(inline command) or
powerShell -File
(script path).
Either way, your original attempt could not have worked, because literal { "c": "some setting" ... }
cannot be recognized as a single argument, due to containing whitespace and not being enclosed in quotes overall; the command added later, with enclosing "..."
, lacks escaping of the embedded "
.
The following commands demonstrate the required syntax for the scenarios discussed, using a simplified JSON string.
To make the -File
commands runnable, create a script.ps1
file in the current dir. with the following content: ConvertFrom-Json $Args[0]
Calling from cmd.exe
/ a batch file
Embedded "
must be escaped as \"
(even though PowerShell-internally you'd use `"
).
Important:
- If the JSON text contains
cmd.exe
metacharacters (invariably between \"...\"
runs), you must ^
-escape them individually, because cmd.exe
, due to not recognizing \"
as an escaped "
, considers these substrings unquoted; e.g., \"some & setting\"
must be escaped as \"some ^& setting\"
; the cmd.exe
metacharacters that need escaping here are:
& | < > ^
cmd.exe
-style environment-variable references such as %USERNAME%
are interpolated - cmd.exe
has no literal string syntax, it only recognizes "..."
, where interpolation does take place, just as in unquoted tokens.
If you want to pass such a token as-is, i.e., to suppress interpolation, the escaping syntax depends on whether you're calling from the command line or a batch file, sadly: use %^USERNAME%
from the former, and %%USERNAME%%
from the latter - see this answer for the gory details.
Note how the -Command
calls simply add another layer of quoting, by enclosing the "..."
string in '...'
. This is required, because with -Command
PowerShell treats the arguments it receives as PowerShell source code rather than as literal arguments (the latter is what happens with -File
); if it weren't for the enclosing '...'
, the overall enclosing "..."
would be stripped before interpretation.
With -File
:
# With a literal string:
powershell -File ./script.ps1 "{ \"c\": \"some setting\", \"unknown\": \"b\" }"
# With an expandable string (expanded by the caller):
powershell -File ./script.ps1 "{ \"c\": \"some %USERNAME%\", \"unknown\": \"b\" }"
With -Command
:
# With a literal string:
powershell -Command ConvertFrom-Json '"{ \"c\": \"some setting\", \"unknown\": \"b\" }"'
# With an expandable string (expanded by the caller):
powershell -Command ConvertFrom-Json '"{ \"c\": \"some %USERNAME%\", \"unknown\": \"b\" }"'
Calling from PowerShell itself
Calling from PowerShell makes the need to escape cmd.exe
metacharacters go away, because cmd.exe
is not involved.
PowerShell's string-quoting rules apply, which simplifies matters, although, sadly, you still need to manually \
-escape embedded "
chars.; see this GitHub issue for background.
Update: PowerShell Core 7.2.0-preview.5 introduced an experimental feature, PSNativeCommandArgumentPassing
, which obviates the need for this manual \
-escaping; even though it is very much to be hoped for in this case, experimental features aren't guaranteed to become regular features; as of PowerShell Core 7.2.0-preview.5, the feature is a step in the right direction, but is both buggy and lacks important accommodations for CLIs on Windows - see GitHub issue #15143.
Using outer '...'
quoting simplifies the syntax for the embedded quoting, but that limits you to passing literal strings.
Using outer "..."
allows you to embed variable references and expressions from the caller (which are expanded by the caller, before the argument is passed), but it complicates the syntax, given that an embedded "
must then be doubly escaped as \`"
(sic): first with `
to conform to PowerShell-internal syntax, then with \
to satisfy the PowerShell CLI's requirements.
If your JSON text is not a literal and stored in a variable, you must pass
$jsonVar -replace '"', '\"'
to perform the necessary escaping - see this answer.
With -File
or when calling external programs such as curl.exe
:
# With a literal string:
powershell -File ./script.ps1 '{ \"c\": \"some setting\", \"unknown\": \"b\" }'
# With an expandable string (expanded by the caller):
powershell -File ./script.ps1 "{ \`"c\`": \`"some $env:OS\`", \`"unknown\`": \`"b\`" }"
With -Command
:
# With a literal string:
powershell -Command ConvertFrom-Json '''"{ \"c\": \"some setting\", \"unknown\": \"b\" }"'''
# With an expandable string (expanded by the caller):
powershell -Command ConvertFrom-Json "'{ \`"c\`": \`"some $env:OS\`", \`"unknown\`": \`"b\`" }'"
PowerShell Core: Calling from bash
Bash, like PowerShell, understands both expanding (interpolating) "..."
strings and literal '...'
strings.
Bash, unlike cmd.exe
, recognizes \"
as escaped "
chars. inside "..."
, so there's no need to escape any of Bash's metacharacters.
With -File
:
# With a literal string:
pwsh -File ./script.ps1 '{ "c": "some setting", "unknown": "b" }'
# With an expandable string (expanded by the caller):
pwsh -File ./script.ps1 "{ \"c\": \"some $USER\", \"unknown\": \"b\" }"
With -Command
:
# With a literal string:
pwsh -Command ConvertFrom-Json \''{ "c": "some setting", "unknown": "b" }'\'
# With an expandable string (expanded by the caller):
pwsh -Command ConvertFrom-Json "'{ \"c\": \"some $USER\", \"unknown\": \"b\" }'"