skip to Main Content

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

  1. The very first and the very last quote will be removed in any case
  2. 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


  1. From outside PowerShell, such as from cmd.exe / a batch file, use the following:

    powershell -File .test.ps1 "{"name" : "value"}"
    

    Note:

    • -File is the best way to invoke a script file via powershell.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 `").

      • Note: " works robustly on the PowerShell side, but there can be edge cases where calling from cmd.exe / a batch file requires an alternative form of escaping, namely "^"" (sic) or, with pwsh.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.:

    :: SEE CAVEAT BELOW
    powershell -File .test.ps1 "{'name' : 'value'}"
    

    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 and ConvertTo-Json to System.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 using ConvertFrom-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:

        • Windows PowerShell‘s implementation of ConvertFrom-Json (and ConvertTo-Json) differs from that of PowerShell (Core) 7+: the former uses a custom implementation, the latter is based on the Json.NET library
      • It 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.

    Login or Signup to reply.
  2. You could use the cmdlets of ConvertFrom-Json and ConvertTo-Json to handle your json objects if they are coming from other sources or as strings. Check out the documentation

    Here is an example of how to convert a string json output to a powershell object that can be better manipulated in your script:

    Command-ThatProducesJsonString | ConvertFrom-Json
    

    If you were using this pipe setup to pass the json into your function, you could pipe it in after:

    Command-ThatProducesJsonString | ConvertFrom-Json | Your-Function
    

    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.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search