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:
- Windows PowerShell and Named Pipes by Keith Hill
- Python and Windows Named Pipes in Stack Overflow
- Pywin32 Documentation