Today, I’d like to teach you a little bit about PowerShell eventing. It is an asynchronous event handling because we can check the event queue on our own time as subscribed events fire in the background.
Latest posts by Timothy Warner (see all)

Windows PowerShell eventing

Windows PowerShell eventing

Eventing has been a part of the language since version 2.0. Although, in my opinion, it is more of a “true blue” developer’s topic, we sysadmins have every right to know how to subscribe to and receive event messages from .NET Framework objects through the PowerShell runtime. Let’s get started.

Understanding eventing from a high level

According to the Microsoft Developer Network (MSDN) website, an event is “a message sent by an object to signal the occurrence of an action.” Well, we know that all data contained in a PowerShell pipeline are objects, right? It makes sense that these objects can transmit status messages on their own behalf; this is “eventing.”

Here’s the high-level workflow for how Windows PowerShell eventing works:

  1. You discover the object event or events that you’re interested in monitoring.
  2. You subscribe to the event using the appropriate Register- cmdlet.
  3. You periodically check the event queue programmatically and format your output as appropriate.
  4. You purge events from the queue when you’re finished.

I’ll spend the rest of our time together today walking you through each of the previous steps, one at a time.

To give this tutorial maximum real-world relevance to you, the Windows systems administrator, let’s say that we suspect one of our Windows Server 2012 R2 file servers may be compromised with malware. We’ll use Windows Management Instrumentation (WMI) temporary event subscriptions to record all process start and stop events.

Yes, you heard me correctly: we’ll create a temporary event subscription here, but you can configure permanent event subscriptions if you want to. You’ll find that PowerShell experts even created CLI and graphical front ends for permanent event subscriptions!

Discovering your chosen object event

You can see which, if any, events your favorite Windows PowerShell/.NET objects exposed by pipelining the object into Get-Member:

Get-Process -Name notepad | Get-Member -MemberType Event

    TypeName: System.Diagnostics.Process

