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