Use Asynchronous Event Handling in PowerShell

Use Asynchronous Event Handling in PowerShell

  • Comments 6
  • Likes

Summary: Bruce Payette shows how to use asynchronous event handling in Windows PowerShell.

Microsoft Scripting Guy, Ed Wilson, here. I am really excited about the idea I had for this week, and I hope you will be too. I asked Candace Gillhoolley at Manning Press about posting some sample works from some of the Manning Press library of books. She responded enthusiastically and shared five samples that we will post this week. Today is part one of two parts from Bruce Payette and Windows PowerShell in Action.

Windows PowerShell in Action, Second Edition

Image of book cover

By Bruce Payette

The key difference between event-based scripting and traditional procedural scripting is that, instead of an activity being executed as a result of an action in the script, a script (or at least a portion of it) is executed as a result of an action by the system.. In this article based on chapter 20 of Windows PowerShell in Action, Second Edition, author Bruce Payette discusses asynchronous event-handling models in PowerShell. To save 35% on your next purchase use Promotional Code payette22035 when you check out at www.manning.com.

manning

Asynchronous Event Handling 

The key difference between event-based scripting and traditional procedural scripting is that, instead of an activity being executed as a result of an action in the script, a script (or at least a portion of it) is executed as a result of an action by the system. This pattern is sometimes called inversion of control, but it can be expressed more colorfully as “Don’t call me, I’ll call you.”

NOTE This way of characterizing event-based programming captures the essence of the model perfectly. Crispin Cowan (Linux Security and now Windows Security Guru Extraordinaire) suggested this definition as we were hiking through the Cougar Mountains in Washington. Clearly, inspiration can arrive anywhere.

The traditional and event-driven flow control patterns are shown in figure 1.

Image of flow chart

Figure 1 The normal flow of control in a script is compared to the flow in an event-based script. In the normal flow of control, the main thread of execution always retains control, calling library routines as needed. In event-based programming, the mainline registers a set of callback actions that will be executed when the specified event occurs. The event service then controls the flow of execution.

Take a look at the traditional flow of control illustrated in the figure. In the traditional model, the flow of control always belongs to the mainline of the program. If an action is required, the mainline program directly invokes that action. In contrast, with the eventing pattern, rather than directly initiating actions, the mainline program registers the set of actions with an event source and then goes to sleep. It never initiates any actions on its own. Instead, the event source is responsible for initiating actions as required. In this scenario, you are, in effect, turning control over to the event service.

Sometimes this event service is a library routine that the mainline calls and allows this library to handle dispatching events to the callbacks. This model is frequently used in GUI programming.

NOTE In practice, we’ve been using this callback pattern all along, not just in GUIs. This is how the ForEach-Object and Where-Object cmdlets work: you pass action scriptblocks to the cmdlets and the cmdlets take care of calling your code when it’s needed.

In other situations, the event service may be an active entity like another thread or process. In practice, programs rarely restrict themselves to a single model but instead use different models at different times as appropriate. We’ll explore these models in more detail and you’ll see how to apply them in PowerShell.

Asynchronous events

Asynchronous events are much trickier to deal with than their synchronous cousins. A synchronous event effectively runs on the same thread of execution as everything else. By analogy, this is like attending a formal lecture where the speaker conducts the main thread of conversation but periodically takes questions from the audience. By following this synchronous question-and-answer policy, at no point are there ever two actions (that is, two conversations) occurring at the same. This makes following the flow of conversation much easier. Everything happens deterministically, eliminating any collisions or consistency/coherency issues. Unfortunately that model doesn’t match the way much of the real world works. Real-world events don’t occur in a strict deterministic order—they happen when they happen, interrupting whatever else might be going on at that time. In the lecture analogy, this is like the audience spontaneously yelling out questions, interrupting the speaker, and possibly confusing everyone. This type of concurrent operation makes life difficult for scripters because it means that things may possibly get changed out of order or in unanticipated ways, resulting in inconsistencies and errors.

