From b9172cdd7b47e3c911815695e21ada3f0c1d66b6 Mon Sep 17 00:00:00 2001
From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com>
Date: Wed, 1 Jul 2026 18:03:45 -0400
Subject: [PATCH 01/27] Refactor Windows driver installation and VHF setup
Major refactor of Windows driver packaging and installation:
- Switch WiX packaging from source files (.wxs) to patch files (.xml) with CMake-based extension management
- Remove devcon dependency; use SetupAPI and pnputil for device creation and removal
- Add UMDF library version configuration (defaults to 2.15) with validation during build
- Implement lazy VHF target initialization in driver; gamepad creation opens VHF on demand
- Add detailed trace logging to driver and install scripts for debugging
- Improve device detection to handle both PnP enumeration and registry lookups
- Set VhfMode=1 on root devices before driver start
- Change driver device class from HIDClass to System; include WUDFRD.inf for proper service setup
- Link MSVC runtime statically to avoid VC runtime dependencies in UMDF host
- Add -LogPath parameter to install script for WiX integration
- Update README with comprehensive driver architecture details
---
CMakeLists.txt | 3 +
README.md | 29 +-
cmake/packaging/windows_wix.cmake | 58 ++--
.../libvirtualhid-driver-installer-patch.xml | 35 +++
.../libvirtualhid-driver-installer.wxs | 35 ---
scripts/windows/install-driver.ps1 | 252 ++++++++++++++----
scripts/windows/uninstall-driver.ps1 | 80 +++++-
src/platform/windows/driver/CMakeLists.txt | 34 +++
.../windows/driver/libvirtualhid.inf.in | 32 +--
.../windows/driver/libvirtualhid_umdf.cpp | 109 +++++++-
10 files changed, 505 insertions(+), 162 deletions(-)
create mode 100644 cmake/packaging/wix_resources/libvirtualhid-driver-installer-patch.xml
delete mode 100644 cmake/packaging/wix_resources/libvirtualhid-driver-installer.wxs
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 6115e65..519120c 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -2,6 +2,9 @@
# Project configuration
#
cmake_minimum_required(VERSION 3.24)
+if(POLICY CMP0091)
+ cmake_policy(SET CMP0091 NEW)
+endif()
project(libvirtualhid VERSION 0.0.0
DESCRIPTION "Cross-platform virtual HID device library."
HOMEPAGE_URL "https://app.lizardbyte.dev"
diff --git a/README.md b/README.md
index d88c248..7b552bc 100644
--- a/README.md
+++ b/README.md
@@ -134,22 +134,41 @@ Developer install/uninstall helpers live under `scripts/windows`:
```powershell
powershell -ExecutionPolicy Bypass -File .\scripts\windows\install-driver.ps1 `
- -InfPath .\cmake-build-windows-driver\src\platform\windows\driver\package\Release\libvirtualhid.inf
+ -InfPath .\cmake-build-windows-driver\src\platform\windows\driver\package\Release\libvirtualhid.inf `
+ -LogPath .\cmake-build-windows-driver\install-driver.log
powershell -ExecutionPolicy Bypass -File .\scripts\windows\uninstall-driver.ps1 `
-Force -RemoveCertificateSubject "CN=libvirtualhid CI Test Driver Signing"
```
The helper stages the INF with `pnputil`, updates an existing
`ROOT\LIBVIRTUALHID` device when present, and creates that root-enumerated
-device when it is missing. It uses `devcon.exe` when available, otherwise it
-uses SetupAPI/NewDev directly so MSI installs do not require the WDK tools on
-the target machine.
+device when it is missing. It uses SetupAPI/NewDev directly so MSI installs do
+not require the WDK tools on the target machine. Existing devices are detected
+by matching the `ROOT\LIBVIRTUALHID` hardware ID. The SetupAPI path creates a
+root-enumerated instance such as `ROOT\LIBVIRTUALHID\####`.
+The install and uninstall helpers also clean up malformed development devices
+left by earlier installer revisions. The WiX installer writes the helper
+transcript to `C:\ProgramData\libvirtualhid\install-driver.log`.
The driver binary is a UMDF DLL installed through the Windows Driver Store, not
a libvirtualhid `.sys` copied into `C:\Windows\System32\drivers`. Windows still
uses its built-in `WUDFRd.sys` and VHF components under `System32\drivers`; the
libvirtualhid-specific sign that installation completed is the
-`ROOT\LIBVIRTUALHID` device and the `\\.\LibVirtualHid` control device.
+`ROOT\LIBVIRTUALHID` device and the `\\.\LibVirtualHid` control device. The INF
+includes the built-in `WUDFRd` install sections for the root `System` control
+device, appends the VHF lower filter, sets `VhfMode=1` for the UMDF VHF source
+stack, and leaves UMDF dispatcher policy at the framework default to match the
+inbox VHF source-driver shape. The installer also writes `VhfMode=1` onto the
+root device before starting the driver so root-enumerated development installs
+get the same VHF source mode as the INF hardware section. The UMDF control
+device starts without opening VHF; gamepad creation opens VHF lazily so
+target-open failures are reported through the create-device response instead of
+making `\\.\LibVirtualHid` unavailable. The generated INF uses the same UMDF
+library version as the WDF headers and stub library selected by CMake. The
+package defaults to UMDF 2.15, matching the inbox VHF UMDF source driver while
+still exposing the framework APIs used by libvirtualhid. The driver target links
+the MSVC runtime statically to avoid requiring VC runtime DLLs in the UMDF host
+process.
Windows driver packages require a signed catalog for normal installation. Pull
request builds generate a short-lived self-signed test certificate, sign
diff --git a/cmake/packaging/windows_wix.cmake b/cmake/packaging/windows_wix.cmake
index 8779038..4a9a59b 100644
--- a/cmake/packaging/windows_wix.cmake
+++ b/cmake/packaging/windows_wix.cmake
@@ -26,25 +26,33 @@ if(NOT WIX_INSTALL_RESULT EQUAL 0)
message(FATAL_ERROR "Failed to install WiX tools locally: ${WIX_INSTALL_OUTPUT}")
endif()
-execute_process(
- COMMAND "${WIX_TOOL_PATH}/wix" extension add WixToolset.UI.wixext/${WIX_UI_VERSION}
- WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
- ERROR_VARIABLE WIX_UI_INSTALL_OUTPUT
- RESULT_VARIABLE WIX_UI_INSTALL_RESULT)
-
-if(NOT WIX_UI_INSTALL_RESULT EQUAL 0)
- message(FATAL_ERROR "Failed to install WiX UI extension: ${WIX_UI_INSTALL_OUTPUT}")
-endif()
-
-execute_process(
- COMMAND "${WIX_TOOL_PATH}/wix" extension add WixToolset.Util.wixext/${WIX_UI_VERSION}
- WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
- ERROR_VARIABLE WIX_UTIL_INSTALL_OUTPUT
- RESULT_VARIABLE WIX_UTIL_INSTALL_RESULT)
-
-if(NOT WIX_UTIL_INSTALL_RESULT EQUAL 0)
- message(FATAL_ERROR "Failed to install WiX Util extension: ${WIX_UTIL_INSTALL_OUTPUT}")
-endif()
+function(libvirtualhid_wix_ensure_extension extension_name extension_version)
+ execute_process(
+ COMMAND "${WIX_TOOL_PATH}/wix" extension list --global
+ WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
+ OUTPUT_VARIABLE WIX_EXTENSION_LIST_OUTPUT
+ ERROR_VARIABLE WIX_EXTENSION_LIST_ERROR
+ RESULT_VARIABLE WIX_EXTENSION_LIST_RESULT)
+
+ if(WIX_EXTENSION_LIST_RESULT EQUAL 0
+ AND WIX_EXTENSION_LIST_OUTPUT MATCHES "${extension_name}[ \t]+${extension_version}")
+ return()
+ endif()
+
+ execute_process(
+ COMMAND "${WIX_TOOL_PATH}/wix" extension add --global "${extension_name}/${extension_version}"
+ WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
+ ERROR_VARIABLE WIX_EXTENSION_INSTALL_OUTPUT
+ RESULT_VARIABLE WIX_EXTENSION_INSTALL_RESULT)
+
+ if(NOT WIX_EXTENSION_INSTALL_RESULT EQUAL 0)
+ message(FATAL_ERROR
+ "Failed to install WiX extension ${extension_name}/${extension_version}: "
+ "${WIX_EXTENSION_INSTALL_OUTPUT}${WIX_EXTENSION_LIST_ERROR}")
+ endif()
+endfunction()
+
+libvirtualhid_wix_ensure_extension(WixToolset.UI.wixext ${WIX_UI_VERSION})
set(CPACK_WIX_ROOT "${WIX_TOOL_PATH}")
set(CPACK_WIX_UPGRADE_GUID "71D7B738-9D83-4E57-82E3-C3106D9F8053")
@@ -52,16 +60,10 @@ set(CPACK_WIX_HELP_LINK "https://app.lizardbyte.dev/support")
set(CPACK_WIX_PRODUCT_URL "${CMAKE_PROJECT_HOMEPAGE_URL}")
set(CPACK_WIX_PROGRAM_MENU_FOLDER "LizardByte")
set(CPACK_WIX_EXTENSIONS
- "WixToolset.UI.wixext"
- "WixToolset.Util.wixext")
-
-message(STATUS "cpack package directory: ${CPACK_PACKAGE_DIRECTORY}")
-
-file(COPY "${CMAKE_CURRENT_LIST_DIR}/wix_resources/"
- DESTINATION "${WIX_BUILD_PARENT_DIRECTORY}/")
+ "WixToolset.UI.wixext")
-set(CPACK_WIX_EXTRA_SOURCES
- "${WIX_BUILD_PARENT_DIRECTORY}/libvirtualhid-driver-installer.wxs")
+set(CPACK_WIX_PATCH_FILE
+ "${CMAKE_CURRENT_LIST_DIR}/wix_resources/libvirtualhid-driver-installer-patch.xml")
file(COPY "${CMAKE_SOURCE_DIR}/LICENSE"
DESTINATION "${CMAKE_BINARY_DIR}")
diff --git a/cmake/packaging/wix_resources/libvirtualhid-driver-installer-patch.xml b/cmake/packaging/wix_resources/libvirtualhid-driver-installer-patch.xml
new file mode 100644
index 0000000..8387faf
--- /dev/null
+++ b/cmake/packaging/wix_resources/libvirtualhid-driver-installer-patch.xml
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/cmake/packaging/wix_resources/libvirtualhid-driver-installer.wxs b/cmake/packaging/wix_resources/libvirtualhid-driver-installer.wxs
deleted file mode 100644
index 984113f..0000000
--- a/cmake/packaging/wix_resources/libvirtualhid-driver-installer.wxs
+++ /dev/null
@@ -1,35 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/scripts/windows/install-driver.ps1 b/scripts/windows/install-driver.ps1
index 31ee9f6..ce44d7d 100644
--- a/scripts/windows/install-driver.ps1
+++ b/scripts/windows/install-driver.ps1
@@ -11,10 +11,44 @@ param(
[string] $HardwareId = "ROOT\LIBVIRTUALHID",
+ [string] $LogPath,
+
[switch] $StageOnly
)
$ErrorActionPreference = "Stop"
+$script:LibVirtualHidTranscriptStarted = $false
+
+function Start-LibVirtualHidTranscript {
+ param([string] $Path)
+
+ if (-not $Path) {
+ return
+ }
+
+ try {
+ $logDirectory = Split-Path -Parent $Path
+ if ($logDirectory) {
+ New-Item -ItemType Directory -Path $logDirectory -Force | Out-Null
+ }
+ Start-Transcript -Path $Path -Append | Out-Null
+ $script:LibVirtualHidTranscriptStarted = $true
+ } catch {
+ Write-Warning "Unable to start libvirtualhid install transcript: $($_.Exception.Message)"
+ }
+}
+
+function Stop-LibVirtualHidTranscript {
+ if (-not $script:LibVirtualHidTranscriptStarted) {
+ return
+ }
+
+ try {
+ Stop-Transcript | Out-Null
+ } catch {
+ Write-Warning "Unable to stop libvirtualhid install transcript: $($_.Exception.Message)"
+ }
+}
function Invoke-CheckedCommand {
param(
@@ -22,38 +56,17 @@ function Invoke-CheckedCommand {
[string] $FilePath,
[Parameter(Mandatory = $true)]
- [string[]] $Arguments
+ [string[]] $Arguments,
+
+ [int[]] $SuccessExitCodes = @(0)
)
& $FilePath @Arguments
- if ($LASTEXITCODE -ne 0) {
+ if ($LASTEXITCODE -notin $SuccessExitCodes) {
throw "$FilePath exited with code $LASTEXITCODE"
}
}
-function Find-Devcon {
- if ($env:DEVCON_EXE -and (Test-Path -LiteralPath $env:DEVCON_EXE)) {
- return $env:DEVCON_EXE
- }
-
- $roots = @(
- $env:WDKContentRoot,
- $env:WindowsSdkDir,
- "${env:ProgramFiles(x86)}\Windows Kits\10"
- ) | Where-Object { $_ -and (Test-Path -LiteralPath $_) }
-
- foreach ($root in $roots) {
- $candidate = Get-ChildItem -LiteralPath $root -Recurse -Filter devcon.exe -ErrorAction SilentlyContinue |
- Where-Object { $_.FullName -match "\\x64\\devcon\.exe$" } |
- Select-Object -First 1
- if ($candidate) {
- return $candidate.FullName
- }
- }
-
- return $null
-}
-
function Import-DriverCertificate {
[CmdletBinding(SupportsShouldProcess)]
param([string] $Path)
@@ -121,7 +134,7 @@ namespace LibVirtualHid.SetupApi {
uint creationFlags,
ref SpDevinfoData deviceInfoData);
- [DllImport("setupapi.dll", SetLastError = true)]
+ [DllImport("setupapi.dll", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern bool SetupDiSetDeviceRegistryProperty(
IntPtr deviceInfoSet,
ref SpDevinfoData deviceInfoData,
@@ -146,9 +159,24 @@ namespace LibVirtualHid.SetupApi {
uint installFlags,
out bool rebootRequired);
+ public static void Update(string infPath, string hardwareId, out bool rebootRequired) {
+ rebootRequired = false;
+
+ if (!UpdateDriverForPlugAndPlayDevices(
+ IntPtr.Zero,
+ hardwareId,
+ infPath,
+ InstallFlagForce | InstallFlagNonInteractive,
+ out rebootRequired)) {
+ ThrowLastWin32Error("UpdateDriverForPlugAndPlayDevices");
+ }
+ }
+
public static void Install(string infPath, string hardwareId, out bool rebootRequired) {
rebootRequired = false;
+ string rootDeviceName = GetRootDeviceName(hardwareId);
+
Guid classGuid;
uint requiredSize;
var className = new StringBuilder(256);
@@ -167,7 +195,7 @@ namespace LibVirtualHid.SetupApi {
if (!SetupDiCreateDeviceInfo(
deviceInfoSet,
- className.ToString(),
+ rootDeviceName,
ref classGuid,
null,
IntPtr.Zero,
@@ -192,15 +220,22 @@ namespace LibVirtualHid.SetupApi {
} finally {
SetupDiDestroyDeviceInfoList(deviceInfoSet);
}
+ }
- if (!UpdateDriverForPlugAndPlayDevices(
- IntPtr.Zero,
- hardwareId,
- infPath,
- InstallFlagForce | InstallFlagNonInteractive,
- out rebootRequired)) {
- ThrowLastWin32Error("UpdateDriverForPlugAndPlayDevices");
+ private static string GetRootDeviceName(string hardwareId) {
+ const string rootPrefix = "ROOT\\";
+ if (!hardwareId.StartsWith(rootPrefix, StringComparison.OrdinalIgnoreCase)) {
+ throw new ArgumentException("Hardware ID must use the ROOT\\ enumerator.", "hardwareId");
}
+
+ string rootDeviceName = hardwareId.Substring(rootPrefix.Length);
+ if (rootDeviceName.Length == 0 || rootDeviceName.Contains("\\")) {
+ throw new ArgumentException(
+ "Hardware ID must be a root-enumerated device ID without an instance suffix.",
+ "hardwareId");
+ }
+
+ return rootDeviceName;
}
private static void ThrowLastWin32Error(string action) {
@@ -216,10 +251,24 @@ namespace LibVirtualHid.SetupApi {
function Get-RootDeviceInstanceId {
param([string] $TargetHardwareId)
+ try {
+ $devices = & pnputil.exe /enum-devices /deviceid $TargetHardwareId /deviceids
+ if ($LASTEXITCODE -eq 0) {
+ $instanceIds = @($devices |
+ Where-Object { $_ -match "^\s*Instance ID\s*:\s*(.+)$" } |
+ ForEach-Object { $Matches[1].Trim() })
+ if ($instanceIds.Count -gt 0) {
+ return $instanceIds
+ }
+ }
+ } catch {
+ Write-Verbose "Unable to enumerate PnP devices with pnputil: $($_.Exception.Message)"
+ }
+
try {
$prefix = "$TargetHardwareId\"
@(Get-CimInstance -ClassName Win32_PnPEntity -ErrorAction Stop |
- Where-Object { $_.PNPDeviceID -like "$prefix*" } |
+ Where-Object { $_.PNPDeviceID -like "$prefix*" -or $_.HardwareID -contains $TargetHardwareId } |
ForEach-Object { $_.PNPDeviceID })
} catch {
Write-Verbose "Unable to enumerate PnP devices: $($_.Exception.Message)"
@@ -227,6 +276,85 @@ function Get-RootDeviceInstanceId {
}
}
+function Get-RegistryRootDevice {
+ param([string] $TargetHardwareId)
+
+ $rootKey = "HKLM:\SYSTEM\CurrentControlSet\Enum\ROOT"
+ Get-ChildItem -LiteralPath $rootKey -ErrorAction SilentlyContinue | ForEach-Object {
+ $rootDeviceId = $_.PSChildName
+ Get-ChildItem -LiteralPath $_.PSPath -ErrorAction SilentlyContinue | ForEach-Object {
+ $instanceId = "ROOT\$rootDeviceId\$($_.PSChildName)"
+ $hardwareIds = @()
+ try {
+ $hardwareIds = @((Get-ItemProperty -LiteralPath $_.PSPath -Name HardwareID -ErrorAction Stop).HardwareID)
+ } catch {
+ $hardwareIds = @()
+ }
+
+ $hasExactHardwareId = $hardwareIds -contains $TargetHardwareId
+ $hasCorruptHardwareId = (
+ $hardwareIds.Count -gt 1 -and
+ -not $hasExactHardwareId -and
+ (($hardwareIds -join "") -ieq $TargetHardwareId)
+ )
+ $hasTargetInstanceId = $instanceId -like "$TargetHardwareId\*"
+
+ if ($hasExactHardwareId -or $hasCorruptHardwareId -or $hasTargetInstanceId) {
+ [pscustomobject]@{
+ InstanceId = $instanceId
+ HasExactHardwareId = $hasExactHardwareId
+ HasCorruptHardwareId = $hasCorruptHardwareId
+ }
+ }
+ }
+ }
+}
+
+function Remove-DeviceInstance {
+ [CmdletBinding(SupportsShouldProcess)]
+ param([string] $InstanceId)
+
+ if ($PSCmdlet.ShouldProcess($InstanceId, "Remove stale libvirtualhid root device")) {
+ Invoke-CheckedCommand -FilePath "pnputil.exe" -Arguments @("/remove-device", $InstanceId)
+ }
+}
+
+function Set-RootDeviceVhfMode {
+ [CmdletBinding(SupportsShouldProcess)]
+ param([string] $InstanceId)
+
+ $deviceRegistryPath = "HKLM:\SYSTEM\CurrentControlSet\Enum\$InstanceId"
+ if (-not (Test-Path -LiteralPath $deviceRegistryPath)) {
+ Write-Verbose "Unable to set VhfMode because $deviceRegistryPath does not exist."
+ return
+ }
+
+ if ($PSCmdlet.ShouldProcess($InstanceId, "Set VhfMode=1 for UMDF VHF source device")) {
+ New-ItemProperty -LiteralPath $deviceRegistryPath -Name "VhfMode" -Value 1 -PropertyType DWord -Force | Out-Null
+ Write-Information "Set VhfMode=1 on $InstanceId." -InformationAction Continue
+ }
+}
+
+function Update-RootDeviceDriverWithSetupApi {
+ [CmdletBinding(SupportsShouldProcess)]
+ param(
+ [Parameter(Mandatory = $true)]
+ [string] $Path,
+
+ [Parameter(Mandatory = $true)]
+ [string] $TargetHardwareId
+ )
+
+ Add-SetupApiRootDeviceInstaller
+ $rebootRequired = $false
+ if ($PSCmdlet.ShouldProcess($TargetHardwareId, "Update libvirtualhid development device driver")) {
+ [LibVirtualHid.SetupApi.RootDeviceInstaller]::Update($Path, $TargetHardwareId, [ref] $rebootRequired)
+ }
+ if ($rebootRequired) {
+ Write-Warning "Windows reported that a reboot is required to finish installing the libvirtualhid driver."
+ }
+}
+
function Install-RootDeviceWithSetupApi {
param(
[Parameter(Mandatory = $true)]
@@ -244,30 +372,44 @@ function Install-RootDeviceWithSetupApi {
}
}
-$resolvedInf = (Resolve-Path -LiteralPath $InfPath).Path
-Import-DriverCertificate -Path $CertificatePath
+Start-LibVirtualHidTranscript -Path $LogPath
-if ($PSCmdlet.ShouldProcess($resolvedInf, "Stage libvirtualhid driver package")) {
- Invoke-CheckedCommand -FilePath "pnputil.exe" -Arguments @("/add-driver", $resolvedInf, "/install")
-}
+try {
+ $resolvedInf = (Resolve-Path -LiteralPath $InfPath).Path
+ Import-DriverCertificate -Path $CertificatePath
-if ($StageOnly) {
- return
-}
+ if ($PSCmdlet.ShouldProcess($resolvedInf, "Stage libvirtualhid driver package")) {
+ Invoke-CheckedCommand -FilePath "pnputil.exe" -Arguments @("/add-driver", $resolvedInf) -SuccessExitCodes @(0, 5)
+ }
-if ((Get-RootDeviceInstanceId -TargetHardwareId $HardwareId).Count -gt 0) {
- Write-Information "The $HardwareId device already exists." -InformationAction Continue
- return
-}
+ if ($StageOnly) {
+ return
+ }
-$devcon = Find-Devcon
-if ($devcon) {
- if ($PSCmdlet.ShouldProcess($HardwareId, "Create libvirtualhid development device with devcon")) {
- Invoke-CheckedCommand -FilePath $devcon -Arguments @("install", $resolvedInf, $HardwareId)
+ $registryRootDevices = @(Get-RegistryRootDevice -TargetHardwareId $HardwareId)
+ foreach ($device in ($registryRootDevices | Where-Object { $_.HasCorruptHardwareId })) {
+ Remove-DeviceInstance -InstanceId $device.InstanceId
}
- return
-}
-if ($PSCmdlet.ShouldProcess($HardwareId, "Create libvirtualhid development device with SetupAPI")) {
- Install-RootDeviceWithSetupApi -Path $resolvedInf -TargetHardwareId $HardwareId
+ $rootDevices = @(Get-RootDeviceInstanceId -TargetHardwareId $HardwareId)
+ if ($rootDevices.Count -gt 0) {
+ Write-Information "Updating the existing $HardwareId device driver." -InformationAction Continue
+ foreach ($rootDevice in $rootDevices) {
+ Set-RootDeviceVhfMode -InstanceId $rootDevice
+ }
+ Update-RootDeviceDriverWithSetupApi -Path $resolvedInf -TargetHardwareId $HardwareId
+ return
+ }
+
+ if ($PSCmdlet.ShouldProcess($HardwareId, "Create libvirtualhid development device with SetupAPI")) {
+ Install-RootDeviceWithSetupApi -Path $resolvedInf -TargetHardwareId $HardwareId
+ }
+
+ $rootDevices = @(Get-RootDeviceInstanceId -TargetHardwareId $HardwareId)
+ foreach ($rootDevice in $rootDevices) {
+ Set-RootDeviceVhfMode -InstanceId $rootDevice
+ }
+ Update-RootDeviceDriverWithSetupApi -Path $resolvedInf -TargetHardwareId $HardwareId
+} finally {
+ Stop-LibVirtualHidTranscript
}
diff --git a/scripts/windows/uninstall-driver.ps1 b/scripts/windows/uninstall-driver.ps1
index 3012415..b80e226 100644
--- a/scripts/windows/uninstall-driver.ps1
+++ b/scripts/windows/uninstall-driver.ps1
@@ -63,6 +63,7 @@ function Find-PublishedName {
$drivers = & pnputil.exe /enum-drivers
$currentPublished = $null
$currentOriginal = $null
+ $publishedNames = @()
foreach ($line in $drivers) {
if ($line -match "^\s*Published Name\s*:\s*(.+)$") {
@@ -74,21 +75,35 @@ function Find-PublishedName {
if ($line -match "^\s*Original Name\s*:\s*(.+)$") {
$currentOriginal = $Matches[1].Trim()
if ($currentPublished -and $currentOriginal -ieq $TargetOriginalName) {
- return $currentPublished
+ $publishedNames += $currentPublished
}
}
}
- return $null
+ return $publishedNames
}
function Get-RootDeviceInstanceId {
param([string] $TargetHardwareId)
+ try {
+ $devices = & pnputil.exe /enum-devices /deviceid $TargetHardwareId /deviceids
+ if ($LASTEXITCODE -eq 0) {
+ $instanceIds = @($devices |
+ Where-Object { $_ -match "^\s*Instance ID\s*:\s*(.+)$" } |
+ ForEach-Object { $Matches[1].Trim() })
+ if ($instanceIds.Count -gt 0) {
+ return $instanceIds
+ }
+ }
+ } catch {
+ Write-Verbose "Unable to enumerate PnP devices with pnputil: $($_.Exception.Message)"
+ }
+
try {
$prefix = "$TargetHardwareId\"
@(Get-CimInstance -ClassName Win32_PnPEntity -ErrorAction Stop |
- Where-Object { $_.PNPDeviceID -like "$prefix*" } |
+ Where-Object { $_.PNPDeviceID -like "$prefix*" -or $_.HardwareID -contains $TargetHardwareId } |
ForEach-Object { $_.PNPDeviceID })
} catch {
Write-Verbose "Unable to enumerate PnP devices: $($_.Exception.Message)"
@@ -96,6 +111,36 @@ function Get-RootDeviceInstanceId {
}
}
+function Get-RegistryRootDeviceInstanceId {
+ param([string] $TargetHardwareId)
+
+ $rootKey = "HKLM:\SYSTEM\CurrentControlSet\Enum\ROOT"
+ Get-ChildItem -LiteralPath $rootKey -ErrorAction SilentlyContinue | ForEach-Object {
+ $rootDeviceId = $_.PSChildName
+ Get-ChildItem -LiteralPath $_.PSPath -ErrorAction SilentlyContinue | ForEach-Object {
+ $instanceId = "ROOT\$rootDeviceId\$($_.PSChildName)"
+ $hardwareIds = @()
+ try {
+ $hardwareIds = @((Get-ItemProperty -LiteralPath $_.PSPath -Name HardwareID -ErrorAction Stop).HardwareID)
+ } catch {
+ $hardwareIds = @()
+ }
+
+ $hasExactHardwareId = $hardwareIds -contains $TargetHardwareId
+ $hasCorruptHardwareId = (
+ $hardwareIds.Count -gt 1 -and
+ -not $hasExactHardwareId -and
+ (($hardwareIds -join "") -ieq $TargetHardwareId)
+ )
+ $hasTargetInstanceId = $instanceId -like "$TargetHardwareId\*"
+
+ if ($hasExactHardwareId -or $hasCorruptHardwareId -or $hasTargetInstanceId) {
+ $instanceId
+ }
+ }
+ }
+}
+
function Remove-DriverCertificate {
[CmdletBinding(SupportsShouldProcess)]
param([string] $Subject)
@@ -127,23 +172,34 @@ foreach ($instanceId in (Get-RootDeviceInstanceId -TargetHardwareId $HardwareId)
}
}
-if (-not $PublishedName) {
- $PublishedName = Find-PublishedName -TargetOriginalName $OriginalName
+foreach ($instanceId in (Get-RegistryRootDeviceInstanceId -TargetHardwareId $HardwareId | Select-Object -Unique)) {
+ if ($PSCmdlet.ShouldProcess($instanceId, "Remove libvirtualhid registry-discovered development device with pnputil")) {
+ Invoke-CheckedCommand -FilePath "pnputil.exe" -Arguments @("/remove-device", $instanceId) -IgnoreFailure
+ }
+}
+
+$publishedNames = @()
+if ($PublishedName) {
+ $publishedNames += $PublishedName
+} else {
+ $publishedNames = @(Find-PublishedName -TargetOriginalName $OriginalName)
}
-if (-not $PublishedName) {
+if ($publishedNames.Count -eq 0) {
Write-Warning "No staged libvirtualhid driver package matching $OriginalName was found."
Remove-DriverCertificate -Subject $RemoveCertificateSubject
return
}
-$deleteArgs = @("/delete-driver", $PublishedName, "/uninstall")
-if ($Force) {
- $deleteArgs += "/force"
-}
+foreach ($driverPackage in $publishedNames) {
+ $deleteArgs = @("/delete-driver", $driverPackage, "/uninstall")
+ if ($Force) {
+ $deleteArgs += "/force"
+ }
-if ($PSCmdlet.ShouldProcess($PublishedName, "Delete libvirtualhid driver package")) {
- Invoke-CheckedCommand -FilePath "pnputil.exe" -Arguments $deleteArgs
+ if ($PSCmdlet.ShouldProcess($driverPackage, "Delete libvirtualhid driver package")) {
+ Invoke-CheckedCommand -FilePath "pnputil.exe" -Arguments $deleteArgs
+ }
}
Remove-DriverCertificate -Subject $RemoveCertificateSubject
diff --git a/src/platform/windows/driver/CMakeLists.txt b/src/platform/windows/driver/CMakeLists.txt
index 0e197c6..8a2daf1 100644
--- a/src/platform/windows/driver/CMakeLists.txt
+++ b/src/platform/windows/driver/CMakeLists.txt
@@ -53,6 +53,12 @@ set(_lvh_wdk_shared_include_candidates)
set(_lvh_wdf_library_candidates)
set(_lvh_wdk_um_library_candidates)
set(_lvh_wdk_tool_candidates)
+set(LIBVIRTUALHID_UMDF_LIBRARY_VERSION "2.15" CACHE STRING
+ "UMDF library version used for the Windows driver package")
+option(LIBVIRTUALHID_WINDOWS_DRIVER_ENABLE_VHF
+ "Attach the Windows driver package to the inbox Virtual HID Framework lower filter"
+ ON)
+string(REGEX REPLACE "\\." "\\\\." _lvh_umdf_library_version_regex "${LIBVIRTUALHID_UMDF_LIBRARY_VERSION}")
foreach(lvh_wdk_root_cmake IN LISTS _lvh_wdk_roots)
if(EXISTS "${lvh_wdk_root_cmake}")
file(GLOB _lvh_wdf_include_glob
@@ -83,12 +89,14 @@ endforeach()
if(_lvh_wdf_include_candidates)
list(SORT _lvh_wdf_include_candidates COMPARE NATURAL ORDER DESCENDING)
+ list(FILTER _lvh_wdf_include_candidates INCLUDE REGEX "/${_lvh_umdf_library_version_regex}$")
endif()
if(_lvh_wdk_shared_include_candidates)
list(SORT _lvh_wdk_shared_include_candidates COMPARE NATURAL ORDER DESCENDING)
endif()
if(_lvh_wdf_library_candidates)
list(SORT _lvh_wdf_library_candidates COMPARE NATURAL ORDER DESCENDING)
+ list(FILTER _lvh_wdf_library_candidates INCLUDE REGEX "/${_lvh_umdf_library_version_regex}$")
endif()
if(_lvh_wdk_um_library_candidates)
list(SORT _lvh_wdk_um_library_candidates COMPARE NATURAL ORDER DESCENDING)
@@ -141,6 +149,31 @@ message(STATUS "WDF UMDF stub library: ${LIBVIRTUALHID_WDF_DRIVER_STUB_UM_LIBRAR
message(STATUS "VHF UMDF library: ${LIBVIRTUALHID_VHF_UM_LIBRARY}")
message(STATUS "NTDLL import library: ${LIBVIRTUALHID_NTDLL_LIBRARY}")
+get_filename_component(_lvh_wdf_include_version "${LIBVIRTUALHID_WDF_INCLUDE_DIR}" NAME)
+get_filename_component(_lvh_wdf_stub_directory "${LIBVIRTUALHID_WDF_DRIVER_STUB_UM_LIBRARY}" DIRECTORY)
+get_filename_component(_lvh_wdf_stub_version "${_lvh_wdf_stub_directory}" NAME)
+if(NOT _lvh_wdf_include_version MATCHES "^2\\.[0-9]+$")
+ message(FATAL_ERROR
+ "Could not determine the UMDF library version from ${LIBVIRTUALHID_WDF_INCLUDE_DIR}.")
+endif()
+if(NOT _lvh_wdf_include_version STREQUAL LIBVIRTUALHID_UMDF_LIBRARY_VERSION)
+ message(FATAL_ERROR
+ "UMDF include version ${_lvh_wdf_include_version} does not match requested "
+ "version ${LIBVIRTUALHID_UMDF_LIBRARY_VERSION}.")
+endif()
+if(NOT _lvh_wdf_stub_version STREQUAL LIBVIRTUALHID_UMDF_LIBRARY_VERSION)
+ message(FATAL_ERROR
+ "UMDF stub library version ${_lvh_wdf_stub_version} does not match requested "
+ "version ${LIBVIRTUALHID_UMDF_LIBRARY_VERSION}.")
+endif()
+message(STATUS "UMDF library version: ${LIBVIRTUALHID_UMDF_LIBRARY_VERSION}")
+if(LIBVIRTUALHID_WINDOWS_DRIVER_ENABLE_VHF)
+ set(LIBVIRTUALHID_DRIVER_VHF_ADDREG ",DeviceInstall_Vhf_AddReg")
+else()
+ set(LIBVIRTUALHID_DRIVER_VHF_ADDREG "")
+endif()
+message(STATUS "Windows driver VHF lower filter: ${LIBVIRTUALHID_WINDOWS_DRIVER_ENABLE_VHF}")
+
find_program(LIBVIRTUALHID_STAMPINF
NAMES stampinf stampinf.exe
PATHS ${_lvh_wdk_tool_candidates})
@@ -186,6 +219,7 @@ target_link_libraries(libvirtualhid_umdf
"${LIBVIRTUALHID_VHF_UM_LIBRARY}"
"${LIBVIRTUALHID_NTDLL_LIBRARY}")
set_target_properties(libvirtualhid_umdf PROPERTIES
+ MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>"
OUTPUT_NAME libvirtualhid_umdf
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/package")
diff --git a/src/platform/windows/driver/libvirtualhid.inf.in b/src/platform/windows/driver/libvirtualhid.inf.in
index 11db515..c609865 100644
--- a/src/platform/windows/driver/libvirtualhid.inf.in
+++ b/src/platform/windows/driver/libvirtualhid.inf.in
@@ -1,11 +1,12 @@
;
; libvirtualhid UMDF2 control driver package.
+; This package includes UMDF startup file tracing for local driver bring-up.
;
[Version]
Signature="$WINDOWS NT$"
-Class=HIDClass
-ClassGuid={745A17A0-74D3-11D0-B6FE-00A0C90F57DA}
+Class=System
+ClassGuid={4D36E97D-E325-11CE-BFC1-08002BE10318}
Provider=%ManufacturerName%
DriverVer=*,@LIBVIRTUALHID_DRIVER_VERSION@
CatalogFile=libvirtualhid.cat
@@ -28,36 +29,37 @@ libvirtualhid_umdf.dll=1
[DeviceInstall.NT]
CopyFiles=UMDriverCopy
+Include=wudfrd.inf
+Needs=WUDFRD.NT
[DeviceInstall.NT.HW]
-AddReg=DeviceInstall_AddReg
+AddReg=DeviceInstall_Device_AddReg@LIBVIRTUALHID_DRIVER_VHF_ADDREG@
+Include=wudfrd.inf
+Needs=WUDFRD.NT.HW
[UMDriverCopy]
libvirtualhid_umdf.dll
-[DeviceInstall_AddReg]
-HKR,,"LowerFilters",0x00010000,"vhf"
+[DeviceInstall_Device_AddReg]
+HKR,,Security,,"D:P(A;;GA;;;SY)(A;;GRGWGX;;;BA)(A;;GRGWGX;;;AU)"
-[DeviceInstall.NT.Services]
-AddService=WUDFRd,0x000001fa,WUDFRD_ServiceInstall
+[DeviceInstall_Vhf_AddReg]
+HKR,,"LowerFilters",0x00010008,"vhf"
+HKR,,VhfMode,0x00010001,0x1
-[WUDFRD_ServiceInstall]
-DisplayName=%WudfRdDisplayName%
-ServiceType=1
-StartType=3
-ErrorControl=1
-ServiceBinary=%12%\WUDFRd.sys
+[DeviceInstall.NT.Services]
+Include=wudfrd.inf
+Needs=WUDFRD.NT.Services
[DeviceInstall.NT.Wdf]
UmdfService=libvirtualhid_umdf,libvirtualhid_umdf_Install
UmdfServiceOrder=libvirtualhid_umdf
[libvirtualhid_umdf_Install]
-UmdfLibraryVersion=2.0
+UmdfLibraryVersion=@LIBVIRTUALHID_UMDF_LIBRARY_VERSION@
ServiceBinary=%13%\libvirtualhid_umdf.dll
[Strings]
ManufacturerName="LizardByte"
DiskName="libvirtualhid UMDF Driver Install Disk"
DeviceName="libvirtualhid Virtual HID Control Device"
-WudfRdDisplayName="Windows Driver Foundation - User-mode Driver Framework Reflector"
diff --git a/src/platform/windows/driver/libvirtualhid_umdf.cpp b/src/platform/windows/driver/libvirtualhid_umdf.cpp
index 2381fe7..4143bce 100644
--- a/src/platform/windows/driver/libvirtualhid_umdf.cpp
+++ b/src/platform/windows/driver/libvirtualhid_umdf.cpp
@@ -31,7 +31,9 @@
#include
#include
#include
+#include
#include
+#include
#include