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
77 changes: 63 additions & 14 deletions .github/workflows/ci-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -496,7 +496,7 @@ jobs:
--xml-pretty \
-o reports/coverage.xml

- name: Uninstall Windows driver installer
- name: Uninstall Windows driver installer with an open consumer handle
if: >-
always() &&
runner.os == 'Windows'
Expand All @@ -507,20 +507,69 @@ jobs:
-ErrorAction SilentlyContinue |
Select-Object -First 1
if ($installer) {
$logPath = Join-Path $env:RUNNER_TEMP "libvirtualhid-driver-uninstall.log"
$process = Start-Process `
-FilePath msiexec.exe `
-ArgumentList @("/x", $installer.FullName, "/qn", "/norestart", "/L*v", $logPath) `
-PassThru `
-NoNewWindow
if (!$process.WaitForExit([int] [TimeSpan]::FromMinutes(5).TotalMilliseconds)) {
Get-Content -LiteralPath $logPath -Tail 200 -ErrorAction SilentlyContinue
Stop-Process -Id $process.Id -Force -ErrorAction SilentlyContinue
throw "Windows driver installer uninstall timed out after 5 minutes."
$nativeMethods = @'
using System;
using System.Runtime.InteropServices;

public static class LibVirtualHidUninstallTest
{
public const uint GenericReadWriteAccess = 0xC0000000;

[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern IntPtr CreateFile(
string fileName,
uint desiredAccess,
uint shareMode,
IntPtr securityAttributes,
uint creationDisposition,
uint flagsAndAttributes,
IntPtr templateFile);

[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool CloseHandle(IntPtr handle);
}
'@
Add-Type -TypeDefinition $nativeMethods
$invalidHandle = [IntPtr]::new(-1)
$deviceHandle = [LibVirtualHidUninstallTest]::CreateFile(
"\\.\LibVirtualHid",
[LibVirtualHidUninstallTest]::GenericReadWriteAccess,
0x00000003,
[IntPtr]::Zero,
3,
0x40000080,
[IntPtr]::Zero
)
if ($deviceHandle -eq $invalidHandle) {
Write-Warning "Unable to hold the libvirtualhid control device; continuing cleanup."
} else {
Write-Host "Holding the libvirtualhid control device open during MSI uninstall."
}
if ($process.ExitCode -notin @(0, 3010)) {
Get-Content -LiteralPath $logPath -ErrorAction SilentlyContinue
throw "Windows driver installer uninstall exited with code $($process.ExitCode)."

$logPath = Join-Path $env:RUNNER_TEMP "libvirtualhid-driver-uninstall.log"
$driverLogPath = Join-Path $env:ProgramData "libvirtualhid\uninstall-driver.log"
try {
$process = Start-Process `
-FilePath msiexec.exe `
-ArgumentList @("/x", $installer.FullName, "/qn", "/norestart", "/L*v", $logPath) `
-PassThru `
-NoNewWindow
if (!$process.WaitForExit([int] [TimeSpan]::FromMinutes(5).TotalMilliseconds)) {
Get-Content -LiteralPath $logPath -Tail 200 -ErrorAction SilentlyContinue
Get-Content -LiteralPath $driverLogPath -Tail 200 -ErrorAction SilentlyContinue
Stop-Process -Id $process.Id -Force -ErrorAction SilentlyContinue
throw "Windows driver installer uninstall timed out after 5 minutes."
}
if ($process.ExitCode -notin @(0, 3010)) {
Get-Content -LiteralPath $logPath -ErrorAction SilentlyContinue
Get-Content -LiteralPath $driverLogPath -ErrorAction SilentlyContinue
throw "Windows driver installer uninstall exited with code $($process.ExitCode)."
}
} finally {
if ($deviceHandle -ne $invalidHandle) {
[void] [LibVirtualHidUninstallTest]::CloseHandle($deviceHandle)
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,13 @@
Impersonate="no" />
<CustomAction Id="CA_LibVirtualHidUninstallDriver"
Directory="INSTALL_ROOT"
ExeCommand="&quot;[WindowsFolder]System32\WindowsPowerShell\v1.0\powershell.exe&quot; -WindowStyle Hidden -NoProfile -ExecutionPolicy Bypass -File &quot;[INSTALL_ROOT]scripts\windows\uninstall-driver.ps1&quot; -Force -RemoveCertificateSubject &quot;CN=libvirtualhid CI Test Driver Signing&quot;"
ExeCommand="&quot;[WindowsFolder]System32\WindowsPowerShell\v1.0\powershell.exe&quot; -WindowStyle Hidden -NoProfile -ExecutionPolicy Bypass -File &quot;[INSTALL_ROOT]scripts\windows\uninstall-driver.ps1&quot; -Force -RemoveCertificateSubject &quot;CN=libvirtualhid CI Test Driver Signing&quot; -LogPath &quot;[CommonAppDataFolder]libvirtualhid\uninstall-driver.log&quot;"
Execute="deferred"
Return="check"
Impersonate="no" />
<CustomAction Id="CA_LibVirtualHidUninstallDriverSilent"
Directory="INSTALL_ROOT"
ExeCommand="&quot;[WindowsFolder]System32\WindowsPowerShell\v1.0\powershell.exe&quot; -WindowStyle Hidden -NoProfile -ExecutionPolicy Bypass -File &quot;[INSTALL_ROOT]scripts\windows\uninstall-driver.ps1&quot; -Force -RemoveCertificateSubject &quot;CN=libvirtualhid CI Test Driver Signing&quot;"
ExeCommand="&quot;[WindowsFolder]System32\WindowsPowerShell\v1.0\powershell.exe&quot; -WindowStyle Hidden -NoProfile -ExecutionPolicy Bypass -File &quot;[INSTALL_ROOT]scripts\windows\uninstall-driver.ps1&quot; -Force -RemoveCertificateSubject &quot;CN=libvirtualhid CI Test Driver Signing&quot; -LogPath &quot;[CommonAppDataFolder]libvirtualhid\uninstall-driver.log&quot;"
Execute="deferred"
Return="check"
Impersonate="no" />
Expand Down
15 changes: 10 additions & 5 deletions docs/windows-driver.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,8 @@ powershell -ExecutionPolicy Bypass -File .\scripts\windows\test-browser-gamepad.
-GamepadAdapterPath .\cmake-build-windows-driver\examples\Release\gamepad_adapter.exe `
-GamepadProfile xseries
powershell -ExecutionPolicy Bypass -File .\scripts\windows\uninstall-driver.ps1 `
-Force -RemoveCertificateSubject "CN=libvirtualhid CI Test Driver Signing"
-Force -RemoveCertificateSubject "CN=libvirtualhid CI Test Driver Signing" `
-LogPath .\cmake-build-windows-driver\uninstall-driver.log
```

The WiX installer also places validation files under the default install root,
Expand All @@ -174,10 +175,14 @@ The install helper also clears any legacy broker service `Environment` value so
licensing configuration cannot be overridden on the user's machine. The
uninstall helper stops and deletes that service before removing the driver
package. It discovers staged OEM INF names through language-neutral DISM and
CIM objects instead of parsing localized `pnputil` labels. Uninstall fails if a
command fails or if the broker service, root device, or staged driver package
is still present after cleanup, so the MSI cannot silently report a complete
removal while driver state remains.
CIM objects instead of parsing localized `pnputil` labels. If an application
has an outstanding device handle, the helper records the initial device-removal
failure and continues with the forced driver-package uninstall, which can finish
or schedule the removal. Uninstall still fails if package removal fails or if
the broker service, root device, or staged driver package remains after cleanup,
so the MSI cannot silently report a complete removal while driver state remains.
MSI uninstall diagnostics are appended to
`C:\ProgramData\libvirtualhid\uninstall-driver.log`.

The installed-driver test fails if the root device is not started, if
`\\.\LibVirtualHid` cannot be opened, or if a held `gamepad_adapter` instance
Expand Down
39 changes: 0 additions & 39 deletions scripts/windows/install-driver.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -24,45 +24,6 @@ $script:LibVirtualHidBrokerServiceName = "libvirtualhid_broker"
$script:LibVirtualHidBrokerServiceDisplayName = "libvirtualhid Broker"
. (Join-Path $PSScriptRoot "libvirtualhid-driver-common.ps1")

function Start-LibVirtualHidTranscript {
[CmdletBinding(SupportsShouldProcess)]
param([string] $Path)

if (-not $Path) {
return
}

try {
$logDirectory = Split-Path -Parent $Path
if ($logDirectory) {
New-Item -ItemType Directory -Path $logDirectory -Force | Out-Null
}
if ($PSCmdlet.ShouldProcess($Path, "Start libvirtualhid install transcript")) {
Start-Transcript -Path $Path -Append | Out-Null
$script:LibVirtualHidTranscriptStarted = $true
}
} catch {
Write-Warning "Unable to start libvirtualhid install transcript: $($_.Exception.Message)"
}
}

function Stop-LibVirtualHidTranscript {
[CmdletBinding(SupportsShouldProcess)]
param()

if (-not $script:LibVirtualHidTranscriptStarted) {
return
}

try {
if ($PSCmdlet.ShouldProcess("libvirtualhid install transcript", "Stop transcript")) {
Stop-Transcript | Out-Null
}
} catch {
Write-Warning "Unable to stop libvirtualhid install transcript: $($_.Exception.Message)"
}
}

function Invoke-CheckedCommand {
param(
[Parameter(Mandatory = $true)]
Expand Down
39 changes: 39 additions & 0 deletions scripts/windows/libvirtualhid-driver-common.ps1
Original file line number Diff line number Diff line change
@@ -1,3 +1,42 @@
function Start-LibVirtualHidTranscript {
[CmdletBinding(SupportsShouldProcess)]
param([string] $Path)

if (-not $Path) {
return
}

try {
$logDirectory = Split-Path -Parent $Path
if ($logDirectory) {
New-Item -ItemType Directory -Path $logDirectory -Force | Out-Null
}
if ($PSCmdlet.ShouldProcess($Path, "Start libvirtualhid driver transcript")) {
Start-Transcript -Path $Path -Append | Out-Null
$script:LibVirtualHidTranscriptStarted = $true
}
} catch {
Write-Warning "Unable to start libvirtualhid driver transcript: $($_.Exception.Message)"
}
}

function Stop-LibVirtualHidTranscript {
[CmdletBinding(SupportsShouldProcess)]
param()

if (-not $script:LibVirtualHidTranscriptStarted) {
return
}

try {
if ($PSCmdlet.ShouldProcess("libvirtualhid driver transcript", "Stop transcript")) {
Stop-Transcript | Out-Null
}
} catch {
Write-Warning "Unable to stop libvirtualhid driver transcript: $($_.Exception.Message)"
}
}

function Get-LibVirtualHidRootDeviceInstanceId {
param([string] $TargetHardwareId)

Expand Down
116 changes: 74 additions & 42 deletions scripts/windows/uninstall-driver.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,13 @@ param(

[string] $RemoveCertificateSubject,

[string] $LogPath,

[switch] $Force
)

$ErrorActionPreference = "Stop"
$script:LibVirtualHidTranscriptStarted = $false
. (Join-Path $PSScriptRoot "libvirtualhid-driver-common.ps1")

function Invoke-CheckedCommand {
Expand Down Expand Up @@ -125,6 +128,31 @@ function Assert-PublishedName {
}
}

function Remove-LibVirtualHidDeviceInstance {
[CmdletBinding(SupportsShouldProcess)]
param(
[Parameter(Mandatory = $true)]
[string] $InstanceId
)

if (-not $PSCmdlet.ShouldProcess($InstanceId, "Remove libvirtualhid development device with pnputil")) {
return
}

$output = @(& pnputil.exe /remove-device $InstanceId 2>&1)
$exitCode = $LASTEXITCODE
foreach ($line in $output) {
Write-Information ([string] $line) -InformationAction Continue
}

if ($exitCode -ne 0) {
Write-Warning (
"pnputil.exe /remove-device $InstanceId exited with code $exitCode. " +
"Continuing with forced driver-package removal; final state verification will still fail if cleanup is incomplete."
)
}
}

function Assert-LibVirtualHidRemoved {
param(
[string] $TargetOriginalName,
Expand Down Expand Up @@ -173,56 +201,60 @@ function Remove-DriverCertificate {
}
}

$publishedNames = @()
if ($PublishedName) {
Assert-PublishedName -Name $PublishedName
$publishedNames += $PublishedName
} else {
try {
$publishedNames = @(
Find-PublishedName `
-TargetOriginalName $OriginalName `
-TargetHardwareId $HardwareId
)
} catch {
throw "Unable to discover the staged libvirtualhid driver package through Windows APIs: $($_.Exception.Message)"
Start-LibVirtualHidTranscript -Path $LogPath

try {
$publishedNames = @()
if ($PublishedName) {
Assert-PublishedName -Name $PublishedName
$publishedNames += $PublishedName
} else {
try {
$publishedNames = @(
Find-PublishedName `
-TargetOriginalName $OriginalName `
-TargetHardwareId $HardwareId
)
} catch {
throw "Unable to discover the staged libvirtualhid driver package through Windows APIs: $($_.Exception.Message)"
}
}
}

Remove-LibVirtualHidBrokerService -Name $BrokerServiceName
Remove-LibVirtualHidBrokerService -Name $BrokerServiceName

$deviceInstanceIds = @(
Get-LibVirtualHidRootDeviceInstanceId -TargetHardwareId $HardwareId
Get-LibVirtualHidRegistryRootDevice -TargetHardwareId $HardwareId |
Select-Object -ExpandProperty InstanceId
) | Select-Object -Unique
$deviceInstanceIds = @(
Get-LibVirtualHidRootDeviceInstanceId -TargetHardwareId $HardwareId
Get-LibVirtualHidRegistryRootDevice -TargetHardwareId $HardwareId |
Select-Object -ExpandProperty InstanceId
) | Select-Object -Unique

foreach ($instanceId in $deviceInstanceIds) {
if ($PSCmdlet.ShouldProcess($instanceId, "Remove libvirtualhid development device with pnputil")) {
Invoke-CheckedCommand -FilePath "pnputil.exe" -Arguments @("/remove-device", $instanceId)
foreach ($instanceId in $deviceInstanceIds) {
Remove-LibVirtualHidDeviceInstance -InstanceId $instanceId
}
}

if ($publishedNames.Count -eq 0) {
Write-Warning "No staged libvirtualhid driver package matching $OriginalName was found."
} else {
foreach ($driverPackage in $publishedNames) {
Assert-PublishedName -Name $driverPackage
$deleteArgs = @("/delete-driver", $driverPackage, "/uninstall")
if ($Force) {
$deleteArgs += "/force"
}
if ($publishedNames.Count -eq 0) {
Write-Warning "No staged libvirtualhid driver package matching $OriginalName was found."
} else {
foreach ($driverPackage in $publishedNames) {
Assert-PublishedName -Name $driverPackage
$deleteArgs = @("/delete-driver", $driverPackage, "/uninstall")
if ($Force) {
$deleteArgs += "/force"
}

if ($PSCmdlet.ShouldProcess($driverPackage, "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
}
}
}
}

if (-not $WhatIfPreference) {
Assert-LibVirtualHidRemoved `
-TargetOriginalName $OriginalName `
-TargetHardwareId $HardwareId `
-ServiceName $BrokerServiceName
if (-not $WhatIfPreference) {
Assert-LibVirtualHidRemoved `
-TargetOriginalName $OriginalName `
-TargetHardwareId $HardwareId `
-ServiceName $BrokerServiceName
}
Remove-DriverCertificate -Subject $RemoveCertificateSubject
} finally {
Stop-LibVirtualHidTranscript
}
Remove-DriverCertificate -Subject $RemoveCertificateSubject
Loading