Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion .github/workflows/ci-driver.yml
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,25 @@ jobs:
run: >-
cmake --build cmake-build-driver
--config ${{ env.DRIVER_BUILD_CONFIG }}
--target libvirtualhid_windows_catalog libvirtualhid_broker gamepad_adapter virtualhid_control
--target
libvirtualhid_windows_catalog
libvirtualhid_driver_setup
libvirtualhid_broker
gamepad_adapter
virtualhid_control
--parallel 2

- name: Validate Windows driver setup helper
shell: pwsh
run: |
$setupHelperPath = Join-Path `
.\cmake-build-driver\src\platform\windows\driver `
"${{ env.DRIVER_BUILD_CONFIG }}\libvirtualhid_driver_setup.exe"
& $setupHelperPath --help
if ($LASTEXITCODE -ne 0) {
throw "libvirtualhid_driver_setup.exe failed with exit code $LASTEXITCODE."
}

- name: Validate Azure signing configuration
if: >-
github.event_name == 'push' &&
Expand Down
5 changes: 5 additions & 0 deletions cmake/packaging/windows.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ if(NOT TARGET virtualhid_control)
"so the virtualhid_control UI tool can be packaged.")
endif()

if(NOT TARGET libvirtualhid_driver_setup)
message(FATAL_ERROR
"The Windows driver installer requires the native libvirtualhid_driver_setup helper.")
endif()

