Powershellで名前付きパイプを扱う

.netで名前付きパイプを扱う方法ですね。



同期バージョン

とりあえずEchoサーバー

$pipe = New-Object System.IO.Pipes.NamedPipeServerStream "PshPipeName", InOut
$pipe.WaitForConnection()
$buf = New-Object byte[] 1024
$loop = $true
while($loop) {
	$len = $pipe.Read($buf, 0, $buf.Length)
	$cmd = [System.Text.Encoding]::Unicode.GetString($buf, 0, $len)
	if($cmd -match '^exit|^quit') {
		$loop = $false
	} else {
		$wb = [System.Text.Encoding]::Unicode.GetBytes($cmd)
		$pipe.Write($wb, 0, $wb.Length)
	}
}
$pipe.Close()

クライアント側

$pipe = New-Object System.IO.Pipes.NamedPipeClientStream ".", "PshPipeName", InOut
$pipe.Connect()
$buf = New-Object byte[] 1024

$wb = [System.Text.Encoding]::Unicode.GetBytes("Hello, world.")
$pipe.Write($wb, 0, $wb.Length)
$len = $pipe.Read($buf, 0, $buf.Length)
[System.Text.Encoding]::Unicode.GetString($buf, 0, $len)

$wb = [System.Text.Encoding]::Unicode.GetBytes("exit")
$pipe.Write($wb, 0, $wb.Length)

$pipe.Close()


サーバー側で作成するパイプは、接続数を定義したり

$pipe = New-Object System.IO.Pipes.NamedPipeServerStream "PshPipeName", InOut, 1

他オプションを定義したりできます。

$pipe = New-Object System.IO.Pipes.NamedPipeServerStream "PshPipeName", InOut, 1, Byte, None, 1024, 1024


が、バッファがなくなったり、読み込むものがないのにReadしたりすると、ブロックします。
ということで、非同期を検討する。

非同期

AsyncCallbackは、スクリプトブロックをキャストすればOK的な文章を見たのだけど、、、上手くいかない。
やむなくブリッジクラスを作ってしのぐ。
非同期版Echoサーバー

if (-not ("CallbackEventBridge" -as [type])) {
	Add-Type @"
		using System;
		public sealed class CallbackEventBridge
		{
			public event AsyncCallback CallbackComplete = delegate {};
			private void CallbackInternal(IAsyncResult result)
			{
				CallbackComplete(result);
			}
			public AsyncCallback Callback
			{
				get { return new AsyncCallback(CallbackInternal); }
			}
		}
"@}
$po = New-Object PSObject
$po | Add-Member NoteProperty "buf" (New-Object byte[] 1024)
$po | Add-Member NoteProperty "pipe" (New-Object System.IO.Pipes.NamedPipeServerStream "PshPipeName", InOut, 1, Byte, Asynchronous, 1024, 1024)
$po | Add-Member NoteProperty "wb" (New-Object CallbackEventBridge)
$po | Add-Member NoteProperty "rb" (New-Object CallbackEventBridge)
$callback = {
	param($asyncResult)
	# 本当は書き込んだサイズをチェックすべきかも
	$asyncResult.AsyncState.EndWrite($asyncResult)
}
Register-ObjectEvent -InputObject $po.wb -EventName CallbackComplete -action $callback > $null
$callback = {
	param($asyncResult)
	$asyncResult.AsyncState.pipe.EndWaitForConnection($asyncResult)
}
Register-ObjectEvent -InputObject $po.rb -EventName CallbackComplete -action $callback > $null
$ar = $po.pipe.BeginWaitForConnection($po.rb.Callback, $po)
while(!$ar.IsCompleted) {
	Start-Sleep -Milliseconds 100
}

$callback = {
	param($asyncResult)
	$po = $asyncResult.AsyncState
	$len = $po.pipe.EndRead($asyncResult)
	$cmd = [System.Text.Encoding]::Unicode.GetString($po.buf, 0, $len)
	if($cmd -match '^exit|^quit') {
		$po.pipe.Close()
	} else {
		$wb = [System.Text.Encoding]::Unicode.GetBytes($cmd)
		$ar = $po.pipe.BeginWrite($wb, 0, $wb.Length, $po.wb.Callback, $po)
		while(!$ar.IsCompleted -and $po.pipe.IsConnected) {
			Start-Sleep -Milliseconds 100
		}
	}
}
Register-ObjectEvent -InputObject $po.rb -EventName CallbackComplete -action $callback > $null
while($po.pipe.IsConnected) {
	$ar = $po.pipe.BeginRead($po.buf, 0, $po.buf.Length, $po.rb.Callback, $po)
	while(!$ar.IsCompleted) {
		Start-Sleep -Milliseconds 100
	}
}
$po.pipe.Close()

なんというスクリプト量の増加、、、。
一応、クライアント側も非同期版で書いてみる(whileループで同期するけど)

if (-not ("CallbackEventBridge" -as [type])) {
	Add-Type @"
		using System;
		public sealed class CallbackEventBridge
		{
			public event AsyncCallback CallbackComplete = delegate {};
			private void CallbackInternal(IAsyncResult result)
			{
				CallbackComplete(result);
			}
			public AsyncCallback Callback
			{
				get { return new AsyncCallback(CallbackInternal); }
			}
		}
"@}
$po = New-Object PSObject
$po | Add-Member NoteProperty "buf" (New-Object byte[] 1024)
$po | Add-Member NoteProperty "pipe" (New-Object System.IO.Pipes.NamedPipeClientStream ".", "PshPipeName", InOut, Asynchronous)
$po | Add-Member NoteProperty "wb" (New-Object CallbackEventBridge)
$po | Add-Member NoteProperty "rb" (New-Object CallbackEventBridge)
$po | Add-Member NoteProperty "res" ""
$callback = {
	param($asyncResult)
	# 本当は書き込んだサイズをチェックすべきかも
	$asyncResult.AsyncState.EndWrite($asyncResult)
}
Register-ObjectEvent -InputObject $po.wb -EventName CallbackComplete -action $callback > $null
$callback = {
	param($asyncResult)
	$len = $asyncResult.AsyncState.pipe.EndRead($asyncResult)
	$po.res = [System.Text.Encoding]::Unicode.GetString($asyncResult.AsyncState.buf, 0, $len)
}
Register-ObjectEvent -InputObject $po.rb -EventName CallbackComplete -action $callback > $null

while(!$po.pipe.IsConnected) {
	# 無限リトライでなく、あきらめるということも知る必要があるかもしれない
	$po.pipe.Connect(100) > $null
}

$wb = [System.Text.Encoding]::Unicode.GetBytes("Hello, world.")
$ar = $po.pipe.BeginWrite($wb, 0, $wb.Length, $po.wb.Callback, $po)
while(!$ar.IsCompleted) {
	Start-Sleep -Milliseconds 100
}
$ar = $po.pipe.BeginRead($po.buf, 0, $po.buf.Length, $po.rb.Callback, $po)
while(!$ar.IsCompleted) {
	Start-Sleep -Milliseconds 100
}
$po.res

$wb = [System.Text.Encoding]::Unicode.GetBytes("exit")
$ar = $po.pipe.BeginWrite($wb, 0, $wb.Length, $po.wb.Callback, $po)
while(!$ar.IsCompleted) {
	Start-Sleep -Milliseconds 100
}

$po.pipe.Close()