Skip to content

Instantly share code, notes, and snippets.

@awsr
Last active June 21, 2020 04:16
Show Gist options
  • Save awsr/189a3381b10e8779f33489556b6b4d1e to your computer and use it in GitHub Desktop.
Save awsr/189a3381b10e8779f33489556b6b4d1e to your computer and use it in GitHub Desktop.
PowerShell script for monitoring USB device disconnections
<#PSScriptInfo
.VERSION 1.2
.GUID a164a9e6-875a-4be8-a7ef-a69a35873a52
.AUTHOR Awsr
#>
$global:USBScriptBlock = {
$TestUSB = (Get-CimInstance Win32_USBControllerDevice).Dependent.DeviceID
if ($TestUSB.Count -lt $global:PnpCount) {
[void] $global:USBEvents.TryAdd($TestUSB)
}
}
function global:Get-USBEvents {
[CmdletBinding()]
Param()
Write-Host -ForegroundColor Green "`n--------------------------"
Write-Host -ForegroundColor Green "USB device event triggered"
Write-Host -ForegroundColor Green "--------------------------`n"
Write-Host "Waiting..."
# Brief pause to make sure we're not stopping too early (extreme edge case)
Start-Sleep -Seconds 1
# Pause monitoring
$global:Timer.Stop()
Unregister-Event USBDebugging
# Initialize empty objects
$InnerResults = [System.Collections.Generic.Dictionary[string, string]]::new()
$global:WorkingData = @()
# Begin processing
Write-Host -ForegroundColor Green "Processing $($USBEvents.Count) events...`n"
while ($USBEvents.TryDequeue([ref]$WorkingData)) {
$BaselineDeviceIds | ForEach-Object {
# Check for missing devices
if (-not $WorkingData.Contains($_) -and -not $InnerResults.ContainsKey($_)) {
$InnerResults.Add($_, $PnpDefinitions[$_])
}
}
}
$global:Results.Add($InnerResults)
Write-Host -ForegroundColor Green "Results available in `$Results[$ResultsCount]"
$global:ResultsCount++
$InnerResults | Out-Host
if ($KeepVal) {
Register-CimIndicationEvent -ClassName Win32_DeviceChangeEvent -SourceIdentifier USBDebugging -Action $global:USBScriptBlock | Out-Null
$global:Timer.Start()
}
else {
Unregister-Event USBEventChecker
$global:Timer.Close()
Write-Host "(If the prompt isn't showing below this, just press Enter)"
}
}
<#
.DESCRIPTION
Script to monitor for USB device disconnections. Will probably rewrite this using Runspaces if the response time isn't fast enough.
.PARAMETER PnpDetails
Open a GridView window showing detailed information for all PnP devices on the system.
Not recommended while monitoring is active.
.PARAMETER Keep
Keep monitoring after getting results.
#>
function global:Start-USBMonitor {
[CmdletBinding()]
Param(
[Parameter()]
[switch]$PnpDetails,
[Parameter()]
[switch]$Keep
)
$global:KeepVal = $Keep
if ($PnpDetails) {
# Warning: Lots of data to render, so vertical scrolling is CPU-heavy
Get-PnpDevice | Select-Object -Property * | Out-GridView
return
}
# Cleanup from previous runs in current session if run multiple times
if ((Get-Job).Count -gt 0 -or (Get-EventSubscriber).Count -gt 0) {
Unregister-Event -SourceIdentifier USBDebugging -ErrorAction Ignore
Unregister-Event -SourceIdentifier USBEventChecker -ErrorAction Ignore
Remove-Job -Name USBDebugging, USBEventChecker -Force -ErrorAction Ignore
if (Test-Path Variable:Global:Timer) {
$global:Timer.Close()
}
}
$global:Results = [System.Collections.Generic.List[System.Collections.Generic.Dictionary[string, string]]]::new()
$global:ResultsCount = 0
# Get current list of USB devices
$BaselineUSB = Get-CimInstance Win32_USBControllerDevice
$global:BaselineDeviceIds = $BaselineUSB.Dependent.DeviceID
# Initialize hashtable
$global:PnpDefinitions = @{}
# Get PnP device names
Get-PnpDevice | ForEach-Object {
if ($_.DeviceID -in $BaselineDeviceIds) {
$global:PnpDefinitions[$_.DeviceID] = $_.Name
}
}
if ($BaselineDeviceIds.Count -ne $PnpDefinitions.Count) {
Write-Warning "Some names could not be resolved."
}
# Save to variable for speed
[int]$global:PnpCount = $PnpDefinitions.Count
# Setup collection using a thread-safe type as a precaution due to potentially multiple events running very quickly
$global:USBEvents = [System.Collections.Concurrent.ConcurrentQueue[object]]::new()
# Check devices on change event and add to queue for processing if a device is missing
Register-CimIndicationEvent -ClassName Win32_DeviceChangeEvent -SourceIdentifier USBDebugging -Action $global:USBScriptBlock | Out-Null
# ----------------------------------------
if (-not (Test-Path Variable:Global:Timer) -or -not $global:Timer.Enabled) {
# Timer setup (making global to ensure it's always available for the function)
$global:Timer = [System.Timers.Timer]::new(10000)
$global:Timer.AutoReset = $true
}
# Check queue on timer interval
Register-ObjectEvent -InputObject $global:Timer -EventName Elapsed -SourceIdentifier USBEventChecker -Action {
if ($USBEvents.Count -gt 0) {
& Get-USBEvents
}
} | Out-Null
# Activate timer
$global:Timer.Start()
Write-Host -ForegroundColor Green "Waiting for Win32_DeviceChangeEvent"
Write-Host "Note: Script should be restarted if new devices are added while this is running (you can just run Start-USBMonitoring again)"
Write-Host "Event queue will be checked every 10 seconds."
if ($Keep) {
Write-Host -ForegroundColor Yellow "Use the command `"stopit`" to stop monitoring"
}
}
function global:stopit {
Unregister-Event USBDebugging -ErrorAction Ignore
Unregister-Event -SourceIdentifier USBEventChecker -ErrorAction Ignore
Remove-Job -Name USBDebugging, USBEventChecker -Force -ErrorAction Ignore
if (Test-Path Variable:Global:Timer) {
$global:Timer.Close()
}
Write-Host "USB monitoring stopped"
}
Write-Host -NoNewLine "To run monitoring, type "
Write-Host -NoNewline -ForegroundColor Green "Start-USBMonitor"
Write-Host -NoNewline " or "
Write-Host -NoNewline -ForegroundColor Green "Start-USBMonitor -Keep"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment