Handling Events with PowerShell and .NET (Part 1)

Table of Contents

Post Featured Image

The Oxford dictionary defines an event as “a thing that happens, especially something important”. In the computing world most people associate events with logging, however events go beyond that. In fact, a log is nothing but an output from an event.

This blog post covers two types of events, PowerShell Engine Events and .NET Object Events (click here for part 2 of the series, Handling events with PowerShell and WMI). 

Keep on reading if you want to learn how to handle both PowerShell Engine Events and .NET Object Events and which PowerShell commandlets will get you there.

The Basics: From PowerShell Classes and Objects to Events

Before we delve into the details of PowerShell events, we first need to understand classes and objects.
  • A class is a collection of properties and methods.
  • An object is an instance of the class which is being used to access properties and methods.

Simple, right? Let’s simplify this using a real-world example.

I’m an object of my parents class. My parents have certain properties such as black hair, brown eyes etc. and certain methods such as cooking, dancing etc.

Let’s take a simple example of a class defined in PowerShell:

class PrintName {#Define a class
    [Int] $count #Property
    [Void] WriteHello([string] $Name){ #Function
        Write-Host "Hello $Name"
    }
}
$Obj = [PrintName]::new() #Instantiate an Object

$Obj.count = 1 #Assign value for count property
Write-Host "Property Value " $obj.count
$Obj.WriteHello('Sonny') #Call GetProcessPath method
"`n"

Which delivers the following output:

Property Value 1
Hello Sonny

In the example above we created a class called “PrintName” with one property called “count” and method called “WriteHello”. But why are we talking about classes?

Well, to understand events we need to first understand class structure because an event is nothing but a special kind of method.

Special, because other pieces of code can subscribe to events (this is also known as event subscription).

PowerShell Engine Events

In order to enable other programs to be informed about certain actions or state changes by a specific program, we need to create events in that program.

Thus, other programs can subscribe to these events and get informed when certain actions or state changes happen in your program (don’t worry, when we discuss .NET Framework events things would become clearer).

PowerShell provides different cmdlets for working with events. Let’s start with the New-Event cmdlet (Figure 1), which, as the name suggests, creates a new Event.

Creating an event: New-Event cmdlet

PowerShell provides different cmdlets for working with events. Let’s start with the New-Event cmdlet (Figure 1), which, as the name suggests, creates a new Event.
New-Event 

    [-SourceIdentifier]  

    [[-Sender] ] 

    [[-EventArguments] <psobject[]>] 

    [[-MessageData] ] 

    []</psobject[]>

Source: Microsoft

 

Viewing the event queue: Get-Event cmdlet

Once an Event is created it is added to the Event queue. To view the event queue, we use the Get-Event cmdlet. Figure 2 shows that there are two events in the queue.
Get-Event
   [[-SourceIdentifier] ]
   []
Get-Event
   [-EventIdentifier] 
   []

Source: Microsoft

The output of the Get-Event cmdlet shows, that there are currently two events in the event queue

Figure 2: The Get-Event cmdlet gets events in the PowerShell event queue for the current session

Removing events from the queue: Remove-Event cmdlet

Now how do we remove events from the queue? We use the Remove-Event cmdlet as shown in Figure 3.
Remove-Event
      [-SourceIdentifier] 
      [-WhatIf]
      [-Confirm]
      []
Remove-Event
      [-EventIdentifier] 
      [-WhatIf]
      [-Confirm]
      []

Source: Microsoft

 Remove-Event cmdlet deletes events from the event queue

Figure 3: The Remove-Event cmdlet deletes events from the event queue in the current session.

As we can see, we only have one event left in the event queue.

Before we move on, let’s clear the event queue by running the following cmdlet

Remove-Event -SourceIdentifier *

At this point there are no events in our event queue.

Subscribing to an event: Register-EngineEvent and Get-EventSubscriber cmdlet

Events are not useful unless someone has subscribed to them. In order to subscribe to an event, we use the Register-EngineEvent cmdlet as shown in Figure 4. Just like Events, Event Subscription is added to the session.

