Files
usbipd-win/Usbipd/CommandHandlersInstaller.cs
2025-11-12 12:25:48 +01:00

320 lines
13 KiB
C#

// SPDX-FileCopyrightText: 2024 Frans van Dorsselaer
//
// SPDX-License-Identifier: GPL-3.0-only
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Usbipd.Automation;
using Windows.Win32;
using Windows.Win32.Devices.DeviceAndDriverInstallation;
using Windows.Win32.Foundation;
using Windows.Win32.System.Services;
namespace Usbipd;
sealed partial class CommandHandlers : ICommandHandlers
{
async Task<ExitCode> ICommandHandlers.InstallerDelay(IConsole console, CancellationToken cancellationToken)
{
ConsoleTools.ReportInfo(console, $"delay");
await Task.Delay(TimeSpan.FromSeconds(2), cancellationToken);
return ExitCode.Success;
}
Task<ExitCode> ICommandHandlers.InstallerInstallDriver(IConsole console, CancellationToken cancellationToken)
{
ConsoleTools.ReportInfo(console, "install_driver");
ConsoleTools.ReportInfo(console, $"Installing VBoxUSB version {DriverDetails.Instance.Version}");
// See: https://learn.microsoft.com/en-us/windows-hardware/drivers/install/preinstalling-driver-packages
if (!PInvoke.SetupCopyOEMInf(DriverDetails.Instance.DriverPath, null, OEM_SOURCE_MEDIA_TYPE.SPOST_PATH, 0, null))
{
console.ReportLastWin32Error(nameof(PInvoke.SetupCopyOEMInf));
return Task.FromResult(ExitCode.Failure);
}
return Task.FromResult(ExitCode.Success);
}
Task<ExitCode> ICommandHandlers.InstallerUninstallDriver(IConsole console, CancellationToken cancellationToken)
{
ConsoleTools.ReportInfo(console, $"uninstall_driver");
ConsoleTools.ReportInfo(console, $"Uninstalling VBoxUSB version {DriverDetails.Instance.Version}");
if (!PInvoke.DiUninstallDriver(HWND.Null, DriverDetails.Instance.DriverPath, 0, out var needReboot))
{
console.ReportLastWin32Error(nameof(PInvoke.DiUninstallDriver));
return Task.FromResult(ExitCode.Failure);
}
// This is a best effort, we ignore reboot.
console.ReportInfo($"Need reboot: {(bool)needReboot}");
return Task.FromResult(ExitCode.Success);
}
Task<ExitCode> ICommandHandlers.InstallerInstallMonitor(IConsole console, CancellationToken cancellationToken)
{
ConsoleTools.ReportInfo(console, "install_monitor");
// NOTE: This cannot be done from WiX, since WiX cannot create SERVICE_KERNEL_DRIVER; removal from WiX works though.
using var manager = PInvoke.OpenSCManager(string.Empty, PInvoke.SERVICES_ACTIVE_DATABASE, PInvoke.SC_MANAGER_ALL_ACCESS);
if (manager.IsInvalid)
{
console.ReportLastWin32Error(nameof(PInvoke.OpenSCManager));
return Task.FromResult(ExitCode.Failure);
}
using var service = PInvoke.CreateService(manager, "VBoxUSBMon", "VirtualBox USB Monitor Service",
(uint)GENERIC_ACCESS_RIGHTS.GENERIC_ALL, ENUM_SERVICE_TYPE.SERVICE_KERNEL_DRIVER, SERVICE_START_TYPE.SERVICE_DEMAND_START,
SERVICE_ERROR.SERVICE_ERROR_NORMAL,
Path.Combine(Path.GetDirectoryName(DriverDetails.Instance.DriverPath)!, "VBoxUSBMon.sys"),
null, null, null, null);
if (service.IsInvalid)
{
console.ReportLastWin32Error(nameof(PInvoke.CreateService));
return Task.FromResult(ExitCode.Failure);
}
return Task.FromResult(ExitCode.Success);
}
Task<ExitCode> ICommandHandlers.InstallerUninstallOldDrivers(IConsole console, CancellationToken cancellationToken)
{
ConsoleTools.ReportInfo(console, "uninstall_old_drivers");
var success = true;
var needReboot = false;
using var deviceInfoSet = PInvoke.SetupDiCreateDeviceInfoList(DriverDetails.Instance.ClassGuid, default);
if (deviceInfoSet.IsInvalid)
{
console.ReportLastWin32Error(nameof(PInvoke.SetupDiCreateDeviceInfoList));
return Task.FromResult(ExitCode.Failure);
}
SP_DEVINSTALL_PARAMS_W deviceInstallParams = new()
{
cbSize = (uint)Unsafe.SizeOf<SP_DEVINSTALL_PARAMS_W>(),
FlagsEx = SETUP_DI_DEVICE_INSTALL_FLAGS_EX.DI_FLAGSEX_ALLOWEXCLUDEDDRVS,
};
if (!PInvoke.SetupDiSetDeviceInstallParams(deviceInfoSet, null, deviceInstallParams))
{
console.ReportLastWin32Error(nameof(PInvoke.SetupDiSetDeviceInstallParams));
return Task.FromResult(ExitCode.Failure);
}
if (!PInvoke.SetupDiBuildDriverInfoList(deviceInfoSet, SETUP_DI_DRIVER_TYPE.SPDIT_CLASSDRIVER))
{
console.ReportLastWin32Error(nameof(PInvoke.SetupDiBuildDriverInfoList));
return Task.FromResult(ExitCode.Failure);
}
SP_DRVINFO_DATA_V2_W driverInfoData = new()
{
cbSize = (uint)Unsafe.SizeOf<SP_DRVINFO_DATA_V2_W>(),
};
for (var index = 0u; ; index++)
{
cancellationToken.ThrowIfCancellationRequested();
if (!PInvoke.SetupDiEnumDriverInfo(deviceInfoSet, null, SETUP_DI_DRIVER_TYPE.SPDIT_CLASSDRIVER, index, ref driverInfoData))
{
if ((WIN32_ERROR)Marshal.GetLastPInvokeError() == WIN32_ERROR.ERROR_NO_MORE_ITEMS)
{
break;
}
console.ReportLastWin32Error(nameof(PInvoke.SetupDiEnumDriverInfo));
return Task.FromResult(ExitCode.Failure);
}
if (!PInvoke.SetupDiGetDriverInfoDetail(deviceInfoSet, null, driverInfoData, null, out var requiredSize)
&& ((WIN32_ERROR)Marshal.GetLastPInvokeError() != WIN32_ERROR.ERROR_INSUFFICIENT_BUFFER))
{
console.ReportLastWin32Error(nameof(PInvoke.SetupDiGetDriverInfoDetail));
success = false;
continue;
}
if (requiredSize < Unsafe.SizeOf<SP_DRVINFO_DETAIL_DATA_W>())
{
// This can happen for drivers that do not have any ID at all (neither hardware ID nor compatible ID).
// For the AsRef below we need at least the base size.
requiredSize = (uint)Unsafe.SizeOf<SP_DRVINFO_DETAIL_DATA_W>();
}
var buffer = new byte[requiredSize];
ref var details = ref MemoryMarshal.AsRef<SP_DRVINFO_DETAIL_DATA_W>(buffer.AsSpan());
details.cbSize = (uint)Unsafe.SizeOf<SP_DRVINFO_DETAIL_DATA_W>();
if (!PInvoke.SetupDiGetDriverInfoDetail(deviceInfoSet, null, driverInfoData, buffer))
{
console.ReportLastWin32Error(nameof(PInvoke.SetupDiGetDriverInfoDetail));
success = false;
continue;
}
var hardwareId = (details.CompatIDsOffset > 0) ?
new string(details.HardwareID.AsSpan(checked((int)(details.CompatIDsOffset - 1)))) : string.Empty;
if (!VidPid.TryParseId(hardwareId, out var vidPid))
{
// This can happen for other drivers -> not a failure.
continue;
}
if (vidPid != DriverDetails.Instance.VidPid)
{
// This is not a VBoxUSB driver -> don't touch it.
continue;
}
var version = new Version(
(int)((driverInfoData.DriverVersion >> 48) & 0xffff),
(int)((driverInfoData.DriverVersion >> 32) & 0xffff),
(int)((driverInfoData.DriverVersion >> 16) & 0xffff),
(int)(driverInfoData.DriverVersion & 0xffff));
if (version == DriverDetails.Instance.Version)
{
// This is the current VBoxUSB driver -> don't remove it.
continue;
}
var infFileName = details.InfFileName.ToString();
ConsoleTools.ReportInfo(console, $"Uninstalling old VBoxUSB version {version}, {infFileName}");
if (!PInvoke.DiUninstallDriver(HWND.Null, infFileName, 0, out var tmpNeedReboot))
{
console.ReportLastWin32Error(nameof(PInvoke.DiUninstallDriver));
success = false;
continue;
}
if (tmpNeedReboot)
{
needReboot = true;
}
}
// This is a best effort, we ignore reboot.
console.ReportInfo($"Need reboot: {needReboot}");
return Task.FromResult(success ? ExitCode.Success : ExitCode.Failure);
}
Task<ExitCode> ICommandHandlers.InstallerUpdateDrivers(IConsole console, CancellationToken cancellationToken)
{
ConsoleTools.ReportInfo(console, "update_drivers");
var success = true;
var needReboot = false;
// NOTE: It is not actually possible to update devices that are not present.
// Instead, this will unforce the device the next time it is plugged in (and the device will get the default PnP driver).
foreach (var device in WindowsDevice.GetAll(DriverDetails.Instance.ClassGuid, false)
.Where(d => d.HasVBoxDriver && d.DriverVersion != DriverDetails.Instance.Version))
{
cancellationToken.ThrowIfCancellationRequested();
console.ReportInfo($"Updating device VBoxUSB driver to version {DriverDetails.Instance.Version}: {device.InstanceId}");
try
{
// NOTE: Setting any driver will enable the device.
// NOTE: Force will set a NULL driver first.
if (DriverTools.ForceVBoxDriver(device))
{
needReboot = true;
}
}
catch (Win32Exception e)
{
console.ReportError(e.Message);
success = false;
}
}
// This is a best effort, we ignore reboot.
console.ReportInfo($"Need reboot: {needReboot}");
return Task.FromResult(success ? ExitCode.Success : ExitCode.Failure);
}
/// <summary>
/// Uninstalls all stub devices. This assumes everything is detached at this point.
/// This is used by the uninstaller.
/// This is also used by the installer, to ensure that any future attaches use the newly installed driver.
/// </summary>
Task<ExitCode> ICommandHandlers.InstallerUninstallStubs(IConsole console, CancellationToken cancellationToken)
{
ConsoleTools.ReportInfo(console, "uninstall_stubs");
var success = true;
var needReboot = false;
foreach (var device in WindowsDevice.GetAll(DriverDetails.Instance.ClassGuid, false).Where(d => d.IsStub))
{
cancellationToken.ThrowIfCancellationRequested();
console.ReportInfo($"Uninstalling stub device: {device.InstanceId}");
try
{
if (DriverTools.UninstallStubDevice(device))
{
needReboot = true;
}
}
catch (Win32Exception e)
{
console.ReportError(e.Message);
success = false;
}
}
// This is a best effort, we ignore reboot.
console.ReportInfo($"Need reboot: {needReboot}");
return Task.FromResult(success ? ExitCode.Success : ExitCode.Failure);
}
/// <summary>
/// Disable all forced devices.
/// This is used during update. It ensures VBoxUSBMon can stop while still keeping the driver forced.
/// </summary>
Task<ExitCode> ICommandHandlers.InstallerDisableForced(IConsole console, CancellationToken cancellationToken)
{
ConsoleTools.ReportInfo(console, "disable_forced");
var success = true;
// NOTE: It is not possible (nor needed) to disable devices that are not present.
foreach (var device in WindowsDevice.GetAll(DriverDetails.Instance.ClassGuid, true).Where(d => d.HasVBoxDriver && !d.IsDisabled))
{
cancellationToken.ThrowIfCancellationRequested();
console.ReportInfo($"Disabling device: {device.InstanceId}");
var configRet = PInvoke.CM_Disable_DevNode(device.Node, PInvoke.CM_DISABLE_UI_NOT_OK);
if (configRet != CONFIGRET.CR_SUCCESS)
{
console.ReportConfigRet(nameof(PInvoke.CM_Disable_DevNode), configRet);
success = false;
}
}
return Task.FromResult(success ? ExitCode.Success : ExitCode.Failure);
}
/// <summary>
/// Enable all forced devices.
/// This is used during (or rather: after) update.
/// </summary>
Task<ExitCode> ICommandHandlers.InstallerEnableForced(IConsole console, CancellationToken cancellationToken)
{
ConsoleTools.ReportInfo(console, "enable_forced");
var success = true;
foreach (var device in WindowsDevice.GetAll(DriverDetails.Instance.ClassGuid, true).Where(d => d.HasVBoxDriver && d.IsDisabled))
{
cancellationToken.ThrowIfCancellationRequested();
console.ReportInfo($"Enabling device: {device.InstanceId}");
var configRet = PInvoke.CM_Enable_DevNode(device.Node, 0);
if (configRet != CONFIGRET.CR_SUCCESS)
{
console.ReportConfigRet(nameof(PInvoke.CM_Enable_DevNode), configRet);
success = false;
}
}
return Task.FromResult(success ? ExitCode.Success : ExitCode.Failure);
}
}