I want to use events in PowerShell to separate responsibilities and eliminate dependencies between modules. A child module needs to notify a parent module about its activity and sometimes it needs information in return from the parent module when it raises an event.
This is what I have tried so far. In a real-world scenario I would put the registration-, unregistration and handler function parts in a parent module and the New-Event parts in a child module.
function MyHandler { param ( [System.Management.Automation.PSEventArgs]$Event ) Write-Host "In MyHandler now!" -ForegroundColor Yellow Write-Host "Sender : $($Event.Sender)" Write-Host "SourceArgs : $($Event.SourceArgs)" Write-Host "MessageData : $($Event.MessageData)" Write-Host "TimeGenerated : $($Event.TimeGenerated)" $Event.MessageData = "This does not make it back." return "Neither does this." } # subscribe [String]$MessageData = "Input" Register-EngineEvent -SourceIdentifier Pipo -Action {MyHandler $Event } # raise event and see what we get back New-Event -Sender "Me" -SourceIdentifier "Pipo" -EventArguments "MyEventArgs" -MessageData $MessageData # cleanup Unregister-Event -SourceIdentifier Pipo
This does trigger the call to MyHandler and I can access passed arguments and event properties. The problem is I cannot find a way to get data back from the event handler function to the raiser of the event. My mindset is probably too much C#-oriented, I may be trying to do things in a way that I should go about differently in PS. The whole concept of creating an object and send it down the pipeline in order to raise an event seems odd to me and I do not understand what really happens.
What is the right way to do this?
1 Answers
Answers 1
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_Initialized Subscribe-Event -Name ProcessedData -Handler $function:Handle_ProcessedData Write-Host "" Write-Host "calling with active subscriptions" Write-Host "=================================" Do-ServerStuff Unsubscribe-Event -Name Initialized Unsubscribe-Event -Name ProcessedData Write-Host "" Write-Host "calling again with no active subscriptions" Write-Host "=================================" Do-ServerStuff Remove-Module -Name "Server"
Then, in the same folder, put this as "Server.psm1":
[ScriptBlock]$script:Handler_Initialized = $null [ScriptBlock]$script:Handler_ProcessedData = $null function 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!
0 comments:
Post a Comment