14

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

mklement0
  • 312,089
  • 56
  • 508
  • 622
KoenJ
  • 1,043
  • 2
  • 13
  • 24
  • 1
    `the double quotes seem to be stripped.` how do you assert that? – njzk2 Jul 14 '14 at 21:00
  • I created a dummy Console application that just echo's the incoming args. – KoenJ Jul 14 '14 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`](https://github.com/PowerShell/PowerShell/pull/14692) possibly requiring _opt-in_ - assuming it becomes an _official_ feature. – mklement0 May 13 '21 at 13:45
  • If you want to use the payload data from a file, see [this answer](https://stackoverflow.com/a/68677994/6357360). – meJustAndrew Aug 06 '21 at 09:54

4 Answers4

19

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.

mklement0
  • 312,089
  • 56
  • 508
  • 622
Keith Hill
  • 184,219
  • 38
  • 329
  • 358
  • 1
    I also wrote a blog post on this feature a couple of years ago http://rkeithhill.wordpress.com/2012/01/02/powershell-v3-ctp2-provides-better-argument-passing-to-exes/ – Keith Hill Jul 15 '14 at 16:48
  • @StevenPenny, there was a typo in the answer (since corrected); apart from that, it still works, but using `--%`, the [stop-parsing symbol](https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_Parsing) comes at a cost, as discussed in the previous comments. – mklement0 Mar 29 '21 at 20:46
  • @mklement0 even corrected the `--%` operator is pointless, as it still strips out double quotes randomly, and makes for an awful shell experience – Zombo Mar 29 '21 at 22:02
  • @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. – mklement0 Mar 29 '21 at 22:05
  • Update: This worked fine on Powershell 7.2 too – Arindam Roychowdhury Feb 17 '22 at 23:48
3
  • PowerShell's escape character is ` (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, as of PowerShell 7.2.x this is NOT enough, though the experimental PSNativeCommandArgumentPassing feature available since PowerShell Core 7.2.0-preview.5 may fix this at least for some external programs, including curl.exe; read on for details.

  • As of 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 ""-escaping only (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 '"', '\"'
# 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.

  • A - presumably - opt-in fix is now being considered for some future version, post v7.2 - see GitHub issue #14747 - and using it, once available, would obviate the need for the manual escaping.

    • Since PowerShell Core 7.2.0-preview.5, experimental feature PSNativeCommandArgumentPassing with an attempted fix is available, but, unfortunately, it looks like it will lack important accommodations for high-profile CLIs on Windows (though curl.exe wouldn't be affected) - 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 opt-in 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
'@
mklement0
  • 312,089
  • 56
  • 508
  • 622
  • 1
    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 – Zombo Mar 28 '21 at 03:08
  • 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](https://github.com/PowerShell/PowerShell/issues/14747#issuecomment-782892238) remains to be seen. That you don't like the reality of the situation is not a shortcoming of this answer. – mklement0 Mar 28 '21 at 03:17
0

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

hazartilirot
  • 75
  • 1
  • 9
-1

Set the content type:

curl -H "Content-Type: application/json" -d '{"password":"keypass"}' https://build.phonegap.com/api/v1/key
David Pullar
  • 696
  • 6
  • 18
  • 2
    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.. – KoenJ Jul 14 '14 at 21:14
  • 3
    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`. – MortenB Mar 13 '20 at 15:09
  • 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+](https://github.com/PowerShell/PowerShell/blob/master/README.md), and does invoke `curl.exe` there. – mklement0 Mar 29 '21 at 21:07