One-click verified NVIDIA clean installs. Reinstalls the old-school Control Panel and restores all your settings.
Features • Getting Started • Security Model • Technical Notes
NvDriverKit is a plain Win32 C application that automates a full NVIDIA driver clean install. Click one button and it checks for a newer driver, drives NVCleanstall through a verified clean-install build, installs it, reinstalls the NVIDIA Control Panel, and puts every one of your driver settings back.
Modern DCH driver packages no longer ship the Control Panel applet, and a clean install erases your profiles and DSR factors. NvDriverKit handles both, so a clean install costs you nothing.
No frameworks, no runtime dependencies, ~136 KB.
- One click to install. Version check, wizard, build, install, post-install and reboot prompt in a single run.
- Reinstalls the Control Panel from the Microsoft Store, which DCH driver packages leave out.
- Restores your settings: profiles from
MySettings.nip, plus DSR Factors and Smoothness. - Fetch Settings. A second button saves what the driver is set to now, so the first run has something to restore. No PowerShell prompt needed.
- Shareable package. The built installer lands on your Desktop so you can keep or pass it on.
- Verified, not assumed. Every checkbox is read back, every hash re-checked, the installed version re-read.
- Signature and hash pinned. Authenticode, signer thumbprint and SHA-256, with trust-on-first-use.
- Silent post-install. No console window; the script's output is relayed into the app log.
- Hardened binary. ASLR, high-entropy ASLR, DEP, stack protector, statically linked.
Preflight
elevated? else stop
read installed driver from the display class key
MySettings.nip present? else stop
NVCleanstall: Authenticode + signer pin + hash pin, staged to ProgramData
NvPostInstall.ps1: hash pin, staged to ProgramData
clear any stale %TEMP%\NVCleanstall workspace
write the tweak manifest to the registry, read it back
Wizard
page "Select Driver Version To Install"
read the available version off the best-driver label
same as installed? -> "Up to date", close, done
select "Install best driver for my hardware"
fallback servers off, background update check on
page "Select Components To Install"
click Minimum, verify exactly one box is ticked and it is Display Driver
page "Installation Tweaks"
set every checkbox by AutomationId, then read all of them back
page "Finished"
Build Package -> save dialog -> wait for the .exe to stop growing
Install
package is saved to the Desktop, so it can be shared or kept
hash it, copy to the admin-only run directory, confirm the hashes match
run THAT copy (never the Desktop one - the Desktop is user-writable)
delete the staged copy once the installer exits, and sweep any leftovers
re-read the driver version; it must match what we set out to install
Post-install
powershell -NoProfile -ExecutionPolicy Bypass -File NvPostInstall.ps1
1. reinstall the NVIDIA Control Panel from the Microsoft Store
2. confirm the NVIDIA Display Container service is running
3. import MySettings.nip
4. rewrite the DSR Factors / Smoothness bytes, per monitor
no console window; every line it prints is relayed into the app's log
write the GPU interrupt priority to the device key, and read it back
offer a reboot (once, from the app)
Nothing is installed unless every check above passed.
- Windows 10 or 11, x64, with an NVIDIA GPU
- Administrator rights
- NVCleanstall 1.19.0
NvPostInstall.ps1, which ships in this repo and inside the release zip- WinLibs GCC (UCRT) to build; no Windows SDK required
set PATH=C:\winlibs-extract\mingw64\bin;%PATH%
build.batMinGW-w64; needs gcc and windres on PATH. Produces build\NvDriverKit.exe,
statically linked with no non-system DLL dependencies.
In VS Code, Ctrl+Shift+B runs the same script.
Alternatively, from an x64 Native Tools Command Prompt:
build-msvc.batMSVC adds Control Flow Guard, CET shadow stack and /analyze, which MinGW
cannot provide. The source is identical.
To redraw the icon (nine sizes, 16 to 256):
powershell -ExecutionPolicy Bypass -File res\make-icon.ps1Do this before your first install, while your driver settings are the way you like them. The run refuses to start without it, because a clean install erases everything it restores.
Run NvDriverKit.exe as administrator and click Fetch Settings. It reads
the driver, saves what it finds, and reports how many settings it captured.
Nothing is installed and nothing about the driver is changed.
This writes Documents\NvPostInstall\MySettings.nip and DsrSettings.json,
following a relocated Documents folder if you have moved it.
The same thing without the GUI, if you would rather use a prompt:
.\NvPostInstall.ps1 -CaptureRun build\NvDriverKit.exe as administrator. The manifest asks for it.
| Flag | Effect |
|---|---|
| (none) | Full run |
--capture |
Fetches and saves your current settings, the same path the Fetch Settings button takes |
--force |
Proceed even when the installed driver is already current (clean reinstall of the same version) |
--dry-run |
Drives the wizard and verifies everything, stops at the Finished page without building |
--probe |
Launches NVCleanstall and dumps the UIA control tree |
--probe-attach |
Dumps an NVCleanstall window that is already open |
--probe-back N |
Dumps the current page, then clicks Back and dumps again, N times |
Any page step that fails also dumps the live control tree before stopping, so a broken run leaves behind exactly what is needed to re-map it.
Warning
Do not start NVCleanstall while another copy is running. Both instances
share %TEMP%\NVCleanstall, and the second one starting will destroy the
first one's workspace.
A complete run takes roughly 7 minutes: ~40 s through the wizard, ~2 min to download and build the package, ~3 min to install, ~20 s for the post-install script.
Tagging is the whole release process. Pushing a v* tag runs
release.yml, which stamps the version into
res/app.rc, src/common.h and res/app.manifest, builds with MinGW-w64,
verifies the binary and publishes a GitHub Release carrying the zip and its
SHA-256:
git tag -s v1.0.1 -m "NvDriverKit v1.0.1"
git push origin v1.0.1Every push and pull request on main runs
ci.yml, which builds and runs the same binary
checks without publishing anything.
Neither workflow can exercise the driver install: that needs an NVIDIA GPU and
NVCleanstall, so it stays a local --dry-run. What CI does guarantee is that
the code still compiles clean, and that the hardening flags, the single
requireAdministrator manifest and the absence of MinGW runtime DLLs have not
regressed.
%ProgramData%\NvDriverKit\config.ini, written with defaults on first run:
[paths]
NVCleanstall=F:\Windows\Downloads\NVCleanstall_1.19.0.exe
PostInstallScript=F:\Windows\Downloads\NvPostInstall.ps1
[pins]
nvcleanstall_sha256=...
postinstall_sha256=...- Elevated,
uiAccess=false. We never drive a higher-integrity UI. - Signer pin. NVCleanstall must chain-validate and its signer thumbprint
must equal
67E2A570…42D307B7(TechPowerUp LLC). - Hash pin, trust-on-first-use. Both inputs are SHA-256'd and recorded in
config.inion first run. A later mismatch aborts and prints both hashes. Clear the[pins]entry to accept a deliberate update. - No execution from user-writable paths. Both inputs normally live in
Downloads, which standard users can write, and an elevated process must
never execute from there. They are copied into
%ProgramData%\NvDriverKit\bin, whose DACL is reset to SYSTEM + Administrators full control, Users read/execute. The DACL of anything we are about to run is walked first (including its parent directory) and the run is refused if any other principal holds write, delete or WRITE_DAC. - Process creation.
CreateProcessWwithlpApplicationNameset to an absolute, already-verified path. Nosystem(), noShellExecute, no PATH resolution, so there is no unquoted-path or search-order hijack to exploit. - Verify, don't assume. Every checkbox is read back after being set. The installed version is re-read after the install, and the post-install script does not run if it doesn't match.
- Audit log. Append-only, UTF-16, in
%ProgramData%\NvDriverKit, with the SHA-256 of everything executed.
NvDriverKit/
├── src/
│ ├── common.h # Interfaces, pins, AutomationIds
│ ├── main.c # Win32 GUI, worker thread, reboot prompt
│ ├── wizard.c # Orchestration: wizard, build, install, post-install
│ ├── uia.c # UI Automation helpers
│ ├── nv.c # Driver version parsing, tweak manifest, device key
│ ├── sec.c # Authenticode, hashes, DACLs, staging
│ └── log.c # Append-only audit log
├── res/
│ ├── app.ico # Nine sizes, 16 to 256
│ ├── app.manifest # requireAdministrator, uiAccess=false
│ ├── app.rc # Icon + VERSIONINFO
│ ├── manifest.rc # Compiled to default-manifest.o, shadows MinGW's
│ └── make-icon.ps1 # Regenerates app.ico reproducibly
├── build.bat # MinGW-w64
└── build-msvc.bat # MSVC: adds CFG, CET, /analyze
1. Auto-reboot would skip the post-install script. The captured config had
UnattendedReboot: true. If the NVIDIA installer reboots on its own,
NvPostInstall.ps1 never runs, the .nip never imports and the DSR bytes never
get written. src/nv.c forces that field off and this program owns the reboot
decision instead.
2. The .nip precondition is checked before the wipe, not after. A clean
install erases your driver settings; the script restores them from
Documents\NvPostInstall\MySettings.nip. If that file is missing, the run stops
before anything is touched and tells you to click Fetch Settings first.
3. Stale workspace. NVCleanstall reuses %TEMP%\NVCleanstall. A leftover
tree from an earlier session can be picked up mid-build, so it is deleted
first, under a guard that only ever removes a directory of that exact name
directly under %TEMP%.
4. NVCleanstall self-updates. It checks for new versions on its own, and a silent bump breaks every automation path. The exe is pinned by signer thumbprint and SHA-256; an unexpected build stops the run instead of being clicked blind.
| Checked | Result |
|---|---|
Compiles, zero warnings at -Wall -Wextra -Wshadow |
yes |
| Standalone binary | about 136 KB, system DLLs only, no bundled runtime |
| ASLR / high-entropy ASLR / DEP | on (DllCharacteristics 0x0160) |
| Control Flow Guard | off (MinGW cannot emit it; use build-msvc.bat) |
| Embedded manifest | exactly one, requireAdministrator, no asInvoker |
| Version math + live registry read | 14/14 assertions, reads 621.01 correctly |
| Signature pin, hash pin, staging, DACL refusal | exercised on a live run |
| "Up to date" short-circuit | correct: 621.01 installed vs 621.01 available |
| All four wizard pages driven and verified | yes, via --dry-run --force |
| Build → Desktop → install → script → reboot | completed end to end |
nv_set_interrupt_priority |
writes and reads back on the real device |
Silent script relay + stdin to Read-Host |
verified against a stand-in script |
| Full run with the silent relay and registry write | completed; no console window, script exit 0, DevicePriority = 3 |
The size is approximate because it depends on the toolchain. A local build with a different GCC will differ by a
few KB and may pull one more UCRT API set, so verify-binary.ps1 asserts the properties
that matter rather than an exact byte count.
What the UIA probe established
NVCleanstall 1.19.0 is a WPF app and gives every control a stable
AutomationId, so the automation matches on ids rather than labels wherever an
id is known. Three things the screenshots got wrong:
BestDriveris empty when the page first appears, because the version lookup is still running and the combo readsLoading.... Searching its text for-desktop-winfound nothing. The code now pollsBestDriverfor a non-empty name, up to 90 s.- The two page-1 checkbox labels are longer than they look. The real names are
"Use fallback servers (slower, bypass Cloudflare content delivery network in problem areas)"and"Periodically check for driver updates in background (without contacting NVIDIA servers)". Exact-name matching failed on both; they are matched by id now. - NVCleanstall shows its own update nag (
UpdateAvailableLink) right on page 1, which is why the exe is hash-pinned.
NVCleanstall does not apply PreviousTweaks to the UI. A --dry-run proved
it: every checkbox on the Tweaks page came up off, and UnattendedReboot came
up on. Those are its defaults, not the saved settings. The "Use Previous Settings"
button exists (UsePrevious) but would not activate through either Invoke or
the accessibility default action. So writing the registry alone configures
nothing; each control has to be driven directly. The registry write is kept
because NVCleanstall persists it for the next manual run, but it is no longer
the mechanism.
Every checkbox on the Tweaks page has an AutomationId identical to its key in
PreviousTweaks, which is what makes driving it directly clean.
Component rows are ListItem elements wrapping an unnamed CheckBox. The
label lives on the row, and reads "Display Driver", not "Display Driver (required)" as the UI shows it.
Mapped and confirmed:
| Page | Ids |
|---|---|
| Select Driver Version | RadioAutodetect, BestDriver, CurrentDriverVersion, DeviceName, UseFallbackServers, CheckInBackground |
| Select Components | ButtonAll, ButtonRecommended, ButtonMinimum; rows are ListItems |
| Installation Tweaks | one id per manifest key, plus combos MSIPolicy, MSIPriority, NvEncPatchVersions, and UsePrevious |
| Finished | BuildSFX ("Build Package"), RunInstaller, ShowInFolder, CopyFolder |
| Every page | ButtonNext, ButtonPrev |
The driver itself is downloaded during Build Package, not earlier: the
component list comes from cached metadata, so --dry-run reaches the Finished
page in well under a minute and never pulls the ~800 MB payload. That makes
--dry-run --force a cheap way to re-verify the whole wizard path.
Why the interrupt priority is set through the registry
The Interrupt Policy/Priority combos cannot be automated at all. Dumping one reads:
MSIPriority: type=50003 enabled=yes patterns=none 0 descendant(s)
A ComboBox by control type that exposes no UI Automation patterns and has no children. ValuePattern, SelectionPattern, ExpandCollapse and LegacyIAccessible all come back empty, and focus-plus-type-ahead cannot be confirmed because there is nothing to read back. Three attempts failed before the dump made it obvious.
So the UI is left alone and the priority is written straight to the GPU's device key after the install instead:
HKLM\SYSTEM\CurrentControlSet\Enum\PCI\VEN_10DE&DEV_*\<instance>
\Device Parameters\Interrupt Management\Affinity Policy\DevicePriority
The device is found by walking Enum\PCI for the instance whose Service is
nvlddmkm, the value from the manifest is written, and it is read back to
confirm. 0 Undefined, 1 Low, 2 Normal, 3 High. The driver only reads it
at device start, which the reboot covers.
The tweak manifest
g_manifest in src/nv.c is the single source of truth for the Installation
Tweaks page. It is written to
HKCU\Software\techPowerUp\NVCleanstall\PreviousTweaks as JSON, then verified
twice: once by reading the registry back, once by reading the UI.
DisableNvContainer must stay false: NvPostInstall.ps1 requires the
NVDisplay.ContainerLocalSystem service, and the NVIDIA Control Panel will not
open without it.
When NVCleanstall updates
The hash pin will stop the run. Then:
NvDriverKit.exe --probeand read the dump in the log.- Update
NVCS_WINDOW_TITLEand any changed control names insrc/common.handsrc/nv.c. - Clear the
[pins]entry fornvcleanstall_sha256. --dry-runto confirm every page and checkbox still resolves.
- The automation is validated against NVCleanstall 1.19.0 only.
- Combo boxes (Interrupt Policy/Priority, NVENC patch target) are logged but not hard-verified in the UI; their values are enforced through the registry manifest instead.
- Secure Boot is fine. The re-signed package installs via NVCleanstall's own root certificate. Do not enable Memory Integrity (HVCI); a patched, re-signed driver package will not load with it on.
MIT. See LICENSE.
