feat(windows-packaging): dev-iteration scripts — reset + redeploy pf-vdisplay driver
Today's manual driver recovery (wedged under ADD/REMOVE churn → ERROR_NOT_FOUND) and the manual
host-stop/install/host-start dance around drivers/deploy-dev.ps1 are now two scripts:
* reset-pf-vdisplay.ps1 — recover a wedged driver: stop host → pnputil /remove-device the ghost
"Generic Monitor (punktfunk)" nodes → Disable+Enable the adapter
(Restart-PnpDevice doesn't exist on the box PS) → start host. No reboot
(the box boots to Proxmox). -Verify probes to confirm ADD recovered.
* redeploy-pf-vdisplay.ps1 — one-shot dev redeploy wrapping deploy-dev.ps1 with the host stop/start
(the running host holds the driver DLL) + a post-install adapter reload
(pnputil updates the store but the live device keeps the old binary).
Both standalone (don't touch deploy-dev.ps1). README gains a "Dev iteration on the test box" section.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -71,21 +71,47 @@ read it from `%ProgramData%\punktfunk\web-password`.
|
|||||||
| `install-pf-vdisplay.ps1` | Runs at install time (elevated): trust cert → gated device-node create (nefconc) → `pnputil` install. |
|
| `install-pf-vdisplay.ps1` | Runs at install time (elevated): trust cert → gated device-node create (nefconc) → `pnputil` install. |
|
||||||
| `../../scripts/windows/web-run.cmd` | The `PunktfunkWeb` task action: loads the mgmt token + login password env, runs the bundled `bun` on the Nitro server (`:3000`). |
|
| `../../scripts/windows/web-run.cmd` | The `PunktfunkWeb` task action: loads the mgmt token + login password env, runs the bundled `bun` on the Nitro server (`:3000`). |
|
||||||
| `../../scripts/windows/web-setup.ps1` | Install-time (elevated): write the ACL'd console password, register the `PunktfunkWeb` task + firewall rule, start it. |
|
| `../../scripts/windows/web-setup.ps1` | Install-time (elevated): write the ACL'd console password, register the `PunktfunkWeb` task + firewall rule, start it. |
|
||||||
| `pf-vdisplay/` | **Vendored** signed pf-vdisplay driver: `pf_vdisplay.inf` / `pf_vdisplay.cat` / `pf_vdisplay.dll` / `punktfunk-driver.cer`. Built from `vdisplay-driver/`. |
|
| `pf-vdisplay/` | **Vendored** signed pf-vdisplay driver: `pf_vdisplay.inf` / `pf_vdisplay.cat` / `pf_vdisplay.dll` / `punktfunk-driver.cer`. Built from `drivers/`. |
|
||||||
| `vdisplay-driver/` | The all-Rust IddCx **driver source** (`pf-vdisplay` crate + vendored `wdf-umdf*` bindings) + `deploy-dev.ps1` (build/sign/install for dev). |
|
| `drivers/` | The all-Rust IddCx **driver source** workspace: the `pf-vdisplay` crate on `wdk-sys` / windows-drivers-rs + the owned `pf-vdisplay-proto` ABI + `wdk-iddcx` / `wdk-probe`, plus `deploy-dev.ps1` (build/sign/install for dev). |
|
||||||
|
| `reset-pf-vdisplay.ps1` | **Dev:** recover a wedged driver — stop host → reap ghost monitor nodes → reload the adapter → start host (no reboot). See *Dev iteration* below. |
|
||||||
|
| `redeploy-pf-vdisplay.ps1` | **Dev:** one-shot redeploy — (optional) build → stop host → `deploy-dev.ps1 -Install` → reload adapter → start host. |
|
||||||
| `nvenc/nvenc.def`, `nvenc/gen-nvenc-importlib.ps1` | Synthesise `nvencodeapi.lib` for the `--features nvenc` link (llvm-dlltool / lib.exe). |
|
| `nvenc/nvenc.def`, `nvenc/gen-nvenc-importlib.ps1` | Synthesise `nvencodeapi.lib` for the `--features nvenc` link (llvm-dlltool / lib.exe). |
|
||||||
|
|
||||||
> **Vendored driver:** pf-vdisplay is our **all-Rust IddCx** virtual display (UMDF2), built from
|
> **Vendored driver:** pf-vdisplay is our **all-Rust IddCx** virtual display (UMDF2), built from
|
||||||
> `packaging/windows/vdisplay-driver/`. It replaced the vendored SudoVDA C++ driver — full story in
|
> `packaging/windows/drivers/`. It replaced the vendored SudoVDA C++ driver — full story in
|
||||||
> [`docs/windows-virtual-display-rust-port.md`](../../docs/windows-virtual-display-rust-port.md). The
|
> [`docs/windows-virtual-display-rust-port.md`](../../docs/windows-virtual-display-rust-port.md). The
|
||||||
> **signed** output (`pf_vdisplay.dll`/`.inf`/`.cat` + `punktfunk-driver.cer`; signer
|
> **signed** output (`pf_vdisplay.dll`/`.inf`/`.cat` + `punktfunk-driver.cer`; signer
|
||||||
> `punktfunk-ds-test` — the same cert the gamepad drivers ship, Class=Display, HWID `root\pf_vdisplay`)
|
> `punktfunk-ds-test` — the same cert the gamepad drivers ship, Class=Display, HWID `root\pf_vdisplay`)
|
||||||
> is checked in under `pf-vdisplay/`. To refresh it after a driver-source change, rebuild + re-sign with
|
> is checked in under `pf-vdisplay/`. To refresh it after a driver-source change, rebuild + re-sign with
|
||||||
> `vdisplay-driver/deploy-dev.ps1` and copy the staged `pf_vdisplay.{dll,inf,cat}` over the vendored
|
> `drivers/deploy-dev.ps1` and copy the staged `pf_vdisplay.{dll,inf,cat}` over the vendored
|
||||||
> copies. nefcon (the device-node tool — the install creates the node with it, **never** `devgen`, which
|
> copies. nefcon (the device-node tool — the install creates the node with it, **never** `devgen`, which
|
||||||
> leaves persistent phantom devices) **is** fetched + SHA-256-verified from its pinned release in
|
> leaves persistent phantom devices) **is** fetched + SHA-256-verified from its pinned release in
|
||||||
> `stage-pf-vdisplay.ps1`.
|
> `stage-pf-vdisplay.ps1`.
|
||||||
|
|
||||||
|
## Dev iteration on the test box (driver)
|
||||||
|
|
||||||
|
Two helpers wrap the painful manual steps of iterating on the pf-vdisplay driver against a live host
|
||||||
|
service. Run **elevated**; both default to the `PunktfunkHost` service.
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# Recover a WEDGED driver. Symptom: every session fails with
|
||||||
|
# create virtual output: pf-vdisplay ADD ...: DeviceIoControl(0x222400): Element nicht gefunden (0x80070490)
|
||||||
|
# i.e. ERROR_NOT_FOUND — sustained ADD/REMOVE churn exhausted the IddCx monitor slots (ghost
|
||||||
|
# "Generic Monitor (punktfunk)" nodes pile up, target_ids climb). A host restart's CLEAR_ALL does NOT
|
||||||
|
# fix it; the driver instance must be reloaded. This clears the ghosts + cycles the adapter (no reboot —
|
||||||
|
# this box boots to Proxmox).
|
||||||
|
powershell -ExecutionPolicy Bypass -File reset-pf-vdisplay.ps1 -Verify -Probe C:\t-goal1\debug\punktfunk-probe.exe
|
||||||
|
|
||||||
|
# Redeploy a driver build cleanly (stop host → install with a strictly-increasing DriverVer → reload
|
||||||
|
# adapter → start host). -Build runs `cargo build` first, but ONLY from an MSVC dev shell
|
||||||
|
# (LIBCLANG_PATH + Version_Number=10.0.26100.0); otherwise build separately and omit -Build.
|
||||||
|
powershell -ExecutionPolicy Bypass -File redeploy-pf-vdisplay.ps1 -Build -Verify -Probe C:\t-goal1\debug\punktfunk-probe.exe
|
||||||
|
```
|
||||||
|
|
||||||
|
The driver should reclaim monitor slots on REMOVE so churn can't wedge it; until it does, `reset` is
|
||||||
|
the recovery. From a Linux box drive either over SSH, e.g.
|
||||||
|
`ssh user@box 'powershell -ExecutionPolicy Bypass -File C:\...\reset-pf-vdisplay.ps1'`.
|
||||||
|
|
||||||
## Build locally (Windows, MSVC + Windows SDK + Inno Setup)
|
## Build locally (Windows, MSVC + Windows SDK + Inno Setup)
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
|
|||||||
@@ -0,0 +1,99 @@
|
|||||||
|
#requires -Version 5.1
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
One-shot DEV redeploy of the pf-vdisplay (punktfunk) virtual-display driver on the test box:
|
||||||
|
(optional) build -> stop host -> stage+sign+install -> reload the adapter -> start host.
|
||||||
|
|
||||||
|
.DESCRIPTION
|
||||||
|
Wraps drivers/deploy-dev.ps1 (which stages the freshly built pf_vdisplay.dll, clears its
|
||||||
|
FORCE_INTEGRITY PE bit, signs it, stamps a STRICTLY-INCREASING DriverVer, builds+signs the catalog,
|
||||||
|
and pnputil-installs it) with the two things the dev loop always needs around it:
|
||||||
|
|
||||||
|
* The running host service HOLDS the driver's control device, and pnputil can't replace a busy
|
||||||
|
DLL — so the host must be stopped across the install. This stops it first and starts it after.
|
||||||
|
* pnputil /add-driver /install updates the driver STORE, but the OS keeps the LIVE adapter on the
|
||||||
|
old binary until the device is reloaded — so this cycles the adapter (reset-pf-vdisplay.ps1)
|
||||||
|
after install, which also clears the ghost monitor nodes for a clean slate.
|
||||||
|
|
||||||
|
Run ELEVATED. Use -Build only from an MSVC dev shell (the driver's cargo build needs LIBCLANG_PATH
|
||||||
|
+ Version_Number=10.0.26100.0, per drivers/deploy-dev.ps1); otherwise build separately and omit it.
|
||||||
|
|
||||||
|
.PARAMETER Build Run `cargo build` in packaging/windows/drivers first (needs the MSVC env).
|
||||||
|
.PARAMETER Service Host service name. Default PunktfunkHost.
|
||||||
|
.PARAMETER Thumbprint Passthrough to deploy-dev.ps1 (test-cert SHA-1). Omit to use its default.
|
||||||
|
.PARAMETER Nefconc Passthrough to deploy-dev.ps1 (nefconc.exe path). Omit to use its default.
|
||||||
|
.PARAMETER Verify After redeploy, probe to confirm ADD works (passes through to the reset's
|
||||||
|
-Verify; needs -Probe or punktfunk-probe.exe on PATH).
|
||||||
|
.PARAMETER Probe Path to punktfunk-probe.exe for -Verify.
|
||||||
|
|
||||||
|
.EXAMPLE
|
||||||
|
# already built the driver in an MSVC shell -> deploy it cleanly:
|
||||||
|
powershell -ExecutionPolicy Bypass -File redeploy-pf-vdisplay.ps1
|
||||||
|
.EXAMPLE
|
||||||
|
# build + deploy + verify, from an MSVC dev shell:
|
||||||
|
powershell -ExecutionPolicy Bypass -File redeploy-pf-vdisplay.ps1 -Build -Verify -Probe C:\t-goal1\debug\punktfunk-probe.exe
|
||||||
|
#>
|
||||||
|
[CmdletBinding()]
|
||||||
|
param(
|
||||||
|
[switch]$Build,
|
||||||
|
[string]$Service = 'PunktfunkHost',
|
||||||
|
[string]$Thumbprint,
|
||||||
|
[string]$Nefconc,
|
||||||
|
[switch]$Verify,
|
||||||
|
[string]$Probe
|
||||||
|
)
|
||||||
|
$ErrorActionPreference = 'Stop'
|
||||||
|
|
||||||
|
$here = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||||
|
$driversDir = Join-Path $here 'drivers'
|
||||||
|
$deploy = Join-Path $driversDir 'deploy-dev.ps1'
|
||||||
|
$reset = Join-Path $here 'reset-pf-vdisplay.ps1'
|
||||||
|
foreach ($f in @($deploy, $reset)) { if (-not (Test-Path $f)) { throw "missing helper: $f" } }
|
||||||
|
|
||||||
|
# 1) Optional rebuild (MSVC dev shell only).
|
||||||
|
if ($Build) {
|
||||||
|
Write-Host "==> cargo build (pf-vdisplay driver, $driversDir)"
|
||||||
|
Push-Location $driversDir
|
||||||
|
try {
|
||||||
|
cargo build
|
||||||
|
if ($LASTEXITCODE -ne 0) { throw "cargo build failed ($LASTEXITCODE) — is this an MSVC dev shell with LIBCLANG_PATH + Version_Number set?" }
|
||||||
|
}
|
||||||
|
finally { Pop-Location }
|
||||||
|
}
|
||||||
|
|
||||||
|
# 2) Stop the host (it holds the driver DLL; pnputil can't replace a busy binary).
|
||||||
|
$svc = Get-Service $Service -ErrorAction SilentlyContinue
|
||||||
|
if ($svc -and $svc.Status -eq 'Running') {
|
||||||
|
Write-Host "==> stopping $Service"
|
||||||
|
Stop-Service $Service -Force
|
||||||
|
Start-Sleep -Seconds 2
|
||||||
|
}
|
||||||
|
|
||||||
|
# 3) Stage + sign + install (strictly-increasing DriverVer so pnputil takes the new binary).
|
||||||
|
$deployArgs = @{ Install = $true }
|
||||||
|
if ($Thumbprint) { $deployArgs.Thumbprint = $Thumbprint }
|
||||||
|
if ($Nefconc) { $deployArgs.Nefconc = $Nefconc }
|
||||||
|
Write-Host "==> deploy-dev.ps1 -Install"
|
||||||
|
& $deploy @deployArgs
|
||||||
|
|
||||||
|
# 4) Reload the adapter so the OS loads the freshly-installed binary (+ clear ghost nodes). The reset
|
||||||
|
# leaves the host alone (-NoHost) — we own the service lifecycle here.
|
||||||
|
Write-Host "==> reloading the pf-vdisplay adapter (clean slate)"
|
||||||
|
& $reset -NoHost
|
||||||
|
|
||||||
|
# 5) Start the host.
|
||||||
|
if ($svc) {
|
||||||
|
Write-Host "==> starting $Service"
|
||||||
|
Start-Service $Service
|
||||||
|
Start-Sleep -Seconds 3
|
||||||
|
Write-Host " $Service status: $((Get-Service $Service -ErrorAction SilentlyContinue).Status)"
|
||||||
|
}
|
||||||
|
|
||||||
|
# 6) Optional verification probe.
|
||||||
|
if ($Verify) {
|
||||||
|
$vArgs = @{ NoHost = $true; KeepGhosts = $true; Verify = $true }
|
||||||
|
if ($Probe) { $vArgs.Probe = $Probe }
|
||||||
|
& $reset @vArgs
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "pf-vdisplay redeploy done."
|
||||||
@@ -0,0 +1,130 @@
|
|||||||
|
#requires -Version 5.1
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Recover the pf-vdisplay (punktfunk) virtual-display driver after it WEDGES under rapid ADD/REMOVE
|
||||||
|
churn — no reboot. The dev-iteration counterpart to redeploy-pf-vdisplay.ps1.
|
||||||
|
|
||||||
|
.DESCRIPTION
|
||||||
|
Sustained connect/disconnect churn (e.g. a client reconnect loop × the host's 8 pipeline-build
|
||||||
|
retries — ~100 ADD/REMOVE cycles) exhausts the driver's IddCx monitor slots: the per-monitor
|
||||||
|
target_ids climb, ghost "Generic Monitor (punktfunk)" device nodes pile up, and eventually
|
||||||
|
IOCTL_ADD returns 0x80070490 ERROR_NOT_FOUND ("Element nicht gefunden"). Every session then fails
|
||||||
|
to create a virtual output -> the client gets a hard blackscreen. A host-service restart's
|
||||||
|
IOCTL_CLEAR_ALL does NOT recover it; the driver instance itself must be reloaded.
|
||||||
|
|
||||||
|
Steps (run ELEVATED):
|
||||||
|
1. Stop the host service (it holds the driver's control device).
|
||||||
|
2. pnputil /remove-device the GHOST (Status != OK = not-present) punktfunk virtual-monitor nodes
|
||||||
|
that accumulated — the root of the slot exhaustion.
|
||||||
|
3. Disable + Enable the pf-vdisplay adapter (ROOT\DISPLAY\*, "punktfunk Virtual Display") to
|
||||||
|
reload the IddCx driver instance and reset its monitor list. (Restart-PnpDevice does NOT exist
|
||||||
|
on this box's PowerShell, so we disable+enable explicitly.)
|
||||||
|
4. Restart the host service.
|
||||||
|
Avoids a reboot on purpose (this box boots to Proxmox).
|
||||||
|
|
||||||
|
.PARAMETER Service Host service name. Default PunktfunkHost.
|
||||||
|
.PARAMETER AdapterName FriendlyName substring of the IddCx adapter to cycle. Default "punktfunk
|
||||||
|
Virtual Display" (NOT SudoVDA's "SudoMaker Virtual Display Adapter").
|
||||||
|
.PARAMETER GhostMatch FriendlyName substring of the virtual monitors to reap. Default "punktfunk".
|
||||||
|
.PARAMETER KeepGhosts Skip the ghost-node cleanup; only cycle the adapter.
|
||||||
|
.PARAMETER NoHost Don't stop/start the host service (just reset the driver) — used by
|
||||||
|
redeploy-pf-vdisplay.ps1, which manages the service itself.
|
||||||
|
.PARAMETER Verify After recovery, run a punktfunk-probe loopback and report whether ADD works
|
||||||
|
again (best-effort; needs punktfunk-probe.exe on PATH or via -Probe).
|
||||||
|
.PARAMETER Probe Path to punktfunk-probe.exe for -Verify.
|
||||||
|
|
||||||
|
.EXAMPLE
|
||||||
|
powershell -ExecutionPolicy Bypass -File reset-pf-vdisplay.ps1
|
||||||
|
.EXAMPLE
|
||||||
|
powershell -ExecutionPolicy Bypass -File reset-pf-vdisplay.ps1 -Verify -Probe C:\t-goal1\debug\punktfunk-probe.exe
|
||||||
|
#>
|
||||||
|
[CmdletBinding()]
|
||||||
|
param(
|
||||||
|
[string]$Service = 'PunktfunkHost',
|
||||||
|
[string]$AdapterName = 'punktfunk Virtual Display',
|
||||||
|
[string]$GhostMatch = 'punktfunk',
|
||||||
|
[switch]$KeepGhosts,
|
||||||
|
[switch]$NoHost,
|
||||||
|
[switch]$Verify,
|
||||||
|
[string]$Probe
|
||||||
|
)
|
||||||
|
$ErrorActionPreference = 'Continue'
|
||||||
|
|
||||||
|
function Get-PfAdapter {
|
||||||
|
Get-PnpDevice -Class Display -ErrorAction SilentlyContinue |
|
||||||
|
Where-Object { $_.FriendlyName -match $AdapterName } | Select-Object -First 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# 1) Stop the host so it isn't mid-IOCTL during the reset (it holds the control device).
|
||||||
|
$svc = Get-Service $Service -ErrorAction SilentlyContinue
|
||||||
|
$hostWasRunning = $svc -and $svc.Status -eq 'Running'
|
||||||
|
if (-not $NoHost -and $hostWasRunning) {
|
||||||
|
Write-Host "==> stopping $Service"
|
||||||
|
Stop-Service $Service -Force
|
||||||
|
Start-Sleep -Seconds 2
|
||||||
|
}
|
||||||
|
|
||||||
|
# 2) Reap the ghost (not-present) punktfunk virtual-monitor device nodes.
|
||||||
|
if (-not $KeepGhosts) {
|
||||||
|
$ghosts = Get-PnpDevice -Class Monitor -ErrorAction SilentlyContinue |
|
||||||
|
Where-Object { $_.Status -ne 'OK' -and $_.FriendlyName -match $GhostMatch }
|
||||||
|
Write-Host "==> removing $($ghosts.Count) ghost virtual-monitor node(s)"
|
||||||
|
$removed = 0
|
||||||
|
foreach ($g in $ghosts) {
|
||||||
|
pnputil /remove-device $g.InstanceId *> $null
|
||||||
|
if ($LASTEXITCODE -eq 0) { $removed++ }
|
||||||
|
}
|
||||||
|
Write-Host " removed $removed"
|
||||||
|
}
|
||||||
|
|
||||||
|
# 3) Reload the IddCx adapter instance (disable + enable) to clear its monitor list.
|
||||||
|
$ad = Get-PfAdapter
|
||||||
|
if (-not $ad) {
|
||||||
|
Write-Warning "pf-vdisplay adapter '$AdapterName' not found (Class Display) — is the driver installed?"
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Write-Host "==> cycling adapter $($ad.InstanceId)"
|
||||||
|
Disable-PnpDevice -InstanceId $ad.InstanceId -Confirm:$false -ErrorAction Continue
|
||||||
|
Start-Sleep -Seconds 3
|
||||||
|
Enable-PnpDevice -InstanceId $ad.InstanceId -Confirm:$false -ErrorAction Continue
|
||||||
|
Start-Sleep -Seconds 3
|
||||||
|
$st = (Get-PnpDevice -InstanceId $ad.InstanceId -ErrorAction SilentlyContinue).Status
|
||||||
|
if ($st -ne 'OK') {
|
||||||
|
# One retry — a disabled root device occasionally needs a second enable to come back OK.
|
||||||
|
Enable-PnpDevice -InstanceId $ad.InstanceId -Confirm:$false -ErrorAction Continue
|
||||||
|
Start-Sleep -Seconds 2
|
||||||
|
$st = (Get-PnpDevice -InstanceId $ad.InstanceId -ErrorAction SilentlyContinue).Status
|
||||||
|
}
|
||||||
|
Write-Host " adapter status: $st"
|
||||||
|
}
|
||||||
|
|
||||||
|
# 4) Restart the host.
|
||||||
|
if (-not $NoHost -and $svc) {
|
||||||
|
Write-Host "==> starting $Service"
|
||||||
|
Start-Service $Service
|
||||||
|
Start-Sleep -Seconds 3
|
||||||
|
Write-Host " $Service status: $((Get-Service $Service -ErrorAction SilentlyContinue).Status)"
|
||||||
|
}
|
||||||
|
|
||||||
|
# 5) Optional: probe to confirm ADD recovers.
|
||||||
|
if ($Verify) {
|
||||||
|
if (-not $Probe) {
|
||||||
|
$Probe = (Get-Command punktfunk-probe.exe -ErrorAction SilentlyContinue).Source
|
||||||
|
}
|
||||||
|
if (-not $Probe -or -not (Test-Path $Probe)) {
|
||||||
|
Write-Warning "-Verify: punktfunk-probe.exe not found (pass -Probe <path>); skipping verification."
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$log = Join-Path $env:ProgramData 'punktfunk\logs\host.log'
|
||||||
|
Write-Host "==> verifying with $Probe"
|
||||||
|
& $Probe *> $null
|
||||||
|
Start-Sleep -Seconds 2
|
||||||
|
$last = Get-Content $log -Tail 80 -ErrorAction SilentlyContinue |
|
||||||
|
Select-String -Pattern 'pf-vdisplay created|Element nicht|0x80070490' | Select-Object -Last 1
|
||||||
|
if ($last -match 'created') { Write-Host " OK: ADD succeeded after reset." }
|
||||||
|
elseif ($last) { Write-Warning " ADD still failing after reset: $($last.Line.Trim())" }
|
||||||
|
else { Write-Warning " no ADD outcome found in the log; check $log." }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "pf-vdisplay reset done."
|
||||||
Reference in New Issue
Block a user