Inter-Process Communication with Named Pipes between Python and PowerShell

I have a hunch that I may later need IPC between Python and PowerShell applications on Windows, so I did some research, and managed to make a small example of IPC using named pipes. Here the PowerShell app is the server, waiting for connections, and Python app is the client.

simplepipeserver.ps1:

$pipeName = "TestPipe"
$pipeServer = New-Object System.IO.Pipes.NamedPipeServerStream($pipeName)
try {
    while ($true) {
        "Waiting for connection on '$pipeName'"
        $pipeServer.WaitForConnection()
        "Connection established"
        $pipeReader = New-Object System.IO.StreamReader($pipeServer)
        $pipeWriter = New-Object System.IO.StreamWriter($pipeServer)
        $pipeWriter.AutoFlush = $true
        $request = $pipeReader.ReadLine()
        "Received request: $request"
        if ($request -eq "exit") {
            "Exiting"
            exit
        }
        elseif ($request -eq "") {
            "Empty input"
            $pipeServer.Disconnect()
            "Disconnected"
            continue
        }
        elseif ($request -eq $none) {
            "Remote disconnected before sending"
            $pipeServer.Disconnect()
            "Disconnected"
            continue
        }
        "Working hard for the result..."
        Start-Sleep -Seconds 2
        $result = "Your input: $request"
        "Sending result: '$result'"
        $pipeWriter.Write($result)
        $pipeServer.Disconnect()
        "Disconnected"
    }
}
finally {
    $pipeServer.Dispose()
}

simplepipeclient.py:

import sys
import time

# From pywin32 package:
import pywintypes
import win32file
import win32pipe


PIPENAME = "TestPipe"
TIMEOUT = 5     # seconds


print(f"Connecting to the pipe '{PIPENAME}'")
attempts = 0
while True:
    attempts += 1
    try:
        handle = win32file.CreateFile(
            fr"\\.\\pipe\{PIPENAME}",
            win32file.GENERIC_READ | win32file.GENERIC_WRITE,
            0,
            None,
            win32file.OPEN_EXISTING,
            0,
            None,
        )
    except pywintypes.error:
        if attempts >= 10:
            print("Giving up, could not connect")
            sys.exit(2)
        print("Retrying...")
        time.sleep(1)
    else:
        break
print("Connected")
win32pipe.SetNamedPipeHandleState(
    handle,
    win32pipe.PIPE_READMODE_BYTE | win32pipe.PIPE_NOWAIT,
    None,
    None,
)
data = input("Enter input: ")
data = data + "\n"
print("Sending request")
win32file.WriteFile(handle, data.encode())
print("Waiting for response")
start_time = time.monotonic()
result = ""
while True:
    try:
        status, data = win32file.ReadFile(handle, 65536)
    except pywintypes.error as e:
        error_code = e.args[0]
        error_msg = e.args[2]
        if error_code == 232:
            # 232 = "The pipe is being closed", apparently meaning that
            #   the pipe is still up but waiting for data to come
            if time.monotonic() > start_time + TIMEOUT:
                print("Timeout while waiting for result")
                break
            time.sleep(0.2)
            continue
        elif error_code in [109, 233]:
            # 109 = "The pipe has been ended"
            # 233 = "No process is on the other end of the pipe"
            # We have it all (if any)
            break
        else:
            print(
                f"Error {error_code} while trying to "
                f"read the pipe: '{error_msg}'"
            )
            break
    # We have data, handle it
    result = result + data.decode()
if result:
    print(f"Response received: '{result}'")
else:
    print("Pipe closed with no data")
win32file.CloseHandle(handle)

The Python app requires pywin32 package to be installed, so I installed it in a virtual environment.

This is how the execution looks like on Windows 10. First started the server:

PS C:\devel> .\simplepipeserver.ps1
Waiting for connection on 'TestPipe'

Running the client (in another window/tab):

PS C:\devel> .\pipevenv\Scripts\python simplepipeclient.py
Connecting to the pipe 'TestPipe'
Connected
Enter input: Markku Leiniö
Sending request
Waiting for response
Response received: 'Your input: Markku Leiniö'
PS C:\devel>

Meanwhile in the server side:

Connection established
Received request: Markku Leiniö
Working hard for the result...
Sending result: 'Your input: Markku Leiniö'
Disconnected
Waiting for connection on 'TestPipe'

Entering “exit” as input in the client will stop the server.

Ctrl-C will not stop the server while waiting for the connection, that’s something that maybe requires some further thinking if the server is to be run interactively.

Sources and further reading:

Leave a Reply