if(NOT TARGET libvirtualhid_broker)
message(FATAL_ERROR
"The Windows driver installer requires LIBVIRTUALHID_BUILD_WINDOWS_BROKER=ON "
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@
<CPackWiXFragment Id="#PRODUCT">
<CustomAction Id="CA_LibVirtualHidInstallDriver"
Directory="INSTALL_ROOT"
ExeCommand="&quot;[WindowsFolder]System32\WindowsPowerShell\v1.0\powershell.exe&quot; -WindowStyle Hidden -NoProfile -ExecutionPolicy Bypass -File &quot;[INSTALL_ROOT]scripts\windows\install-driver.ps1&quot; -InfPath &quot;[INSTALL_ROOT]drivers\windows\libvirtualhid.inf&quot; -CertificatePath &quot;[INSTALL_ROOT]certificates\libvirtualhid-ci-test.cer&quot; -BrokerPath &quot;[INSTALL_ROOT]services\windows\libvirtualhid_broker.exe&quot; -LogPath &quot;[CommonAppDataFolder]libvirtualhid\install-driver.log&quot;"
ExeCommand="&quot;[WindowsFolder]System32\WindowsPowerShell\v1.0\powershell.exe&quot; -WindowStyle Hidden -NoProfile -ExecutionPolicy Bypass -File &quot;[INSTALL_ROOT]scripts\windows\install-driver.ps1&quot; -InfPath &quot;[INSTALL_ROOT]drivers\windows\libvirtualhid.inf&quot; -CertificatePath &quot;[INSTALL_ROOT]certificates\libvirtualhid-ci-test.cer&quot; -BrokerPath &quot;[INSTALL_ROOT]services\windows\libvirtualhid_broker.exe&quot; -SetupPath &quot;[INSTALL_ROOT]tools\windows\libvirtualhid_driver_setup.exe&quot; -LogPath &quot;[CommonAppDataFolder]libvirtualhid\install-driver.log&quot;"
Execute="deferred"
Return="check"
Impersonate="no" />
<CustomAction Id="CA_LibVirtualHidInstallDriverSilent"
Directory="INSTALL_ROOT"
ExeCommand="&quot;[WindowsFolder]System32\WindowsPowerShell\v1.0\powershell.exe&quot; -WindowStyle Hidden -NoProfile -ExecutionPolicy Bypass -File &quot;[INSTALL_ROOT]scripts\windows\install-driver.ps1&quot; -InfPath &quot;[INSTALL_ROOT]drivers\windows\libvirtualhid.inf&quot; -CertificatePath &quot;[INSTALL_ROOT]certificates\libvirtualhid-ci-test.cer&quot; -BrokerPath &quot;[INSTALL_ROOT]services\windows\libvirtualhid_broker.exe&quot; -LogPath &quot;[CommonAppDataFolder]libvirtualhid\install-driver.log&quot;"
ExeCommand="&quot;[WindowsFolder]System32\WindowsPowerShell\v1.0\powershell.exe&quot; -WindowStyle Hidden -NoProfile -ExecutionPolicy Bypass -File &quot;[INSTALL_ROOT]scripts\windows\install-driver.ps1&quot; -InfPath &quot;[INSTALL_ROOT]drivers\windows\libvirtualhid.inf&quot; -CertificatePath &quot;[INSTALL_ROOT]certificates\libvirtualhid-ci-test.cer&quot; -BrokerPath &quot;[INSTALL_ROOT]services\windows\libvirtualhid_broker.exe&quot; -SetupPath &quot;[INSTALL_ROOT]tools\windows\libvirtualhid_driver_setup.exe&quot; -LogPath &quot;[CommonAppDataFolder]libvirtualhid\install-driver.log&quot;"
Execute="deferred"
Return="check"
Impersonate="no" />
Expand Down
13 changes: 8 additions & 5 deletions docs/windows-driver.md
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ cmake -S . -B cmake-build-windows-driver -G "Visual Studio 17 2022" -A x64 `
-DLIBVIRTUALHID_BUILD_WINDOWS_DRIVER=ON -DLIBVIRTUALHID_ENABLE_PACKAGING=ON `
-DBUILD_TESTS=OFF -DBUILD_EXAMPLES=ON -DLIBVIRTUALHID_BUILD_TOOLS=ON
cmake --build cmake-build-windows-driver --config Release `
--target libvirtualhid_windows_catalog libvirtualhid_broker gamepad_adapter virtualhid_control
--target libvirtualhid_windows_catalog libvirtualhid_driver_setup libvirtualhid_broker gamepad_adapter virtualhid_control
cpack -G WIX -C Release --config .\cmake-build-windows-driver\CPackConfig.cmake
```

Expand All @@ -183,6 +183,7 @@ Developer 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 `
-SetupPath .\cmake-build-windows-driver\src\platform\windows\driver\Release\libvirtualhid_driver_setup.exe `
-BrokerPath .\cmake-build-windows-driver\src\platform\windows\broker\Release\libvirtualhid_broker.exe `
-LogPath .\cmake-build-windows-driver\install-driver.log
powershell -ExecutionPolicy Bypass -File .\scripts\windows\test-installed-driver.ps1 `
Expand All @@ -201,18 +202,20 @@ defaults to `C:\Program Files\libvirtualhid`:

- `tools\windows\gamepad_adapter.exe`
- `tools\windows\virtualhid_control.exe`
- `tools\windows\libvirtualhid_driver_setup.exe`
- `services\windows\libvirtualhid_broker.exe`

The source-tree validation scripts remain developer and CI helpers. They are not
packaged as reviewer-facing MSI validation scripts because the native
`virtualhid_control.exe` tool can create, exercise, and inspect virtual
gamepads and mice interactively.

The install helper stages the INF with `pnputil`, updates an existing
The install script 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 SetupAPI/NewDev directly, so MSI installs do
not require WDK tools on the target machine. When a broker executable is present,
the helper also installs and starts the `libvirtualhid_broker` Windows service
device when it is missing. A packaged, architecture-matched native helper makes
the SetupAPI/NewDev calls, so MSI installs do not depend on PowerShell runtime
C# compilation or require WDK tools on the target machine. When a broker executable is present,
the install script also installs and starts the `libvirtualhid_broker` Windows service
with a service SID. The service `ImagePath` is stored as a literal quoted path,
and installation fails if the registry value is not safely quoted. This avoids
CWE-428 unquoted-service-path escalation when the install root contains spaces.
Expand Down
187 changes: 50 additions & 137 deletions scripts/windows/install-driver.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ param(

[string] $BrokerPath,

[string] $SetupPath,

[string] $LogPath,

[switch] $StageOnly
Expand Down Expand Up @@ -59,6 +61,24 @@ function Resolve-LibVirtualHidBrokerPath {
return $null
}

function Resolve-LibVirtualHidDriverSetupPath {
param([string] $Path)

if ($Path) {
if (-not (Test-Path -LiteralPath $Path)) {
throw "The driver setup helper was not found at $Path"
}
return (Resolve-Path -LiteralPath $Path).Path
}

$packagedPath = Join-Path $PSScriptRoot "..\..\tools\windows\libvirtualhid_driver_setup.exe"
if (Test-Path -LiteralPath $packagedPath) {
return (Resolve-Path -LiteralPath $packagedPath).Path
}

throw "The libvirtualhid driver setup helper was not found. Pass its path with -SetupPath."
}

function Get-LibVirtualHidQuotedServiceBinaryPath {
param(
[Parameter(Mandatory = $true)]
Expand Down Expand Up @@ -212,126 +232,6 @@ function Import-DriverCertificate {
}
}

function Add-SetupApiRootDeviceInstaller {
if (([System.Management.Automation.PSTypeName] "LibVirtualHid.SetupApi.RootDeviceInstaller").Type) {
return
}

Add-Type -TypeDefinition @"
using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Text;

namespace LibVirtualHid.SetupApi {
public static class RootDeviceInstaller {
private const uint DicdGenerateId = 0x00000001;
private const uint DifRegisterDevice = 0x00000019;
private const uint InstallFlagForce = 0x00000001;
private const uint InstallFlagNonInteractive = 0x00000004;
private const uint SpdrpHardwareId = 0x00000001;
private static readonly IntPtr InvalidHandleValue = new IntPtr(-1);

[StructLayout(LayoutKind.Sequential)]
private struct SpDevinfoData {
public uint cbSize;
public Guid ClassGuid;
public uint DevInst;
public IntPtr Reserved;
}

[DllImport("setupapi.dll", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern bool SetupDiGetINFClass(string infName, out Guid classGuid, StringBuilder className, uint classNameSize, out uint requiredSize);

[DllImport("setupapi.dll", SetLastError = true)]
private static extern IntPtr SetupDiCreateDeviceInfoList(ref Guid classGuid, IntPtr hwndParent);

[DllImport("setupapi.dll", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern bool SetupDiCreateDeviceInfo(IntPtr deviceInfoSet, string deviceName, ref Guid classGuid, string deviceDescription, IntPtr hwndParent, uint creationFlags, ref SpDevinfoData deviceInfoData);

[DllImport("setupapi.dll", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern bool SetupDiSetDeviceRegistryProperty(IntPtr deviceInfoSet, ref SpDevinfoData deviceInfoData, uint property, byte[] propertyBuffer, uint propertyBufferSize);

[DllImport("setupapi.dll", SetLastError = true)]
private static extern bool SetupDiCallClassInstaller(uint installFunction, IntPtr deviceInfoSet, ref SpDevinfoData deviceInfoData);

[DllImport("setupapi.dll", SetLastError = true)]
private static extern bool SetupDiDestroyDeviceInfoList(IntPtr deviceInfoSet);

[DllImport("newdev.dll", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern bool UpdateDriverForPlugAndPlayDevices(IntPtr hwndParent, string hardwareId, string fullInfPath, 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);
if (!SetupDiGetINFClass(infPath, out classGuid, className, (uint) className.Capacity, out requiredSize)) {
ThrowLastWin32Error("SetupDiGetINFClass");
}

IntPtr deviceInfoSet = SetupDiCreateDeviceInfoList(ref classGuid, IntPtr.Zero);
if (deviceInfoSet == InvalidHandleValue) {
ThrowLastWin32Error("SetupDiCreateDeviceInfoList");
}

try {
var deviceInfoData = new SpDevinfoData { cbSize = (uint) Marshal.SizeOf(typeof(SpDevinfoData)) };

if (!SetupDiCreateDeviceInfo(deviceInfoSet, rootDeviceName, ref classGuid, null, IntPtr.Zero, DicdGenerateId, ref deviceInfoData)) {
ThrowLastWin32Error("SetupDiCreateDeviceInfo");
}

byte[] hardwareIds = Encoding.Unicode.GetBytes(hardwareId + "\0\0");
if (!SetupDiSetDeviceRegistryProperty(deviceInfoSet, ref deviceInfoData, SpdrpHardwareId, hardwareIds, (uint) hardwareIds.Length)) {
ThrowLastWin32Error("SetupDiSetDeviceRegistryProperty");
}

if (!SetupDiCallClassInstaller(DifRegisterDevice, deviceInfoSet, ref deviceInfoData)) {
ThrowLastWin32Error("SetupDiCallClassInstaller");
}
} finally {
SetupDiDestroyDeviceInfoList(deviceInfoSet);
}
}

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) {
int error = Marshal.GetLastWin32Error();
throw new InvalidOperationException(
action + " failed with Win32 error " + error + ": " + new Win32Exception(error).Message);
}
}
}
"@
}

function Remove-DeviceInstance {
[CmdletBinding(SupportsShouldProcess)]
param([string] $InstanceId)
Expand Down Expand Up @@ -391,16 +291,20 @@ function Update-RootDeviceDriverWithSetupApi {
[string] $Path,

[Parameter(Mandatory = $true)]
[string] $TargetHardwareId
[string] $TargetHardwareId,

[Parameter(Mandatory = $true)]
[string] $SetupHelperPath
)

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."
Invoke-CheckedCommand `
-FilePath $SetupHelperPath `
-Arguments @("update", $Path, $TargetHardwareId) `
-SuccessExitCodes @(0, 3010)
if ($LASTEXITCODE -eq 3010) {
Write-Warning "Windows reported that a reboot is required to finish installing the libvirtualhid driver."
}
}
}

Expand All @@ -410,15 +314,13 @@ function Install-RootDeviceWithSetupApi {
[string] $Path,

[Parameter(Mandatory = $true)]
[string] $TargetHardwareId
[string] $TargetHardwareId,

[Parameter(Mandatory = $true)]
[string] $SetupHelperPath
)

Add-SetupApiRootDeviceInstaller
$rebootRequired = $false
[LibVirtualHid.SetupApi.RootDeviceInstaller]::Install($Path, $TargetHardwareId, [ref] $rebootRequired)
if ($rebootRequired) {
Write-Warning "Windows reported that a reboot is required to finish installing the libvirtualhid driver."
}
Invoke-CheckedCommand -FilePath $SetupHelperPath -Arguments @("install", $Path, $TargetHardwareId)
}

Start-LibVirtualHidTranscript -Path $LogPath
Expand All @@ -435,6 +337,8 @@ try {
return
}

$resolvedSetup = Resolve-LibVirtualHidDriverSetupPath -Path $SetupPath

$registryRootDevices = @(Get-LibVirtualHidRegistryRootDevice -TargetHardwareId $HardwareId)
foreach ($device in ($registryRootDevices | Where-Object { $_.HasCorruptHardwareId -or $_.HasLegacyHidClass })) {
Remove-DeviceInstance -InstanceId $device.InstanceId
Expand All @@ -446,7 +350,10 @@ try {
foreach ($rootDevice in $rootDevices) {
Set-RootDeviceVhfMode -InstanceId $rootDevice
}
Update-RootDeviceDriverWithSetupApi -Path $resolvedInf -TargetHardwareId $HardwareId
Update-RootDeviceDriverWithSetupApi `
-Path $resolvedInf `
-TargetHardwareId $HardwareId `
-SetupHelperPath $resolvedSetup
foreach ($rootDevice in $rootDevices) {
Restart-RootDevice -InstanceId $rootDevice
}
Expand All @@ -455,14 +362,20 @@ try {
}

if ($PSCmdlet.ShouldProcess($HardwareId, "Create libvirtualhid development device with SetupAPI")) {
Install-RootDeviceWithSetupApi -Path $resolvedInf -TargetHardwareId $HardwareId
Install-RootDeviceWithSetupApi `
-Path $resolvedInf `
-TargetHardwareId $HardwareId `
-SetupHelperPath $resolvedSetup
}

$rootDevices = @(Get-LibVirtualHidRootDeviceInstanceId -TargetHardwareId $HardwareId)
foreach ($rootDevice in $rootDevices) {
Set-RootDeviceVhfMode -InstanceId $rootDevice
}
Update-RootDeviceDriverWithSetupApi -Path $resolvedInf -TargetHardwareId $HardwareId
Update-RootDeviceDriverWithSetupApi `
-Path $resolvedInf `
-TargetHardwareId $HardwareId `
-SetupHelperPath $resolvedSetup
foreach ($rootDevice in $rootDevices) {
Restart-RootDevice -InstanceId $rootDevice
}
Expand Down
22 changes: 22 additions & 0 deletions src/platform/windows/driver/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,25 @@ endif()
message(STATUS "stampinf executable: ${LIBVIRTUALHID_STAMPINF}")
message(STATUS "inf2cat executable: ${LIBVIRTUALHID_INF2CAT}")

add_executable(libvirtualhid_driver_setup
"${CMAKE_CURRENT_SOURCE_DIR}/libvirtualhid_driver_setup.cpp")
target_compile_features(libvirtualhid_driver_setup PRIVATE cxx_std_23)
target_compile_definitions(libvirtualhid_driver_setup
PRIVATE
NOMINMAX
WIN32_LEAN_AND_MEAN
_WIN32_WINNT=0x0A00)
target_compile_options(libvirtualhid_driver_setup PRIVATE /W4)
if(LIBVIRTUALHID_WARNINGS_AS_ERRORS)
target_compile_options(libvirtualhid_driver_setup PRIVATE /WX)
endif()
target_link_libraries(libvirtualhid_driver_setup
PRIVATE
newdev
setupapi)
set_property(TARGET libvirtualhid_driver_setup PROPERTY
MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")

configure_file(
"${CMAKE_CURRENT_SOURCE_DIR}/libvirtualhid.inf.in"
"${CMAKE_CURRENT_BINARY_DIR}/libvirtualhid.inf"
Expand Down Expand Up @@ -256,6 +275,9 @@ add_custom_target(libvirtualhid_windows_catalog
VERBATIM)

if(LIBVIRTUALHID_INSTALL)
install(TARGETS libvirtualhid_driver_setup
RUNTIME DESTINATION "tools/windows"
COMPONENT driver)
install(TARGETS libvirtualhid_umdf
RUNTIME DESTINATION "drivers/windows"
COMPONENT driver)
Expand Down
Loading
Loading