Extending Battery Monitoring with WMI Events in PowerShell

Over the course of several articles I’ve been exploring different ways to take advantage of a PowerShell tool I built to report on remaining battery life. In a previous article, I showed you a simple script that used the Wscript.Shell COM object to display a pop-up message if the remaining battery life was at or below a given threshold. It works just fine except I still have to manually invoke the script. Obviously I don’t want to have to do any more work than I have to, probably like many of you, so I need a way to have my script run “auto-magically.” As it turns out, since I am using a WMI class, there is another option available to me, and that is a WMI event subscription.

Using a WMI Event Subscription

Using WMI, you can create a special relationship called an event subscription. As I will show you, creating this in PowerShell is very easy. The primary piece is referred to as the event consumer. This will be PowerShell command that responds to a special type of filter or query. Most event monitoring will use what is referred to as a temporary event consumer. When the process that created the event consumer ends, so does the subscription. The net effect is that when I close my PowerShell session my monitoring also ends. There are ways to create permanent event subscriptions but they are a bit more complicated and more than what I need for my requirements.
To set up the WMI event subscriber, I will need a special type of query. There are several special system classes that are typically used.

List of classes that you will need for the WMI event subscriber. (Image Credit: Jeff Hicks)
List of classes that you will need for the WMI event subscriber. (Image Credit: Jeff Hicks)

However, I’m not simply querying for any instances, I need WMI to periodically check, which is accomplished with polling. An event query typically starts “Select * from __InstanceModificationEvent Within 60 …” This looks very similar to other WMI queries except that WMI will check within every 60 seconds. Your polling interval should be short enough to catch what you need but not so short that you are dealing with constant results. For my purposes, I could probably check anywhere between 1 and 15 minutes.
The __InstanceModificationEvent will apply to any WMI object that changes. As you can imagine things are changing in WMI and Windows constantly so I need to narrow down the query. I am only concerned for changes to anything that is a Win32_Battery instance.

"Select * from __InstanceModificationEvent within 60 where TargetInstance ISA 'Win32_Battery'"

The __InstanceModificationEvent class has a property called TargetInstance, which can be the type of object you want to watch. Also note the use of the ISA operator. This is a WMI operatory that is like an equals for classes. But I can also take this query further.

"Select * from __InstanceModificationEvent within 60 where TargetInstance ISA 'Win32_Battery' AND TargetInstance.EstimatedChargeRemaining<=40"

I can create a complex query that will also look at target instance property. In my case, the EstimatedChargeRemaining. My query now says to check every 60 seconds for changes to the Win32_Battery class, where the estimate charge remaining is less than or equal to 40.

When this query returns something, then I want my pop-up script to run. Here’s my script to create this type of event subscription.

#number of seconds to check
$poll = 120
#estimated battery charge remaining threshold
$remaining = 75
$query = "Select * from __InstanceModificationEvent within $poll where TargetInstance ISA 'Win32_Battery' AND TargetInstance.EstimatedChargeRemaining<=$remaining"
#steps to take when a matching event fires
$action={
C:\scripts\watch-battery.ps1 -Limit $remaining -Timeout 10
}
Register-WmiEvent -Query $query -SourceIdentifier "Battery Monitor" -Action $action

When the query gets a result, the code in the $Action scriptblock executes. The Register-WMIEvent cmdlet creates an event subscription. You can see all your subscriptions with Get-EventSubscriber.

Viewing subscriptions with the Get-EventSubscriber cmdlet in Windows PowerShell. (Image Credit: Jeff Hicks)
Viewing subscriptions with the Get-EventSubscriber cmdlet in Windows PowerShell. (Image Credit: Jeff Hicks)

Or specify the SourceIdentifier. It doesn’t apply in this situation, but you can also register event subscriptions that don’t take any action.
Those subscriptions will log the event, which you can see with Get-Event. Because I’m doing something, Get-Event will never show anything. But now when the query criteria is met I get my desired pop-up warning.
Battery Alert pop-up warning. (Image Credit: Jeff Hicks)
Battery Alert pop-up warning. (Image Credit: Jeff Hicks)

The event monitoring will run for as long as the PowerShell session is open. If you want to remove the subscription, then all you have to do is unregister the event.

Get-EventSubscriber -SourceIdentifier "Battery Monitor" | Unregister-Event

If you want to use the newer CIM cmdlets, then all you need to do is change the command to create the event subscription.

Register-CimIndicationEvent -Query $query -SourceIdentifier "Battery Monitor" -Action $action

The query remains the same. You will still get an event subscription.

Getting an event subscription in Windows PowerShell. (Image Credit: Jeff Hicks)
Getting an event subscription in Windows PowerShell. (Image Credit: Jeff Hicks)

And you also get a background job waiting to run. The job name is the same as the event source identifier.
The job name is the same as the event source identifier. (Image Credit: Jeff Hicks)
The job name is the same as the event source identifier. (Image Credit: Jeff Hicks)

This job is the true action.
Using Get-EventSubscriber. (Image Credit: Jeff Hicks)
Using Get-EventSubscriber. (Image Credit: Jeff Hicks)

The job will show as NotStarted until the monitoring criteria is met. Once the criteria is met, then the job launches and runs my script. At this point the job will continue to run. It is important to note that if you stop the job, that will also unregister the event. Otherwise you can also use Unregister-Event, as I did with WMI.

Finally, I have a way to getting an interactive notification if I forget about my battery. Since it is temporary I can put the script that creates the event subscription in my profile.

#dot source the create battery watcher event
. C:\scripts\Battery-CIMEvent.ps1

That is plenty permanent enough for me and if I want to change the threshold all I need to do is modify the script and restart PowerShell.
I’ve gone from a simple WMI query to show me how much battery time remains to a simple monitoring tool that offers a graphical interface. I accomplished it all with minimal PowerShell scripting using basic cmdlets and techniques. What tools are you trying to build? If you get stuck, I hope you’ll post in the site’s PowerShell forum.