PowerShell mangles quotes in CLI args, so pass JSON by file
A curl or gh command with inline JSON works in bash and cmd but the payload arrives corrupted from PowerShell. PowerShell rewrites embedded double quotes when handing args to native executables. Pass the payload by file or stdin instead.
TL;DR · THE FIX
PowerShell rewrites embedded double quotes when passing arguments to native exes, so inline JSON arrives mangled and the server 400s. Pass the payload by file (curl -d @body.json, gh api --input body.json) or stdin. Inline escape hatches: the --% stop-parsing token, or $PSNativeCommandArgumentPassing = 'Standard' on PowerShell 7.3+.
The symptom
A request with an inline JSON body worked from bash and from cmd, then 400’d every time from PowerShell:
curl -X POST https://api.example.com/things `
-H "Content-Type: application/json" `
-d "{""name"":""test"",""count"":3}"
The server complained the body was malformed. The same JSON, pasted into a file and posted from curl on a Mac, went through fine, so neither the API nor the JSON was at fault.
What’s happening
PowerShell doesn’t pass your argument string to a native executable verbatim. It re-parses it and rebuilds the command line, and its rules for embedded double quotes differ from cmd’s, so the inner quotes get dropped or doubled in transit. By the time curl.exe receives the argument the JSON is no longer valid. Windows PowerShell 5.1 is the worst offender, and it bites in 7.x too. The shell is rewriting your escaping.
The fix
Stop passing complex quoted payloads inline. Put the body in a file and reference it, which sidesteps the quoting entirely:
'{"name":"test","count":3}' | Out-File body.json -Encoding utf8
curl -X POST https://api.example.com/things -H "Content-Type: application/json" -d "@body.json"
# gh has the same escape hatch:
gh api /repos/owner/repo/issues --input body.json
Piping the payload to the command’s stdin works too. If you must inline it, the stop-parsing token --% tells PowerShell to pass the rest of the line literally, as in curl --% -d "{\"name\":\"test\"}" ..., at the cost of also turning off variable expansion for the rest of the line. On PowerShell 7.3+, setting $PSNativeCommandArgumentPassing = 'Standard' makes inline quoting behave the way you’d expect.
The lesson
When a native CLI works elsewhere but mangles its payload from PowerShell, the shell is rewriting your quotes. Pass anything with embedded quotes by file or stdin and the problem disappears. Keep inline JSON for bash; in PowerShell, reach for -d "@file.json".
Discussion
Powered by GitHub. Sign in to leave a comment.