Pipes/NamedPipes.ps1
$ErrorActionPreference = "Stop" 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); } } } "@} class Pipe { Pipe() { } static [string] convertToCliXML ([pscustomobject]$obj){ $tempFilePath = [System.IO.Path]::GetTempFileName() $obj | Export-Clixml -Path $tempFilePath -Force [string]$cliXML = Get-Content $tempFilePath -Raw -Encoding UTF8 #-ErrorAction Stop Remove-Item -Path $tempFilePath | Out-Null return $cliXML } static [pscustomobject] convertFromCliXML ([string]$cliXML){ [pscustomobject]$obj = $null if( $cliXML.Contains("http://schemas.microsoft.com/powershell/2004/04")) { $tempFilePath = [System.IO.Path]::GetTempFileName() $cliXML | Set-Content $tempFilePath -Force -Encoding UTF8 -NoNewline #-ErrorAction Stop $obj = Import-Clixml $tempFilePath Remove-Item -Path $tempFilePath | Out-Null } else { if($cliXML -ne "NULL") { #Write-Host "convertFromCliXML - skipped" $obj = [pscustomobject]$cliXML } else { #Write-Host "convertFromCliXML - NULL" $obj = $null } } return $obj } } class NamedPipeServer : Pipe { hidden $_pipeName = ""; hidden $_pipeClosedByClient = $false hidden [System.IO.Pipes.NamedPipeServerStream]$_serverPipe = $null; hidden $_readBuffer = $null; hidden $_OnConnectedCallbackBridge = $null; # this is our internal async event bridge hidden [System.Management.Automation.ScriptBlock]$_OnConnectedScriptBlock = {}; # this is the scriptblock the user wants to call hidden $_OnDataAvailableCallbackBridge = $null; # this is our internal async event bridge hidden [System.Management.Automation.ScriptBlock]$_OnDataAvailableScriptBlock = {}; # this is the scriptblock the user wants to call hidden $_OnDataWrittenCallbackBridge = $null; # this is our internal async event bridge # Constructor NamedPipeServer ([string]$PipeName) : base () { $this._pipeName = $PipeName $this._pipeClosedByClient = $false $this._readBuffer = New-Object byte[] 65536 #$this._serverPipe = New-Object System.IO.Pipes.NamedPipeServerStream $PipeName, InOut, 1, Byte, Asynchronous, 65536, 65536 #$this.Start() # Create a bridge for OnConnected async events $this._OnConnectedCallbackBridge = New-Object CallbackEventBridge Register-ObjectEvent -InputObject $this._OnConnectedCallbackBridge -EventName CallbackComplete ` -Action { param($asyncResult) $asyncResult.AsyncState.OnClientConnected.Invoke($asyncResult); } > $null # Create a bridge for OnDataAvailable async events $this._OnDataAvailableCallbackBridge = New-Object CallbackEventBridge Register-ObjectEvent -InputObject $this._OnDataAvailableCallbackBridge -EventName CallbackComplete ` -Action { param($asyncResult) $asyncResult.AsyncState.OnDataAvailable.Invoke($asyncResult); } > $null # Create a bridge for OnDataWritten async events $this._OnDataWrittenCallbackBridge = New-Object CallbackEventBridge Register-ObjectEvent -InputObject $this._OnDataWrittenCallbackBridge -EventName CallbackComplete ` -Action { param($asyncResult) $asyncResult.AsyncState.OnDataWritten.Invoke($asyncResult); } > $null } [void]WaitForConnection(){ #Write-Host "WaitForConnection" $this._serverPipe.WaitForConnection(); } [void]BeginWaitForConnection([System.Management.Automation.ScriptBlock]$Callback = {}){ #Write-Host "BeginWaitForConnection" $this._OnConnectedScriptBlock = $Callback; # Save the script the user wants to call on connect $this._serverPipe.BeginWaitForConnection($this._OnConnectedCallbackBridge.Callback, $this); } hidden [void]OnClientConnected($asyncResult){ #Write-Host "OnClientConnected" $this._serverPipe.EndWaitForConnection($asyncResult) $this._OnConnectedScriptBlock.Invoke($this); } [void]BeginRead([System.Management.Automation.ScriptBlock]$Callback){ #Write-Host "BeginRead" $this._OnDataAvailableScriptBlock = $Callback; # Save the script the user wants to call when data is available $this._serverPipe.BeginRead($this._readBuffer, 0, $this._readBuffer.Length, $this._OnDataAvailableCallbackBridge.Callback, $this) } hidden [void]OnDataAvailable($asyncResult){ #Write-Host "OnDataAvailable" $MessageLength = $this._serverPipe.EndRead($asyncResult) if ($MessageLength -gt 0){ $PipeText = [System.Text.Encoding]::UTF8.GetString($this._readBuffer, 0, $MessageLength) #$this._OnDataAvailableScriptBlock.Invoke($PipeText, $this); [pscustomobject]$obj = [Pipe]::convertFromCliXML( $PipeText ) $this._OnDataAvailableScriptBlock.Invoke($obj, $this); $this.BeginRead($this._OnDataAvailableScriptBlock); } else { # Pipe was closed # Write-Host "Pipe closed" -ForegroundColor Red $this._pipeClosedByClient = $true $PipeText = "" $this._OnDataAvailableScriptBlock.Invoke($PipeText, $this); } } [void]BeginWrite([string]$Message){ $_writeBufferLocal = [System.Text.Encoding]::UTF8.GetBytes($Message) $this._serverPipe.BeginWrite($_writeBufferLocal, 0, $_writeBufferLocal.Length, $this._OnDataWrittenCallbackBridge.Callback, $this) } hidden [void]OnDataWritten($asyncResult){ $asyncResult._serverPipe.EndWrite($asyncResult) } [void]Write([string]$Message){ $_writeBufferLocal = [System.Text.Encoding]::UTF8.GetBytes($Message) $this._serverPipe.Write($_writeBufferLocal, 0, $_writeBufferLocal.Length) } [Boolean] IsClosedByClient(){ return $this._pipeClosedByClient } [Boolean] IsConnected(){ return $this._serverPipe.IsConnected } [string] GetPipeName(){ return $this._pipeName } [void] Start(){ $this._serverPipe = New-Object System.IO.Pipes.NamedPipeServerStream $($this._pipeName), InOut, 1, Byte, Asynchronous, 65536, 65536 } [void] Stop (){ $this._serverPipe.Close(); } } class NamedPipeClient : Pipe { hidden $_pipeName = ""; hidden [System.IO.Pipes.NamedPipeClientStream]$_clientPipe = $null; hidden $_readBuffer = $null; hidden $_OnDataAvailableCallbackBridge = $null; # this is our internal async event bridge hidden [System.Management.Automation.ScriptBlock]$_OnDataAvailableScriptBlock = {}; # this is the scriptblock the user wants to call hidden $_OnDataWrittenCallbackBridge = $null; # this is our internal async event bridge NamedPipeClient ([string]$PipeName) : base () { $this._pipeName = $PipeName [string]$PipeServer = "." $this._readBuffer = New-Object byte[] 65536 $this._clientPipe = New-Object System.IO.Pipes.NamedPipeClientStream $PipeServer, $PipeName, InOut, Asynchronous # Create a bridge for OnDataAvailable async events $this._OnDataAvailableCallbackBridge = New-Object CallbackEventBridge Register-ObjectEvent -InputObject $this._OnDataAvailableCallbackBridge -EventName CallbackComplete ` -Action { param($asyncResult) $asyncResult.AsyncState.OnDataAvailable.Invoke($asyncResult); } > $null # Create a bridge for OnDataWritten async events $this._OnDataWrittenCallbackBridge = New-Object CallbackEventBridge Register-ObjectEvent -InputObject $this._OnDataWrittenCallbackBridge -EventName CallbackComplete ` -Action { param($asyncResult) $asyncResult.AsyncState.OnDataWritten.Invoke($asyncResult); } > $null } [void]BeginRead([System.Management.Automation.ScriptBlock]$Callback){ $this._OnDataAvailableScriptBlock = $Callback; # Save the script the user wants to call when data is available $this._clientPipe.BeginRead($this._readBuffer, 0, $this._readBuffer.Length, $this._OnDataAvailableCallbackBridge.Callback, $this) } hidden [void]OnDataAvailable($asyncResult){ $MessageLength = $this._clientPipe.EndRead($asyncResult) if ($MessageLength -gt 0){ $PipeText = [System.Text.Encoding]::UTF8.GetString($this._readBuffer, 0, $MessageLength) $this._OnDataAvailableScriptBlock.Invoke($PipeText, $this); $this.BeginRead($this._OnDataAvailableScriptBlock); } else { # Pipe was closed #Write-Verbose "Pipe closed" -ForegroundColor Red } } [void]BeginWrite([string]$Message){ $_writeBufferLocal = [System.Text.Encoding]::UTF8.GetBytes($Message) $this._clientPipe.BeginWrite($_writeBufferLocal, 0, $_writeBufferLocal.Length, $this._OnDataWrittenCallbackBridge.Callback, $this) } hidden [void]OnDataWritten($asyncResult){ $asyncResult._clientPipe.EndWrite($asyncResult) } [void] Write([string]$Message){ $_writeBufferLocal = [System.Text.Encoding]::UTF8.GetBytes($Message) $this._clientPipe.Write($_writeBufferLocal, 0, $_writeBufferLocal.Length) } [string] Read() { $PipeText = "" $MessageLength = $this._clientPipe.Read($this._readBuffer, 0, $this._readBuffer.Length) if ($MessageLength -gt 0){ $PipeText = [System.Text.Encoding]::UTF8.GetString($this._readBuffer, 0, $MessageLength) } else { # Pipe was closed #Write-Host "Pipe closed" -ForegroundColor Red $this._clientPipe.Close(); } return $PipeText } Connect([int]$Timeout = 2000){ $this._clientPipe.Connect($Timeout); } Connect(){ $this._clientPipe.Connect(2000); } [Boolean] IsConnected(){ return $this._clientPipe.IsConnected } [string] GetPipeName(){ return $this._pipeName } Close (){ $this._clientPipe.Close(); } } class PipeClient : NamedPipeClient { $Caller = $null PipeClient ([string]$PipeName) : base ($PipeName) { } [pscustomobject] ReadObject() { $obj = $null try { $PipeText = ([NamedPipeClient]$this).Read() [pscustomobject]$obj = [Pipe]::convertFromCliXML( $PipeText ) if( $($obj.getType().Name) -eq "String") { if($obj -eq "invalid") { $obj = $null} } } catch {} return $obj } [void] Write( [pscustomobject]$obj ) { [string]$Message = [Pipe]::convertToCliXML( $obj ) ([NamedPipeClient]$this).BeginWrite($Message) } } class PipeServer : NamedPipeServer { $Caller = $null hidden $_timer = $null # hidden $_doEvents = $false hidden $_messageCallback = $null hidden $_closeCallback = $null # Constructor PipeServer ([string]$PipeName, [boolean]$DoEvents ) : base ($PipeName){ $this._doEvents = $DoEvents $this._messageCallback = $null $this._closeCallback = $null $this.Open() } hidden [void] StartTimer() { if($this._doEvents) { if( -not $this._timer) { $this._timer = [System.Windows.Forms.Timer]::new() $this._timer.Interval = 1000 $this._timer.add_tick( { Write-Host '' -NoNewline # DoEvents doesn't works without this [System.Windows.Forms.Application]::DoEvents() }) #$this._timerCallback ) } #Write-Host "starting DoEvents timer ..." $this._timer.start() } } hidden [void] StopTimer() { if($this._doEvents) { $this._timer.stop() } #Write-Host "DoEvents timer stopped" } hidden [void] StartServer() { $this.Start() $this.BeginWaitForConnection({ $_this = $($args[0]) $pipename = $_this.GetPipeName() Write-Host "[server] $pipename opened by client"; $_this.BeginRead({ $client_msg = $($args[0]) $_this = $($args[1]) if ( $client_msg -eq "" ) { $pipename = $_this.GetPipeName() Write-Host "[server] $pipename closed by client" $closed = $_this.IsClosedByClient() if( $closed ) { $_this.Restart() } } else { if( $_this._messageCallback) { $_this._messageCallback.Invoke($_this, $client_msg) } } }) }) } hidden [void] StopServer() { $this.Stop() } [void] OnClose( [System.Management.Automation.ScriptBlock]$onCloseCallback ) { $this._closeCallback = $onCloseCallback } [void] OnMessage( [System.Management.Automation.ScriptBlock]$onMessageCallback ) { $this._messageCallback = $onMessageCallback } [void] Write([string]$Message){ $this.BeginWrite($Message) } [void] Write( [pscustomobject]$obj ) { [string]$Message = [Pipe]::convertToCliXML( $obj ) $this.BeginWrite($Message) } [void] Open (){ $this.StartTimer() $this.StartServer() } [void] Close (){ $this.StopServer() $this.StopTimer() if( $this._closeCallback) { $this._closeCallback.Invoke($this) } } [void] Restart() { $pipename = $this.GetPipeName() Write-Host "[server] $pipename restarted" $this.Close() $this.Open() } [void] Dispose (){ $this.StopServer() $this.StopTimer() #$this._timer.Dispose($true) } } |