Reputation: 348
I'm passing the following as an argument to powershell (v4 on w7):
-debugWrite -fileName SettingsFile -jsonContent { "c": "some setting", "d": "unknown", "b": "some thing", "a": 1 }
But PS gets hung up on the JSON. I've tried delimiting the \double-quotes\ and putting everything after -jsonContent in 'single quotes', but to no avail.
Here is the Windows 7 (PS4) environment PS is running in:
note: "..." obfuscation refers to the same dir. IOW, all files live in the same directory.
A batch file is run, kicking off the whole thing:
"C:\...\script.bat" > "C:\...\script-output.txt" 2>&1
This runs script.bat
and outputs to script-output.txt
. script.bat
is a long 1-liner:
%WINDIR%\sysnative\windowspowershell\v1.0\powershell.exe -ExecutionPolicy Bypass -File "C:\...\customscript.PS1" --% -fileName DropFileToCreate -jsonContent "{ "c": "some setting", "d": "unknown", "b": "some thing", "a": 1 }" -debugWrite
Legend:
DropFileToCreate
- the filename passed to the PS script, used to create a file in same dir.
-jsonContent
- A named parameter in the script (see below for header of customscript.PS1
)
In the above example, the JSON is:
"{ "c": "some setting", "d": "unknown", "b": "some thing", "a": 1 }"
-debugWrite
- A switch parameter (used here to enable Write-Host debugging)
Finally, a bit of the customscript.PS1
:
Param (
[Parameter(Mandatory = $True)]
[String]
$fileName,
[Parameter(Mandatory = $True)]
$jsonContent,
[Parameter(Mandatory = $False)]
[Switch]
$debugWrite = $False
)
[...]
The JSON is easier seen, and spaces explained, if expressed as:
{
"c": "some setting",
"d": "unknown",
"b": "some thing",
"a": 1
}
Upvotes: 3
Views: 17728
Reputation: 3357
I would suggest using a variable to pass the string. For the example JSON given, it avoids escaping entirely. ConvertFrom-Json
can be used as a test app.
First in PowerShell, observe
PS C:\> $psJson = '{ "c": "some setting", "d": "unknown", "b": "some thing", "a": 1 }'
PS C:\> ConvertFrom-Json $psJson
c d b a
- - - -
some setting unknown some thing 1
From CMD, we can achieve the same result. When PowerShell is opened from a batch, it inherits the environment. Instead of passing the string, set an environment variable and use it like a global
C:\>set dosJson={ "c": "some setting", "d": "unknown", "b": "some thing", "a": 1 }
C:\>powershell -command "& { ConvertFrom-Json $env:dosJson }"
c d b a
- - - -
some setting unknown some thing 1
EDIT:
It should be pointed out that this works for -Command
and not -File
, because the PowerShell environment is not available to the -File
args. Of course you can run a script inside a -Command
. Using a trivial test script test.ps1
C:\>type test.ps1
param ([string] $json)
ConvertFrom-Json $json
C:\>powershell -command "& { C:\test.ps1 $env:dosJson }"
c d b a
- - - -
some setting unknown some thing 1
So the script.bat
in the question would have to be changed to -Command
.
Upvotes: 3
Reputation: 437111
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]
cmd.exe
/ a batch fileEmbedded "
must be escaped as \"
(even though PowerShell-internally you'd use `"
).
Important:
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 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\`" }'"
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\" }'"
Upvotes: 16