Name               MemberType Definition
----               ---------- ----------
Disposed           Event      System.EventHandler Disposed(System.Object, Sy...
ErrorDataReceived  Event      System.Diagnostics.DataReceivedEventHandler Er...
Exited             Event      System.EventHandler Exited(System.Object, Syst...
OutputDataReceived Event      System.Diagnostics.DataReceivedEventHandler Ou...

Of the four built-in process events, the only one that makes sense to me immediately is Exited, which fires when the process exits from its runspace. That makes sense.

I’ve found that the object events that PowerShell natively exposes don’t give us the specific data we’re after.

As I said earlier, we want Windows PowerShell to fire events when new processes are started. You and I both know that the WMI/Common Information Model (CIM) repository stores this information. We simply need to apply some research and a good WMI Query Language (WQL) query to fetch our results. Try this for process starts:

SELECT * FROM Win32_ProcessStartTrace

And this for process terminations:

SELECT * FROM Win32_ProcessStopTrace

By the way, I’m adapting this Process Watcher example from the work of Windows PowerShell MVPs Boe Prox and Richard Siddaway.

Subscribing to .NET events in Windows PowerShell

We can create subscriptions to three different event types, and the Windows PowerShell team gave us a cmdlet for each type:

The following event subscription looks a bit gnarly at first glance. However, stick with me and you’ll be fine; I’ll explain what’s going on line by line:

$hindex = '"{0}","{1}","{2}","{3}","{4}"'
$header = $hindex -f 'Host', 'Process ID', 'Process Name', 'Timestamp', 'Source ID'

Add-Content -Path 'C:\process-start.txt' -Value $header

$pstart = 'SELECT * FROM Win32_ProcessStartTrace'
$pstop = 'SELECT * FROM Win32_ProcessStopTrace'

$action = {
                $nevent = $Event.SourceEventArgs.NewEvent
                $hindex = '"{0}","{1}","{2}","{3}","{4}"'
                $data = $hindex -f $Event.Sender.Scope.Path.Server, $nevent.ProcessID, $nevent.ProcessName, $Event.TimeGenerated, $Event.SourceIdentifier
                Add-Content -Path 'C:\process-start.txt' -Value $data
Register-WmiEvent -Query $pstart -SourceIdentifier 'Process.Start' -Action $action
Register-WmiEvent -Query $pstop -SourceIdentifier 'Process.Stop' -Action $action

Get-Content -Path "c:\process-start.txt"

1-2: All we’re doing here is applying some PowerShell composite string formatting to set up our log file. The $hindex variable defines five index positions, and the $header variable links the index positions to five friendly column names with the -f operator. You can use that {} composite string formatting syntax to adjust column widths and alignment and even apply styles such as currency formatting.

4: Here, we create a new log file to store our captured events and specify the $header contents as our first row. After all, what’s a log file without column headings?

6-7: These variables simply store our WQL queries: one for process starts and the other for process stops. We could just write the queries longhand in our Register-WmiEvent statements, but we can make our registrations more compact by creating separate variables.

9-14: We need to create an action that PowerShell takes when a process start or stop event is detected. Our action taps deeply into the $Event automatic variable and pulls out useful properties that (not coincidentally) align with the column headings we defined in the $header variable earlier in the script.

The $Event automatic variable stores metadata about (you guessed it) events that PowerShell/.NET objects generate.

10: The $nevent variable is simply a shortcut way to refer to the $Event.SourceEventArgs.NewEvent object.

11: Once again, we are going to use composite string formatting to make five-column output.

12: The $data variable holds the actual properties that we want to see in our process-start.txt log file.

13: Here, we use Add-Content to put the event metadata into the log file.

15-16: As I stated earlier, we make our Register-WmiEvent statements easier to read because we’ve parameterized the -Query and -Action values.

18: This is simply a shortcut to view the log file contents on the PowerShell console.

Checking the event queue

We can list our current session’s event subscriptions with the Get-EventSubscriber cmdlet:


SubscriptionId   : 1
SourceObject     : System.Management.ManagementEventWatcher
EventName        : EventArrived
SourceIdentifier : Process.Start
Action           : System.Management.Automation.PSEventJob
HandlerDelegate  : 
SupportEvent     : False
ForwardEvent     : False

SubscriptionId   : 2
SourceObject     : System.Management.ManagementEventWatcher
EventName        : EventArrived
SourceIdentifier : Process.Stop
Action           : System.Management.Automation.PSEventJob
HandlerDelegate  : 
SupportEvent     : False
ForwardEvent     : False

As you can see in the Action property, PowerShell considers these temporary event subscriptions to be honest-to-goodness background jobs (run Get-Job to prove this to yourself).

To fire example process start events, start Notepad and Calc from Windows. After they’re open, close them to fire process stop events.

Let’s have a look at that logfile:

Get-Content -Path "C:\process-start.txt"

"Host","Process ID","Process Name","Timestamp","Source ID"
"localhost","3316","calc.exe","4/30/2015 9:36:47 AM","Process.Start"
"localhost","4064","notepad.exe","4/30/2015 9:36:51 AM","Process.Start"
"localhost","3316","calc.exe","4/30/2015 9:36:54 AM","Process.Stop"
"localhost","4064","notepad.exe","4/30/2015 9:36:55 AM","Process.Stop"

Now we’re cooking with gas! If our file server is indeed infected with malware, we should be able to track the rogue process starts and stops and isolate the problem in no time.

What’s even cooler is that, thanks to Windows PowerShell remoting, we can subscribe to events generated on remote computers as well.

Removing subscriptions and purging the queue

Remember that these event subscriptions persist only for the current Windows PowerShell session. Therefore, closing your console window automagically makes your event subscriptions go “bye bye.”

We can remove individual events:

Get-Event –SourceIdentifier | Remove-Event

Or we can unregister entire event subscriptions:

Unregister-Event –SourceIdentifier Process.Start
Unregister-Event –SourceIdentifier Process.Stop

Lastly, we can purge the entire subscription event queue by leveraging the Windows PowerShell pipeline:

Get-EventSubscriber | Unregister-Event -Force


Remember that what we did in this article is create temporary event subscriptions. This means that our subscriptions will monitor and potentially report on subscribed events only for as long as your console session is active. Alternatively, of course, you can unregister your subscription from the current session.

An important note: if you try this exercise in Windows PowerShell v5 preview, you’ll get unexpected results. Specifically, I couldn’t get my subscription to track process start events at all. Remember that PowerShell v5 is not fully “baked,” so make sure you experiment with this feature in Windows PowerShell v4 or earlier.

Note to self: I should write 4sysops blog posts on (a) creating permanent event subscriptions, and (b) using composite string formatting to get precisely the tabular output you need. Thanks a lot for reading, and take care.

1 Comment
  1. Avatar
    Reid 6 years ago

    “I should write 4sysops blog posts on (a) creating permanent event subscriptions”

    Please do! I Created a script that remaps a printer on rdp reconnect, and I would really like to turn it into a permanent event.


Leave a reply

Please enclose code in pre tags: <pre></pre>

Your email address will not be published. Required fields are marked *


© 4sysops 2006 - 2023


Please ask IT administration questions in the forums. Any other messages are welcome.


Log in with your credentials


Forgot your details?

Create Account