From 73ab7fff7b0128f9aefb81bd1051d6a5a61dca78 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Tue, 8 Sep 2026 11:47:05 -0400 Subject: [PATCH] fix(installer): replace Add-Type with native setup helper --- .github/workflows/ci-driver.yml | 18 +- cmake/packaging/windows.cmake | 5 + .../libvirtualhid-driver-installer-patch.xml | 4 +- docs/windows-driver.md | 13 +- scripts/windows/install-driver.ps1 | 187 +++++------------- src/platform/windows/driver/CMakeLists.txt | 22 +++ .../driver/libvirtualhid_driver_setup.cpp | 184 +++++++++++++++++ 7 files changed, 288 insertions(+), 145 deletions(-) create mode 100644 src/platform/windows/driver/libvirtualhid_driver_setup.cpp diff --git a/.github/workflows/ci-driver.yml b/.github/workflows/ci-driver.yml index 7eb1649..30ffc96 100644 --- a/.github/workflows/ci-driver.yml +++ b/.github/workflows/ci-driver.yml @@ -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' && diff --git a/cmake/packaging/windows.cmake b/cmake/packaging/windows.cmake index ee25ea3..bae08b1 100644 --- a/cmake/packaging/windows.cmake +++ b/cmake/packaging/windows.cmake @@ -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 " diff --git a/cmake/packaging/wix_resources/libvirtualhid-driver-installer-patch.xml b/cmake/packaging/wix_resources/libvirtualhid-driver-installer-patch.xml index f283dfe..7ae86b6 100644 --- a/cmake/packaging/wix_resources/libvirtualhid-driver-installer-patch.xml +++ b/cmake/packaging/wix_resources/libvirtualhid-driver-installer-patch.xml @@ -2,13 +2,13 @@ diff --git a/docs/windows-driver.md b/docs/windows-driver.md index 1808a3f..1f690af 100644 --- a/docs/windows-driver.md +++ b/docs/windows-driver.md @@ -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 ``` @@ -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 ` @@ -201,6 +202,7 @@ 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 @@ -208,11 +210,12 @@ 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. diff --git a/scripts/windows/install-driver.ps1 b/scripts/windows/install-driver.ps1 index ed0eb60..43ed7af 100644 --- a/scripts/windows/install-driver.ps1 +++ b/scripts/windows/install-driver.ps1 @@ -13,6 +13,8 @@ param( [string] $BrokerPath, + [string] $SetupPath, + [string] $LogPath, [switch] $StageOnly @@ -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)] @@ -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) @@ -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." + } } } @@ -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 @@ -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 @@ -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 } @@ -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 } diff --git a/src/platform/windows/driver/CMakeLists.txt b/src/platform/windows/driver/CMakeLists.txt index a9f7cbb..51a50bf 100644 --- a/src/platform/windows/driver/CMakeLists.txt +++ b/src/platform/windows/driver/CMakeLists.txt @@ -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$<$:Debug>") + configure_file( "${CMAKE_CURRENT_SOURCE_DIR}/libvirtualhid.inf.in" "${CMAKE_CURRENT_BINARY_DIR}/libvirtualhid.inf" @@ -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) diff --git a/src/platform/windows/driver/libvirtualhid_driver_setup.cpp b/src/platform/windows/driver/libvirtualhid_driver_setup.cpp new file mode 100644 index 0000000..b729621 --- /dev/null +++ b/src/platform/windows/driver/libvirtualhid_driver_setup.cpp @@ -0,0 +1,184 @@ +// SPDX-FileCopyrightText: 2026 LIZARDBYTE LLC +// SPDX-License-Identifier: LicenseRef-LizardByte-SAL-1.0 + +/** + * @file src/platform/windows/driver/libvirtualhid_driver_setup.cpp + * @brief Native SetupAPI helper for installing the libvirtualhid root device. + */ + +#ifndef NOMINMAX + #define NOMINMAX +#endif +#ifndef WIN32_LEAN_AND_MEAN + #define WIN32_LEAN_AND_MEAN +#endif + +// platform includes +// clang-format off +#include +#include +#include +// clang-format on + +// standard includes +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + constexpr std::wstring_view root_prefix = L"ROOT\\"; + + class device_info_set { + public: + explicit device_info_set(HDEVINFO value): + value_ {value} {} + + ~device_info_set() { + if (value_ != INVALID_HANDLE_VALUE) { + static_cast(SetupDiDestroyDeviceInfoList(value_)); + } + } + + device_info_set(const device_info_set &) = delete; + device_info_set &operator=(const device_info_set &) = delete; + device_info_set(device_info_set &&) = delete; + device_info_set &operator=(device_info_set &&) = delete; + + [[nodiscard]] HDEVINFO get() const { + return value_; + } + + private: + HDEVINFO value_; + }; + + [[nodiscard]] std::wstring windows_error_message(DWORD error) { + std::array buffer {}; + const auto length = FormatMessageW( + FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + nullptr, + error, + 0, + buffer.data(), + static_cast(buffer.size()), + nullptr + ); + + std::wstring message; + if (length != 0) { + message.assign(buffer.data(), length); + while (!message.empty() && (message.back() == L'\r' || message.back() == L'\n')) { + message.pop_back(); + } + } + return message; + } + + int report_last_error(std::wstring_view action) { + auto error = GetLastError(); + if (error == ERROR_SUCCESS) { + error = ERROR_GEN_FAILURE; + } + + std::wcerr << action << L" failed with Win32 error " << error; + if (const auto message = windows_error_message(error); !message.empty()) { + std::wcerr << L": " << message; + } + std::wcerr << L'\n'; + return EXIT_FAILURE; + } + + [[nodiscard]] std::optional root_device_name(std::wstring_view hardware_id) { + if (hardware_id.size() <= root_prefix.size() || CompareStringOrdinal(hardware_id.data(), static_cast(root_prefix.size()), root_prefix.data(), static_cast(root_prefix.size()), TRUE) != CSTR_EQUAL) { + return std::nullopt; + } + + const auto name = hardware_id.substr(root_prefix.size()); + if (name.contains(L'\\')) { + return std::nullopt; + } + return std::wstring {name}; + } + + int install_root_device(const std::wstring &inf_path, const std::wstring &hardware_id) { + const auto device_name = root_device_name(hardware_id); + if (!device_name) { + std::wcerr << L"Hardware ID must be a root-enumerated device ID without an instance suffix: " << hardware_id << L'\n'; + return EXIT_FAILURE; + } + + GUID class_guid {}; + if (std::array class_name {}; SetupDiGetINFClassW(inf_path.c_str(), &class_guid, class_name.data(), static_cast(class_name.size()), nullptr) == FALSE) { + return report_last_error(L"SetupDiGetINFClass"); + } + + device_info_set devices {SetupDiCreateDeviceInfoList(&class_guid, nullptr)}; + if (devices.get() == INVALID_HANDLE_VALUE) { + return report_last_error(L"SetupDiCreateDeviceInfoList"); + } + + SP_DEVINFO_DATA device_info {}; + device_info.cbSize = sizeof(device_info); + if (SetupDiCreateDeviceInfoW(devices.get(), device_name->c_str(), &class_guid, nullptr, nullptr, DICD_GENERATE_ID, &device_info) == FALSE) { + return report_last_error(L"SetupDiCreateDeviceInfo"); + } + + std::vector hardware_ids {hardware_id.begin(), hardware_id.end()}; + hardware_ids.push_back(L'\0'); + hardware_ids.push_back(L'\0'); + if (const auto hardware_id_bytes = std::as_bytes(std::span {hardware_ids}); SetupDiSetDeviceRegistryPropertyW(devices.get(), &device_info, SPDRP_HARDWAREID, std::bit_cast(hardware_id_bytes.data()), static_cast(hardware_id_bytes.size())) == FALSE) { + return report_last_error(L"SetupDiSetDeviceRegistryProperty"); + } + + if (SetupDiCallClassInstaller(DIF_REGISTERDEVICE, devices.get(), &device_info) == FALSE) { + return report_last_error(L"SetupDiCallClassInstaller"); + } + + return EXIT_SUCCESS; + } + + int update_root_device(const std::wstring &inf_path, const std::wstring &hardware_id) { + BOOL reboot_required = FALSE; + if (UpdateDriverForPlugAndPlayDevicesW(nullptr, hardware_id.c_str(), inf_path.c_str(), INSTALLFLAG_FORCE | INSTALLFLAG_NONINTERACTIVE, &reboot_required) == FALSE) { + return report_last_error(L"UpdateDriverForPlugAndPlayDevices"); + } + + if (reboot_required != FALSE) { + return ERROR_SUCCESS_REBOOT_REQUIRED; + } + return EXIT_SUCCESS; + } + + void print_usage() { + std::wcout << L"Usage: libvirtualhid_driver_setup.exe \n"; + } +} // namespace + +int wmain(int argc, wchar_t **argv) { + if (argc == 2 && std::wstring_view {argv[1]} == L"--help") { + print_usage(); + return EXIT_SUCCESS; + } + if (argc != 4) { + print_usage(); + return EXIT_FAILURE; + } + + const std::wstring_view action {argv[1]}; + if (action == L"install") { + return install_root_device(argv[2], argv[3]); + } + if (action == L"update") { + return update_root_device(argv[2], argv[3]); + } + + std::wcerr << L"Unknown action: " << action << L'\n'; + print_usage(); + return EXIT_FAILURE; +}