In order to view current subscriptions in a session, use the Get-EventSubscriber cmdlet. Both Register-EngineEvent and Get-EventSubscriber cmdlet are shown in Figure 4.

Register-EngineEvent
        [-SourceIdentifier] 
        [[-Action] ]
        [-MessageData ]
        [-SupportEvent]
        [-Forward]
        [-MaxTriggerCount ]
        []

Source: Microsoft

Get-EventSubscriber
   [[-SourceIdentifier] ]
   [-Force]
   []
Get-EventSubscriber
   [-SubscriptionId] 
   [-Force]
   []

Source: Microsoft

 Subscribing to 'MyEvent' by using the Register-EngineEvent cmdlet

Figure 4: By using the Register-EngingeEvent cmdlet we subscribe to “MyEvent” event

Raising events in PowerShell

In the example above we create a new subscription for an event called “MyEvent”. Now let’s raise the event named “MyEvent”.

As we can see in Figure 5, when we raise the event named “MyEvent”, the event subscriber is invoked and executes the Action Script Block.

 The Action block is being executed when a new event named "MyEvent" is invoked

Unsubscribing from an event: Unregister-Event cmdlet

In order to remove the subscription from the current session we use the Unregister-Event cmdlet as shown in Figure 6.
Unregister-Event
          [-SourceIdentifier] 
          [-Force]
          [-WhatIf]
          [-Confirm]
          []
Unregister-Event
          [-SubscriptionId] 
          [-Force]
          [-WhatIf]
          [-Confirm]
          []

Source: Microsoft

 Cancelling an event subscription with the Unregister-Event cmdlet in PowerShell

Figure 6: Unregistering the event subscriber

        

.NET Framework Events

So far, we have discussed PowerShell engine event. Now let’s switch our focus to .NET framework events. A complete reference for .NET can be found at .NET API browser.

Let’s start by taking a simple example of the Process class which is part of the System.Diagnostic namespace.

As we can see in Figure 7 the Process class has constructor, properties, methods and events. Collectively they are also known as class members. The Process class also has three events:

  • ErrorDataReceived
  • Exited and
  • OutputDataReceived

In order to access class members, we need to either instantiate a new Object or use existing objects. Once we have an object, we can use it to subscribe to the events.

 Figure 7: Exited event for System.Diagnostic.Process class

Figure 7: Exited event for System.Diagnostic.Process class in .NET

Example: Handling Process.Exited Events in PowerShell

PowerShell provides the Register-ObjectEvent cmdlet to subscribe to .NET framework events.

Let’s take an example, suppose that we want to subscribe to the “exiting” event of Process class. In order to do so, we first need an Object of Process class. We can then use the object to subscribe to the Member events of the Process class.

The exiting event is invoked when a process exits. In our example we will use the calculator process object to subscribe to the exiting event.

Any process running on Windows is an Object. To get a list of process objects on windows we can use the Get-CimInstance cmdlet. As you can see in Figure 8 the Get-CimInstance returns the objects and by combining it with the Get-Member cmdlet we can view the class members.

Get-CimInstance
   [-ClassName] 
   [-ComputerName <string[]>]
   [-KeyOnly]
   [-Namespace ]
   [-OperationTimeoutSec ]
   [-QueryDialect ]
   [-Shallow]
   [-Filter ]
   [-Property <string[]>]
   []</string[]></string[]>

Source: Microsoft

 Figure 8: Using Get-CimInstance cmdlet to view running processes

Figure 8: Using Get-CimInstance cmdlet to view running processes

For our example we launch the calculator process in our code. We always have the option to use an already existing process object. In the code below we are first starting the calculator process and capturing the object in $CalcProcessObj variable.

$CalcProcessObj = [System.Diagnostics.Process]::Start("calc.exe")
Register-ObjectEvent -InputObject $CalcProcessObj -EventName Exited -Action { cmd.exe /c ping -n 2 8.8.8.8 }

Next, we use the Register-ObjectEvent cmdlet to subscribe to the exited event for the calculator process.

