- Using PowerShell with $PSStyle - Mon, Jan 24 2022
- Clean up user profiles with PowerShell - Mon, Jun 9 2014
- Track user logons with a PowerShell script - Fri, Jun 6 2014
Now, before we get too far into this I want to point out that I’m not a big fan of using PowerShell scripts to monitor events on a long-term basis. If you really need to know when a critical service has stopped, you need to invest in appropriate management software. PowerShell, and what I’m going to demonstrate, is great for short-term, ad-hoc monitoring such as when troubleshooting a problem.
Using WMI Events ^
The first technique is to use WMI. In much the same way you subscribe to an RSS feed to be notified when there is something new, we do the same thing. We set up a subscription in WMI to a particular type of event. The event is formulated by a query. In this case the class is one of several system classes related to events.
- __InstanceOperationEvent
- __InstanceCreationEvent
- __InstanceModificationEvent
- __InstanceDeletionEvent
For a service, we’re primarily interested in modification events. That is, anything that modifies the service. We start with a query like this:
Select * from __InstanceModificationEvent
Without getting too deep into the mechanics, suffice it to say there is a performance price for this type of query. When the query is running PowerShell will check WMI constantly to see if something has changed. That is very resource intensive, especially when querying a remote computer. Instead, we can tell PowerShell to only check, or poll, within a certain time frame, say every 10 seconds.
Select * from __InstanceModificationEvent within 10
Because this is a system class, the query right now would fire events for everything in WMI that is changing. We need to limit the query further. Here we can use a special property called TargetInstance and ISA operator, to tell WMI only watch for instances that are of a specific class.
Select * from __InstanceModificationEvent within 10 where TargetInstance ISA 'Win32_Service'
But, we want to narrow this down to a single service. When the event fires, the TargetInstance object will be written to the pipeline, which will be a service object. This means we can filter on service object properties.
Select * from __InstanceModificationEvent within 10 where TargetInstance ISA 'Win32_Service' AND TargetInstance.Name='bits' and TargetInstance.State='Stopped'
I will save this to a variable, $query. Next this query needs to be registered with WMI to create the event subscription. As luck would have it there is a cmdlet called Register-WMIEvent.
When the query is registered, whenever the event is detected, in this case when the BITS service stops, PowerShell will record the event. For right now let’s register this for the local computer and not do anything but display a message.
Register-WMIEvent -Query $query -sourceIdentifier "StoppedService" -MessageData "The BITS Service has stopped"
The cmdlet won’t write anything to the pipeline, so use Get-EventSubscriber to see the subscription.
PS Scripts:\> Get-EventSubscriber SubscriptionId : 2 SourceObject : System.Management.ManagementEventWatcher EventName : EventArrived SourceIdentifier : StoppedService Action : HandlerDelegate : SupportEvent : False ForwardEvent : False
This subscription will only last for as long as this PowerShell session is running. How can you tell when an event has fired? Use the Get-Event cmdlet.
PS Scripts:\> get-event
PS Scripts:\>
So nothing yet. I’ll stop the BITS service and wait about 10 seconds to see what happens. Well, nothing interactively. There’s no popup or anything like that. But checking the event queue again shows an event.
PS Scripts:\> get-event ComputerName : RunspaceId : 52cef69a-b09d-46fc-be81-6156bf2ce556 EventIdentifier : 2 Sender : System.Management.ManagementEventWatcher SourceEventArgs : System.Management.EventArrivedEventArgs SourceArgs : {System.Management.ManagementEventWatcher, System.Management.EventArrivedEventArgs} SourceIdentifier : StoppedService TimeGenerated : 2/15/2013 10:20:52 AM MessageData : The BITS Service has stopped
This is a pretty rich object. If I restart the service and it stops again, I’ll get another event. Or if I have multiple subscriptions running, all the events will share the same queue. This is where the SourceIdentifier comes in handy.
PS Scripts:\> $evt = get-event –sourceidentifier StoppedService
I have to drill down a bit, but I can get to not only the target instance object, but the previous instance.
PS Scripts:\> $evt.SourceEventArgs.NewEvent __GENUS : 2 __CLASS : __InstanceModificationEvent __SUPERCLASS : __InstanceOperationEvent __DYNASTY : __SystemClass __RELPATH : __PROPERTY_COUNT : 4 __DERIVATION : {__InstanceOperationEvent, __Event, __IndicationRelated, __Sys… __SERVER : SERENITY __NAMESPACE : //./root/CIMV2 __PATH : PreviousInstance : System.Management.ManagementBaseObject SECURITY_DESCRIPTOR : TargetInstance : System.Management.ManagementBaseObject TIME_CREATED : 130054152528314476 PSComputerName : SERENITY PS Scripts:\> $evt.SourceEventArgs.NewEvent.PreviousInstance.State Running PS Scripts:\> $evt.SourceEventArgs.NewEvent.TargetInstance.State Stopped
With this type of subscription you have to monitor the event queue. But you can also create a more active subscription.
Responding to Events ^
I’m going to revise my query to check for any service that changes.
$query = "Select * from __InstanceModificationEvent within 10 where TargetInstance ISA 'Win32_Service'"
When the service changes its state, say from running to stopped, I want to log the change to a text file. I’ll create a scriptblock with the PowerShell commands I want to execute when a matching event fires.
action={ $previous = $Event.SourceEventArgs.NewEvent.PreviousInstance $target = $Event.SourceEventArgs.NewEvent.TargetInstance if ($previous.state -ne $target.state) { #log the change $msg="{0} Service: {1} has changed state from {2} to {3}" -f (Get- Date),$Target.Name,$Previous.State,$Target.State Write $msg | Out-File -filepath "c:work\ServiceLog.txt" -append } }
$Event is an automatic variable for the event object that we looked at earlier. If the state of the previous and target instance is different, then the change is logged. I register this action with Register-WmiEvent.
Register-WMIEvent -Query $query -sourceIdentifier "ServiceChange" -Action $action -computername $ENV:COMPUTERNAME
Now when a service starts or stops, the text file is update. However, the event queue won’t show anything when you use Get-Event. If you use an action, Get-Event won’t show anything.
If you would like something more interactive, here’s a sample using the PopUp method from the Wscript.Shell COM Object.
$action = { $previous = $Event.SourceEventArgs.NewEvent.PreviousInstance $target = $Event.SourceEventArgs.NewEvent.TargetInstance if ($previous.state -ne $target.state) { $wshell = new-object -COM "Wscript.Shell" $msg="{0} Service: {1} has changed state from {2} to {3}" -f (Get- Date),$Target.Name,$Previous.State,$Target.State #display a message box for 15 seconds. Use -1 to force user to click OK. $wshell.Popup($msg,15,"Service Alert",64) | Out-Null } } Register-WMIEvent -Query $query -sourceIdentifier "ServiceAlert" -Action $action -computername $ENV:COMPUTERNAME
But now look what I get:
Service Events
The message box will automatically close in 15 seconds.
Cleaning Up ^
At some point you will want to clean up. Everything goes away if you close your PowerShell session. Otherwise you can remove the event subscription.
PS Scripts:\> Get-EventSubscriber | unregister-event
This will remove all subscriptions. Or you can specify an identifier. To clear the event queue, run this:
PS Scripts:\> get-event | Remove-Event
Summary ^
These techniques should work for both PowerShell 2.0 and 3.0. And let me re-iterate that I think they are best used for ad-hoc or temporary monitoring. Be sure to read full cmdlet help and examples for everything I’ve demonstrated and try this out in a non-production environment.
The next post in this series will describe how to query events using CIM.