Files

390 lines
12 KiB
PowerShell

<#
.SYNOPSIS
Stages and installs the libvirtualhid Windows UMDF development driver.
#>
[CmdletBinding(SupportsShouldProcess)]
param(
[Parameter(Mandatory = $true)]
[string] $InfPath,
[string] $CertificatePath,
[string] $HardwareId = "ROOT\LIBVIRTUALHID",
[string] $BrokerPath,
[string] $SetupPath,
[string] $LogPath,
[switch] $StageOnly
)
$ErrorActionPreference = "Stop"
$script:LibVirtualHidTranscriptStarted = $false
$script:LibVirtualHidBrokerServiceName = "libvirtualhid_broker"
$script:LibVirtualHidBrokerServiceDisplayName = "libvirtualhid Broker"
. (Join-Path $PSScriptRoot "libvirtualhid-driver-common.ps1")
function Invoke-CheckedCommand {
param(
[Parameter(Mandatory = $true)]
[string] $FilePath,
[Parameter(Mandatory = $true)]
[string[]] $Arguments,
[int[]] $SuccessExitCodes = @(0)
)
& $FilePath @Arguments
if ($LASTEXITCODE -notin $SuccessExitCodes) {
throw "$FilePath exited with code $LASTEXITCODE"
}
}
function Resolve-LibVirtualHidBrokerPath {
param([string] $Path)
if ($Path) {
if (-not (Test-Path -LiteralPath $Path)) {
throw "The broker executable was not found at $Path"
}
return (Resolve-Path -LiteralPath $Path).Path
}
$packagedPath = Join-Path $PSScriptRoot "..\..\services\windows\libvirtualhid_broker.exe"
if (Test-Path -LiteralPath $packagedPath) {
return (Resolve-Path -LiteralPath $packagedPath).Path
}
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)]
[string] $Path
)
if ($Path.Contains('"')) {
throw "The broker executable path must not contain quotation marks: $Path"
}
return "`"$Path`""
}
function Get-LibVirtualHidScBinaryPathArgument {
param(
[Parameter(Mandatory = $true)]
[string] $Path
)
# Windows PowerShell 5.1 needs triple quotes in a native-command argument to pass one literal quote
# through to sc.exe while keeping a path containing spaces in a single argv element.
return (Get-LibVirtualHidQuotedServiceBinaryPath -Path $Path).Replace('"', '"""')
}
function Assert-LibVirtualHidBrokerServiceImagePath {
param(
[Parameter(Mandatory = $true)]
[string] $Name,
[Parameter(Mandatory = $true)]
[string] $Path
)
$registryPath = "HKLM:\SYSTEM\CurrentControlSet\Services\$Name"
$imagePath = (Get-ItemProperty -LiteralPath $registryPath -Name "ImagePath").ImagePath
$expectedPath = Get-LibVirtualHidQuotedServiceBinaryPath -Path $Path
if ($imagePath -cne $expectedPath) {
throw "The $Name service ImagePath is not safely quoted. Expected $expectedPath but found $imagePath."
}
}
function Clear-LibVirtualHidBrokerServiceEnvironment {
[CmdletBinding(SupportsShouldProcess)]
param([string] $Name)
$registryPath = "HKLM:\SYSTEM\CurrentControlSet\Services\$Name"
if (-not (Test-Path -LiteralPath $registryPath)) {
return
}
if ($PSCmdlet.ShouldProcess($Name, "Clear legacy libvirtualhid broker service environment")) {
Remove-ItemProperty -LiteralPath $registryPath -Name "Environment" -ErrorAction SilentlyContinue
}
}
function Stop-LibVirtualHidBrokerService {
[CmdletBinding(SupportsShouldProcess)]
param([string] $Name)
$service = Get-Service -Name $Name -ErrorAction SilentlyContinue
if (-not $service -or $service.Status -eq "Stopped") {
return
}
if ($PSCmdlet.ShouldProcess($Name, "Stop libvirtualhid broker service")) {
Stop-Service -Name $Name -Force -ErrorAction Stop
$service.WaitForStatus("Stopped", [TimeSpan]::FromSeconds(15))
}
}
function Install-LibVirtualHidBrokerService {
[CmdletBinding(SupportsShouldProcess)]
param([string] $Path)
$resolvedBroker = Resolve-LibVirtualHidBrokerPath -Path $Path
if (-not $resolvedBroker) {
Write-Verbose "No libvirtualhid broker executable was found; skipping broker service registration."
return
}
$serviceBinaryPath = Get-LibVirtualHidScBinaryPathArgument -Path $resolvedBroker
$serviceRegistered = $false
$service = Get-Service -Name $script:LibVirtualHidBrokerServiceName -ErrorAction SilentlyContinue
if ($service) {
Stop-LibVirtualHidBrokerService -Name $script:LibVirtualHidBrokerServiceName
if ($PSCmdlet.ShouldProcess($script:LibVirtualHidBrokerServiceName, "Update libvirtualhid broker service")) {
Invoke-CheckedCommand -FilePath "sc.exe" -Arguments @(
"config",
$script:LibVirtualHidBrokerServiceName,
"binPath=",
$serviceBinaryPath,
"start=",
"auto",
"DisplayName=",
$script:LibVirtualHidBrokerServiceDisplayName
)
$serviceRegistered = $true
}
} else {
if ($PSCmdlet.ShouldProcess($script:LibVirtualHidBrokerServiceName, "Install libvirtualhid broker service")) {
$quotedServicePath = Get-LibVirtualHidQuotedServiceBinaryPath -Path $resolvedBroker
New-Service `
-Name $script:LibVirtualHidBrokerServiceName `
-BinaryPathName $quotedServicePath `
-DisplayName $script:LibVirtualHidBrokerServiceDisplayName `
-StartupType Automatic | Out-Null
$serviceRegistered = $true
}
}
if ($serviceRegistered) {
Assert-LibVirtualHidBrokerServiceImagePath -Name $script:LibVirtualHidBrokerServiceName -Path $resolvedBroker
}
Clear-LibVirtualHidBrokerServiceEnvironment -Name $script:LibVirtualHidBrokerServiceName
if ($PSCmdlet.ShouldProcess($script:LibVirtualHidBrokerServiceName, "Enable libvirtualhid broker service SID")) {
Invoke-CheckedCommand -FilePath "sc.exe" -Arguments @(
"sidtype",
$script:LibVirtualHidBrokerServiceName,
"unrestricted"
)
}
if ($PSCmdlet.ShouldProcess($script:LibVirtualHidBrokerServiceName, "Set libvirtualhid broker service description")) {
Invoke-CheckedCommand -FilePath "sc.exe" -Arguments @(
"description",
$script:LibVirtualHidBrokerServiceName,
"Authorizes libvirtualhid virtual gamepad creation and license state."
)
}
if ($PSCmdlet.ShouldProcess($script:LibVirtualHidBrokerServiceName, "Start libvirtualhid broker service")) {
Start-Service -Name $script:LibVirtualHidBrokerServiceName
}
}
function Import-DriverCertificate {
[CmdletBinding(SupportsShouldProcess)]
param([string] $Path)
if (-not $Path -or -not (Test-Path -LiteralPath $Path)) {
return
}
$resolvedCertificate = (Resolve-Path -LiteralPath $Path).Path
foreach ($store in @("Cert:\LocalMachine\Root", "Cert:\LocalMachine\TrustedPublisher")) {
if ($PSCmdlet.ShouldProcess($store, "Trust libvirtualhid driver certificate $resolvedCertificate")) {
Import-Certificate -FilePath $resolvedCertificate -CertStoreLocation $store | Out-Null
}
}
}
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 Restart-RootDevice {
[CmdletBinding(SupportsShouldProcess)]
param([string] $InstanceId)
if (-not $InstanceId) {
return
}
if (-not $PSCmdlet.ShouldProcess($InstanceId, "Restart libvirtualhid development device")) {
return
}
$output = @(pnputil.exe /restart-device $InstanceId 2>&1)
$exitCode = $LASTEXITCODE
foreach ($line in $output) {
Write-Information $line -InformationAction Continue
}
if ($exitCode -ne 0) {
throw "pnputil.exe /restart-device $InstanceId exited with code $exitCode"
}
if ($output -match "reboot is needed") {
Write-Warning "Windows reported that a reboot is required to reload the libvirtualhid UMDF driver."
}
}
function Update-RootDeviceDriverWithSetupApi {
[CmdletBinding(SupportsShouldProcess)]
param(
[Parameter(Mandatory = $true)]
[string] $Path,
[Parameter(Mandatory = $true)]
[string] $TargetHardwareId,
[Parameter(Mandatory = $true)]
[string] $SetupHelperPath
)
if ($PSCmdlet.ShouldProcess($TargetHardwareId, "Update libvirtualhid development device 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."
}
}
}
function Install-RootDeviceWithSetupApi {
param(
[Parameter(Mandatory = $true)]
[string] $Path,
[Parameter(Mandatory = $true)]
[string] $TargetHardwareId,
[Parameter(Mandatory = $true)]
[string] $SetupHelperPath
)
Invoke-CheckedCommand -FilePath $SetupHelperPath -Arguments @("install", $Path, $TargetHardwareId)
}
if ($MyInvocation.InvocationName -eq ".") {
return
}
Start-LibVirtualHidTranscript -Path $LogPath
try {
$resolvedInf = (Resolve-Path -LiteralPath $InfPath).Path
Import-DriverCertificate -Path $CertificatePath
if ($PSCmdlet.ShouldProcess($resolvedInf, "Stage libvirtualhid driver package")) {
Invoke-CheckedCommand -FilePath "pnputil.exe" -Arguments @("/add-driver", $resolvedInf) -SuccessExitCodes @(0, 5)
}
if ($StageOnly) {
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
}
$rootDevices = @(Get-LibVirtualHidRootDeviceInstanceId -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 `
-SetupHelperPath $resolvedSetup
foreach ($rootDevice in $rootDevices) {
Restart-RootDevice -InstanceId $rootDevice
}
Install-LibVirtualHidBrokerService -Path $BrokerPath
return
}
if ($PSCmdlet.ShouldProcess($HardwareId, "Create libvirtualhid development device with SetupAPI")) {
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 `
-SetupHelperPath $resolvedSetup
foreach ($rootDevice in $rootDevices) {
Restart-RootDevice -InstanceId $rootDevice
}
Install-LibVirtualHidBrokerService -Path $BrokerPath
} finally {
Stop-LibVirtualHidTranscript
}