Installation¶
HIDMaestro is a C# SDK plus a UMDF2 driver. There is no end-user installer because there is no end-user app — consumers like PadForge reference the SDK and call HMContext.InstallDriver() themselves on first run.
This page covers the three reasons you'd be installing anything:
- You're building an app on top of the SDK → reference
HIDMaestro.Coreand callInstallDriver. - You're building HIDMaestro from source → clone, run
scripts\build_all.cmd. - You already have a consumer that uses HIDMaestro (PadForge etc.) → that consumer handles install for you, nothing to do here. See Driver Install and Signing for what's actually happening behind the scenes.
Requirements¶
| Requirement | Details |
|---|---|
| OS | Windows 10 or 11, x64. UMDF2 framework version 2.15. Validated on Windows 11 26100 / 26200 (IoT Enterprise LTSC 2024). Windows 10 19044 (LTSC) tested as a slow-hardware fixture for swap_regression. |
| Privilege | CreateController and InstallDriver need SeLoadDriverPrivilege — standard admin elevation. Read-only operations (HMDeviceExtractor.ListDevices, LoadProfilesFromDirectory, GetProfile) work as a standard user. |
| Runtime | .NET 10 (net10.0-windows10.0.26100.0 for the SDK). Targets [SupportedOSPlatform("windows10.0.26100.0")]. |
| Driver-store space | ~40 MB per installed driver package. The DriverStore caches every install regardless of whether you uninstall — budget for it on dev machines that iterate. |
No EV certificate. No bcdedit /set testsigning. No reboot. The driver self-signs at install time using a locally generated certificate trusted by the target machine; see Driver Install and Signing for the full sequence.
Adding the SDK to your app¶
The SDK is distributed as HIDMaestro.Core.dll. Until a NuGet feed is published, reference it from a release ZIP or build it from source.
<ItemGroup>
<Reference Include="HIDMaestro.Core">
<HintPath>..\HIDMaestro\sdk\HIDMaestro.Core\bin\Release\net10.0-windows10.0.26100.0\HIDMaestro.Core.dll</HintPath>
</Reference>
</ItemGroup>
The DLL is single-file: 228 profile JSONs, the UMDF2 driver binaries (HIDMaestro.dll, HMXInput.dll), the helper EXE (hmswd.exe), the signing toolchain (signtool.exe, Inf2Cat.exe, the Microsoft hardware-workflow catalog assemblies), and both INFs are all embedded as resources. You ship the SDK; the SDK ships everything else.
Target frameworks the consuming csproj needs:
<PropertyGroup>
<TargetFramework>net10.0-windows10.0.26100.0</TargetFramework>
<Platforms>x64</Platforms>
</PropertyGroup>
x86 / Any CPU are not supported — the driver is x64-only and the SDK's signing toolchain calls 64-bit native binaries.
Three lines to a live virtual controller¶
using var ctx = new HMContext();
ctx.LoadDefaultProfiles();
ctx.InstallDriver();
using var ctrl = ctx.CreateController(ctx.GetProfile("xbox-360-wired")!);
ctrl.SubmitState(new HMGamepadState
{
Axes = HMGamepadStateHelpers.StandardAxes(ctrl.Profile, leftStickX: 1.0f),
});
That's the whole thing. InstallDriver is idempotent — if the package is already in the DriverStore at the same version it returns immediately. The controller is removed when using falls out of scope. joy.cpl shows "Controller (XBOX 360 For Windows)" while the process is alive.
For a richer walkthrough including output capture and PID FFB, see Quickstart.
Building from source¶
Prerequisites¶
| Tool | Version | Purpose |
|---|---|---|
| Visual Studio 2022+ | with the "Desktop development with C++" workload | cl.exe and link.exe for driver.c, companion.c, hmswd.c |
| Windows SDK / WDK | 10.0.26100.0 | hidport.h, wdf.h, UMDF 2.15 libs |
| .NET 10 SDK | latest | dotnet build for the C# SDK and test app |
One-shot build¶
build_all.cmd runs four steps in order:
scripts\build.cmd— compilesdriver.c→build\HIDMaestro.dll, buildshmswd.exe, stamps both INFs with a fresh1.x.y.HHmmDriverVer.scripts\build_companion.cmd— compilescompanion.c→build\HMXInput.dll.dotnet build sdk\HIDMaestro.Core(phase 1) — copies the freshly built native binaries intoResources/.dotnet build sdk\HIDMaestro.Core(phase 2) — rebuilds with the populatedResources/so the embedded payload is current.
Two phases exist because MSBuild evaluates the embedded-resource glob before the PackResources pre-build target runs. On a fresh clone with empty build\, a one-phase build produces an SDK assembly with no driver inside. The two-phase variant is idempotent — re-run any time you touch driver/ or sdk/.
After this completes:
:: Smallest possible SDK consumer (5 seconds)
dotnet run --project example\SdkDemo
:: Full test app (admin required for create; cleans up on exit)
dotnet build test
test\bin\Release\net10.0-windows10.0.26100.0\win-x64\HIDMaestroTest.exe emulate xbox-360-wired
The test app self-elevates via UAC if launched non-elevated. To launch from an elevated terminal directly so stdin remains live (required for the regression battery), use gsudo or run from an Administrator PowerShell.
Debug builds are blocked¶
Directory.Build.props declares <Configurations>Release</Configurations> and a FailOnDebug MSBuild target that hard-fails any dotnet build -c Debug. This was a real incident: the embedded HMXInput.dll and INFs came from a stale Resources/ snapshot when a Debug build's BeforeBuild ran against an older build/ payload while a parallel Release rebuild had moved on. Single-config builds eliminate the failure mode.
Driver install internals (high-level)¶
HMContext.InstallDriver() does this on every call:
- Sweep ghost devices first. Calls
RemoveAllVirtualControllersto evict any virtuals left behind by a previous crashed session that's still bound to the old INF. Without this,pnputil /delete-driver /uninstall /forcewould refuse to delete the package and the install would silently restore the stale binary frompnputil's internal cache. - Check the driver-store hash. If the embedded payload's SHA-256 matches what's currently installed, return immediately. No work, no UAC, no PnP churn.
- Extract the embedded payload to
%TEMP%\HIDMaestro\<hash>\. Idempotent on the hash — same payload reuses the directory. - Generate a self-signed certificate if one isn't in
Cert:\LocalMachine\Rootalready. Adds it to Root and TrustedPublisher. The certificate is per-machine and survives reboots.bcdedit /set testsigningis not required. - Generate
.catcatalogs viaInf2Cat.exeand sign the INFs and binaries withsigntool.exe. pnputil /add-driver hidmaestro.inf /installand the same forhidmaestro_xusb.inf. After this returns, the driver is in the DriverStore and ready to bind to virtual devices.- Delete the temp extraction. Nothing is left in the consuming app's directory.
The full breakdown is in Driver Install and Signing. Consumers don't need to understand any of it — one call, idempotent, self-healing.
Uninstalling¶
There is no global uninstaller. The driver is removed by the consumer via:
HMContext.RemoveAllVirtualControllers(); // evict every live + ghost devnode
DriverBuilder.Uninstall(); // pnputil /delete-driver hidmaestro.inf
Or through the test app:
The cleanup command sweeps every HIDMAESTRO* PnP devnode (plain HID, XUSB companion, and SWD-enumerated parents), removes the driver packages, and deletes the certificate from Root + TrustedPublisher.
NEVER
pnputil /delete-driver /uninstall /forceon the active driver. It leaves devices in Code 14 ("restart required"). Always runHIDMaestroTest cleanup(orHMContext.RemoveAllVirtualControllers) FIRST to evict the devices, then plainpnputil /delete-driver(no/uninstall) if a package delete is even needed.InstallDriveris idempotent across version bumps — manual uninstall before reinstall is almost never the right move.
See also¶
- Quickstart — first-controller walkthrough using
example/SdkDemo. - Driver Install and Signing — full breakdown of the embedded payload, certificate management, and
pnputilmechanics. - Build and Release — the two-phase build, INF stamping, and release ZIP recipe.
- Troubleshooting —
pnputilerrors, stale binaries, devnode ghosts.