20

I'm trying to pass a JSON string from within a powershell script to the build.phonegap.com api, using curl.
According to phonegap's forum, when running on a Windows machine, the JSON data has to be formatted as:

curl.exe -ku user@email:mypass -X PUT -d "data={\"password\":\"keypass\"}" https://build.phonegap.com/api/v1/key

Indeed, this does run fine when invoked from the command line.
However, when I try to invoke this from within a powershell script, the double quotes seem to be stripped.

So far, I have tried:

  • Putting the JSON in single quoted string:
curl.exe -ku user@email:mypass -X PUT -d '"data={\"password\":\"keypass\"}"'  https://build.phonegap.com/api/v1/key
  • Putting the JSON in single quoted string, without the DOS escape backslashes:
curl.exe -ku user@email:mypass -X PUT -d '"data={"password":"keypass"}"'  https://build.phonegap.com/api/v1/key
  • Putting the JSON in single quoted string, escaping the double quotes and backslashes (DOS style with a backslash):
curl.exe -ku user@email:mypass -X PUT -d '\"data={\\\"password\\\":\\\"keypass\\\"}\"'  https://build.phonegap.com/api/v1/key
  • Putting the JSON in a double quoted string, escaping the double quotes with the powershell backtick character (`):
curl.exe -ku user@email:mypass -X PUT -d "`"data={\`"password\`":\`"build*2014`\`"}`""  https://build.phonegap.com/api/v1/key

Any idea how to achieve this?

Thanks for your time, Koen

4
  • 2
    the double quotes seem to be stripped. how do you assert that? Commented Jul 14, 2014 at 21:00
  • I created a dummy Console application that just echo's the incoming args. Commented Jul 14, 2014 at 21:08
  • Revisiting your other solution attempts: The last one should actually work, although the inner enclosing " aren't necessary, and while the workaround with the additional \ -escaping is effective, it (a) shouldn't be necessary and (b) would break if and when the underlying PowerShell problem is fixed, though this may be mitigated by the fix - currently (since PowerShell Core 7.2.0-preview.5) only available as experimental feature PSNativeCommandArgumentPassing possibly requiring opt-in - assuming it becomes an official feature. Commented May 13, 2021 at 13:45
  • If you want to use the payload data from a file, see this answer. Commented Aug 6, 2021 at 9:54

6 Answers 6

23

Try using the --% operator to put PowerShell into simple (dumb) argument parsing mode:

curl.exe --% -ku user@email:mypass -X PUT -d "data={\"password\":\"keypass\"}" https://build.phonegap.com/api/v1/key

This is quite often useful for invoking exes with argument syntax that runs afoul of PowerShell's argument syntax. This does require PowerShell V3 or higher.

Sign up to request clarification or add additional context in comments.

5 Comments

I also wrote a blog post on this feature a couple of years ago rkeithhill.wordpress.com/2012/01/02/…
@StevenPenny, there was a typo in the answer (since corrected); apart from that, it still works, but using --%, the stop-parsing symbol comes at a cost, as discussed in the previous comments.
@mklement0 even corrected the --% operator is pointless, as it still strips out double quotes randomly, and makes for an awful shell experience
@StevenPenny Yes, it makes for an awful shell experience, and plenty of pointers have now been provided as to why and how. No, it doesn't strip out double quotes randomly.
Update: This worked fine on Powershell 7.2 too
14

Update:

  • PowerShell (Core) 7 (in v7.3+) mostly fixed the problem, with selective exceptions on Windows - see this answer and further below for details.

  • For cross-version, cross-edition, cross-platform code, the Native module discussed below may still be of interest.


tl;dr:

In Windows PowerShell (and PowerShell (Core) 7 up to the NOW OBSOLETE v7.2.x), you unfortunately must manually \-escape embedded " characters in arguments passed to external programs.

# From inside PowerShell:
# Note the outer '...' quoting and the unexpected need to escape
# the embedded " chars. as \" (unexpected, because PowerShell itself doesn't
# require " inside '...' to be escaped; also, PowerShell's escape char. is `).
# If outer "..." quoting must be used, use \`" (sic) to escape the embeded "
curl.exe -ku user@email:mypass -X PUT -d 'data={\"password\":\"keypass\"}' https://build.phonegap.com/api/v1/key

Read on for why that is necessary.


  • PowerShell's escape character is ` (the so-called backtick), so in order to embed " characters in a "..." (double-quoted, interpolating) string, use `" (or "") rather than \"; by contrast, inside a '...' (single-quoted, verbatim) string, " need not be escaped.

    • In your attempt, PowerShell didn't see \" as an escaped " and therefore saw multiple "..." strings, which ultimately - when PowerShell of necessity applied its on demand re-quoting behind the scenes, passed two separate string arguments that individually didn't need double-quoting, due to not containing spaces, namely: verbatim data={\ and password\:\keypass\}

    • Using PowerShell's quoting rules, you should have used:

      • either:

        • "data={`"password`":`"keypass`"}"
      • or, more simply, given that no string interpolation is needed, via a verbatim, single-quoted string, inside of which " chars. don't require escaping:

        • 'data={"password":"keypass"}'
      • Unfortunately, however, up to PowerShell 7.2.x this is NOT enough; read on for details.

  • Up to PowerShell 7.2.x, an unexpected extra layer of escaping of embedded " characters is needed, using \-escaping when calling (most) external programs:

    • In Windows PowerShell there are edge cases where this approach doesn't work, in which case use of --% is required (see below). Notably, escaping '"foo bar"' as '\"foo bar\"' doesn't work, due to the enclosing \" being at the very start and end of the string - see this answer for details.

    • Also, some external programs on Windows understand only ""-escaping (e.g. msiexec); for them, use -replace '"', '""' in order to programmatically perform the extra escaping, assuming the value contains at least one space. Do the same for programs that do not support embedded " chars. at all (WSH), so that the embedded " at least do not break argument boundaries (but they will be stripped).

    • For programs that expect \"-escaping, use the following -replace operation to robustly perform the extra escaping programmatically:

      • '...' -replace '(\\*)"', '$1$1\"'
      • If the input string contains no preexisting verbatim \" sequences, you can simplify to '...' -replace '"', '\"' or, for better performance, '...'.Replace('"', '\"')
