17

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
  • 382,024
  • 64
  • 607
  • 775
KoenJ
  • 1,093
  • 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

5 Answers5

20

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
  • 382,024
  • 64
  • 607
  • 775
Keith Hill
  • 194,368
  • 42
  • 353
  • 369
  • 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://learn.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
  • 1
    Update: This worked fine on Powershell 7.2 too – Arindam Roychowdhury Feb 17 '22 at 23:48
11

Update:

  • PowerShell 7.3.0 mostly fixed the problem, with selective exceptions on Windows - see this answer for details.

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

tl;dr:

In Windows PowerShell and PowerShell (Core) up to 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 ""-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.

  • 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
'@
mklement0
  • 382,024
  • 64
  • 607
  • 775
  • 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
  • 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 – Luiz Felipe Jul 27 '22 at 15:36
  • perhaps all programs should support a base64 encoding of their arguments/command line like pwsh does – Luiz Felipe Jul 27 '22 at 15:38
  • 1
    @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. – mklement0 Jul 27 '22 at 20:36
  • yes it is, but that boat sailed some couple of decades ago. It appears they are going to do that with the new option in the next version, they are going to special case cmd.exe and other old utils. – Luiz Felipe Aug 05 '22 at 16:37
  • 1
    @LuizFelipe, not special-case, unfortunately (which, [when done right, would be a blessing](https://github.com/PowerShell/PowerShell/issues/15143)): the current plan is to keep the old, _broken_ behavior (broken on the PowerShell side). – mklement0 Aug 05 '22 at 16:43
  • I get why keeping the old behavior, but can't they at least make the readline respect the new behavior and only use the old behavior for scripts ? that way you get retro-compatibility and the new behavior when you use the shell from the keyboard. So you have the cake and eat it too. – Luiz Felipe Aug 10 '22 at 02:59
  • @LuizFelipe, I think that selectively keeping the old, badly broken behavior is a terrible idea, especially knowing that a near-perfect fix is available - doing so will ensure continued headaches, both for newcomers and even for seasoned users who won't remember or know in advance what behavior to expect in a given situation. As such, I'm not weighing in on a debate about PSReadLine behavior. – mklement0 Aug 10 '22 at 03:06
2

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

data={"""password""":"""keypass"""}
bryanbcook
  • 16,210
  • 2
  • 40
  • 69
Andres
  • 21
  • 1
  • beautiful ```pwsh -nop -c ' bash -c ''printf """""""\\033[38;2;255;0;0mHi\\033[0m"""""""'' '``` . thank you – Luiz Felipe Jul 27 '22 at 15:29
  • 1
    @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](https://stackoverflow.com/a/49060341/45375). – mklement0 Aug 10 '22 at 03:23
  • Stuck back on PSVersion 5.1.19041.2673 and had to finally use the triple quotes from command line. Have not tried from script.... – GTAE86 May 18 '23 at 16:43
0

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

hazartilirot
  • 195
  • 1
  • 2
  • 11
  • Your answer repeats the techniques from preexisting answers, without explanation, buried in a lot of incidental information. – mklement0 Feb 28 '23 at 23:26
-1

Set the content type:

curl -H "Content-Type: application/json" -d '{"password":"keypass"}' https://build.phonegap.com/api/v1/key
David Pullar
  • 706
  • 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