Welcome PowerShell User! This recipe is just one of the hundreds of useful resources contained in the PowerShell Cookbook.

If you own the book already, login here to get free, online, searchable access to the entire book's content.

If not, the Windows PowerShell Cookbook is available at Amazon, or any of your other favourite book retailers. If you want to see what the PowerShell Cookbook has to offer, enjoy this free 90 page e-book sample: "The Windows PowerShell Interactive Shell".

31.1 Respond to Automatically Generated Events

Problem

You want to respond automatically to a .NET, WMI, or engine event.

Solution

Use the -Action parameter of the Register-ObjectEvent, Register-CimIndicationEvent, and Register-EngineEvent cmdlets to be notified when an event arrives and have PowerShell invoke the script block you supply:

PS > $timer = New-Object Timers.Timer
PS > $timer.Interval = 1000
PS > Register-ObjectEvent $timer Elapsed -SourceIdentifier Timer.Elapsed `
    -Action { $GLOBAL:lastRandom = Get-Random }

Id              Name            State      HasMoreData     Location
--              ----            -----      -----------     --------
2               Timer.Elapsed   NotStarted False

PS > $timer.Enabled = $true
PS > $lastRandom
836077209
PS > $lastRandom
2030675971
PS > $lastRandom
1617766254
PS > Unregister-Event Timer.Elapsed

Discussion

PowerShell’s event registration cmdlets give you a consistent way to interact with many different event technologies: .NET events, WMI events, and PowerShell engine events.

By default, when you register for an event, PowerShell adds a new entry to the sessionwide event repository called the event queue. You can use the Get-Event cmdlet to see events added to this queue and the Remove-Event cmdlet to remove events from this queue.

In addition to its support for manual processing of events, you can also supply a script block to the -Action parameter of the event registration cmdlets. When you provide a script block to the -Action parameter, PowerShell automatically processes events when they arrive.

However, doing two things at once means multithreading. And multithreading? Thar be dragons! To prevent you from having to deal with multithreading issues, PowerShell tightly controls the execution of these script blocks. When it’s time to process an action, it suspends the current script or pipeline, executes the action, and then resumes where it left off. It processes only one action at a time.

PS > $timer = New-Object Timers.Timer
PS > $timer.Interval = 1000
PS > Register-ObjectEvent $timer Elapsed -SourceIdentifier Timer.Elapsed `
    -Action { Write-Host "Processing event" }
$timer.Enabled = $true

PS > while($true) { Write-Host "Processing loop"; Sleep 1 }
Processing loop
Processing event
Processing loop
Processing event
Processing loop
Processing event
Processing loop
Processing event
Processing loop
(...)

Inside the -Action script block, PowerShell gives your script access to five automatic variables:

$eventSubscriber

The subscriber (event registration) that generated this event.

$event

The details of the event itself: MessageData, TimeGenerated, etc.

$args

The arguments and parameters of the event handler. Most events place the event sender and customized event information as the first two arguments, but this depends on the event handler.

$sender

The object that fired the event (if any).

$eventArgs

The customized event information that the event defines, if any. For example, the Timers.Timer object provides a TimerElapsedEventArgs object for this parameter. This object includes a SignalTime parameter, which identifies exactly when the timer fired. Likewise, WMI events define an object that places most of the information in the $eventArgs.NewEvent property.

In addition to the script block that you supply to the -Action parameter, you can also supply any objects you’d like to the -MessageData parameter during your event registration. PowerShell associates this data with any event notifications it generates for this event registration.

To prevent your script block from accidentally corrupting the state of scripts that it interrupts, PowerShell places it in a very isolated environment. Primarily, PowerShell gives you access to your event action through its job infrastructure. As with other PowerShell jobs, you can use the Receive-Job cmdlet to retrieve any output generated by your event action:

PS > $timer = New-Object Timers.Timer
PS > $timer.Interval = 1000
PS > Register-ObjectEvent $timer Elapsed -SourceIdentifier Timer.Elapsed `
    -Action {
        $SCRIPT:triggerCount = 1 + $SCRIPT:triggerCount
        "Processing Event $triggerCount"
    }
PS > $timer.Enabled = $true

Id              Name            State      HasMoreData     Location
--              ----            -----      -----------     --------
1               Timer.Elapsed   NotStarted False

PS > Get-Job 1
Id              Name            State      HasMoreData     Location
--              ----            -----      -----------     --------
1               Timer.Elapsed   Running    True


PS > Receive-Job 1
Processing Event 1
Processing Event 2
Processing Event 3
(...)

For more information about working with PowerShell jobs, see Recipe 1.6.

In addition to exposing your event actions through a job interface, PowerShell also uses a module to ensure that your -Action script block is not impacted by (and does not impact) other scripts running on the system. As with all modules, $GLOBAL variables are shared by the entire session. $SCRIPT variables are shared and persisted for all invocations of the script block. All other variables persist only for the current triggering of your event action. For more information about PowerShell modules, see Recipe 11.7.

For more information about useful .NET and WMI events, see Appendix I.

See Also

Recipe 1.6, “Invoke a Long-Running or Background Command”

Recipe 11.7, “Write Commands That Maintain State”

Appendix I, Selected Events and Their Uses