WmiListener.psm1
<#
WmiListener - Process/Service orchestration via WMI event subscriptions Author: Joel055 License: MIT https://github.com/Joel055/WmiListener #> if ([System.Environment]::OSVersion.Platform -ne "Win32NT") { throw "WmiListener requires Windows. WMI permanent event subscriptions are only supported on Windows platforms." } if (-not ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole(544)) { throw "WmiListener requires elevation. Run PowerShell as Administrator to import the module." } function Register-Listener { [CmdletBinding()] param ( [Parameter(Mandatory=$true)] [string[]]$Targets, [switch]$TargetIsService, [Parameter(Mandatory=$true)] [ValidateSet("Start", "Stop")] [string]$MonitorAction, [string[]]$Processes = @(), [ValidateSet("Start", "Stop")] [string]$ProcessesAction = "Stop", [string[]]$Services = @(), [ValidateSet("Start", "Stop")] [string]$ServicesAction = "Stop", [string[]]$Commands = @(), [switch]$IncludeMirror ) if (-not ($Processes.Count -or $Services.Count -or $Commands.Count)) { throw "You must specify at least one of -Processes, -Services, or -Commands." } $Targets = $Targets | ForEach-Object {$_.ToLower()} $MonitorAction = $MonitorAction.Substring(0,1).ToUpper() + $MonitorAction.Substring(1).ToLower() function Get-Basename { param ([array]$Path) $Path | ForEach-Object {[System.IO.Path]::GetFileNameWithoutExtension($_)} } $targetsWQL = ($Targets | ForEach-Object {"(TargetInstance.Name='$_')"}) -join " OR " Write-Verbose "Targets: $Targets" Write-Verbose "targetsWQL: $targetsWQL" if ($TargetIsService.IsPresent) { $targetClass = 'Win32_Service' } else { $targetClass = 'Win32_Process' } if ($MonitorAction -eq "Start") { $listenerQuery = "SELECT * FROM __InstanceCreationEvent WITHIN 1 WHERE TargetInstance ISA '$targetClass' AND ($targetsWQL)" } else { $listenerQuery = "SELECT * FROM __InstanceDeletionEvent WITHIN 1 WHERE TargetInstance ISA '$targetClass' AND ($targetsWQL)" } Write-Verbose "WQL ListenerQuery: $listenerQuery" # Build a list of actions $cmdParts = @() # Services if ($Services) { $cmdParts += ($Services | ForEach-Object { if ($ServicesAction -eq "Start") { "Start-Service -Name '$_' -ErrorAction SilentlyContinue" } else { "Stop-Service -Name '$_' -ErrorAction SilentlyContinue" } }) } # Processes if ($Processes) { if ($ProcessesAction -eq "Start") { $cmdParts += ($Processes | ForEach-Object { "Start-Process '$_'" }) } else { $procNames = Get-Basename($Processes) $cmdParts += ($procNames | ForEach-Object { "Stop-Process -Name '$_' -Force -ErrorAction SilentlyContinue" }) } } # Commands if ($Commands) { $cmdParts += $Commands } $cmdString = $cmdParts -join "; " $action = "powershell.exe -Command `"$cmdString`"" # Create WMI objects $instanceName = "$MonitorAction[" + ((Get-Basename $Targets) -join "-").Replace(' ', '') + "]" if ($instanceName -in (Get-Listener)) { throw "Listener-instance `"$instanceName`" already exists." } $filter = Set-WmiInstance -Namespace root\subscription -Class __EventFilter -Arguments @{ Name = "${instanceName}:Listener" EventNamespace = "root\cimv2" QueryLanguage = "WQL" Query = $listenerQuery } Write-Verbose "Filter created." $consumer = Set-WmiInstance -Namespace root\subscription -Class CommandLineEventConsumer -Arguments @{ Name = "${instanceName}:Consumer" CommandLineTemplate = $action } Write-Verbose "Consumer created." $null = Set-WmiInstance -Namespace root\subscription -Class __FilterToConsumerBinding -Arguments @{ Filter = $filter.__RELPATH Consumer = $consumer.__RELPATH } Write-Verbose "Binding created." Write-Host "Created listener " -ForegroundColor Green -NoNewline Write-Host "$instanceName" -ForegroundColor Yellow # Create inversed listener if ($IncludeMirror) { if ($ProcessesAction -eq "Stop") { $hasOnlyNames = $Processes | Where-Object { -not ($_ -like "*\*") } if ($hasOnlyNames) { $namesStr = ($hasOnlyNames -join '", "') $mirrorWarning = "Mirror listener may fail to start process(es) [`"$namesStr`"] because only the executable name(s) was provided. Consider using full paths." } } $inverseParam = $PSBoundParameters $inverseParam["includeMirror"] = $false # Flip the action of each parameter foreach ($key in $inverseParam.Keys) { if ($inverseParam[$key] -eq "Start") { $inverseParam[$key] = "Stop" } elseif ($inverseParam[$key] -eq "Stop") { $inverseParam[$key] = "Start" } } & $MyInvocation.MyCommand.Name @inverseParam if ($mirrorWarning) { Write-Warning $mirrorWarning } } Write-Host "" } function Get-Listener { [CmdletBinding()] [OutputType([String])] param ( [Parameter(ValueFromPipeline=$true, Position=0)] [ValidatePattern("Listener$")] [string]$Listener ) begin { # Look for __EventFilter objects with the suffix "Listener" that were created by the current user $filter = @() $userSID = [System.Security.Principal.WindowsIdentity]::GetCurrent().User.Value $allObjects = Get-WmiObject -Namespace root\subscription -Class __EventFilter | Where-Object {$_.Name -like "*Listener"} | Where-Object {(New-Object System.Security.Principal.SecurityIdentifier($_.CreatorSID, 0)).Value -eq $userSID} } process { if ($Listener) {$filter += $Listener} } end { if ($filter) { $validObjects = $allObjects | Where-Object {$_.Name -in $filter} } else { $validObjects = $allObjects } if ($validObjects) { foreach ($obj in $validObjects) { Write-Output(($obj.Name -split ':')[0]) } } else { Write-Verbose "No user-defined listeners found." } } } function Remove-Listener { [CmdletBinding()] param ( [Parameter(Mandatory=$true, ValueFromPipeline=$true, Position=0)] [ValidatePattern("^\S.*")] [string]$Listener ) begin {$allListeners = @()} process {$allListeners += $Listener} end { foreach ($listener in $allListeners) { # Fetches associated WMI objects based on the listenernames provided $wmiPaths = @( "CommandLineEventConsumer.Name='${listener}:Consumer'", "__EventFilter.Name='${listener}:Listener'", "__FilterToConsumerBinding.Consumer=`"CommandLineEventConsumer.Name=\`"${listener}:Consumer\`"`",Filter=`"__EventFilter.Name=\`"${listener}:Listener\`"`"" ) foreach ($path in $wmiPaths) { try { ([WMI]"\\.\root\subscription:$path").Delete() Write-Verbose "Deleted: \\.\root\subscription:$path" } catch { if ($_.Exception.Message -like "*Not found*") { Write-Verbose "Object $path not found for Listener `"$listener`", skipping." } else { Write-Warning "Error removing `"$listener`": $($_.Exception.Message)" } } } } } } |