How to get a return value from an engine event handler in PowerShell? How to get a return value from an engine event handler in PowerShell? powershell powershell

How to get a return value from an engine event handler in PowerShell?


I found a way that addresses my needs. It turns out I was searching for the wrong keywords, when searching for PowerShell and callbacks I found this question which helped me a lot (particularly Duncan's answer):

Pass a function as a parameter in PowerShell

I worked it into a full example. This is a client script, to be saved as "Client.ps1":

Import-Module -Name ".\Server.psm1" -DisableNameChecking$script:ClientVar = "Not seen by server, returned by event handler."function Handle_Initialized {  Write-Host "Handler Initialized is being called."  # a return value is optional  return "=== $script:ClientVar ==="}function Handle_ProcessedData {  param (    $Argument1,    $Argument2,    $Argument3  )  Write-Host "Handler ProcessedData is called."  Write-Host "Arguments are $argument1, $argument2 and $argument3."  # a return value is optional  return "=== $argument1, $argument2, $argument3 ==="}Subscribe-Event -Name Initialized  -Handler $function:Handle_InitializedSubscribe-Event -Name ProcessedData -Handler $function:Handle_ProcessedDataWrite-Host ""Write-Host "calling with active subscriptions"Write-Host "================================="Do-ServerStuffUnsubscribe-Event -Name InitializedUnsubscribe-Event -Name ProcessedDataWrite-Host ""Write-Host "calling again with no active subscriptions"Write-Host "================================="Do-ServerStuffRemove-Module -Name "Server"

Then, in the same folder, put this as "Server.psm1":

[ScriptBlock]$script:Handler_Initialized   = $null[ScriptBlock]$script:Handler_ProcessedData = $nullfunction Subscribe-Event {  param (    [String]$Name,    [ScriptBlock]$Handler  )  switch ($Name) {    Initialized   { $script:Handler_Initialized   = $Handler }    ProcessedData { $script:Handler_ProcessedData = $Handler }  }}function Unsubscribe-Event {  param (    [String]$Name  )  switch ($Name) {    Initialized   { $script:Handler_Initialized   = $null }    ProcessedData { $script:Handler_ProcessedData = $null }  }}function Raise-Initialized {  param (  )  if ($script:Handler_Initialized) {    return & $script:Handler_Initialized  }}function Raise-ProcessedData {  param (    [Object]$Argument1,    [Object]$Argument2,    [Object]$Argument3  )  if ($script:Handler_ProcessedData) {    return & $script:Handler_ProcessedData -Argument1 $Argument1 -Argument2 $Argument2 -Argument3 $Argument3  }}function Do-ServerStuff {  Write-Host "Before raising event Initialized."  Raise-Initialized  Write-Host "After raising event Initialized."  Write-Host ""  Write-Host "Before raising event ProcessedData."  Raise-ProcessedData -Argument1 "AAA" -Argument2 "BBB" -Argument3 "CCC"  Write-Host "After raising event ProcessedData."}

And you will find that you have an extensible synchronous event handling system.

Most of the plumbing is in the server module which dictates the prototypes for the handler functions. The client code merely subscribes to and unsubscribes from events and provides the handler implementations. It supports named parameters for all handlers as well as return values (really output values) in the usual PowerShell way. The server knows nothing about any of its clients which I like a lot, dependencies go one way only.

For example, this scheme allows you to build modules that perform pure core logic and nothing else. Rather than logging through some global object you can have the server module raise an event that sends a message and the client can then decide what to do with it and where to send it. This increases the usability of server modules and makes them testable.

It may be a matter of taste, I typically favor using events over dependency injection. With dependency injection the server still needs to know about the type that is injected, with events this is not the case.

Enjoy!