In Windows PowerShell v1, there was no support for the asynchronous pattern, which made it pretty much impossible to handle asynchronous events. In fact, out of concern over the possibility that things might happen out of order, PowerShell actively checks to see if you’re trying to perform asynchronous actions and shuts down (that is, calls the FailFast() API, causing a crash) if it detects them.

NOTE The rationale behind this behavior was, essentially, that it’s better to be absolutely useless than to be possibly wrong. This is a somewhat extreme view and not everyone agrees with this line of reasoning. On the other hand, crashing and thereby halting an operation rather than, say, possibly launching a rocket at the wrong target does make a certain amount of sense. You can’t un-launch a rocket. Saying “Sorry, my bad” after blowing up a city just doesn’t cover it.

To allow for robust handling of asynchronous events, Windows PowerShell v2 added an eventing subsystem that uses a centralized event manager to ensure that this occurs in a rational sequence. This subsystem takes care of all the bookkeeping and synchronization needed to ensure a stable and consistent system without a lot of work on the part of the script author. In the next section, we’ll introduce the model PowerShell uses for doing this.

Subscriptions, registrations, and actions

The scripting model PowerShell uses for handling asynchronous events involves a few core concepts. The first concept is the idea of an event subscription, where you select the type of events you want to know about and then subscribe to be notified when they occur. These subscriptions are registered with a source identifier, which allows you to give a friendly name to each subscription. Once registered, the event subscription will be notified about relevant events as soon as they occur and will continue to receive notifications until the subscription is cancelled by explicitly unregistering it.

Each event subscription may optionally specify an action to be taken. With these concepts in mind, we’ll look at the eventing cmdlets in the next section.

The eventing cmdlets

The PowerShell eventing cmdlets are shown in table 1. These cmdlets allow you to register and unregister event subscriptions and list the existing subscriptions. You can also list pending events (as opposed to subscriptions) and handle or remove them as desired. There is also a cmdlet that allows scripts to generate their own events.

Table 1 The PowerShell eventing cmdlets

Cmdlet name

Description

Register-ObjectEvent

This cmdlet registers an event subscription for events generated by .NET objects.

Register-WmiEvent

Registers an event subscription for events generated by WMI objects.

Register-EngineEvent

Registers an event subscription for events generated by PowerShell itself.

Get-EventSubscriber

Gets a list of the registered event subscriptions in the session.

Unregister-Event

Removes one or more of the registered event subscriptions.

Wait-Event

Waits for an event to occur. This cmdlet can wait for a specific event or any event. It also allows a timeout to be specified, limiting how long it will wait for the event. The default is to wait forever.

Get-Event

Gets pending unhandled events from the event queue.

Remove-Event

Removes a pending event from the event queue.

New-Event

This cmdlet is called in a script to allow the script to add its own events to the event queue.

When handling events, you need to be able to register actions in response to these events. You do so using cmdlets but, because there are several types or sources of events, there are also several event registration cmdlets, as you saw in the table. The three event subscription registration cmdlets are Register-EngineEvent, Register-ObjectEvent, and Register-WmiEvent. PowerShell-specific events are handled using the Register-EngineEvent cmdlet, asynchronous events on .NET objects are handled using Register-ObjectEvent, and WMI events are addressed with Register-WmiEvent.

Next, we’ll focus on .NET events—the so-called object events. In the process of doing this, we’ll also cover most of the core eventing concepts that apply when working with any of the event sources.

Working with asynchronous .NET events

You use the Register-ObjectEvent cmdlet to create subscriptions for asynchronous events on .NET objects. The signature for this cmdlet is shown in figure 2.

Image of cmdlet signature

Figure 2 The signature of the Register-ObjectEvent cmdlet. This cmdlet is used to set up event handling for asynchronous events generated by .NET objects.

Let’s see how these parameters are used. First you need to identify the event you’re interested in. For .NET events, this means that you need an object and the name of the event member on that object to bind. This is the same pattern you’ve already seen with Windows Forms and WPF, where, for example, a Button object has a Click event accessed through the add_Click() member.

Once you’ve decided on the event to handle, you need to specify what to do with the event. The -Action parameter on the cmdlet allows you to provide a scriptblock to execute when an event fires. This scriptblock will receive a lot of information about the event when it’s run, but there may be some additional, custom data that you want to pass to the event handler. You can do this with the MessageData parameter.