# Note: Escaping the embedded " chars. as `" is enough for PowerShell itself,
#       but, unfortunately, not when calling *external programs*.
#       The `-replace` operation performs the necessary additional \-escaping.
$passwd = 'foo'
curl.exe -ku user@email:mypass -X PUT -d (
  "data={`"password`": `"$passwd`"}" -replace '(\\*)"', '$1$1\"'
) https://build.phonegap.com/api/v1/key
  • This shouldn't be required, but is due to a bug since v1 that hasn't been fixed for fear of breaking backward compatibility - see this answer.

  • PowerShell v7.3 mostly fixed the issue, but with selective exceptions on Windows.

    • The selective exceptions - see the description of the $PSNativeCommandArgumentPassing preference variable - are unfortunate, because they retain the old, broken behavior for the programs on the exception list, notably cmd.exe and the WSH (Windows Scripting Host) excecutables as well as their associated script files (.cmd, .bat, .js, .vbs, .wsf), with the risk of future, piecemeal additions to the exception list - see this summary from GitHub issue #18660

    • Sadly, an opportunity to avoid the need for these exceptions - via behind-the-scenes accommodations for high-profile CLIs on Windows - was passed up; see this summary from GitHub issue #15143.

  • A backward- and forward-compatible helper function is the ie function from the Native module (Install-Module Native), which obviates the need for the extra escaping, contains important accommodations for high-profile CLIs on Windows, and will continue to work as expected even with the fix in place:
    ie curl.exe ... -d "data={`"password`": `"$passwd`"}" ... )

  • Using --%, the stop-parsing symbol, as in Keith Hill's answer is a suboptimal workaround that also doesn't require the extra \-escaping, however:

    • --% has inherent limitations - see GitHub docs issue #6149 - and is virtually useless on Unix-like platforms - see GitHub docs issue #4963.
    • The only - awkward and side effect-producing - way to embed PowerShell variable values in the arguments following --% is to (a) define them as environment variables (e.g., $env:passwd = 'foo') and (b) to reference these variables cmd.exe-style, even on Unix (e.g., %passwd%).
  • An alternative workaround - especially if you need to include the values of PowerShell variables or expressions in your calls - is to call via cmd /c with a single argument containing the entire command line; for quoting convenience, the following example uses a here-string (see the bottom section of this answer for an overview of PowerShell's string literals):

# Use @"<newline>...<newline>"@ if you need to embed PowerShell variables / expressions.
cmd /c @'
curl.exe -ku user@email:mypass -X PUT -d "data={\"password\":"\keypass\"}" https://build.phonegap.com/api/v1/key
'@

9 Comments

Thanks for the detailed answer, but frankly none of these are acceptable solutions. I want to run a command like this: curl.exe -d '{"north": "east west"}' https://reqbin.com/echo/post/json without all the extra escaping. Since I am using single quotes, it should work. With Bash, you can use single quotes instead of escaping. The fact that PowerShell cant do this is pathetic
The answer lays out the reality of the situation - as much as you (and I) may dislike it - and provides the best workarounds currently available. I agree that PowerShell's shortcoming in this area is very unfortunate, and I've been working hard to get them to fix it. An opt-in fix may finally be coming, but to what degree it will fix the problems on Windows remains to be seen. That you don't like the reality of the situation is not a shortcoming of this answer.
It from the haydays of cmd.exe doing magical things, and powershell trying to be compatible to it, its all an unfortunate tragedy of the commons
@LuizFelipe, ultimately Windows is to blame here, for making applications do their own command-line parsing, opening the door for everyone to make up their own rules. cmd.exe certainly made up wacky rules, and PowerShell added its own broken behavior. cmd.exe is no longer actively maintained, so my hope was that PowerShell would not only fix its own mistakes, but compensate for cmd.exe's - alas, only the former will happen and possibly even require opt-in. Base64 encoding is fine (powershell.exe supports it too), but to be useful you need a simple way to create it from the calling shell.
@LuizFelipe, not special-case, unfortunately (which, when done right, would be a blessing): the current plan is to keep the old, broken behavior (broken on the PowerShell side).
|
2

Finally found the solution. Istead of using one " use 3 of them ("""), and thats it. So it would be:

