|
14 | 14 | # powershell.exe -NoProfile -Command "Start-Process powershell -Verb RunAs ` |
15 | 15 | # -ArgumentList '-NoProfile','-ExecutionPolicy','Bypass','-File','<this file>'" |
16 | 16 | # |
| 17 | +# **There is no `Restart-PnpDevice`.** Windows PowerShell's PnpDevice module |
| 18 | +# ships Get-, Enable- and Disable-PnpDevice and nothing else, so a script that |
| 19 | +# calls Restart-PnpDevice fails with CommandNotFoundException on every device |
| 20 | +# and exits 1 -- while a scheduled task wrapping it reports LastTaskResult 1 |
| 21 | +# and looks, from the outside, exactly like a board that refused to restart. |
| 22 | +# Found the expensive way on 2026-09-17, mid flash cycle. `pnputil |
| 23 | +# /restart-device` is the real mechanism; Disable+Enable is the fallback for a |
| 24 | +# Windows older than 2004. |
17 | 25 | [CmdletBinding()] |
18 | 26 | param( |
19 | 27 | # Espressif's vendor ID. Narrow it (e.g. 'USB\VID_303A&PID_4001*') when two |
20 | 28 | # boards are attached and only one should be restarted. |
21 | 29 | [string]$Match = 'USB\VID_303A*' |
22 | 30 | ) |
23 | 31 |
|
24 | | -$devices = Get-PnpDevice -PresentOnly | Where-Object { $_.InstanceId -like $Match } |
| 32 | +$identity = [Security.Principal.WindowsIdentity]::GetCurrent() |
| 33 | +$principal = New-Object Security.Principal.WindowsPrincipal($identity) |
| 34 | +if (-not $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) { |
| 35 | + Write-Output "not elevated -- every restart below will fail with Access is denied" |
| 36 | +} |
| 37 | + |
| 38 | +# Restart the composite parent, not its MI_ children: re-enumerating the |
| 39 | +# parent brings the interfaces with it, and a child whose parent is about to |
| 40 | +# vanish reports a confusing failure of its own. |
| 41 | +$devices = Get-PnpDevice -PresentOnly | |
| 42 | + Where-Object { $_.InstanceId -like $Match -and $_.InstanceId -notmatch '&MI_' } |
25 | 43 | if (-not $devices) { |
26 | 44 | Write-Output "no device matching $Match is attached" |
27 | 45 | exit 1 |
28 | 46 | } |
| 47 | + |
| 48 | +$failed = 0 |
29 | 49 | foreach ($d in $devices) { |
30 | 50 | Write-Output ("restarting {0} [{1}]" -f $d.InstanceId, $d.Status) |
| 51 | + $out = & pnputil.exe /restart-device $d.InstanceId 2>&1 |
| 52 | + if ($LASTEXITCODE -eq 0 -and ($out -join ' ') -notmatch 'Failed to restart') { |
| 53 | + Write-Output " ok (pnputil)" |
| 54 | + continue |
| 55 | + } |
| 56 | + Write-Output (" pnputil: {0}" -f (($out | Where-Object { $_ -match '\S' }) -join '; ')) |
31 | 57 | try { |
32 | | - Restart-PnpDevice -InstanceId $d.InstanceId -Confirm:$false -ErrorAction Stop |
33 | | - Write-Output " ok" |
| 58 | + Disable-PnpDevice -InstanceId $d.InstanceId -Confirm:$false -ErrorAction Stop |
| 59 | + Start-Sleep -Milliseconds 700 |
| 60 | + Enable-PnpDevice -InstanceId $d.InstanceId -Confirm:$false -ErrorAction Stop |
| 61 | + Write-Output " ok (disable/enable)" |
34 | 62 | } catch { |
35 | 63 | Write-Output (" failed: {0}" -f $_.Exception.Message) |
| 64 | + $failed++ |
36 | 65 | } |
37 | 66 | } |
| 67 | + |
38 | 68 | Start-Sleep -Seconds 3 |
39 | 69 | Get-PnpDevice -PresentOnly | |
40 | 70 | Where-Object { $_.InstanceId -like 'USB\VID_303A*' } | |
41 | 71 | Select-Object Status, InstanceId | |
42 | 72 | Format-Table -AutoSize | Out-String | Write-Output |
| 73 | + |
| 74 | +exit $(if ($failed) { 1 } else { 0 }) |
0 commit comments