Register-ObjectEvent
        [-InputObject] 
        [-EventName] 
        [[-SourceIdentifier] ]
        [[-Action] ]
        [-MessageData ]
        [-SupportEvent]
        [-Forward]
        [-MaxTriggerCount ]
        []

Source: Microsoft

When the calculator process exits our action block will be executed. The Action block contain a simple command to send 2 pings to IP address 8.8.8.8. Figure 9 shows the output that we have successfully subscribed to the event.

 Figure 9: Output after running the Register-ObjectEvent cmdlet

Figure 9: Output after running the Register-ObjectEvent cmdlet

Once we exit the calculator process, we can see that the Action Script block was executed and resulted in 2 pings being sent to IP address 8.8.8.8 (Figure 10).

 Figure 10: Wireshark showing 2 pings sent to IP address 8.8.8.8

Figure 10: Wireshark showing 2 pings sent to IP address 8.8.8.8

As we have seen before we can use the Get-EventSubscriber cmdlet for getting a list of all event subscriptions.

The output in our example is shown in Figure 11.

 Figure 11: List of event subscribers for current session
 

Figure 11: Retrieving a list of event subscribers for the current session with Get-EventSubscriber cmdlet

Similarly, we can use the Unregister-Event cmdlet to unregister an event subscription (don’t you love the verb-noun representation of cmdlets 😊), as can be seen in Figure 12 .

Figure 12: Unregistering an event subscriber with the Unregister-Event cmdlet

Figure 12: Unregistering an event subscriber with the Unregister-Event cmdlet

Example: Handling FileSystemWatcher Class Events in PowerShell

Let’s take another example of FileSystemWatcher class which provide some interesting Events (shown in Figure 13) that we can subscribe to, such as file or directory creation, deletion etc.

Suppose we want to subscribe to file/directory creation event.

We follow the same steps from our last example:

  • first we need to get the object
  • then we use the Register-ObjectEvent cmdlet to subscribe to the event
Figure 13: Events for FileSystemWatcher class

Figure 13: Events for FileSystemWatcher class in .NET

Let’s first create an Object of FileSystemWatcher class

$testobj1 = New-Object -TypeName System.IO.FileSystemWatcher

Next, we give our object the directory path to watch for file/directory creation

$testobj1.Path = "C:\Users\Public"

Lastly, we use the Register-ObjectEvent cmdlet to subscribe to the event.
Register-ObjectEvent -InputObject $testobj1 -EventName Created -Action { cmd.exe /c ping -n 2 8.8.8.8 }

Example: Handling System.Diagnostics.EventLog Class Events in PowerShell

Here’s another example: the System.Diagnostics.EventLog class provides an Event called “EntryWritten” which is raised when a log entry is written.

We follow the same steps as before: we instantiate an object, set the Log property for the object (= which log we want to watch) and then use the Register-ObjectEvent cmdlet to subscribe to the EntryWritten event.

$testobj1 = New-Object -TypeName System.Diagnostics.EventLog

$testobj1.Log = "Security"

Register-ObjectEvent -InputObject $testobj1 -EventName EntryWritten -Action { Write-Host "Meow" }

Please note: this code requires admin level permission as we are working with logging.

 EntryWritten event for EventLog class

Figure 14: EntryWritten event for EventLog class

For this demonstration I switched from VS Code to ISE. Figure 15 shows how we subscribed to the EntryWritten event for Security logs. Our action blocks write “Meow” each time the event is invoked.

 Subscribing for “EntryWritten” event

Figure 15: Subscribing for “EntryWritten” event 

Conclusion

There are few things which we need to keep in mind
  • Events, event subscriptions, and the event queue exist only in the current session. If we close the current session, the event queue is discarded, and the event subscription is canceled.
  • There are two separate cmdlets to work with PowerShell engine events (Register-EngineEvent) and .NET Framework events (Register-ObjectEvent)

In the next part we will be discussing, how to handle WMI eventing with PowerShell.


Full control and complete traceability

You can leverage your monitoring capabilities even more by using ScriptRunners Report/Audit DB Connector. It lets you store all your reports on script execution in one central repository and provides you with complete traceability of all processes.


 

About the author: