skip to Main Content
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?

2

Answers


  1. Chosen as BEST ANSWER

    Finally I used this approach

    command = "powershell.exe ConvertTo-Json ( Invoke-RestMethod -Method Post -Uri %s -ContentType application/json -Body '%s' -Headers ( Invoke-Expression %s ) -UseBasicParsing )" 
                          % (url, json.dumps(self.asset).replace('"', '"""'), headers)
    

    • 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.

      • By contrast, if you use 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) "...".

    • If (another) shell, notably 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.
    • Separately, "..." 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.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search