Tome's Land of IT

IT Notes from the Powertoe – Tome Tanasovski

Category Archives: events

Interprocess Communication (IPC) Between Asynchronous Events Triggered in C# to PowerShell

I played with Apache Zookeeper and PowerShell all weekend.  While I won’t dig into what I’ve been doing deeply in this article, I will tell you that the .NET interface requires a lot of asynch callbacks (event triggering).  Fortunately, I worked out a way to not only receive an event from C# (easy Bruce Payette taught us that years ago), but I also found a way to communicate variables into the runsapce of the action that gets executed when an event is triggered.

We’ll start with a simple C# class with an event to subscribe to.  In this case, I’m going to subscribe to the Changed event of my TestEvent.Watcher class.  This class has a single method which I can use to invoke the event with a string message attached to it.  This code should be invoked prior to running anything else in this post:

$code = @"
namespace TestEvent
{
    using System;

    public class Watcher
    {
        public delegate void ChangedEvent(object sender, WatcherEventArgs e);
        public event ChangedEvent Changed;

        public virtual void InvokeEvent(string message)
        {
            if (Changed != null) {
                Changed(this, new WatcherEventArgs(message));
            }
        }
    }
    public class WatcherEventArgs : EventArgs
    {
        public string message;
        public WatcherEventArgs(string message) {
            this.message = message;
        }
    }
}
"@
add-type -typedefinition $code

Here is the code to create the subscription and invoke the event. Register-ObjectEvent creates a job to run the event actions:

$watcher = new-object testevent.watcher

$job = Register-ObjectEvent -InputObject $watcher -EventName Changed -Action {
    $eventargs.message
}

$watcher.InvokeEvent('Triggering this message')

sleep 1 # just to ensure that the trigger happens
$job |receive-job

When we invoke, it looks like this:

PS C:\> .\test.ps1
Triggering this message

In Zookeeper, I needed to pass the current and valid connection object I had to the watcher so that when the event was triggered it would be sure to use the live object. Unfortunately, I didn’t see a way to pass arguments to the scriptblock, but I realized that I had full access to the $sender variable. In the above example,$sender is the testevent.watcher object I created and stored in the $watcher variable. Therefore, I figured I could just use add-member on $watcher to attach whatever objects I need to the action scriptblock.  These could then be accessed via $sender,

$watcher = new-object testevent.watcher
$watcher |add-member -NotePropertyName 'object' -NotePropertyValue 'value at watcher creation'

$job = Register-ObjectEvent -InputObject $watcher -EventName Changed -Action {
    $eventargs.message
    $sender.object
}

$watcher.InvokeEvent('Triggering this message')

sleep 1 # just to ensure that the trigger happens
$job |receive-job

The results of the above show that $sender.object indeed has the value I set when I registered the event.

PS C:\> .\test.ps1
Triggering this message
value at watcher creation

With that accomplished, I had to test whether or not the parent script could modify the $watcher object prior to the trigger and still set the value. This would enable me to pass live objects if they ever changed or needed to be updated by the parent script.

$watcher = new-object testevent.watcher
$watcher |add-member -NotePropertyName 'object' -NotePropertyValue 'value at watcher creation'

$job = Register-ObjectEvent -InputObject $watcher -EventName Changed -Action {
    $eventargs.message
    $sender.object
}

$watcher.object = 'value at watcher invokation'
$watcher.InvokeEvent('Triggering this message')

sleep 1 # just to ensure that the trigger happens
$job |receive-job

As you can see, success! The object is updated and passed at the time of the trigger rather than at the time of registration.

C:\> .\test.ps1
Triggering this message
value at watcher invokation

In case your interested, communication from the triggered job to the parent (the other half of IPC) is very easy. Simply use the messages/objects from the output of the scriptblock itself. In other words, your parent script will need to call receive-job and deal with the messages/output accordingly.

UPDATE

The above technique is sound and still useful for controlling messages to an event trigger.  It’s a very clean technique to use the $sender object.  However, I realized further in my testing and playing that it’s not always necessary.  Actions have access to GLOBAL and SCRIPT scope variable.  Not only can they access those variables on demand, but they can set them too.  This winds up being an even easier medium for transferring state because it’s actually just updating the live state.

Advertisements
%d bloggers like this: