What actually happens when Windows 11 Pro reboots at 3 AM
You launch a twelve-hour LoRA training run. You go to bed. At 3 AM, Windows installs a cumulative update and reboots. Your training evaporates at epoch 7. The login screen kindly tells you in the morning that “we picked a time that works for you.” It did not.
The first reflex is to open services.msc and set wuauserv (Windows Update) to Manual or Disabled. The next morning, it’s back to Automatic. The reflex after that is to go deeper — and discover that on Windows 11 Pro:
wuauservis no longer the service that decides when to update. UsoSvc — the Update Orchestrator Service — wakes it up when it wants to.- There is a hidden service called WaaSMedicSvc (Windows Update Medic) whose job is to watch the state of the Windows Update services and “heal” them if they’ve been touched. It’s protected by a
TrustedInstallerACL — even an administrator can’t change itsStarttype without re-owning the registry key first. - There is a scheduled task at
\Microsoft\Windows\WindowsUpdate\Scheduled Startwhose only job is to re-launchwuauservon a timer. Even if you stop the service, the task re-arms it on its next tick.
So one switch isn’t enough. Three are. And — this is the important part — there’s exactly one place Windows respects without a self-healing fight: Group Policy. If you set NoAutoUpdate=1 in the right registry key under HKLM\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU, even WaaSMedicSvc honours it. That’s the durable layer. By itself though, Group Policy only blocks the auto-install: UsoSvc can still trigger a background download and stage an install for the next reboot. So you also need to disable the scheduled task and the orchestrator service to fully stop the cycle.
The three layers, one by one
Layer 1 — Group Policy
The most durable layer. NoWinUpdate writes two values to HKLM\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU:
NoAutoUpdate=1(DWord). Tells Windows that the administrator of the machine has opted out of automatic install.AUOptions=2(DWord). Translates to “notify download, notify install” — the policy value that keeps the UI sane while never auto-pulling.
What makes this layer durable is that the self-healing Medic service explicitly respects Group Policy. It will not re-enable auto-update against an explicit policy, because doing so would override an administrator decision. This is the foundation of the entire approach.
Layer 2 — Scheduled task
The task at \Microsoft\Windows\WindowsUpdate\Scheduled Start is disabled via schtasks /Change /TN "..." /Disable. Without this, even if wuauserv is set to Disabled, the task wakes it back up on its next firing.
One subtlety: for the audit (reading the current state) NoWinUpdate uses PowerShell’s Get-ScheduledTask, which returns a typed enum (Ready / Disabled / Running) that’s identical across Windows locales. Parsing the text output of schtasks /Query would have meant handling localised strings (“Prêt”, “Désactivé”, …) and that would have been fragile. For the write path we use schtasks itself, which is more standard and only needs the exit code. Mixed by design: typed read, simple write.
Layer 3 — Services
The two services to flip are wuauserv (Windows Update) and UsoSvc (Update Orchestrator). NoWinUpdate writes Start=4 (Disabled) directly to HKLM\SYSTEM\CurrentControlSet\Services\ rather than going through sc.exe config.
Why registry direct: sc.exe config needs WRITE_DAC on the service object, which is variable across Windows builds and historical patch states. Writing the registry value directly when running elevated is more permissive and more deterministic — and it’s exactly what sc.exe ends up doing internally. Bonus: perfect symmetry with the restore path. The mapping is well-known: 2 = Automatic, 3 = Manual, 4 = Disabled.
What NoWinUpdate deliberately does not touch
The list of services that could in theory be involved in Windows Update is much longer than three. Several common community “fixes” recommend disabling additional services. NoWinUpdate doesn’t, for specific reasons:
WaaSMedicSvc(Windows Update Medic). Its ACL prevents even an administrator from changing itsStarttype. Disabling it cleanly means re-owning the services subkey, modifying the DACL to grant AdministratorsFullControl, making the change, restoring the DACL, restoring ownership — a chain of mutations that can leave the service in a non-reversible state if interrupted. And there’s no need: withNoAutoUpdate=1set on the Group Policy layer, Medic’s healing has no effect on what we care about. The principle is “do less, restore more.”BITS(Background Intelligent Transfer Service). Used by a lot of other things on the system — modern apps, the Microsoft Store, OneDrive, Edge sync. Disabling it breaks more than it fixes.DoSvc(Delivery Optimization). The Microsoft Store relies on it to install and update Store apps. Disabling it breaks the Store.- Windows Defender update channel. Defender’s virus definitions update over a separate channel (
MpSigStub.exeand the Microsoft Update endpoint that bypasses Windows Update entirely). That’s the good news: disabling Windows Update doesn’t stop antivirus definitions. You keep the protection.
Backup and restore — the part that matters most
Every Disable action captures a snapshot first, before any registry or task is touched, into backups/wu_backup_<yyyyMMdd_HHmmss>.json:
{
"created_at" : "2026-05-03T14:22:11.0000000Z",
"gp_key_existed" : true,
"no_auto_update" : null,
"au_options" : null,
"task_was_enabled" : true,
"services" : { "wuauserv": 3, "UsoSvc": 2 }
}
The gp_key_existed flag is the edge case that took a moment to get right. If the policy registry key didn’t exist at all before the disable (typical on a fresh machine with no group policy applied), the restore needs to know whether to delete the values we created or to leave them alone with their original (possibly meaningful) prior state. The flag distinguishes “there was nothing” from “there was NoAutoUpdate=0 explicitly”. The conservative choice: only delete the specific values, leave the now-empty subkey in place. An empty policy subkey is harmless.
The restore reads the latest backup file (sort by name = sort chronologically thanks to the timestamped filename format), applies the captured values back through the three layers in order, and your machine ends up exactly where it was before the most recent Disable. Old backups are never auto-deleted; if you need to roll back further you can swap the file order manually, but in practice the “restore to just before the last disable” is what everyone actually wants.
Defence in depth — the small things
A handful of details that aren’t obvious but pay back across the lifetime of the tool:
- UAC at launch via
app.manifestwithrequireAdministrator. The whole flow needs elevation; let Windows ask once at startup rather than fail per-operation. Plus an in-codeWindowsPrincipal.IsInRole(Administrator)check as defence in depth — if someone bypasses the manifest exotic-loader-style, we refuse to mutate state. - UTF-8 forced on the standard streams of the PowerShell and
schtaskssub-processes. Without that, on a Windows installed in a non-English locale (cp1252 on a French Windows is the canonical case), a UTF-8 output from the sub-process gets mojibake-d and the JSON parse fails on the C# side. One line, saves an entire class of bugs. - Audit fires automatically at startup, synchronous, in the
WindowsUpdateViewModelconstructor. It takes about 200 ms total for all three layers, and it gives the user the real current state the moment the Control tab opens — no need to click “Audit” first. - Backup files use lexicographic-equals-chronological naming (
wu_backup_yyyyMMdd_HHmmss.json), soArray.Sort(files)[^1]is the most recent. No name parsing, noFile.GetLastWriteTimestat calls. Small thing, less code, less surface for bugs.
What the version pill does
One ergonomic detail that started in NoWinUpdate V1.1 and propagated to the whole ExpSoft catalogue: the version pill in the Welcome tab is a styled Button, not just a static label. At app startup, UpdateService fetches https://nicolas-riquier.com/ExpSoft_Versions/versions.json in the background and compares against the compiled Version. If a newer release is available, the pill lights up orange and becomes clickable — clicking jumps to the More Tools tab where the live products page is already loaded in WebView2, with the latest release info one scroll away.
It’s zero friction. No popup, no “Would you like to update now?” modal, no telemetry beyond a single GET. If the network is down or the versions endpoint fails, the pill falls back to its neutral state and the user never sees an error.
Credits and license
NoWinUpdate is a pure-C# WPF app (.NET 8, MIT, © Microsoft), single Release configuration, no Python, no GPU, no FFmpeg, no model weights — just the three layers and a small UI. The only external library beyond .NET itself is Microsoft.Web.WebView2 (used by the More Tools tab to embed the live products page). Both are Microsoft, distribution-permitted.
The Windows administration primitives — Group Policy registry keys, schtasks, the service control registry layout — are documented Microsoft Windows APIs. NoWinUpdate doesn’t hook anything, doesn’t inject anything, doesn’t patch any binary. It writes the same registry values an administrator would write by hand, with a backup taken before every change and a restore button on every screen.
Take-aways
- On modern Windows 11 Pro, “disable Windows Update” needs three layers, not one. Group Policy is the only durable layer; the other two close the gaps but are themselves healed by Medic if you don’t hold them with policy.
- Don’t fight Medic head-on. Its ACL is hostile, and its job is the same as ours up to a point — it only acts when there’s no policy. Set the policy, and Medic stays out of the way.
- Backup before every change, with a complete snapshot of what each layer looked like. Restore is then a fixed-cost operation, not a registry-archaeology session.
- Audit before showing any UI state. The 200 ms cost is invisible; the gain is that the user sees reality, not stale settings.