import requests
import subprocess
import base64
credentials = "login:password"
url = f'urlXXXurl'
body = '{"id": 4986986, "key": "2df534ee-270b-4ab4-83fb-1b308febacce", ...}'
headers = '"""@{Authorization = \'Basic %s\'}"""' % base64.b64encode(credentials.encode("ascii")).decode("ascii")
command = 'powershell.exe Invoke-RestMethod -Method Post -Uri %s -ContentType application/json -Body """%s""" -Headers ( Invoke-Expression %s ) -UseBasicParsing' % (url, body.replace('"', '\"'), headers)
response = subprocess.check_call(command)
Is there some kind of necessary conversion, so that JSON would be recognizable by PowerShell?
The only immediate problem with the approach in your question was the use of """..."""
around the -Body
argument (resulting in a "..."
string from PowerShell's perspective):
Doing so requires two escaping techniques: \
(or """
) in order to preserve any "
characters as part of the PowerShell command passed to the the (implied) -Command
parameter of PowerShell's CLI and `
-escaping of the "
chars. embedded in the value (...
).
While you could therefore have used body.replace('"', '`\"')
(note the `
), it is simpler to use '...'
for strings,[1] given that '
does
not require escaping on the command line and allows embedding "
characters as-is (albeit as \"
via the CLI).
There is no need for ( Invoke-Expression %s )
, given that you can formulate the string representing the -Header
argument directly as a PowerShell hashtable literal.
As Mathias points out, if you want the command to output a JSON string, use Invoke-WebRequest
and access the output object's .Content
property.
Invoke-RestMethod
, PowerShell automatically and invariably parses a JSON response into an object graph (which is what necessitated the ConvertTo-Json
call in your own answer, which isn't needed if you use (Invoke-WebRequest).Content
).Applying the above to your code, using Python v3.6+ f-strings:
import requests
import subprocess
import base64
credentials = 'login:password'
url = 'urlXXXurl'
body = '{"id": 4986986, "key": "2df534ee-270b-4ab4-83fb-1b308febacce", ...}'
# Construct the header as a string representing a PowerShell hashtable literal.
# Note the the use of {{ and }} to use *literal* { and } chars. in the f-string.
headers = f"@{{Authorization = 'Basic {base64.b64encode(credentials.encode('ascii')).decode('ascii')}'}}"
# Escape the " chars. in JSON as \",
# so that the PowerShell CLI doesn't strip them.
bodyEscaped = body.replace('"', '\"')
# Note:
# Due to use of '...' PowerShell strings, the assumption is that
# neither header nor bodyEscaped themselves contain '
# If they did, these embedded ' would have to be escaped as ''
command = f"""\
powershell.exe -NoProfile -Command
(
Invoke-WebRequest -Uri '{url}' -Body '{bodyEscaped}' -Headers {headers} -Method Post -ContentType application/json -UseBasicParsing
).Content
"""
response = subprocess.check_call(command)
Note that neither the -NoProfile
nor the -Command
parameter of the Windows PowerShell CLI are strictly needed here:
-NoProfile
bypasses profile (initialization-file) loading and is therefore both potentially faster and makes for a more predictable execution environment.-Command
(-c
) would be implied if not used, but is included for conceptual clarity (not least because pwsh
, the PowerShell (Core) CLI, now requires -Command
, as it defaults to -File
).Finally, note that - for simplicity - what constitutes the PowerShell command is technically passed as multiple arguments rather than as a single string enclosed in (unescaped) "..."
.
cmd.exe
, were involved in the call (it isn't with subprocess.call()
), use of "..."
would be advisable, to prevent accidental interpretation of characters such as &
and |
by that shell."..."
would also be needed in the rare event that your PowerShell command has embedded strings with runs of multiple spaces that need to be preserved as such (without "..."
enclosure, such runs would be folded into a single space each).[1] This assumes that you don't need PowerShell's string interpolation, which requires "..."
strings.