Skip to content

Instantly share code, notes, and snippets.

@shoddyguard
Created June 22, 2020 23:26
Show Gist options
  • Save shoddyguard/0edb7b34e1c8c91a409d8c28c2e8c632 to your computer and use it in GitHub Desktop.
Save shoddyguard/0edb7b34e1c8c91a409d8c28c2e8c632 to your computer and use it in GitHub Desktop.
Simple event monitor for HID UPS such as Salicru Home
#Requires -RunAsAdministrator
<#
.SYNOPSIS
Installs a basic HID UPS monitor that triggers a script in the event of power loss or battery drain.
.DESCRIPTION
Recently my UPS software (Powermaster) stopped working for my Salicru 850 home after some Windows update knocked it out (Server 2019).
I noticed it was still detected as a HID battery device, so I wrote this simple script to trigger a secondary script in the event of power loss.
This script creates a permanent event consumer (persists through reboots) to keep an eye on the power status and then trigger a script on either AC Power or battery threshold falling below a certain level.
.NOTES
This script is quite basic and I am using it in conjuction with Puppet to ensure things get set correctly, I've done my best to adapt it into a gist that hopefully someone will find handy.
The secondary script I am using creates event logs upon power loss etc, hence the section at the end of this script.
Handy resources used in creating this script:
- https://powershell.one/wmi/root/cimv2/win32_powermanagementevent (really good website with a lot of useful examples and information on the win32_powermanagementevent class)
- https://docs.microsoft.com/en-us/windows/win32/cimwin32prov/win32-powermanagementevent
- https://www.scriptrunner.com/en/blog/events-powershell-wmi/ (taught me how to write a persistent CIM check)
- https://petri.com/extending-battery-monitoring-wmi-events-powershell (gave me the inspiration to do the BatteryThreshold check)
#>
[CmdletBinding()]
param
(
# The type of event that will act as our trigger
[Parameter(Mandatory = $true)]
[ValidateSet('PowerEvent', 'BatteryThreshold')]
[string]
$EventType,
# The amount of time (in seconds) to poll WMI
[Parameter()]
[string]
$PollRate = '10',
# The script that you would like to run in the event of an event trigger
[Parameter()]
[string]
$ScriptPath = '<%= @trigger_script_path %>',
# If using the 'BatteryThreshold' EventType this is the percentage of battery that will trigger the script
[Parameter()]
[string]
$EventThreshold = '95'
)
switch ($EventType)
{
'PowerEvent'
{
$WQLQuery = "select * from Win32_PowerManagementEvent within $PollRate where eventtype = 10" # filter only on EventType 10 which is a change in power
}
'BatteryThreshold'
{
$WQLQuery = "Select * from __InstanceModificationEvent within $PollRate where TargetInstance ISA 'Win32_Battery' AND TargetInstance.EstimatedChargeRemaining<=$EventThreshold"
}
}
$FilterName = 'PowerMonitor'
$ConsumerName = 'PowerMonitorConsumer'
$CommandLineTemplate = "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -ExecutionPolicy Bypass -File $ScriptPath"
$ExecutablePath = "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe"
$ConsumerArguments = @{Name = $ConsumerName; CommandLineTemplate = $CommandLineTemplate; ExecutablePath = $ExecutablePath }
$FilterArguments = @{Name = $FilterName; EventNameSpace = "root\cimv2"; QueryLanguage = "WQL"; Query = $WQLQuery }
Write-Verbose "Checking for instances of $FilterName"
$WMIFilterInstance = Get-CimInstance -ClassName __EventFilter -namespace root\subscription -filter "name='$FilterName'"
if (!$WMIFilterInstance)
{
Write-Verbose "Setting up new event filter for $FilterName"
try
{
$WMIFilterInstance = New-CimInstance -ClassName __EventFilter -Namespace "root\subscription" -Property $FilterArguments
}
catch
{
throw "Failed to create event filter $FilterName.`n$($_.Exception.Message)"
}
}
Write-Verbose "Checking for existing instances of $ConsumerName"
$WMIEventConsumer = Get-CimInstance -ClassName CommandLineEventConsumer -Namespace root\subscription -filter "name='$ConsumerName'"
if (!$WMIEventConsumer)
{
Write-Verbose "Setting up new event consumer for $ConsumerName"
try
{
$WMIEventConsumer = New-CimInstance -ClassName CommandLineEventConsumer -Namespace "root\subscription" -Property $ConsumerArguments
}
catch
{
throw "Failed to create the event consumer $ConsumerName.`n$($_.Exception.Message)"
}
}
if (!(Get-CimInstance -ClassName __FilterToConsumerBinding -Namespace root\subscription -Filter "Filter = ""__eventfilter.name='$FilterName'"""))
{
Write-Verbose "Binding filter $FilterName to consumer $ConsumerName"
if (!$WMIEventConsumer -or !$WMIFilterInstance)
{
throw 'Cannot bind filter to consumer as one or both are missing.'
}
try
{
$WMIWventBinding = New-CimInstance -ClassName __FilterToConsumerBinding -Namespace "root\subscription" -Property @{Filter = [Ref] $WMIFilterInstance; Consumer = [Ref] $WMIEventConsumer }
}
catch
{
throw "Failed to bind filter to consumer.`n$($_.Exception.Message)"
}
}
if (!(Get-EventLog -Source 'PowerMonitor' -LogName System -ErrorAction SilentlyContinue))
{
try
{
New-EventLog -LogName System -Source 'PowerMonitor'
}
catch
{
throw "Failed to setup event log.$($_.Exception.Message)"
}
}
Write-Verbose "PowerMonitor succesfully set up."
<# Rough script for removing the event consumer etc #>
$FilterName = 'PowerMonitor'
$ConsumerName = 'PowerMonitorConsumer'
try
{
Get-CimInstance -ClassName __EventFilter -namespace root\subscription -filter "name='$FilterName'" | Remove-CimInstance
Get-CimInstance -ClassName CommandLineEventConsumer -Namespace root\subscription -filter "name='$ConsumerName'" | Remove-CimInstance
Get-CimInstance -ClassName __FilterToConsumerBinding -Namespace root\subscription -Filter "Filter = ""__eventfilter.name='$FilterName'""" | Remove-CimInstance
Remove-EventLog -Source 'PowerMonitor' -ErrorAction Stop
}
catch
{
throw "Failed to uninstall PowerMonitor.`n$($_.Exception.Message)"
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment