function Start-WMIDeviceChangeMonitor {
[CmdletBinding()]
param()
$sourceId = "DeviceChangeEvent"
$query = "SELECT * FROM Win32_DeviceChangeEvent"
# Clean stale subscriptions from previous runs
Unregister-Event -SourceIdentifier $sourceId -ErrorAction SilentlyContinue
Get-Event -SourceIdentifier $sourceId -ErrorAction SilentlyContinue | Remove-Event
Write-Host "Listening for system plug-and-play (PnP) device changes... (Ctrl+C to stop)`n"
# Baseline snapshot
$before = Get-PnpDevice |
Select-Object InstanceId, Status, Class, FriendlyName
# Register new event subscription
$subscription = Register-WmiEvent -Query $query -SourceIdentifier $sourceId
try {
while ($true) {
# Wait for a Win32_DeviceChangeEvent
$evt = Wait-Event -SourceIdentifier $sourceId
# Remove from event queue
Remove-Event -EventIdentifier $evt.EventIdentifier -ErrorAction SilentlyContinue
Write-Verbose ("Event Fired: {0}" -f ($evt.SourceEventArgs.NewEvent | Out-String | select-string -pattern "EventType|TIME_CREATED"))
# Decode type
$etype = $evt.SourceEventArgs.NewEvent.EventType
switch ($etype) {
1 { $type = "ConfigChanged" }
2 { $type = "DeviceArrival" }
3 { $type = "DeviceRemoval" }
default { $type = "Unknown($etype)" }
}
# New snapshot
$after = Get-PnpDevice |
Select-Object InstanceId, Status, Class, FriendlyName
# Compute add/remove
$diff = Compare-Object $before $after -Property InstanceId -PassThru
$added = $diff | Where-Object {$_.SideIndicator -eq "=>"}
$removed = $diff | Where-Object {$_.SideIndicator -eq "<="}
# Compute changed devices
$changed = @()
$common = $after | Where-Object InstanceId -in $before.InstanceId
foreach ($dev in $common) {
$old = $before | Where-Object InstanceId -eq $dev.InstanceId
if ($old) {
if ($old.Status -ne $dev.Status -or
$old.Class -ne $dev.Class -or
$old.FriendlyName -ne $dev.FriendlyName) {
$changed += $dev
}
}
}
# Update baseline
$before = $after
# Output arrivals
if ($added) {
foreach ($dev in $added) {
[pscustomobject]@{
Action = "Arrival"
Timestamp = Get-Date
InstanceId = $dev.InstanceId
FriendlyName = $dev.FriendlyName
Class = $dev.Class
Status = $dev.Status
} | Format-List
}
}
# Output removals
if ($removed) {
foreach ($dev in $removed) {
[pscustomobject]@{
Action = "Removal"
Timestamp = Get-Date
InstanceId = $dev.InstanceId
FriendlyName = $dev.FriendlyName
Class = $dev.Class
Status = $dev.Status
} | Format-List
}
}
# Output config changes
if ($changed -and $type -eq "ConfigChanged") {
foreach ($dev in $changed) {
[pscustomobject]@{
Action = "Updated"
Timestamp = Get-Date
InstanceId = $dev.InstanceId
FriendlyName = $dev.FriendlyName
Class = $dev.Class
Status = $dev.Status
} | Format-List
}
}
}
}
finally {
Write-Host "`nCleanup..." -ForegroundColor Yellow
# Cleanup subscription + queued events
Unregister-Event -SourceIdentifier $sourceId -ErrorAction SilentlyContinue
Get-Event -SourceIdentifier $sourceId -ErrorAction SilentlyContinue |
Remove-Event -ErrorAction SilentlyContinue
# Dispose watcher
if ($subscription -and $subscription.SourceObject) {
try { $subscription.SourceObject.Stop() } catch {}
try { $subscription.SourceObject.Dispose() } catch {}
}
Write-Host "Done." -ForegroundColor Green
}
}