PowerShell and Events: Engine Events

There are 4 types of events  that you can register and use for Event Handling: Engine Events, Object events (.Net),CIM (PowerShell V3 only) and WMI events . These provide a great way to provide some asynchronous event handling to monitor various activities on your system. Another type of event is used by Windows Forms and WPF, but if you want to see examples of those, I would suggest checking out my articles on WPF.

Generally, the most used type of event handling that I see is usually WMI events followed by Object (.Net) events with Engine events ranking at the bottom. In the case of my next set of articles, I am taking the reverse route by talking about Engine events and working my way up to WMI/CIM events.

This article, as the title mentions, will work with Engine Events and how we can subscribe to events (Register-EngineEvent) used by the PowerShell engine (all 2 of them) and also how we can generate custom events to watch for using New-Event.

The PowerShell Engine

There are 2 events that you can watch for with the PowerShell engine (Exiting and OnIdle). You can find this out also by running the following command:

[System.Management.Automation.PsEngineEvent] | gm -static -type property

   TypeName: System.Management.Automation.PSEngineEvent

Name    MemberType Definition
—-    ———- ———-
Exiting Property   static string Exiting {get;}
OnIdle  Property   static string OnIdle {get;}

Not really a lot to work with, but it is still something for the PowerShell engine. So with that, lets work with these two EngineEvents and see some examples.

PowerShell.Exiting and PowerShell.OnIdle

#Simple example using Register-EngineEvent and PowerShell.Exiting engine event ([System.Management.Automation.PsEngineEvent]::Exiting)
Register-EngineEvent -SourceIdentifier PowerShell.Exiting -Action {
    "PowerShell exited at {0}" -f (Get-Date) | 
        Out-File -FilePath "C:\users\Administrator\desktop\powershell.log" -Append
}

Here we will write a log file giving the date that the PowerShell console is closed. But before I do that we are going to go ahead and set up another event for the OnIdle as well and check on the logfile.

#Simple example using Register-EngineEvent and PowerShell.OnIdle engine event ([System.Management.Automation.PsEngineEvent]::OnIdle)
Register-EngineEvent -SourceIdentifier PowerShell.OnIdle -Action {
    "PowerShell idle at {0}" -f (Get-Date) | 
    Out-File -FilePath "C:\users\Administrator\desktop\powershell.log" -Append
}

image

You will notice that each time you register and event, it will output a System.Management.Automation.PSEventJob object. If you don’t want to see that, just add | Out-Null at the end of the command.

I want to see if we are actually subscribed to the events, so I will use Get-EventSubscriber to look at each of these.

Get-EventSubscriber

SubscriptionId   : 2
SourceObject     :
EventName        :
SourceIdentifier : PowerShell.Exiting
Action           : System.Management.Automation.PSEventJob
HandlerDelegate  :
SupportEvent     : False
ForwardEvent     : False

SubscriptionId   : 3
SourceObject     :
EventName        :
SourceIdentifier : PowerShell.OnIdle
Action           : System.Management.Automation.PSEventJob
HandlerDelegate  :
SupportEvent     : False
ForwardEvent     : False

I think we have spent enough time letting the logfile run, so now I will stop the event subscriber and check out the log.

Get-EventSubscriber | Unregister-Event

You will also note that the jobs also stop as soon as you do this.

image

Ok, now lets back up a bit and pretend that I did not actually kill the event subscribers and instead chose to close the PowerShell console instead. You will remember that the first event subscriber was set to fire as PowerShell is exiting and write to the log file. So if I did that and we then look at the log file, you would see the following:

Get-Content powershell.log

PowerShell idle at 1/30/2013 8:33:15 PM
PowerShell idle at 1/30/2013 8:33:15 PM
PowerShell idle at 1/30/2013 8:33:16 PM
PowerShell idle at 1/30/2013 8:33:16 PM
PowerShell idle at 1/30/2013 8:33:17 PM
PowerShell idle at 1/30/2013 8:33:17 PM
PowerShell exited at 1/30/2013 8:33:17 PM