Finally, when you have a number of events that you’re working with, the ability to attach a friendly name to the subscription will make things easier to manage. This is what -SourceIdentifier is for: it allows you to name the event registration or event source.

There’s one last parameter that we haven’t discussed yet: -SupportEvent. In larger event-driven scripts, there may be a number of event registrations that only exist to support higher-level constructs within the application. In these scenarios, it’s useful to be able to hide these supporting events much like the rationale behind the way you hide supporting functions in modules. This event-handler hiding is accomplished using the -SupportEvent switch. As in the case of modules, if you do want to see the hidden events, you can specify the -Force switch on Get-EventSubscriber.

Writing a timer event handler

Okay, enough talk—let’s start doing something with .NET events. One of the most obvious examples of an asynchronous event is a timer. A timer event fires at regular intervals regardless of what else is going on. Let’s see how you can set up a subscription events generated by the .NET System.Timers.Timer class.

NOTE These cmdlets can only be used for asynchronous .NET events. It’s not possible to set up event handlers for synchronous events using the PowerShell eventing cmdlets. This is because synchronous events all execute on the same thread and the cmdlets expect (require) that the events will happen on another thread. Without the second thread, the PowerShell engine will simply block the main thread and nothing will ever get executed.

Creating the Timer object

The first thing you need for our example is a Timer object. You use New-Object to create it:

PS (1) > $timer = New-Object System.Timers.Timer

Because events are first-class and exist as members on a class, you can use Get-Member, filtering the results on the Event member type, to see what events this object exposes:

PS (2) > $timer | Get-Member -MemberType Event

 

   TypeName: System.Timers.Timer

 

Name      MemberType    Definition

----       ----------    ----------

Disposed  Event         System.EventHandler Disposed(System.Objec...

Elapsed   Event         System.Timers.ElapsedEventHandler Elapsed...

From this output, you can see that the Elapsed event is what you’re looking for—it fires when the timer period has elapsed.

Setting the timer event parameters

But, you need to know more about this object than just the events—you need to know how to set the timer interval, and start and stop the timer. Again you can use Get-Member to find this information. (Note that the output shown here has been trimmed to the interesting members for brevity’s sake.)

PS (3) > $timer | Get-Member

 

   TypeName: System.Timers.Timer

 

Name      MemberType    Definition

----      ----------    ----------

Disposed  Event         System.EventHandler Disp...

Elapsed   Event         System.Timers.ElapsedEve...

Close     Method        System.Void Close()

Start     Method        System.Void Start()

Stop      Method        System.Void Stop()

ToString  Method        string ToString()

AutoReset Property      System.Boolean AutoReset...

Enabled   Property      System.Boolean Enabled {...

Interval  Property      System.Double Interval {...

When you look at the output, the way to start and stop the timer is obvious. The AutoReset property determines if the timer only fires once (AutoReset = $false) or fires repeatedly every interval (AutoReset = $true). Finally, the Interval property controls the firing interval. Because the value is a double, you can guess that it’s specified in milliseconds.

NOTE Yes, you could’ve gone to the MSDN documentation. But, really, why bother? With Get-Member and a reasonably decent understanding of .NET, Get-Member is frequently all you need. This makes PowerShell a useful tool for developers as well as IT professionals. Even in Visual Studio, sometimes we’ll still flip over to a PowerShell window to search for information about a type. Simple text and typing is still faster sometimes.

Binding the event action

Let’s register for an event on this object, which you do with the following command:

This command attaches a scriptblock to the event that will write out the phrase "<TIMER>" when it fires. You have to use Write-Host in this scriptblock because the output from a triggered event action is simply discarded.

Now you’ll wait a minute…and…nothing happens. This is because you haven’t done all of the other things to the Timer object to make it start firing (though obviously, binding the event handler beforehand is usually a good idea).

Enabling the event

Let’s complete the remaining steps needed to start the timer triggering. Set the interval to 500 milliseconds so the timer will fire in half a second:

You want to fire repeatedly, so set the AutoReset property to $true:

Next you enable the timer by setting the Enabled property to $true (or by calling the Start() method, which also sets Enabled to $true):

 

The timer starts running and you see the output you expected. Next comes the hard part: getting it to stop. The command is easy; just type $timer.Stop() and press ENTER. But in the console shell, the timer is writing to the screen at the same time you’re typing. This results in scrambled output, looking something like this:

<TIMER>

<TIMER>

<TIMER>

<TIMER>

$timer.Stop()<TIMER>

<TIMER>

Using Register-ObjectEvent

As a handy way to remember how to use the Register-ObjectEvent cmdlet, think of assigning the scriptblock to the event member. If PowerShell supported this, it’d look something like this:

The Register-ObjectEvent command allows positional parameters in the same order, so the command would look like

where the order of the elements is the same: object/member/action.

(Here’s another place where the ISE just works better—the timer output doesn’t interfere with the ability to run commands.) Once you’ve stopped the timer, you can restart it by calling the Start() method a second time:

Now that you know how to register a basic event subscription, we’ll look at how to manage these subscriptions. Come back tomorrow as Bruce continues and shows us how to manage subscriptions. Thank you, Bruce.

I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

Ed Wilson, Microsoft Scripting Guy

Your comment has been posted.   Close
Thank you, your comment requires moderation so it may take a while to appear.   Close
Leave a Comment
  • I have been using this stuff a little lately. This is very nicely put and explained well. Thank you.

    Thanks,

    Allan

  • Very interesting stuff.  Thanks, Scripting Guy!

    But I find that if I use the $timer.stop method, it stops momentarily, then starts again.  I imagine this is because of the property autoreset = $true.  The only way I can get the timer to stop (in ISE) is to use the $timer.enabled = $false.

  • Bruce Payette + Ed Wilson = Best Blog Ever!

    Just ordered Powershell in Action: Second Edition a must have for any sysadmin who is interested in powershell.

    Scripting Guy you have done it again.  Thank You for providing the highest level of powershell content as always.

    Sincerely,

    Ryan Shafer

  • That's great!

    I have a question. How can I register the event System.Net.NetworkInformation.NetworkChange.NetworkAvailabilityChanged  and show the network state is changed?

  • I'm having a combobox_SelectedIndexChanged event that isn't firing, and one that is. I've got them back to back at the top of my form load event. When $cmbstates is created, I'm automatically disabling it, and only want to enable it if the condition below is met. We don't care if our foreign offices have states or not, just domestic.

    function LoadForm ($errorhash, $datahash)
    {
    $cmbcountry_SelectedIndexChanged = {
    if ($cmbcountry.text -like "*US*")
    {
    $cmbstates.enabled = $true
    }
    else
    {
    $cmbstates.enabled = $false
    }
    }#end $cmbcountry_selectedindexchanged


    $cmbbox_SelectedIndexChanged = {
    $searchbase = $null
    $mgr = $cmbbox.Text
    $rawou = Get-ADUser -Filter {name -eq $mgr} | select distinguishedname
    $ou = $rawou.distinguishedname.split(",")
    for ($i = 1; $i -le $ou.count - 1 ; $i++)
    {
    switch ($i)
    {
    $($ou.count - 1) {$searchbase += $ou[$i]}
    default {$searchbase = $searchbase + $ou[$i] + ","}
    }
    }
    $realou = "*" + $ou[1] + "*"
    $mgrs = Get-ADUser -Filter * -SearchBase $searchbase -Properties manager | select manager | group manager | select name | sort name
    $cmbmgr.Items.Clear()
    foreach ($m in $mgrs)
    {
    [void]$cmbmgr.Items.Add($m.name)
    }

    }# end $cmbbox_SelectedIndexChanged

    #Load the Windows Form assembly
    Add-Type -AssemblyName system.windows.forms

    #create the main form
    $mainform = LoadMainForm


    ..........more code in the form load function
    }#end form load

    Not a newby to PS in particular or writing code in general. I'm just stumped.

  • NVM. I forgot to add the event handler to the combo box object.