data={"""password""":"""keypass"""}

3 Comments

beautiful pwsh -nop -c ' bash -c ''printf """""""\\033[38;2;255;0;0mHi\\033[0m"""""""'' ' . thank you
@LuizFelipe, the use of PowerShell's CLI (powershell.exe, pwsh) with the -Command (-c) parameter introduces additional quoting considerations, which are separate from what this question is about. In short: In order to pass a " through as part of a -Command argument(s) from outside PowerShell on Windows, use \" . In edge cases, when calling from cmd.exe, use "^"" with powershell.exe and "" with pwsh.exe - see this answer.
Stuck back on PSVersion 5.1.19041.2673 and had to finally use the triple quotes from command line. Have not tried from script....
0

Here's how I did manage to send a json request in PowerShell 7.2 enter image description here

1 Comment

Your answer repeats the techniques from preexisting answers, without explanation, buried in a lot of incidental information.
0

I found the following way to be more readable than escaping the char and work fine inside scripts:

curl.exe -ku user@email:mypass -X PUT -d $(@{ password="keypass" } | convertto-json -compress) https://build.phonegap.com/api/v1/key

The $( ) is executed before the curl itself, converting the hashtable object to json.

Microsoft documentation on the convertto-json: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/convertto-json?view=powershell-7.4

1 Comment

Indeed it is more convenient and robust to construct the payload as a hashtable or custom object and let ConvertTo-Json convert it to JSON, but that is unrelated to the problem at hand: In Windows PowerShell and PowerShell 7.2- you still need to additionally \-escape the " chars. in the resulting JSON string when calling external programs. As an aside: (...) will do and is preferable to $(...), as it has no side effects.
-1

Set the content type:

curl -H "Content-Type: application/json" -d '{"password":"keypass"}' https://build.phonegap.com/api/v1/key

3 Comments

That doesn't make any difference.. Note that the command works fine when invoked from the command line, so the issue is not in the arguments being passed; it has something to do with Powershell and escape characters..
If you call curl in powershell it is an alias for Invoke-WebRequest, and that is not what you want, you should use curl.exe. You can see all aliases with Get-Alias.
Good point, @MortenB - that is definitely a requirement in Windows PowerShell (PowerShell versions up to v5.1). By contrast, curl is no longer a built-in alias in PowerShell's cross-platform edition, PowerShell (Core) 7+, and does invoke curl.exe there.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.