PowerShell MiniShell

It is possible to use a cutdown PowerShell integration to exchange data between a Python process and a PowerShell one. In PowerShell this concept is called a minishell and uses CLIXML strings through stdin and stdout. Using the builtin type serialization it is possible to also exchange more rich types in Python.

Output to PowerShell

The first scenario where the CLIXML minishell is useful is to output data to the calling PowerShell process. Using the ClixmlShell class it is possible to emit Python objects to the calling PowerShell process as well as emitting other stream records. This following Python script can be called in PowerShell

import sys

import psrpcore


class CustomObject:
    def __init__(self) -> None:
        self.attribute = 'value'
        self.other = 123


def main() -> None:
    shell = psrpcore.ClixmlShell()

    # write_output can write any object
    shell.write_output("string value")
    shell.write_output(CustomObject())

    # write_verbose can write verbose records
    shell.write_verbose("verbose record")

    # When finish, write the CLIXML to stdout
    sys.stdout.write(shell.data_to_send())


if __name__ == '__main__':
    main()

In PowerShell this can be captured in PowerShell executing the Python process

$out = python script.py

$out[0]  # string value
$out[1].attribute  # value
$out[1].other  # other

It is important that PowerShell is set to either capture or redirect/pipe the Python process output so it processes the CLIXML. Specific types or direct PSObject manipulation can all be exposed through psrpcore.types see Python to .NET Type Information for more information.

Output to Python

The second scenario is where Python executes a PowerShell process and captures the objects it emits. The captured CLIXML output can be processed through the ClixmlOutput allowing Python to access the objects in a more structured manner. An example, here is a PowerShell script that can output some more structured objects:

[PSCustomObject]@{
    attribute = 'value'
    other = 123
}

Write-Error 'Error Message'

This can be captured in Python with:

import subprocess

import psrpcore


def main() -> None:
    proc = subprocess.run(
        ["pwsh", "-OutputFormat", "xml", "-File", "ps.ps1"],
        capture_output=True,
        check=True,
        text=True,
    )
    out = psrpcore.ClixmlOutput.from_clixml([proc.stdout, proc.stderr])

    for obj in out.output:
        print(obj.attribute)

    for obj in out.error:
        print(f"Error: {obj}")


if __name__ == "__main__":
    main()

It is important that PowerShell is called with -OutputFormat xml to ensure the output is in the CLIXML format. The ClixmlOutput object contains an attribute for every string, in this case the output stream contains 1 entry being the PSCustomObject from the PowerShell script and single error record from the Write-Error call.

Note

There is a bug in PowerShell where ProgressRecords are not emitted when stdout is redirected.