A hardened server that never gets patched is just as compromised as an unpatched default install — it only takes longer to notice. The two disciplines are inseparable, and most shops run them badly on both ends: they apply a hardening PDF once at build time and never re-verify it, and they approve “all updates to all computers” and pray the reboots land on a weekend. This article treats hardening and patching as one system. A baseline shrinks the attack surface to settings you can reason about and prove; a patch pipeline keeps that surface from rotting as new CVEs land every second Tuesday. Neither works alone — an unpatched hardened box still ships with last quarter’s SMB RCE, and a fully-patched default install still runs SMBv1, LLMNR, and a local Administrator with the same password on 400 machines.
You will build a defensible CIS/STIG-aligned baseline using Microsoft’s own free tooling — the Security Compliance Toolkit (SCT), Policy Analyzer, and LGPO.exe — imported through Group Policy so it is enforced, versioned, and reversible rather than hand-typed into regedit. Then you will stand up Windows Server Update Services (WSUS) the way it actually survives a real fleet: a sized content store, scoped products and classifications, computer groups mapped to deployment rings, auto-approval that drives a pilot ring while later rings stay gated, aggressive decline-superseded hygiene, scheduled cleanup, and periodic SUSDB re-indexing so the console does not time out at month twelve. Every operation is real GPO paths and repeatable PowerShell, because a patch program you cannot script decays the day its author goes on leave.
Everything here targets Windows Server 2019/2022/2025 joined to a domain, with clients spanning Server 2016+ and Windows 10/11. By the end you can defend the baseline in an audit, measure patch latency per ring (the metric that actually matters), roll a bad update back cleanly, and know exactly when WSUS is right and when to move roaming or internet-facing machines to Windows Update for Business (WUfB) instead. Adjust ADMX/baseline versions to match your highest OS in the fleet — that one rule prevents more self-inflicted GPO breakage than any other.
What problem this solves
Two production pains, one root system. The first is configuration drift and unprovable hardening. Someone ran a hardening script in 2023, half its settings were later overridden by an operational GPO, nobody documented the deviations, and now an auditor asks “prove SMB signing is required fleet-wide” and you cannot — you have to RDP to a sample and check by hand. Worse, when the baseline did break something (a legacy backup agent that could not negotiate SMB signing), the reflex fix was to weaken the fleet-wide control instead of fixing the one non-compliant endpoint, quietly re-opening the door the baseline closed. Without a managed, versioned baseline and a documented deviation register, hardening is folklore that degrades every time a new admin touches a GPO.
The second pain is uncontrolled patching, with two symmetrical failure modes. Shops that approve everything for everyone get burned by a bad cumulative update that blue-screens a domain controller at 3 a.m., because the DC sat in the same “all computers” bucket as a test VM. Shops that over-correct into approving nothing find, six months later, that Defender flags 47 machines exposed to a wormable RCE patched in March. Neither is a patch program — both are gambling. Add the operational rot specific to WSUS: pull “all products,” never decline superseded updates, never run cleanup, never re-index the SUSDB, and within a year clients spend two hours scanning, the console throws SQL timeouts, and the WID database balloons past 30 GB. The server meant to make patching reliable becomes the reason it is not.
Who hits this: every organization running an on-prem or hybrid Windows fleet without cloud-native patch management — regulated shops (PCI-DSS, HIPAA, ISO 27001, DISA STIG mandates), air-gapped or bandwidth-constrained sites where every machine pulling from Microsoft directly is untenable, and any team told to “get to a documented, ring-based patch cadence with compliance reporting” that needs it scriptable and defensible, not a monthly act of faith.
To frame the whole system before the deep dive, here is every discipline this article covers, the failure it prevents, and the single tool that anchors it:
| Discipline | What breaks without it | Anchor tool | First move |
|---|---|---|---|
| Managed baseline | Unprovable, drifting hardening | Security Compliance Toolkit + GPMC | Snapshot local policy, import MSFT baseline to a pilot OU |
| Attack-surface reduction | Lateral movement via SMBv1/LLMNR/legacy TLS | GPO + PowerShell | Remove SMBv1 feature, disable multicast name resolution |
| Audit + logging | Blind SIEM; no forensic trail | Advanced Audit Policy + PowerShell logging | Enable 4688 with command line, ScriptBlock logging |
| WSUS architecture | Direct-to-Microsoft chaos, no control | WSUS role (WID/SQL) | Scope products/classifications, size content store |
| Ring deployment | All-or-nothing approvals; bad-patch blast radius | Computer groups + client-side targeting | Map groups to rings, auto-approve pilot only |
| Hygiene | Bloated SUSDB, hours-long scans | Cleanup + decline-superseded + SUSDB re-index | Schedule monthly maintenance |
| Compliance + rollback | Can’t prove or reverse patch state | WSUS reporting + wusa /uninstall / DISM |
Track patch-latency p90 per ring |
| Cloud offload | Roaming/DR machines never patch | WUfB / Intune update rings | Move mobile fleet off WSUS |
Learning objectives
By the end of this article you can:
- Establish a CIS/STIG-aligned Windows Server baseline from the Microsoft Security Compliance Toolkit, diff it against your current effective policy with Policy Analyzer, snapshot local policy with LGPO.exe, and import the baseline GPOs into GPMC scoped to a pilot OU first.
- Reduce attack surface deliberately: remove SMBv1 as a feature, enforce SMB signing and encryption, disable LLMNR/NetBIOS/mDNS, configure Schannel (kill SSL 2.0/3.0 and TLS 1.0/1.1, keep 1.2/1.3), and minimize roles/services/features — knowing which controls break legacy dependencies and how to stage them.
- Turn on the logging that matters: advanced audit subcategories, process-creation (4688) with command-line capture, and PowerShell script block + module logging + transcription, and ship it to a SIEM.
- Stand up WSUS with a right-sized database and content store, sync deliberately from upstream, scope products and classifications, and understand upstream/downstream (replica) topologies for multi-site fleets.
- Build computer groups mapped to deployment rings, wire client-side targeting via GPO per OU, drive auto-approval rules for the pilot ring, and promote updates ring-to-ring on a cadence with PowerShell.
- Keep WSUS healthy forever: decline superseded/expired updates, run the cleanup wizard programmatically, re-index the SUSDB, and prune obsolete computers — the difference between a WSUS that lasts five years and one that dies in one.
- Produce compliance reports and measure patch latency (p90 per ring), execute a clean rollback of a bad update, and decide correctly between WSUS, WUfB/Intune, and third-party patching for each slice of your estate.
Prerequisites & where this fits
You should be comfortable with Active Directory and Group Policy — OUs, GPO linking, precedence, security filtering, and gpupdate/gpresult. You should know your way around an elevated PowerShell prompt, understand that Windows features and roles are managed with Install-WindowsFeature/Disable-WindowsOptionalFeature, and be able to read the registry as the ground truth behind most policy settings. Basic networking (TCP ports, DNS, HTTP/HTTPS) and the concept of a Public Key Infrastructure certificate for HTTPS bindings will help for the WSUS SSL section. You do not need prior WSUS experience — we build it from role install.
This sits in the Servers / Security track and is the hub that several adjacent articles spoke into. Hardening service and admin identities is covered in depth in Eliminating Static Service Credentials with gMSA and Windows LAPS — this article assumes Windows LAPS is your local-admin rotation mechanism and does not re-derive it. The lateral-movement controls (SMB signing/encryption, Credential Guard) are expanded in Hardening SMB and Enabling Credential Guard to Block Lateral Movement. The GPO structure and change-control patterns come from Group Policy at Scale: A Maintainable Architecture and Managing GPOs as Code, and enforcing the baseline continuously (rather than one-shot) is the domain of Configuration Management for Windows Server with PowerShell DSC and Ansible. For clusters, patching is a special case handled by Patching Failover Clusters with Cluster-Aware Updating and Stretch Clusters via Storage Replica. The Linux equivalent of this whole pipeline is Automating Linux Patching: dnf-automatic, Live Patching, and Reboot Orchestration.
A quick map of who owns what during a baseline/patch change, so you route decisions correctly:
| Layer | What lives here | Who usually owns it | What it can break |
|---|---|---|---|
| Baseline GPOs | Hardening settings (SMB, TLS, audit, UAC) | Security / platform | Legacy app connectivity, agent auth |
| Operational GPOs | App-specific overrides, mapped drives | App / ops teams | Silent override of a baseline setting |
| WSUS server | Approvals, groups, sync, content | Patch / platform | Fleet-wide reboot timing, scan health |
| Client GPO (WU node) | WSUS URL, target group, install schedule | Platform | Machines pointed at wrong ring/server |
| Golden image | SusClientId, baseline pre-applied | Build / imaging | Phantom clients if ID not cleared |
| Maintenance windows | When reboots land | App owners + change board | Unplanned downtime if misaligned |
Core concepts
Six mental models make every later decision obvious.
A baseline is a diff, not a document. You never hand-craft hardening from a PDF. You start from a vetted baseline (Microsoft’s, which tracks closely to CIS Level 1/2 and DISA STIG), diff it against your current effective policy with Policy Analyzer, and record every deviation as a deliberate, named exception. The value is not the settings themselves — it is that you can answer “what is enforced, why, and what did we consciously relax” at any moment. CIS Benchmarks are the community consensus standard; STIGs (Security Technical Implementation Guides) are the DoD’s stricter, mandated variant. Microsoft’s baselines land between them and ship as importable GPO backups — the pragmatic starting point over a raw CIS build kit.
Local policy and domain policy are different enforcement planes. LGPO.exe applies and backs up local group policy — it is how you snapshot a machine before hardening and how you configure standalone/workgroup boxes that have no domain GPO. In a domain, the correct plane is GPMC: import the baseline GPO backups into new GPOs and link them to OUs. Never apply the baseline locally on domain members and never link it to the domain root — link it to a pilot OU first so one bad setting hits five servers, not five hundred.
Attack-surface reduction is removal, not just disabling. Disabling SMBv1 leaves the feature installed and one errant role-install away from re-enabling it. Removing the Windows optional feature makes the class of bug impossible. The same logic applies to unused roles, services set to Disabled vs actually uninstalled, and legacy protocols (LLMNR, NetBIOS-NS, mDNS) that exist only to be abused for credential relay. The mental test: “can this come back by accident?” If yes, remove it, do not merely toggle it.
WSUS is a controlled cache with an approval gate, not an auto-updater. WSUS does two jobs: it caches update content from Microsoft (or from an upstream WSUS) so 400 clients pull from your LAN instead of the internet, and it gates which updates reach which machines via approvals scoped to computer groups. Nothing installs until you (or an auto-approval rule) approve it for a group. That gate is the entire point — it is what lets you test on a pilot ring before a DC ever sees the update. Clients discover their assigned server and group through GPO, scan against it, download approved content, and install on their configured schedule.
Superseded content is the silent killer. Every Patch Tuesday, new cumulative updates supersede older ones. If you never decline the superseded updates, they accumulate as metadata and content the clients must still evaluate on every scan. This is the single biggest cause of WSUS decay: scans that take minutes balloon to hours, the SUSDB bloats, and console operations time out. Declining superseded/expired updates and running cleanup is not optional maintenance — it is the load-bearing hygiene that keeps WSUS usable.
Rings turn a gamble into a controlled experiment. A deployment ring is a cohort of machines that receives an update after a defined bake time. Ring 0 (pilot) gets it on day 0; the broad fleet on day +7; critical systems (DCs, SQL, line-of-business) last and manually. If a bad update surfaces in the pilot, you decline it before it ever reaches the broad ring. Membership should be declarative — machines self-assign to a ring via client-side targeting tied to their OU — so the topology is code, not console drag-and-drop.
The vocabulary in one table
Before the deep sections, pin down every moving part. The glossary repeats these for lookup; this is the mental model side by side:
| Concept | One-line definition | Where it lives | Why it matters |
|---|---|---|---|
| Security baseline | Vetted set of hardening settings | GPO backups (SCT) | Provable, reversible attack-surface reduction |
| CIS Benchmark / STIG | Community / DoD hardening standards | External specs | What auditors map you against |
| LGPO.exe | Local-policy apply/backup tool | SCT | Snapshot + standalone hardening |
| Policy Analyzer | Diffs policy sets | SCT | Catch settings that break apps before import |
| WSUS | Update caching + approval gate | Server role | Controls what patches which machines get |
| SUSDB | WSUS’s database (WID or SQL) | WID / SQL instance | Bloats without cleanup → console timeouts |
| Computer group | A bucket of client machines | WSUS console | The unit approvals target = a ring |
| Classification | Update type (Security, Critical…) | WSUS metadata | Scope what you sync/approve |
| Product | OS/app an update targets | WSUS metadata | Scope content store size |
| Approval | Decision to install/decline for a group | WSUS | The gate; nothing installs unapproved |
| Superseded | Replaced by a newer update | Update metadata | Decline it or scans rot |
| Client-side targeting | Client self-assigns its group via GPO | Client WU registry | Declarative ring membership |
| Deployment ring | Cohort patched after a bake time | Group + schedule | Blast-radius control |
| Patch latency | Release-to-install gap | Reporting metric | The number that proves the program works |
| WUfB | Windows Update for Business (cloud rings) | Intune / GPO | Patches roaming/internet machines |
Establishing a CIS/STIG baseline with the Security Compliance Toolkit
Microsoft publishes a vetted security baseline for each OS via the Security Compliance Toolkit (SCT) — free, and the right starting point because it ships as importable GPO backups plus the tools to diff and apply them. Do not hand-craft GPOs from a hardening guide; start from the baseline and document your deviations. The SCT contains GPO backups per role (Domain Controller, Member Server, Domain Security), Policy Analyzer (diff engine + a settings viewer), LGPO.exe (local-policy apply/backup), and the ADMX/.PolicyRules reference files.
The workflow is always the same five moves, in order — never skip the snapshot or the diff:
| Step | Tool | Command / action | Why it matters |
|---|---|---|---|
| 1. Snapshot current local policy | LGPO.exe /b |
Back up effective local GPO | Your rollback point + a “before” for diffing |
| 2. Diff MSFT baseline vs current | Policy Analyzer | Load .PolicyRules + local policy |
Catches app-breaking deltas (SMB signing, NTLM) |
| 3. Import baseline GPO into GPMC | Import-GPO |
Create GPO, import the role backup | Enforced, versioned, reversible in a domain |
| 4. Link to a pilot OU | GPMC / New-GPLink |
Never the domain root | One bad setting hits 5 boxes, not 500 |
| 5. Verify + register deviations | gpresult / RSoP |
Confirm applied; log every override | Provable state + documented exceptions |
Snapshot first. Before anything else, back up the current local policy so you can compare and roll back. Run from an elevated prompt in the extracted LGPO folder:
# Back up effective local group policy to a folder, with a friendly name
.\LGPO.exe /b C:\baseline\backup-pre-hardening /n "Pre-hardening snapshot 2026-03-18"
Diff before you import. Policy Analyzer lets you load the Microsoft baseline .PolicyRules files, add your current local policy (or an exported GPO), and view every setting side by side — matches, differences, and settings present in one set but not the other. This is where you catch the controls that will break legacy apps. Load the member-server baseline, add your snapshot, and review the deltas before importing anything.
# Policy Analyzer is a GUI; launch it and File > Add to load .PolicyRules and your snapshot
Start-Process ".\PolicyAnalyzer.exe"
The settings that most reliably break something when you apply a fresh baseline — vet each against your fleet in Policy Analyzer before import:
| Setting | What it enforces | What it breaks | Pre-import check |
|---|---|---|---|
| SMB signing (server, always) | Signed SMB or no connection | Old NAS/agents without SMB2 signing | Inventory SMB clients; test signing |
| SMB1 removal | No SMBv1 protocol | Ancient scanners/MFPs, XP-era clients | Confirm nothing needs SMBv1 |
| NTLM restrictions / auditing | Blocks or audits NTLM auth | Apps hardcoded to NTLM | Audit-only first (log, don’t block) |
| LDAP signing + channel binding | Signed/bound LDAP to DCs | Unpatched LDAP clients, appliances | Enable DC auditing events first |
| LSA / Credential Guard | Isolates secrets | Some kernel drivers, older VPN/EDR | Test on pilot; check driver compat |
| UAC “Admin Approval Mode” | Elevation prompts for admins | Silent installers assuming full token | Test deployment tooling |
| TLS 1.0/1.1 disable (Schannel) | Only TLS 1.2/1.3 | Old SQL drivers, appliance mgmt UIs | Inventory TLS deps before disabling |
Import into GPMC. In a domain, import the baseline GPO backup into a new GPO rather than applying it locally. Each role has its own backup folder and manifest GUID:
Import-Module GroupPolicy
# Create the GPO, then import the member-server baseline backup into it
New-GPO -Name "MSFT WS2022 - Member Server Baseline" | Out-Null
Import-GPO -BackupId "{GUID-FROM-BASELINE-MANIFEST}" `
-Path "C:\baseline\Windows Server-2022-Security-Baseline\GPOs" `
-TargetName "MSFT WS2022 - Member Server Baseline"
The BackupId GUID comes from the manifest.xml (or the GPOs subfolder names) inside the baseline. Apply the member-server baseline to member servers and the Domain Controller baseline only to the Domain Controllers OU — mixing them is a classic mistake (a DC baseline on a member server, or vice-versa, changes settings that do not apply and misses ones that do).
Link to a pilot OU, verify, register deviations. Link the imported GPO to a dedicated pilot OU, not the domain root, so you can validate on a handful of servers first. Order matters: baseline GPOs sit below your operational GPOs in link precedence only where you intentionally override a setting, and every override belongs in a separate, clearly named GPO with a comment explaining why. That override GPO is your deviation register.
# Link the baseline to a pilot OU with the highest precedence within that OU's scope
New-GPLink -Name "MSFT WS2022 - Member Server Baseline" `
-Target "OU=Pilot,OU=Servers,DC=corp,DC=example" -LinkEnabled Yes -Order 1
# On a pilot server, force refresh and confirm which GPOs applied
Invoke-GPUpdate -Force
gpresult /r /scope:computer
How the three baseline standards relate — so you can answer “are we CIS compliant?” honestly:
| Standard | Who defines it | Strictness | How it ships | KloudVin stance |
|---|---|---|---|---|
| CIS Benchmark L1 | Center for Internet Security | Moderate, low-breakage | PDF + build kit (paid GPOs) | Good audit target; MSFT baseline ≈ L1 |
| CIS Benchmark L2 | CIS | High; may impact usability | PDF + build kit | Layer selectively over MSFT baseline |
| DISA STIG | US DoD | Strictest; mandated for DoD | XCCDF/GPO packages | Required only if contractually bound |
| MSFT Security Baseline | Microsoft | Between CIS L1 and L2 | Importable GPO backups (free) | The pragmatic starting point |
Attack-surface reduction: protocols, services, roles
A baseline sets most of this, but you verify and extend it. The principle throughout: remove what can come back, disable what cannot be removed, and stage every change through the same pilot OU.
Kill SMBv1 and enforce signing/encryption
SMBv1 has no place in a 2026 environment — it is unauthenticated, unsigned, and the transport for WannaCry/EternalBlue. Remove the feature outright so an errant role install cannot re-enable it, then enforce signing (and, on sensitive shares, encryption):
# Remove the SMBv1 feature entirely (requires reboot) — not just disable it
Disable-WindowsOptionalFeature -Online -FeatureName SMB1Protocol -NoRestart
# Require SMB signing server-side (the baseline sets this via GPO too — verify both)
Set-SmbServerConfiguration -RequireSecuritySignature $true -EnableSecuritySignature $true -Confirm:$false
# Optionally require SMB encryption on the whole server (or per-share)
Set-SmbServerConfiguration -EncryptData $true -Confirm:$false # server-wide
# Set-SmbShareEncryption -Name "Finance" -Encrypt $true # per-share, less disruptive
The Microsoft baseline already sets the client/server signing policies via GPO (Microsoft network server: Digitally sign communications (always)), but verify it took — a misconfigured signing negotiation between a hardened server and a legacy NAS is the archetypal post-baseline outage. SMB hardening is deep enough that it has its own article; treat this as the patch-pipeline-adjacent summary.
Disable legacy name resolution and unused protocols
LLMNR, NetBIOS name service, and mDNS exist only to be poisoned for credential relay. Disable them fleet-wide:
# Disable LLMNR (also a GPO: Computer Config > Admin Templates > Network > DNS Client > Turn off multicast name resolution)
New-Item -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\DNSClient" -Force | Out-Null
Set-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\DNSClient" `
-Name "EnableMulticast" -Value 0 -Type DWord
# Disable NetBIOS over TCP/IP on all bound adapters (2 = disabled)
$nics = Get-WmiObject Win32_NetworkAdapterConfiguration -Filter "IPEnabled=TRUE"
$nics | ForEach-Object { $_.SetTcpipNetbios(2) | Out-Null }
The protocol/service reduction reference — what to do with each and why:
| Item | Action | Method | Why | Gotcha |
|---|---|---|---|---|
| SMBv1 | Remove feature | Disable-WindowsOptionalFeature |
Wormable, unsigned | Breaks XP-era clients / old MFPs |
| LLMNR | Disable | GPO / registry EnableMulticast=0 |
Credential relay vector | None for domain-joined DNS clients |
| NetBIOS-NS | Disable | SetTcpipNetbios(2) or DHCP option |
Relay + name spoofing | Legacy apps using NetBIOS names |
| mDNS | Disable | EnableMDNS=0 (DNSClient) |
Cross-subnet discovery abuse | Rare app dependencies |
| WDigest | Ensure cleartext off | UseLogonCredential=0 |
Cleartext creds in LSASS | Off by default on modern OS — verify |
| PowerShell v2 | Remove feature | Disable-WindowsOptionalFeature MicrosoftWindowsPowerShellV2 |
Bypasses ScriptBlock logging | Ancient scripts pinned to v2 |
| Print Spooler (non-print servers) | Disable service | Set-Service Spooler -StartupType Disabled |
PrintNightmare class | Breaks printing / some mgmt tools |
| Remote Registry | Disable / demand-start | Set-Service RemoteRegistry |
Remote enumeration | Some inventory tools need it |
| SNMP v1/v2c | Remove or restrict | Feature removal / v3 only | Cleartext community strings | Monitoring using v2c |
| RDP NLA | Require | GPO UserAuthentication=1 |
Pre-auth attack surface | Very old RDP clients |
Schannel / TLS configuration
Disable obsolete protocols at the Schannel layer so .NET, IIS, and SQL inherit a sane default. Disable SSL 2.0/3.0 and TLS 1.0/1.1; keep TLS 1.2 and 1.3. Registry is the reliable method:
$protocols = "SSL 2.0","SSL 3.0","TLS 1.0","TLS 1.1"
$base = "HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols"
foreach ($p in $protocols) {
foreach ($role in "Server","Client") {
$path = "$base\$p\$role"
New-Item -Path $path -Force | Out-Null
Set-ItemProperty -Path $path -Name "Enabled" -Value 0 -Type DWord
Set-ItemProperty -Path $path -Name "DisabledByDefault" -Value 1 -Type DWord
}
}
# Explicitly enable TLS 1.2 (older OS may need it turned ON, not just "not disabled")
foreach ($role in "Server","Client") {
$path = "$base\TLS 1.2\$role"
New-Item -Path $path -Force | Out-Null
Set-ItemProperty -Path $path -Name "Enabled" -Value 1 -Type DWord
Set-ItemProperty -Path $path -Name "DisabledByDefault" -Value 0 -Type DWord
}
A reboot is required for Schannel changes. Before disabling TLS 1.0/1.1 broadly, inventory the fleet — old SQL client drivers, some appliance management interfaces, and legacy .NET apps that never opted into strong crypto are frequent offenders. Stage this through the pilot OU exactly like the baseline. The protocol target state:
| Protocol | Target state | Registry Enabled |
DisabledByDefault |
Notes |
|---|---|---|---|---|
| SSL 2.0 | Disabled | 0 | 1 | Broken; always off |
| SSL 3.0 | Disabled | 0 | 1 | POODLE; always off |
| TLS 1.0 | Disabled | 0 | 1 | Inventory deps first (SQL drivers) |
| TLS 1.1 | Disabled | 0 | 1 | Same as 1.0 |
| TLS 1.2 | Enabled | 1 | 0 | Required baseline |
| TLS 1.3 | Enabled (2022+) | (OS default) | (OS default) | On by default on Server 2022+ |
Role, service, and feature minimization
A server should run exactly the roles it needs and nothing else. Inventory installed features and remove what is not justified:
# What roles/features are installed?
Get-WindowsFeature | Where-Object Installed | Select-Object Name, DisplayName | Sort-Object Name
# Remove an unneeded role and its management tools (example: an accidental print server)
Uninstall-WindowsFeature -Name Print-Services -IncludeManagementTools -Restart:$false
# Audit listening ports to find services you did not know were exposed
Get-NetTCPConnection -State Listen |
Select-Object LocalPort, @{N='Process';E={(Get-Process -Id $_.OwningProcess).ProcessName}} |
Sort-Object LocalPort -Unique
Prefer Server Core for infrastructure roles — no Explorer, no browser, a fraction of the attack surface and the patch footprint (fewer components means fewer updates and reboots). Managing Core headless is covered in Operating Server Core at Scale with Windows Admin Center and PowerShell Remoting.
Local accounts, audit policy, and PowerShell logging
Hardening that you cannot see being violated is half a control. The logging layer is what feeds detection and forensics.
Local account hardening
The highest-value control against lateral movement is randomizing local administrator passwords so a single dumped hash cannot pivot across the fleet. Use Windows LAPS (built into modern Windows, managed via the LAPS PowerShell module), not the legacy MSI. The full design — including gMSA-backed service accounts — is in Eliminating Static Service Credentials with gMSA and Windows LAPS; the patch-relevant summary:
# Confirm Windows LAPS is present and read the current managed password for a host
Get-Command -Module LAPS
Get-LapsADPassword -Identity "SRV-APP-01" -AsPlainText
Configure LAPS policy (backup directory, complexity, rotation age) via the dedicated LAPS GPO node. The baseline also disables the built-in Guest account and renames the built-in Administrator — keep both, and never re-enable Guest for “convenience.”
Advanced audit policy
Stop relying on the nine legacy audit categories; the baseline configures the granular advanced audit subcategories, which are what actually feed a SIEM. Verify the effective configuration and confirm command-line capture:
# Dump effective advanced audit policy
auditpol /get /category:*
# Confirm process creation (4688) with command line is on — attackers live in the command line
auditpol /get /subcategory:"Process Creation"
Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System\Audit" `
-Name "ProcessCreationIncludeCmdLine_Enabled" -ErrorAction SilentlyContinue
The audit subcategories worth forcing on, and what they catch:
| Subcategory | Setting | Key event IDs | What it catches |
|---|---|---|---|
| Process Creation | Success | 4688 | Malware/tool execution (with cmdline) |
| Logon | Success + Failure | 4624 / 4625 | Brute force, lateral logons |
| Account Logon (Kerberos) | Success + Failure | 4768 / 4769 / 4771 | Kerberoasting, ticket abuse |
| Credential Validation | Success + Failure | 4776 | NTLM auth attempts |
| Account Management | Success + Failure | 4720 / 4728 / 4732 | Rogue account/group changes |
| Security Group Management | Success | 4728 / 4732 / 4756 | Privilege escalation via groups |
| Special Logon | Success | 4672 | Admin-equivalent logons |
| Sensitive Privilege Use | Success + Failure | 4673 / 4674 | Debug/backup privilege abuse |
| Audit Policy Change | Success | 4719 | Attacker disabling auditing |
| Handle Manipulation / File Share | Success + Failure | 5140 / 5145 | SMB share access (lateral movement) |
PowerShell logging
Attackers operate in PowerShell, so log it comprehensively — script block logging (records the actual code executed, even obfuscated/deobfuscated), module logging, and transcription to a write-protected share:
$psBase = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\PowerShell"
# Script block logging (Event 4104 in Microsoft-Windows-PowerShell/Operational)
New-Item -Path "$psBase\ScriptBlockLogging" -Force | Out-Null
Set-ItemProperty -Path "$psBase\ScriptBlockLogging" -Name "EnableScriptBlockLogging" -Value 1 -Type DWord
# Module logging for all modules
New-Item -Path "$psBase\ModuleLogging" -Force | Out-Null
Set-ItemProperty -Path "$psBase\ModuleLogging" -Name "EnableModuleLogging" -Value 1 -Type DWord
New-Item -Path "$psBase\ModuleLogging\ModuleNames" -Force | Out-Null
Set-ItemProperty -Path "$psBase\ModuleLogging\ModuleNames" -Name "*" -Value "*"
# Transcription to a locked-down share
New-Item -Path "$psBase\Transcription" -Force | Out-Null
Set-ItemProperty -Path "$psBase\Transcription" -Name "EnableTranscripting" -Value 1 -Type DWord
Set-ItemProperty -Path "$psBase\Transcription" -Name "OutputDirectory" -Value "\\logsrv\pstranscripts$" -Type String
Ship Microsoft-Windows-PowerShell/Operational (4104), the Security log (audit events above), and Microsoft-Windows-WindowsUpdateClient/Operational (for patch telemetry) to your collector. The logs that matter for this article’s two disciplines:
| Log channel | Key events | Feeds | Why |
|---|---|---|---|
| Security | 4688, 4624/4625, 4720, 4719 | SIEM detection | Hardening violations, intrusion |
| Microsoft-Windows-PowerShell/Operational | 4104, 4103 | SIEM | Attacker tradecraft in PS |
| System | 19, 20, 43 (WU) | Patch dashboards | Update install success/failure |
| Microsoft-Windows-WindowsUpdateClient/Operational | 25, 31, 34 | Patch reporting | Scan/download/install detail |
| Setup | (CBS package events) | Patch forensics | Failed cumulative update install |
Standing up WSUS: database, content store, upstream sync
Install the role with the Windows Internal Database (WID) for small/medium fleets, or point at a SQL Server instance for large or multi-server deployments. The content directory must live on a volume with room to grow — plan for tens of GB minimum, far more if you store multiple product/language combinations or drivers.
# Install WSUS with WID and the management console
Install-WindowsFeature -Name UpdateServices, UpdateServices-WidDB, UpdateServices-Services, UpdateServices-UI
# Post-install: point at the content store (must exist, sized volume)
& "$env:ProgramFiles\Update Services\Tools\wsusutil.exe" postinstall CONTENT_DIR=D:\WSUS
WID vs SQL — pick deliberately, because migrating later is painful:
| Factor | WID (Windows Internal Database) | SQL Server |
|---|---|---|
| Cost | Free (built in) | SQL license (Express is free but capped) |
| Size cap | Practically ~10 GB comfortable; grows | Express 10 GB; Standard/Enterprise unbounded |
| Multi-server / NLB WSUS | Not supported (single server) | Required for shared SUSDB / NLB front ends |
| Remote management | Local only for the DB | Full SSMS / remote admin |
| Re-index tooling | Via named-pipe sqlcmd |
Full SQL Agent jobs |
| Best for | Single-server fleets ≤ ~few thousand clients | Large, HA, or multi-front-end deployments |
Run initial configuration through PowerShell so it is repeatable. Sync from Microsoft Update upstream, set only the languages you deploy, then kick the category sync:
$wsus = Get-WsusServer
$config = $wsus.GetConfiguration()
$config.SyncFromMicrosoftUpdate = $true # or point at an upstream WSUS for downstream/replica
$config.AllUpdateLanguagesEnabled = $false
$config.SetEnabledUpdateLanguages("en")
$config.Save()
# Category sync first (metadata), content follows on approval
$sub = $wsus.GetSubscription()
$sub.StartSynchronization()
After the category sync completes, scope products and classifications. Pulling “all products” is the classic mistake that bloats the content store and slows every sync to a crawl:
# Enable only the products you actually run
Get-WsusProduct | Where-Object { $_.Product.Title -match "Windows Server 2022|Windows 11|Microsoft Defender" } |
Set-WsusProduct
# Classifications: security-relevant only — NOT "all"
Get-WsusClassification |
Where-Object { $_.Classification.Title -in @("Security Updates","Critical Updates","Definition Updates","Update Rollups") } |
Set-WsusClassification
Which classifications to sync, and why some are traps:
| Classification | Sync it? | Why / caution |
|---|---|---|
| Security Updates | Yes | The core reason WSUS exists |
| Critical Updates | Yes | Non-security but urgent fixes |
| Definition Updates | Yes (if using WSUS for Defender) | Frequent, small; or offload to cloud-delivered |
| Update Rollups | Yes | Monthly rollups on some products |
| Updates | Selective | Large, noisy; approve individually |
| Feature Packs / Service Packs | Selective | Big content; only when planning upgrades |
| Drivers | No (default) | Enormous content; wrong driver can brick hardware |
| Upgrades (feature updates) | Selective | Multi-GB OS upgrades; manage as projects |
| Tools | No | Rarely needed via WSUS |
For multi-site fleets, understand upstream/downstream topology before you build:
| Topology | How it works | When to use |
|---|---|---|
| Single WSUS | One server syncs from Microsoft, all clients point at it | Single site, ≤ few thousand clients |
| Downstream replica | Downstream syncs updates and approvals from upstream | Branch offices that must mirror HQ decisions exactly |
| Downstream autonomous | Downstream syncs content but has its own approvals | Branches needing local approval control |
| Upstream + branch cache | WSUS metadata central, content via BranchCache/DFS | Bandwidth-constrained WAN links |
| WSUS + Delivery Optimization | Clients peer-share content on the LAN | Reduce WAN pull for large cumulative updates |
Set a recurring synchronization (e.g., daily) once products are dialed in, and never enable Drivers unless you have a specific, tested need.
Computer groups, deployment rings, and approvals
The entire value of WSUS is controlled rollout. Create groups that map to rings, not to org charts.
Ring design
A typical four-ring layout — adjust bake times to your risk tolerance and change windows:
| Ring | Group name | Population | Bake time | Approval mode |
|---|---|---|---|---|
| 0 | Ring0-Pilot |
A few non-critical servers + IT’s own machines | Day 0 | Auto-approve |
| 1 | Ring1-Early |
Broader low-risk workloads (web front ends, stateless) | Day +3 | Auto after pilot clean |
| 2 | Ring2-Broad |
Majority of the fleet | Day +7 | Manual promote |
| 3 | Ring3-Critical |
DCs, SQL, ERP, line-of-business | Day +10–14 | Manual, change-controlled |
$wsus = Get-WsusServer
"Ring0-Pilot","Ring1-Early","Ring2-Broad","Ring3-Critical" |
ForEach-Object { $wsus.CreateComputerTargetGroup($_) }
Client-side targeting
Use client-side targeting — computers self-assign to a group via GPO — so ring membership is declarative and travels with the OU structure, rather than dragging machines around in the console (server-side targeting). You enable it once on the server, then set the group name per-OU via GPO.
Server-side vs client-side targeting:
| Aspect | Server-side targeting | Client-side targeting |
|---|---|---|
| Where membership is decided | Admin drags machines in console | Client reports its group via GPO |
| Declarative? | No — manual, drifts | Yes — tied to OU/GPO |
| Scales to hundreds of machines | Painful | Trivial |
| New machine auto-lands in a ring | No (unassigned) | Yes, by OU |
| Set via | Console | GPO TargetGroup + TargetGroupEnabled |
# Tell WSUS to honor client-set target groups (do this once on the server config)
$config = $wsus.GetConfiguration()
$config.TargetingMode = "Client"
$config.Save()
The client GPO under Computer Config > Administrative Templates > Windows Components > Windows Update:
- Enable client-side targeting → set the target group name to the ring, e.g.
Ring2-Broad. Apply per-OU so each ring’s OU gets the right value. - The registry equivalents the GPO writes:
# What client-side targeting sets under the WU policy key
$wu = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate"
Set-ItemProperty -Path $wu -Name "TargetGroup" -Value "Ring2-Broad" -Type String
Set-ItemProperty -Path $wu -Name "TargetGroupEnabled" -Value 1 -Type DWord
Auto-approval and ring promotion
An auto-approval rule drives the pilot ring so security updates flow without manual touch, while later rings stay gated:
$rule = $wsus.CreateInstallApprovalRule("Auto-approve Security to Pilot")
$class = $wsus.GetUpdateClassifications() |
Where-Object { $_.Title -in @("Security Updates","Critical Updates") }
$cc = New-Object Microsoft.UpdateServices.Administration.UpdateClassificationCollection
$cc.AddRange($class)
$rule.SetUpdateClassifications($cc)
$groups = New-Object Microsoft.UpdateServices.Administration.ComputerTargetGroupCollection
$groups.Add(($wsus.GetComputerTargetGroups() | Where-Object { $_.Name -eq "Ring0-Pilot" }))
$rule.SetComputerTargetGroups($groups)
$rule.Enabled = $true
$rule.Save()
$rule.ApplyRule() # apply to already-synced updates immediately
Promote updates ring-to-ring once they bake. This approves everything currently needed-and-not-declined for the next group:
$target = $wsus.GetComputerTargetGroups() | Where-Object { $_.Name -eq "Ring1-Early" }
$wsus.GetUpdates() |
Where-Object { -not $_.IsDeclined -and $_.IsLatestRevision } |
ForEach-Object { $_.Approve("Install", $target) | Out-Null }
The approval actions available and when to use each:
| Approval action | Effect | When to use |
|---|---|---|
| Install | Download + install on the group | Normal ring promotion |
| Not Approved | Reset to no decision | Undo an approval before install |
| Decline | Hide from all clients; removes from scans | Bad, superseded, or unwanted updates |
| Approve for detection only (deprecated UI) | Report applicability without installing | Rarely — WSUS scans applicability anyway |
| Set a deadline on Install | Force install/reboot by a date | Enforce SLA on the broad/critical rings |
Approvals, declines, and cleanup: the hygiene that keeps WSUS alive
This is the section that separates a WSUS that lasts five years from one that dies in one.
Decline superseded and expired
Superseded content is the silent killer of WSUS performance — it is the main reason clients churn for hours scanning. Decline aggressively:
# Decline superseded and expired updates — the single most important hygiene task
$wsus.GetUpdates() |
Where-Object { $_.IsSuperseded -or $_.PublicationState -eq "Expired" } |
ForEach-Object { $_.Decline() }
A nuance the docs gloss over: do not blindly decline every superseded update the instant it is superseded. A superseding cumulative update sometimes is not yet approved/installed everywhere, and declining the older one too early can leave a gap. The safe rule: decline superseded updates that are older than ~30 days or where the superseding update is already approved to the broad ring.
Run cleanup on a schedule
Run the server cleanup wizard programmatically — this is non-negotiable monthly maintenance, not an occasional chore:
$scope = New-Object Microsoft.UpdateServices.Administration.CleanupScope
$scope.DeclineSupersededUpdates = $true
$scope.DeclineExpiredUpdates = $true
$scope.CleanupObsoleteUpdates = $true
$scope.CleanupUnneededContentFiles = $true
$scope.CleanupObsoleteComputers = $true # removes clients not checked in ~30 days
$scope.CompressUpdates = $true
$mgr = $wsus.GetCleanupManager()
$mgr.PerformCleanup($scope)
Each cleanup task and what it reclaims:
| Cleanup task | What it does | Reclaims | Cadence |
|---|---|---|---|
| Decline superseded | Declines replaced updates | Scan time, metadata | Monthly |
| Decline expired | Declines Microsoft-expired updates | Metadata | Monthly |
| Cleanup obsolete updates | Removes declined/superseded revisions | SUSDB rows | Monthly |
| Cleanup unneeded content files | Deletes content for declined/unapproved | Content disk (GBs) | Monthly |
| Cleanup obsolete computers | Removes stale clients (~30d no report) | SUSDB rows, cleaner reports | Monthly |
| Compress updates | Removes older update revisions | SUSDB rows | Monthly |
Re-index the SUSDB
On large WSUS databases, cleanup is not enough — the SUSDB fragments and console/scan operations time out. Re-index monthly. WID is reachable via a named-pipe connection string with sqlcmd:
# Connect to WID via named pipe and run the well-known WSUS re-index script
$wid = "np:\\.\pipe\MICROSOFT##WID\tsql\query"
sqlcmd -S $wid -i "C:\WSUS-maint\WsusDBMaintenance.sql"
The Microsoft-published WsusDBMaintenance.sql reorganizes/rebuilds the fragmented indexes and updates statistics. Without it, GetUpdates(), console loads, and client scans progressively slow until they fail. On SQL Server, schedule this as a SQL Agent job instead. The monthly maintenance runbook, in order:
| # | Task | Command | Why in this order |
|---|---|---|---|
| 1 | Decline superseded/expired | $_.Decline() on superseded/expired |
Shrinks what cleanup must process |
| 2 | Server cleanup (all scopes) | PerformCleanup($scope) |
Removes obsolete rows + content |
| 3 | Re-index SUSDB | WsusDBMaintenance.sql via sqlcmd |
Defragments after row churn |
| 4 | Update statistics | (part of the script) | Keeps query plans sane |
| 5 | Verify sync + report | Get-WsusServer health, reporting |
Confirm the fleet still checks in |
Automate the whole runbook with a scheduled task running monthly at a quiet hour, and alert on failure.
Client targeting, WUfB vs WSUS, and third-party patching
Pointing clients at WSUS via GPO
Point clients at WSUS and set their install behavior under Computer Config > Administrative Templates > Windows Components > Windows Update. The key settings and the registry values they write:
| GPO setting | Registry value | Example | Note |
|---|---|---|---|
| Specify intranet Microsoft update service location | WUServer, WUStatusServer |
http://wsus.corp.example:8530 |
Both URLs; not port 80/443 |
| Set alternate download server (optional) | UpdateServiceUrlAlternate |
(CDN/DO) | For content offload |
| Enable client-side targeting | TargetGroup, TargetGroupEnabled |
Ring2-Broad, 1 |
Per-OU |
| Configure Automatic Updates | AUOptions |
3 (notify) / 4 (auto+schedule) |
4 = scheduled install |
| Scheduled install day/time | ScheduledInstallDay/Time |
Sat 03:00 | Maintenance window |
| No auto-restart with logged-on users (scheduled) | NoAutoRebootWithLoggedOnUsers |
1 |
Servers with consoles |
| Delay restart for scheduled installs | RebootRelaunchTimeout |
minutes | Give warning time |
| Allow signed updates from intranet MS update | AcceptTrustedPublisherCerts |
1 |
Required for third-party (see below) |
WSUS uses port 8530 (HTTP) and 8531 (HTTPS) by default — not 80/443. Getting the port wrong is the most common “clients can’t connect.” If you front WSUS with HTTPS (recommended, since update metadata is otherwise cleartext), configure the binding, run wsusutil configuresslcertificate, and use the 8531 URL.
WUfB vs WSUS — the right tool for each slice
Windows Update for Business (WUfB) defers and rings updates via policy while machines pull directly from Microsoft’s CDN — no on-prem server, no content store. It is the right tool for roaming laptops, DR/cloud VMs, and internet-facing machines that cannot reliably reach an internal WSUS. WSUS remains right for tightly controlled, bandwidth-sensitive on-prem fleets. Many estates run both: WSUS for the datacenter, WUfB/Intune for the mobile fleet.
| Dimension | WSUS | WUfB (GPO/Intune) |
|---|---|---|
| Content source | Your on-prem cache | Microsoft CDN (direct) |
| Approval model | Explicit per-group approvals | Deferral/ring policy (no manual approve) |
| Bandwidth | LAN pull (one WAN download) | Each client pulls from internet |
| Roaming/internet machines | Poor (must reach WSUS) | Ideal |
| Air-gapped / DMZ | Ideal (offline import possible) | Not possible (needs internet) |
| Granular “hold this exact KB” | Yes (decline) | Limited (pause/rollback window) |
| Reporting | WSUS reports / MECM | Intune / Update Compliance (Log Analytics) |
| Third-party patches | Yes (imported/local publishing) | No (Windows/MS content only) |
| Best for | Datacenter, regulated, WAN-constrained | Laptops, cloud VMs, modern management |
Do not run both WSUS and WUfB feature-update policy against the same machine in conflicting ways — dual-scan confusion (a machine pointed at WSUS but also told to defer via WUfB) is a well-known source of clients that scan the wrong source. Pick one authority per machine.
Third-party patching
WSUS natively only distributes Microsoft content. Third-party apps (Chrome, Firefox, Java, Adobe, 7-Zip) are your largest unpatched attack surface and WSUS ignores them. Options:
| Approach | How it works | Cost | Effort | Best for |
|---|---|---|---|---|
| WSUS local publishing (self-signed) | Publish your own signed update packages to WSUS | Free (PKI + effort) | High | Small custom app set |
| MECM/SCCM + third-party catalog | Config Manager subscribes to a patch catalog | SCCM license | Medium | Existing SCCM shops |
| PatchMyPC / third-party publisher | Commercial catalog publishes into WSUS/SCCM/Intune | Per-seat license | Low | Broad third-party coverage |
| Intune Win32 apps / Enterprise App Management | Cloud-managed app + update rings | Intune license | Medium | Modern-managed devices |
| winget + scheduled task | Package-manager upgrade on a schedule | Free | Medium | Homogeneous, non-regulated fleets |
For local publishing you must trust a signing certificate on clients (AcceptTrustedPublisherCerts=1 plus the cert in the local machine’s Trusted Publishers store), which is why most shops buy a catalog rather than build one.
Compliance reporting, patch latency, and rollback
A patch program you cannot measure is a patch program you cannot defend in an audit.
Compliance reporting
Build a per-computer compliance summary straight from the WSUS object model:
$wsus = Get-WsusServer
$scope = New-Object Microsoft.UpdateServices.Administration.ComputerTargetScope
$wsus.GetComputerTargets($scope) | ForEach-Object {
$c = $_
$s = $c.GetUpdateInstallationSummary()
[pscustomobject]@{
Computer = $c.FullDomainName
Group = ($c.GetComputerTargetGroups() | Select-Object -First 1).Name
NeededCount = $s.NotInstalledCount + $s.DownloadedCount
FailedCount = $s.FailedCount
LastReported = $c.LastReportedStatusTime
OS = $c.OSDescription
}
} | Sort-Object NeededCount -Descending | Format-Table -AutoSize
The compliance states WSUS reports per update/computer, and what each means:
| State | Meaning | Action |
|---|---|---|
| Installed | Update applied successfully | Nothing |
| InstalledPendingReboot | Applied; reboot required to finish | Enforce reboot within SLA |
| NotInstalled | Approved but not yet installed | Watch; chase if past deadline |
| Downloaded | Content on client, awaiting install window | Normal pre-window |
| Failed | Install error (often 0x800f08xx) | Investigate CBS/Setup log |
| NotApplicable | Update does not apply to this machine | Nothing (expected) |
| Unknown / NoStatus | Client hasn’t reported | Fix scan/SusClientId issue |
Patch latency — the metric that actually matters
The metric that matters is patch latency: the gap between an update’s release (second Tuesday) and its install across each ring. Track the 90th percentile per ring, not the average — the average hides the long tail of machines that never reboot. A healthy fleet shows Pilot near-zero, Broad inside your SLA window, and Critical trailing intentionally. When the Critical ring’s p90 blows past its window, chase reboot enforcement, not faster approvals.
| Ring | Target install SLA (from release) | Track | Alert if p90 exceeds |
|---|---|---|---|
| Ring0-Pilot | ≤ 24 hours | p90 latency | 2 days |
| Ring1-Early | ≤ 4 days | p90 latency | 6 days |
| Ring2-Broad | ≤ 8 days | p90 latency | 12 days |
| Ring3-Critical | ≤ 14 days (change-controlled) | p90 latency + reboot state | 21 days |
Verify clients are actually checking in and approvals are landing — never assume a GPO applied just because you linked it:
# Machines that have not reported in 7 days are a red flag (stuck scans, dead clients)
$wsus.GetComputerTargets() |
Where-Object { $_.LastReportedStatusTime -lt (Get-Date).AddDays(-7) } |
Select-Object FullDomainName, LastReportedStatusTime, ComputerRole
Rollback
Every ring exists so a bad update dies in the pilot. But if one reaches production, you need a clean reversal. First decline it in WSUS so no more machines install it, then uninstall it from those that did:
# 1. Stop the bleeding: decline the bad update so it's removed from approvals/scans
$bad = $wsus.GetUpdates() | Where-Object { $_.Title -match "KB5099999" }
$bad | ForEach-Object { $_.Decline() }
# 2. On affected clients: uninstall the specific KB
wusa /uninstall /kb:5099999 /quiet /norestart
# Or via DISM for packages wusa can't remove:
$pkg = (Get-WindowsPackage -Online | Where-Object PackageName -match "5099999").PackageName
Remove-WindowsPackage -Online -PackageName $pkg -NoRestart
The rollback methods, ranked by scope:
| Method | Scope | Command | Caveat |
|---|---|---|---|
| Decline in WSUS | Stops future installs | $update.Decline() |
Does not remove from already-patched hosts |
wusa /uninstall /kb: |
One KB, one host | wusa /uninstall /kb:NNN |
Many cumulative updates block wusa uninstall |
Remove-WindowsPackage (DISM) |
One package, one host | DISM package removal | Needs the exact package name |
| Cumulative-update caveat | LCUs are hard to remove | — | Often must roll forward, not back |
| System Restore / snapshot | Whole OS state | Restore point / VM snapshot | Blunt; loses other changes |
| Reimage from golden image | Whole machine | Rebuild pipeline | Last resort; slow |
The hard truth: modern latest cumulative updates (LCUs) frequently cannot be uninstalled cleanly (only the servicing-stack or the reversible portion comes out). This is why the ring model matters — for LCUs, “roll back” often means “roll forward to the next fixed cumulative update,” and your only real safety net is having caught the bad one in the pilot ring before it spread.
Architecture at a glance
Picture the system as two coupled loops sharing one fleet of servers. The hardening loop starts at the Security Compliance Toolkit on an admin workstation: you snapshot local policy with LGPO, diff the Microsoft baseline against effective policy in Policy Analyzer, then import the role-specific baseline GPO backups into GPMC. Those GPOs — a member-server baseline, a DC baseline, plus a small stack of clearly-named override/deviation GPOs — link first to a Pilot OU, then, once validated, to the broader Servers and Domain Controllers OUs. Group Policy pushes the settings down to every domain-joined server: SMBv1 removed, SMB signing required, Schannel trimmed to TLS 1.2/1.3, advanced audit and PowerShell logging on. Those servers stream 4688 / 4104 / SMB-signing audit events into a SIEM, closing the loop — you can now prove the hardened state and detect violations of it.
The patch loop runs on the same OU structure but through a different control plane. A single WSUS server (WID or SQL SUSDB, a sized content store on D:\WSUS) syncs metadata and content from Microsoft Update upstream — scoped to just the products and the Security/Critical/Definition/Rollup classifications you chose. Client servers discover WSUS via the Windows Update GPO (WUServer=http://wsus.corp.example:8530) and self-assign to a computer group through client-side targeting (TargetGroup set per-OU). Those groups are the deployment rings: Ring0-Pilot gets an auto-approval rule that installs Security/Critical the moment they sync; Ring1-Early, Ring2-Broad, and Ring3-Critical are gated and promoted on a cadence with PowerShell. Every month a scheduled maintenance job declines superseded/expired updates, runs the cleanup wizard, and re-indexes the SUSDB over the WID named pipe so the console never times out. A reporting job pulls per-computer compliance and computes patch-latency p90 per ring, and if a bad KB slips through, the operator declines it in WSUS and uninstalls it on the few pilot hosts that took it — before it ever reached a domain controller.
The two loops meet at the fleet and at the golden image: the image ships with the baseline pre-applied and the SusClientId cleared, so a freshly-provisioned server lands in the right OU, inherits the baseline, and reports to WSUS as a distinct client from its first boot. Read the whole thing as: harden by GPO to a pilot first, patch by ring to a pilot first, prove both with logs and latency, and reverse either safely because you never went fleet-wide blind.
Real-world scenario
Northwind Logistics runs about 520 Windows servers across a primary datacenter (Pune) and two branch DCs, plus ~2,000 client endpoints. The platform team is five engineers. Before this project they had a hardening script from 2023 nobody dared re-run, and a single WSUS approving “all classifications to All Computers.” Their monthly Patch Tuesday routine was: approve everything Wednesday, reboot the fleet Saturday, and firefight whatever broke Monday. Compliance was a spreadsheet updated by hand for the annual ISO 27001 audit.
Two incidents forced the rebuild. First, a March cumulative update bluescreened four domain controllers simultaneously at 03:12 on the reboot Saturday — because the DCs were in the same “All Computers” bucket as everything else, they all took the same bad LCU at once, and the branch offices lost authentication for two hours until a DC was rolled forward. Second, the ISO auditor asked them to “demonstrate SMB signing is enforced fleet-wide” and they could not — the 2023 script’s setting had been silently overridden by a drive-mapping GPO on 140 servers, and nobody knew until they RDP’d to a sample and found signing not required.
The rebuild took six weeks. They imported the Microsoft Server 2022 member-server and DC baselines via GPMC, diffed them in Policy Analyzer against the current mess, and found 23 deviations — 18 they kept as documented exceptions (two legacy appliances that could not do SMB signing, isolated into their own OU with a named override GPO) and 5 that were accidental drift they removed. They linked baselines to a Pilot OU of eight servers for two weeks before rolling to the fleet. On the patch side they scoped WSUS to just Windows Server 2019/2022, Windows 10/11, and Defender products, cut the SUSDB from 31 GB to 6 GB with a first aggressive decline-superseded + cleanup + re-index pass (client scan time dropped from ~40 minutes to under 4), and built four rings with client-side targeting tied to OUs. Domain controllers went into Ring3-Critical, patched manually two weeks behind the pilot, one site at a time.
The payoff showed the very next Patch Tuesday. A problematic definition-adjacent update caused a services-hang on two Ring0-Pilot web servers within hours of auto-approval. The on-call engineer saw it in the pilot, declined the KB before it ever reached Ring1, and filed it as a tracked exception. The broad fleet never saw it. Patch-latency reporting now shows Pilot p90 at 18 hours, Broad at 6 days, Critical at 11 days — all inside SLA — and the ISO auditor got a gpresult-backed, Get-WsusServer-backed compliance export instead of a spreadsheet. The DC bluescreen class of incident became structurally impossible: a bad LCU now hits eight pilot boxes, not four DCs at 3 a.m. The lesson on the wall: “Rings and baselines are the same idea — never let a change go fleet-wide before a small, watched cohort has survived it.”
The incident-to-program transformation, as a before/after:
| Dimension | Before (all-or-nothing) | After (ring + baseline) |
|---|---|---|
| Bad-update blast radius | Whole fleet (4 DCs at once) | 8 pilot servers |
| SUSDB size | 31 GB, console timing out | 6 GB, healthy |
| Client scan time | ~40 min | < 4 min |
| SMB-signing proof | Manual RDP sampling | gpresult export, provable |
| Compliance evidence | Hand-kept spreadsheet | WSUS + GPO scripted export |
| Patch-latency visibility | None | p90 per ring, alerted |
| DC patching | Same bucket as test VMs | Ring3, manual, one site at a time |
Advantages and disadvantages
The WSUS-plus-baseline model is powerful and free, but it is genuinely operationally heavy — weigh it honestly:
| Advantages | Disadvantages |
|---|---|
| Free (WSUS role + SCT); no per-seat licensing | High operational burden: cleanup, re-index, ring promotion are ongoing manual/scripted work |
| Full control — approve/decline this exact KB per ring | WSUS decays without discipline (superseded bloat, SUSDB timeouts) if hygiene lapses |
| LAN content cache — one WAN download for hundreds of clients | Single-server WSUS is a SPOF; HA needs SQL + NLB complexity |
| Air-gap capable (offline metadata/content import) | Poor for roaming/internet machines — they must reach WSUS |
Baseline is provable and reversible (GPO backups, gpresult) |
Baseline can break legacy apps (SMB signing, TLS 1.0 removal); needs careful staging |
| Ring model makes blast radius small and bad updates catchable | No native third-party patching — largest attack surface (Chrome/Java) ignored |
| Everything scriptable (repeatable, survives staff turnover) | LCUs often can’t be cleanly uninstalled — rollback is really “roll forward” |
| Deep, on-prem compliance reporting without cloud dependency | Reporting is clunkier than cloud Update Compliance / Intune dashboards |
The model is right for datacenter fleets, regulated environments, and bandwidth-constrained or air-gapped sites where control and a local cache matter more than operational lightness. It is the wrong sole tool for a laptop-heavy, cloud-first estate — those belong on WUfB/Intune. Most mature shops run both and use this article’s WSUS for the servers, cloud rings for the mobile fleet.
Hands-on lab
Stand up a minimal WSUS, scope it, build a ring, wire a client, and run a hygiene pass — all on two VMs (one server, one client). Budget ~90 minutes. Tear down at the end.
Step 1 — Install WSUS with WID and console. On a Server 2022 member server with a spare data disk mounted as D::
Install-WindowsFeature -Name UpdateServices, UpdateServices-WidDB, UpdateServices-Services, UpdateServices-UI
New-Item -Path "D:\WSUS" -ItemType Directory -Force | Out-Null
& "$env:ProgramFiles\Update Services\Tools\wsusutil.exe" postinstall CONTENT_DIR=D:\WSUS
Expected: Post install is complete and the WSUS console appears under Server Manager > Tools.
Step 2 — Configure upstream, languages, and start the category sync.
$wsus = Get-WsusServer
$cfg = $wsus.GetConfiguration()
$cfg.SyncFromMicrosoftUpdate = $true
$cfg.AllUpdateLanguagesEnabled = $false
$cfg.SetEnabledUpdateLanguages("en")
$cfg.TargetingMode = "Client" # enable client-side targeting now
$cfg.Save()
$wsus.GetSubscription().StartSynchronization()
Expected: Get-WsusServer | Select-Object -ExpandProperty ... synchronization status moves through Downloading categories. This first sync can take 20–60 minutes.
Step 3 — Scope products and classifications (after category sync completes).
Get-WsusProduct | Where-Object { $_.Product.Title -match "Windows Server 2022|Windows 11" } | Set-WsusProduct
Get-WsusClassification |
Where-Object { $_.Classification.Title -in @("Security Updates","Critical Updates") } |
Set-WsusClassification
Validate: Get-WsusProduct | Where-Object { $_.Product.Title -match "Windows Server 2022" } shows the product enabled.
Step 4 — Create rings and an auto-approval rule for the pilot.
"Ring0-Pilot","Ring1-Early","Ring2-Broad","Ring3-Critical" |
ForEach-Object { $wsus.CreateComputerTargetGroup($_) }
$rule = $wsus.CreateInstallApprovalRule("Auto-approve Security to Pilot")
$cc = New-Object Microsoft.UpdateServices.Administration.UpdateClassificationCollection
$cc.AddRange(($wsus.GetUpdateClassifications() | Where-Object { $_.Title -in @("Security Updates","Critical Updates") }))
$rule.SetUpdateClassifications($cc)
$g = New-Object Microsoft.UpdateServices.Administration.ComputerTargetGroupCollection
$g.Add(($wsus.GetComputerTargetGroups() | Where-Object { $_.Name -eq "Ring0-Pilot" }))
$rule.SetComputerTargetGroups($g); $rule.Enabled = $true; $rule.Save()
Validate: $wsus.GetInstallApprovalRules() | Select Name, Enabled lists the rule as True.
Step 5 — Point a test client at WSUS and its ring (on the client VM).
$wu = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate"
New-Item -Path $wu -Force | Out-Null
New-Item -Path "$wu\AU" -Force | Out-Null
Set-ItemProperty $wu -Name WUServer -Value "http://wsus.corp.example:8530" -Type String
Set-ItemProperty $wu -Name WUStatusServer -Value "http://wsus.corp.example:8530" -Type String
Set-ItemProperty $wu -Name TargetGroup -Value "Ring0-Pilot" -Type String
Set-ItemProperty $wu -Name TargetGroupEnabled -Value 1 -Type DWord
Set-ItemProperty "$wu\AU" -Name UseWUServer -Value 1 -Type DWord
Step 6 — Force the client to register and scan.
Restart-Service wuauserv
UsoClient StartScan # modern scan trigger (wuauclt /detectnow is deprecated)
Expected: within a few minutes the client appears in the WSUS console under Ring0-Pilot. Verify from the server:
Get-WsusServer | ForEach-Object { $_.GetComputerTargets() } |
Select-Object FullDomainName, LastReportedStatusTime,
@{N='Group';E={($_.GetComputerTargetGroups()|Select -First 1).Name}}
Step 7 — Run a hygiene pass (decline superseded + cleanup).
$wsus.GetUpdates() | Where-Object { $_.IsSuperseded -or $_.PublicationState -eq "Expired" } | ForEach-Object { $_.Decline() }
$scope = New-Object Microsoft.UpdateServices.Administration.CleanupScope
$scope.DeclineSupersededUpdates = $true; $scope.CleanupObsoleteUpdates = $true
$scope.CleanupUnneededContentFiles = $true; $scope.CompressUpdates = $true
$wsus.GetCleanupManager().PerformCleanup($scope)
Step 8 — Teardown.
# On the client: remove the WU policy so it goes back to default Windows Update
Remove-Item "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate" -Recurse -Force
# On the server: uninstall WSUS and reclaim the content store
Uninstall-WindowsFeature -Name UpdateServices, UpdateServices-WidDB, UpdateServices-Services, UpdateServices-UI
Remove-Item "D:\WSUS" -Recurse -Force -ErrorAction SilentlyContinue
# WID's SUSDB removal may require also removing the Windows Internal Database feature
Common mistakes & troubleshooting
The failure modes here are predictable. Symptom → root cause → how to confirm → fix:
| # | Symptom | Root cause | Confirm | Fix |
|---|---|---|---|---|
| 1 | Baseline broke a legacy agent’s connectivity | SMB signing required; agent can’t negotiate | Get-WinEvent Microsoft-Windows-SMBServer/Security event 1006 names the client |
Upgrade agent; isolate laggards to an override-GPO OU — never disable signing fleet-wide |
| 2 | Fleet-wide outage after linking baseline | Linked to domain root, not pilot OU | GPMC shows link at domain root | Unlink from root; link to Pilot OU; re-pilot |
| 3 | Clients report “can’t connect to update server” | Wrong port (used 80/443) | Get-ItemProperty ...\WindowsUpdate -Name WUServer shows no :8530 |
Set URL to http://wsus:8530 (8531 for HTTPS) |
| 4 | “Phantom” clients sharing one record; never patch | Duplicate SusClientId from cloning without sysprep |
Two machines, same ID in console | Clear SusClientId, delete SoftwareDistribution, re-register (below) |
| 5 | Client scans take hours / times out | Superseded updates never declined | Huge approved/needed count; SUSDB large | Decline superseded + cleanup + SUSDB re-index |
| 6 | Console throws SQL timeouts | Fragmented SUSDB indexes | WsusDBMaintenance.sql reports heavy fragmentation |
Run the re-index script monthly |
| 7 | Content store filling the disk | Drivers synced, or content for declined updates not cleaned | Get-WsusProduct/classifications; disk usage |
Disable Drivers; run cleanup CleanupUnneededContentFiles |
| 8 | Update installs then reverts each reboot | Corrupt component store / pending operation | DISM /Online /Cleanup-Image /CheckHealth; CBS.log |
DISM /RestoreHealth; sfc /scannow |
| 9 | New servers land in “Unassigned Computers” | Client-side targeting GPO not on their OU | gpresult /r shows no TargetGroup |
Link the WU/targeting GPO to that OU |
| 10 | Bad KB reached production | Ring0 auto-approval bypassed testing OR DCs in wrong group | Check group membership; approval history | Decline KB; wusa /uninstall; move DCs to Ring3 |
| 11 | Machine scans Microsoft directly, ignores WSUS | Dual-scan: WSUS + WUfB feature-update policy conflict | AUOptions/UseWUServer vs WUfB deferral policies |
Pick one authority; remove conflicting policy |
| 12 | Update fails with 0x800f0831 / 0x80073701 | Missing prerequisite servicing-stack update or payload | Setup/CBS log names missing package | Approve the matching SSU first; ensure media source |
| 13 | LCU won’t uninstall | Modern cumulative updates block wusa removal | wusa /uninstall errors “not installed/removable” |
Roll forward to next fixed LCU; snapshot restore if critical |
| 14 | Clients not reporting after WSUS migration | Old SusClientId/URL cached |
LastReportedStatusTime stale |
Reset auth + re-register; verify new WUServer |
The canonical client reset for stuck/duplicate clients — the number-one WSUS support flow:
# Stop services, clear the identity + cached state, re-register, rescan
Stop-Service wuauserv, bits -Force
Remove-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows\WindowsUpdate" -Name SusClientId, SusClientIdValidation -ErrorAction SilentlyContinue
Remove-Item "$env:windir\SoftwareDistribution" -Recurse -Force -ErrorAction SilentlyContinue
Start-Service wuauserv, bits
wuauclt /resetauthorization # re-authorize against WSUS
UsoClient StartScan # modern scan trigger
Reconstruct the client-side update log when you need the ground truth (it is ETW now, not a flat file):
Get-WindowsUpdateLog # merges the ETW traces into WindowsUpdate.log on the desktop
The common Windows Update client error codes you will actually see:
| Code | Meaning | Typical cause | Fix |
|---|---|---|---|
| 0x80244022 | HTTP 503 from WSUS | WSUS app pool stopped / recycling | Restart WsusPool in IIS; check IIS |
| 0x8024401C | HTTP 408 timeout to WSUS | WSUS overloaded / SUSDB slow | Re-index SUSDB; check server load |
| 0x80072EE2 | Timeout reaching update source | Firewall/proxy blocking 8530/8531 | Open ports; verify WUServer URL |
| 0x800f0831 | CU install: missing package | Missing prior SSU/prerequisite | Approve/install the SSU first |
| 0x80073701 | ERROR_SXS_ASSEMBLY_MISSING | Component store corruption | DISM /RestoreHealth; sfc /scannow |
| 0x8024000B | Operation cancelled / call not applicable | Superseded/declined mid-scan | Re-scan; clean up superseded |
| 0x80070422 | Service disabled | wuauserv set Disabled |
Set wuauserv to Manual/Automatic |
Best practices
- Start from the vetted baseline, never a PDF. Import the Microsoft SCT baseline, diff it in Policy Analyzer, and keep a deviation register (a separate, named override GPO per exception). Hardening you cannot diff and reverse is folklore.
- Pilot everything. Link baselines and drive auto-approvals to a small Pilot OU/Ring0 first — never the domain root, never “All Computers.” A bad setting or KB should hit 8 machines, not 500.
- Remove, don’t just disable. SMBv1, PowerShell v2, unused roles — remove the feature so it cannot silently return.
- Put domain controllers in the last, manually-gated ring. DCs are the crown jewels and the thing a bad LCU bricks hardest. Patch them behind the pilot, one site at a time.
- Decline superseded/expired monthly, then cleanup, then re-index the SUSDB. In that order. This single runbook is the difference between a healthy WSUS and one that times out at month twelve.
- Scope products and classifications tightly; never sync Drivers. “All products/all classifications” is how the content store and SUSDB balloon and syncs crawl.
- Use client-side targeting tied to OUs. Ring membership is declarative and travels with the org structure — no console drag-and-drop drift.
- Measure patch-latency p90 per ring, not average. The average hides the never-reboots tail. Alert when the Critical ring’s p90 exceeds its window.
- Front WSUS with HTTPS (8531) and enforce reboots within SLA. Metadata is otherwise cleartext, and “InstalledPendingReboot” is not “patched.”
- Clear
SusClientIdin the golden image. Every clone-without-sysprep produces a phantom client that looks compliant and never patches. - Log 4688-with-cmdline and PowerShell 4104 to a SIEM. Hardening you cannot see being violated is half a control.
- Pick one update authority per machine. WSUS or WUfB — dual-scan conflicts send clients to the wrong source.
- Plan rollback as “roll forward” for LCUs. Modern cumulative updates rarely uninstall cleanly; the ring model catching the bad one is your real safety net.
Security notes
The whole article is a security control, but a few cross-cutting points bear emphasizing. Least privilege on WSUS itself: the WSUS server is a fleet-wide software-distribution channel — anyone who can approve updates can, in effect, push code to every machine. Restrict console/approval rights (the WSUS Administrators local group / delegated roles) to a small team, log approval actions, and treat the WSUS server as a Tier 0 / high-value asset — patch it first, harden it hardest, and never let it be a general-purpose box.
Content and metadata integrity: Microsoft signs update packages, and WSUS validates those signatures — but the metadata channel (which updates exist, applicability) is HTTP by default. Enable SSL on WSUS (8531) so a network attacker cannot tamper with scan results, and configure the SSL certificate with wsusutil configuresslcertificate. For third-party local publishing, you are introducing your own signing certificate into every client’s Trusted Publishers store — protect that signing key like a code-signing key, because it is one.
The baseline is a security boundary you must not casually relax. The single most common self-inflicted wound is weakening a fleet-wide control (SMB signing, LDAP signing, TLS minimums) to accommodate one non-compliant legacy device. Always audit which endpoint is failing (the SMB failure-audit channel, LDAP interface events) and fix or isolate that device — put unfixable appliances in their own OU with a documented, separately-named override GPO, and make their patch/config gap a tracked exception, not a hole in the fleet-wide policy. NTLM and LDAP hardening should go audit-first: enable the auditing events, watch for a week to find what still uses the legacy protocol, then enforce. Enforcing blind is how you take out authentication.
Finally, separate identity tiers: the account that administers WSUS and links baseline GPOs should be a privileged, tiered admin account (see the tiered model in Building an AD DS Forest the Right Way), never a daily-driver account, and local admin on the fleet should be randomized by Windows LAPS so a single compromised server does not become a fleet-wide pivot.
Cost & sizing
WSUS is free in licensing — the cost is compute, storage, and operational time. Rough sizing and figures (INR for a self-hosted VM; adjust to your platform):
| Component | Small fleet (≤250 clients) | Medium (250–2,000) | Large (2,000–10,000) |
|---|---|---|---|
| WSUS VM | 2 vCPU / 4–8 GB RAM | 4 vCPU / 8–16 GB RAM | 8 vCPU / 16–32 GB RAM |
| Database | WID | WID or SQL Express | SQL Standard (Express 10 GB cap hurts) |
| Content store | 40–100 GB | 100–300 GB | 300 GB–1 TB+ |
| Monthly VM cost (self-host est.) | ~₹2,500–4,000 | ~₹5,000–9,000 | ~₹12,000–25,000 + SQL license |
| Operational time | ~2–4 hrs/month | ~4–8 hrs/month | ~1 FTE partial + automation |
What drives the bill and how to control it:
| Cost driver | Impact | Control |
|---|---|---|
| Content store size | Disk + backup cost | Scope products/classifications; never sync Drivers; run cleanup |
| SUSDB growth | SQL license tier (Express cap) / IO | Decline superseded + re-index; cleanup obsolete |
| WAN bandwidth | Egress / link saturation | LAN cache is the saving; add BranchCache/DO for branches |
| Operational hours | Staff cost | Automate the monthly runbook; script ring promotion |
| Reboots / downtime | Business impact | Rings + maintenance windows; enforce within SLA, not all at once |
The savings are the point: without WSUS, hundreds of clients each pull every cumulative update from the internet — a 1–2 GB LCU × 500 machines is ~0.5–1 TB of WAN egress every Patch Tuesday. WSUS turns that into a single download cached on the LAN. For air-gapped or metered sites, that alone justifies the server many times over. Free-tier equivalent for cloud-first estates: WUfB is free (no server), trading the LAN cache and granular approval control for zero infrastructure — the right call for laptop-heavy fleets, the wrong one for bandwidth-constrained datacenters.
Interview & exam questions
1. Why import the Microsoft security baseline via GPMC instead of applying it with LGPO on each server?
LGPO applies local policy — fine for standalone/workgroup boxes and for snapshotting, but in a domain, local policy is the weakest link in precedence and drifts per-machine. Importing the baseline GPO backups into GPMC and linking to OUs makes the hardening enforced, versioned, centrally reversible, and provable via gpresult. Maps to SC-100 / security baseline exam objectives.
2. What is the single most important WSUS maintenance task and why? Declining superseded/expired updates, then running cleanup, then re-indexing the SUSDB — monthly, in that order. Superseded updates accumulate as metadata and content that every client must still evaluate on each scan, so neglecting this is the top cause of hours-long scans, a bloated SUSDB, and console timeouts.
3. Explain the difference between server-side and client-side targeting.
Server-side: an admin manually places each machine into a computer group in the console — non-declarative and drifts. Client-side: each machine reports its group via the TargetGroup/TargetGroupEnabled GPO, so ring membership is tied to the OU structure and new machines auto-land in the right ring. Client-side is the scalable, declarative choice.
4. A cumulative update bluescreens some servers. Walk through rollback.
First decline the KB in WSUS to stop further installs. Then on affected hosts, wusa /uninstall /kb:NNNN or Remove-WindowsPackage via DISM. The catch: modern LCUs frequently can’t be uninstalled cleanly, so rollback often means rolling forward to the next fixed cumulative update — which is exactly why the ring model (catch it in the pilot) is the real safety net.
5. When would you choose WUfB over WSUS? For roaming laptops, cloud/DR VMs, and internet-facing machines that can’t reliably reach an internal WSUS — WUfB defers/rings updates via policy while machines pull directly from Microsoft’s CDN, needing no on-prem server. WSUS wins for datacenter, air-gapped, bandwidth-constrained, or regulated fleets needing granular per-KB approval and a LAN cache.
6. What is patch latency and why track p90 per ring rather than average? Patch latency is the gap between an update’s release and its install. The average hides the long tail of machines that never reboot; p90 exposes it. Healthy: Pilot near-zero, Broad in SLA, Critical trailing intentionally. A Critical-ring p90 blowing past its window signals a reboot-enforcement problem, not an approval-speed one.
7. Why remove SMBv1 as a feature rather than disable it? Disabling leaves the feature installed and one errant role-install away from silently returning. Removing the Windows optional feature makes the wormable, unsigned SMBv1 protocol impossible to re-enable by accident — attack-surface reduction should eliminate the class of bug, not just toggle it off.
8. How do you patch domain controllers safely? Put them in the last ring (Ring3-Critical), patched manually two weeks behind the pilot, one site at a time, inside a change window — never in the same “All Computers” bucket as test machines. This prevents a bad LCU from bricking all DCs simultaneously and losing authentication. For clustered roles, use Cluster-Aware Updating instead.
9. What’s the “dual-scan” problem?
A machine pointed at WSUS (UseWUServer=1) but also subject to WUfB feature-update deferral policy can get confused about which source to scan, sometimes pulling feature updates from Microsoft directly against your intent. The fix is to pick one update authority per machine and remove the conflicting policy.
10. Why must PowerShell script block logging (4104) be enabled and why remove PowerShell v2? Attackers operate in PowerShell; 4104 records the actual (even deobfuscated) code executed, feeding SIEM detection. PowerShell v2 bypasses script block logging and modern security features, so removing the v2 engine feature closes a logging-evasion path.
11. What ports does WSUS use and why does it matter? 8530 (HTTP) and 8531 (HTTPS) by default — not 80/443. Configuring clients with the wrong port is the most common “can’t connect to update server.” HTTPS on 8531 also protects the otherwise-cleartext metadata channel from tampering.
12. How do you produce audit-ready patch compliance evidence?
Pull per-computer install summaries from the WSUS object model (GetComputerTargets → GetUpdateInstallationSummary), export needed/failed counts and last-reported times per ring, and pair it with gpresult output proving the baseline applied. This replaces hand-kept spreadsheets with a scripted, reproducible export.
Quick check
- In what order do you run the monthly WSUS hygiene tasks, and why does order matter?
- Which OU should a freshly-imported Microsoft baseline GPO link to first, and what happens if you link it to the domain root?
- Your clients report “can’t connect to the update server.” What is the first setting to check?
- Why is p90 patch latency per ring more useful than the fleet average?
- A bad LCU installed on 200 servers. Why might
wusa /uninstallnot be enough, and what is the real safety net?
Answers
- Decline superseded/expired → run cleanup → re-index the SUSDB. Declining first shrinks what cleanup must process; cleanup removes obsolete rows/content, causing row churn; re-indexing then defragments the resulting indexes. Doing them out of order (e.g., re-indexing before cleanup) wastes effort re-indexing rows you’re about to delete.
- A dedicated Pilot OU of a few servers. Linking to the domain root applies the baseline to every domain-joined machine at once, turning one bad or app-breaking setting into a fleet-wide outage — the exact failure the pilot prevents.
- The
WUServerURL and port — WSUS uses 8530 (HTTP) / 8531 (HTTPS), not 80/443.Get-ItemProperty HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate -Name WUServershould showhttp://<wsus>:8530. - The average is dragged down by the many machines that patch promptly and hides the long tail of machines that never reboot and stay unpatched. p90 surfaces that tail per ring, so you can tell whether the Critical ring specifically is missing its SLA and needs reboot enforcement.
- Modern cumulative updates frequently block
wusa /uninstall(only the reversible/servicing-stack portion comes out). Rollback then means rolling forward to the next fixed cumulative update. The real safety net is the ring model — catching the bad update in the small Ring0 pilot and declining it before it ever reaches the broad or critical rings.
Glossary
- Security Compliance Toolkit (SCT): Microsoft’s free package of vetted security baselines (as GPO backups), Policy Analyzer, and LGPO.exe.
- CIS Benchmark: Center for Internet Security’s community-consensus hardening standard; Level 1 (low-breakage) and Level 2 (stricter).
- STIG (Security Technical Implementation Guide): The US DoD’s strict, mandated hardening standard; stricter than CIS.
- LGPO.exe: Tool to apply and back up local group policy; used for snapshots and standalone/workgroup hardening.
- Policy Analyzer: SCT tool that diffs sets of policy (baseline vs effective) to catch app-breaking deltas before import.
- WSUS (Windows Server Update Services): The server role that caches Microsoft update content and gates which updates reach which machines via approvals.
- SUSDB: The WSUS database (Windows Internal Database or SQL Server) holding update metadata, approvals, and client status.
- WID (Windows Internal Database): The free, built-in database engine WSUS can use instead of full SQL Server; single-server only.
- Computer group / target group: A bucket of WSUS clients; the unit approvals target — used to implement deployment rings.
- Classification: The type of an update (Security, Critical, Definition, Rollup, Driver, Upgrade…); scope these to control content size.
- Product: The OS or application an update targets; scoping products keeps the content store from ballooning.
- Approval: The decision (Install / Decline / Not Approved) to apply or hide an update for a group; nothing installs unapproved.
- Superseded update: An update replaced by a newer one; must be declined regularly or scans and the SUSDB rot.
- Client-side targeting: Clients self-assign to a computer group via the
TargetGroupGPO, tying ring membership to OU structure. - Deployment ring: A cohort of machines patched after a defined bake time (Pilot → Early → Broad → Critical) to control blast radius.
- Patch latency: The gap between an update’s release and its installation; tracked as p90 per ring.
- WUfB (Windows Update for Business): Cloud/GPO-driven update deferral and ringing where clients pull directly from Microsoft — no on-prem server.
- SusClientId: The unique WSUS client identifier; duplicated by cloning without sysprep, causing “phantom” clients.
- Schannel: The Windows TLS/SSL provider; where you disable SSL 2.0/3.0 and TLS 1.0/1.1 and keep 1.2/1.3.
- LCU (Latest Cumulative Update): A monthly rollup update that often cannot be uninstalled cleanly, forcing roll-forward for rollback.
Next steps
- Deepen the credential controls this article assumes with Eliminating Static Service Credentials with gMSA and Windows LAPS.
- Expand the SMB/lateral-movement hardening summarized here in Hardening SMB and Enabling Credential Guard to Block Lateral Movement.
- Make the baseline continuously enforced rather than one-shot with Configuration Management for Windows Server with PowerShell DSC and Ansible and the GPO patterns in Group Policy at Scale.
- Handle clustered workloads correctly with Patching Failover Clusters with Cluster-Aware Updating and Stretch Clusters via Storage Replica, and apply the same ring philosophy to Linux with Automating Linux Patching: dnf-automatic, Live Patching, and Reboot Orchestration.
- Correlate “patch approved” with “exposure closed” using Defender EASM: Discovering and Reducing Your Internet-Facing Attack Surface, and rehearse the response when an unpatched box is hit with Engineering Incident Response: Runbooks, Tabletop Exercises, and Cloud Forensics.