-SupportEvent And Your PowerShell Profile

One last thing with the Register-EngineEvent is that it has a parameter called –SupportEvent, which when used with the cmdlet, doesn’t output an object nor can you use Get-Job to see the job. In fact, if you use Get-EventSubscriber, you will not be able to see the subscription unless you specify the –Force parameter. The reason for this is that there are times when an a specific event subscription is merely a go between for another “parent” event subscriber and the event itself and you do not wish for this subscription to be found.

So with that, lets say that you want to keep the last 100 items of your history when you exit your PowerShelll console and be able to load those back up on your next console session. Put this in for PowerShell profile and you will get that capability! This will use the –SupportEvent because I have no need to view that event subscriber or see the associated job.

If (Test-Path (Join-Path $Home "ps_history.xml")) {
    Add-History -InputObject (Join-Path $Home "ps_history.xml")
}
Register-EngineEvent -SourceIdentifier powershell.exiting -SupportEvent -Action {  
    Get-History -Count 100 | Export-Clixml (Join-Path $Home "ps_history.xml") 
}

Assuming that we put this in my profile, running Get-History would show you the current history of my session.

image

I will now close and re-open my PowerShell session and check my history again to make sure that there is nothing it.

image

Now I will import the file back into my session and view my history again.

image

One last note, the –SupportEvent parameter is available all of the Register-*Event cmdlets and not just Register-EngineEvent.

Creating Custom Events

Up next is New-Event, which allows you to create custom events which are added to the event queue that can be viewed by using the Get-Event cmdlet. This allows you to generate your own types of events that can be handled by the Register-EngineEvent cmdlet to perform any task you want. But before we get back to Register-EngineEvent, lets create a new event and then view it in the queue.

New-Event -SourceIdentifier Something.Else -Sender PowerShell.Something -MessageData "Nothing useful" | Out-Null
Get-Event

image

And with that, you can see the event I created sitting the queue. Nothing too crazy, but lets now throw Register-EngineEvent into the mix and see what happens.

Register-EngineEvent -SourceIdentifier Something.Else -Action {
    Write-Host ("Event from {0} occurred!`nCheck out `$createdEvent to see the event!" -f $Event.Sender)
    $Global:createdEvent = $event
}

#Create the new event again so it gets caught by the subscription
New-Event -SourceIdentifier Something.Else -Sender PowerShell.Something -MessageData "Nothing useful" | Out-Null

 

image

We subscribed to a new event with a SourceIdentifier of ‘Something.Else’ and then generated an actual event with the same SourceIdentifier and just as expected, the subscriber picked up on it and wrote the output to the screen.

You will also note that I created a Global variable to store that event to be viewed in my current console. The reason for that is that the $Event variable is an automatic variable that only has data in the scope of the Action scriptblock. Because of that, we wouldn’t be able to view the data from that variable or the other Event related variables: $Eventargs and $EventSubscriber.

If you want to explore these variables, you can run the following code that will create an event subscription and fire off when you create a new event. The only difference is that this will take you into a nested prompt in which you can actually view these automatic variables, if they have data available.

Register-EngineEvent -SourceIdentifier Something.Else -Action {
    $host.EnterNestedPrompt()
}

#Create the new event again so it gets caught by the subscription
New-Event -SourceIdentifier Something.Else -Sender PowerShell.Something -MessageData "Nothing useful" | Out-Null

image

Forwarding Events

The last thing I will talk about is forwarding events from either a remote system or from a background job that you can take action on, if needed. And when I say a remote system, I mean using PowerShell Remoting, not as though you were using Remote Desktop or some other method of remotely accessing a computer.

The –Forward parameter, but like the –SupportEvent parameter, is not limited to Register-Engine event, but is available in all of the Register-*Event, so the examples here can be used in the other cmdlets as well.

This example shows how you can use this in a background job to write events outside of the job scope using the –Forward parameter.

#Show no events are in queue
Get-Event

