What I want to do is pretty simple. I have a PowerShell script that should accept JSON text as a parameter. It looks like it is not simple at all!
I checked these pages in my attempt to learn how it should be done. about_PowerShell_exe, about_Parsing and about_Quoting_Rules. I found no clear explanations. What I see is, actually, contradicts these documents. Command-line rules are not the same as general ones.
I’m on PowerShell 5.1
I use this script for testing:
test.ps1
param($json)
Write-Host $json
I try to pass this JSON text.
{"name" : "value"}
What I see.
powershell .test.ps1 "{"name" : "value"}"
name : value
No braces. Double quotation marks were completely removed.
Let’s try to escape them.
powershell .test.ps1 "{`"name`" : `"value`"}"
Missing closing '}' in statement block or type definition.
The backtick symbol doesn’t help. Let’s try to double quotation marks. Doubling is a very popular trick in command-line magic.
powershell .test.ps1 "{""name"" : ""value""}"
The string is missing the terminator: ".
Let’s try to triple marks, because why not.
powershell .test.ps1 "{"""name""" : """value"""}"
Unexpected token ':' in expression or statement.
OK, let’s try single quotes.
powershell .test.ps1 '{"name" : "value"}'
{name : value}
We’ve got braces, but not double quotation marks. Let’s try to escape them once again.
powershell .test.ps1 '{`"name`" : `"value`"}'
{`name` : `value`}
Something eats our double quotation marks before PS has a chance to escape them.
Doubling?
powershell .test.ps1 '{""name"" : ""value""}'
{name : value}
Tripling?
powershell .test.ps1 '{"""name""" : """value"""}'
{"name" : "value"}
It works! How ugly it is!
I wonder if somebody has a good explanation of what’s going on. I’d appreciate any link on documentation/article that can explain how it actually works. Tripling quotes doesn’t looks too bad for me, but my JSON parameter has several levels of nesting, it looks like I can end up with things like 7 quotes before an element.
-EncodedCommand in powershel.exe gives me a sad feeling that there is no easy solution.
Update. As @mklement0 pointed out, it is a CMD shell that processes this command line first. CMD uses double quotes to handle parameters that have a space in them. Later, Powershell removes pairs of double quotes. Powershell recognizes as an escape character. That means that
- The very first and the very last quote will be removed in any case
- We can use to escape all internal quotes
powershell .test.ps1 "{"name" : "value"}"
Unexpected token ':' in expression or statement.
in this case, this string reached the powershell.exe, {"name" : "value"}
. PowerShell has tried to parse it, and this syntax is not valid. To make it a string, we need another pair of quotes around, and here powershell’s single quotes are very helpful.
powershell .test.ps1 "'{"name" : "value"}'"
{"name" : "value"}
The better solution is to use -File, as @mklement0 suggested. In this case a parameter won’t be interpreted as a PS code.
powershell -File .test.ps1 "{"name" : "value"}"
This update has been edited after @mklement0 comment to be factually correct.
2
Answers
From outside PowerShell, such as from
cmd.exe
/ a batch file, use the following:Note:
-File
is the best way to invoke a script file viapowershell.exe
, the PowerShell CLI, because it doesn’t subject the subsequent arguments to additional interpretation as PowerShell code (which is what happens by default, which implies-Command
).Note that only
"
chars. have syntactic function on the PowerShell command line, and unescaped ones are removed during command-line parsing.For guidance on when to use
-File
vs.-Command
, see this answer.In its CLI arguments, PowerShell requires
"
chars. that should be preserved as part of an argument / command to be escaped as"
(unlike inside a PowerShell session, where it is`"
)."
works robustly on the PowerShell side, but there can be edge cases where calling fromcmd.exe
/ a batch file requires an alternative form of escaping, namely"^""
(sic) or, withpwsh.exe
, the PowerShell (Core) CLI,""
– see this answer for details.Alternative, with non-standard
'...'
JSON quoting (single-quoting):As zett42 points out, PowerShell’s
ConvertFrom-Json
cmdlet – both in Windows PowerShell and PowerShell (Core) as of 7.3.3 – accepts'...'
in lieu of"..."
around property names and string values (escape embedded'
as ”then), which you can use to avoid the need for escaped
"` chars.:Caveat:
The JSON standard does not allow use of
'...'
, so depending on who consumes the JSON this may not work.Notably, the
System.Text.Json
.NET APIs do not support'...'
.There was a plan – seemingly shelved for now – to shift the implementation of
ConvertFrom-Json
andConvertTo-Json
toSystem.Text.Json
, but, as zett42 points out, this would constitute a major breaking change, making this unlikely to happen.Therefore use
'...'
-quoting only if you know that you’ll be usingConvertFrom-Json
or another JSON parser known to support'...'
.As an aside:
It’s best to stay away from other non-standard JSON features, such as the ability to tolerate an extraneous trailing
,
, support for//
comments, and support for unquoted property names (but not string values), given that cross-edition support is not ensured:ConvertFrom-Json
(andConvertTo-Json
) differs from that of PowerShell (Core) 7+: the former uses a custom implementation, the latter is based on the Json.NET libraryIt is only the Json.NET-based PowerShell (Core) implementation that supports these non-standard features (by invariable default)(
System.Text.Json
supports them on an opt-in basis, but does not support'...'
-quoting, as stated, and also not unquoted property names).In addition to these syntax differences, there are behavioral cross-edition differences as well, namely with respect to:
The default .NET integer data type chosen (
[int]
(System.Int32
) vs.[long]
(System.Int64
) – see this answer.How .NET
[datetime]
instances are serialized and deserialized – see this answer.You could use the cmdlets of
ConvertFrom-Json
andConvertTo-Json
to handle your json objects if they are coming from other sources or as strings. Check out the documentationHere is an example of how to convert a string json output to a powershell object that can be better manipulated in your script:
If you were using this pipe setup to pass the json into your function, you could pipe it in after:
If you want your powershell object to be converted back to json you can use
ConvertTo-Json
on the object. These utilities can make it wildly easier to handle the data since powershell handles data as objects which is different than traditional shells (bash) that handle data as streams of bytes.