Start-Job -Name TestJob -ScriptBlock {
    Register-EngineEvent -SourceIdentifier This.Nothing -Forward
    Start-Sleep -seconds 2
    New-Event -SourceIdentifier This.Nothing -Message "Job ended"
}

#Wait for job to finish
Get-Job | Wait-Job

#Show the new event waiting in queue from background job
Get-Event

image

Let’s make more interesting, I will setup a loop in the job that will write an event every 5 seconds and then register an event subscription at the parent console to write the date each time the event in the job fires.

#Show no events are in queue
Get-Event

#Register the event subscription for the forwarded event
Register-EngineEvent -SourceIdentifier This.Nothing -Action {
    Write-Host ("Date: {0}" -f $event.messagedata) -BackgroundColor Black -ForegroundColor Yellow
}

Start-Job -Name TestJob -ScriptBlock {
    While ($True) {
        Register-EngineEvent -SourceIdentifier This.Nothing -Forward
        Start-Sleep -seconds 5
        New-Event -SourceIdentifier This.Nothing -Message ("{0}" -f (Get-Date))
    }
}

image

And, as I mentioned earlier, this can be used on remote systems such as using PowerShell remoting. The first example is using Invoke-Command against a remote system.

#Show no events are in queue
Get-Event

#Run command from remote system
Invoke-Command -ComputerName DC1.rivendell.com -ScriptBlock {

    #Register the event to forward and create the new event
    Register-EngineEvent -SourceIdentifier This.Nothing -Forward
    New-Event -SourceIdentifier This.Nothing -Message ("Message from {0}" -f $env:COMPUTERNAME) | Out-Null
}

#Show the new event waiting in queue from the remote system
Get-Event

image

This second example uses New-PSSession to create the initial session and then Enter-PSSession to connect into the interactive session to forward an event back to my remote system.

#Show no events are in queue
Get-Event

#Run command from remote system
$session = New-PSSession -ComputerName DC1.rivendell.com 

Enter-PSSession $session

#Register the event to forward and create the new event
Register-EngineEvent -SourceIdentifier This.Nothing -Forward
New-Event -SourceIdentifier This.Nothing -Message ("Message from {0}" -f $env:COMPUTERNAME) | Out-Null

Exit-PSSession

#Show the new event waiting in queue from the remote system
Get-Event

image

Some very cool stuff that you can do with just the engine events! There is a lot of power to be had in these cmdlets for you to explore such as using these events for monitoring activity both locally and remotely using the Register-EngineEvent and New-Event cmdlets.

Hopefully you learned something new with this article and can apply this knowledge to your own projects. I would love to hear what you have done with the engine events in your environment.

This entry was posted in powershell, scripts and tagged , , . Bookmark the permalink.

7 Responses to PowerShell and Events: Engine Events

  1. Pingback: Monitor Process Starts | Making Magic with PowerShell

  2. Heath says:

    I think you may need to change the Add-History line in your example on how to save history to the serialized xml file. So this:

    If (Test-Path (Join-Path $Home “ps_history.xml”)) {
    Add-History -InputObject (Join-Path $Home “ps_history.xml”)
    }

    should be:

    If (Test-Path (Join-Path $Home “ps_history.xml”)) {
    Add-History -InputObject (Import-CliXml (Join-Path $Home “ps_history.xml”))
    }

    Otherwise an exception is thrown because a string is passed as the inputobject to Add-History. Great tip though.

  3. Pingback: PowerShell and Events: WMI Temporary Event Subscriptions | Learn Powershell | Achieve More

  4. Pingback: PowerShell and Events: Object Events | Learn Powershell | Achieve More

  5. Pingback: PowerShell and Events: Engine Events | Learn Powershell | Achieve More « Soyka's Blog

  6. iOnline247 says:

    Have you ever checked out http://powerevents.codeplex.com?

    Cheers,
    Matthew

    • Boe Prox says:

      Hi Matthew,

      I have checked out Powerevents. Trevor did an awesome job putting that module together. That deals with setting permanent WMI consumers which I plan on talking about in an upcoming article along with temp WMI events using Register-WMIEvent.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s