[AUM 5.2] Build Sheet: Decommissioning WSUS01

Microsoft publishes no WSUS decommission guidance anywhere, so this is practitioner method rather than vendor instruction. Stragglers found first, evidence taken off the box, and only then the role, the database, the site and the disk.


Microsoft publishes no guidance for decommissioning a WSUS server. Not a page, not a section, not a paragraph. The documentation set covers installing it, configuring it and maintaining it, and then stops, which means the only officially documented end state for a WSUS deployment is one that keeps running. What follows is practitioner method, and I am labelling it as such rather than dressing it up as vendor instruction. By the end of it WSUS01 is gone, nothing was orphaned, and you have the disk back.


Before step 1

Every ring has been through the source cutover and the security group that filtered the WSUS Group Policy is empty. Every machine that was ever a WSUS client is either cut over or accounted for. You hold local administrator on WSUS01 and rights to remove a server role, delete a Group Policy Object in the domain, and query Resource Graph across the subscriptions holding your machine resources. You have somewhere to put roughly the size of the content volume, because two of the artefacts below are large. This sheet is doc-verified rather than lab-run, and the commands read derived values off the box rather than assuming defaults, because a WSUS install that has been in service for years is rarely on the paths the installer suggested.


What leaves the box, and where it goes

Name the archive for the machine and the date it died, WSUS01-decom-20260812, and put it somewhere with the retention your change process actually requires rather than on the nearest share. Four things travel. Three of them cease to exist the moment the role is removed, and one of them is the reason anybody will thank you in eighteen months.

FileProduced onPurpose on arrival
Approval history exportWSUS01, via Get-WsusUpdateThe record of what was approved and when. It is the only evidence that patching was governed during the WSUS years, and auditors ask for periods that predate anything in Resource Graph.
Client inventory with last contact timesWSUS01, via Get-WsusComputerThe definitive list of what was ever a client, including machines nobody remembers. Produced in step 2 and kept, because it is also your straggler evidence.
Content volume inventoryWSUS01, a size and count report of the content folderJustifies the reclaim to whoever owns the storage, and proves what was deleted if anyone asks later. The files themselves do not travel.
Backup of the WSUS Group Policy ObjectA domain controller, via Backup-GPOThe object is deleted in step 5. The backup is what makes that decision reversible for as long as you keep it.

Step 1. Stop approving and stop synchronising

Freeze the server before you measure it. In the Update Services console, under Options, open Automatic Approvals and disable every enabled rule, then open Synchronization Schedule and set it to Synchronize manually. From this point the server’s view of the world stops moving, which is what makes the last contact times in step 2 mean something.

$wsus = Get-WsusServer

# Disable every enabled automatic approval rule. Enumerate first; rule names are yours, not the product's.
$wsus.GetInstallApprovalRules() | Where-Object { $_.Enabled } | ForEach-Object {
    $_.Name
    $_.Enabled = $false
    $_.Save()
}

# Stop scheduled synchronisation.
$sub = $wsus.GetSubscription()
$sub.SynchronizeAutomatically = $false
$sub.Save()
$sub.SynchronizeAutomatically      # expected: False

Then take the approval history, because it is about to become unrecoverable.

Get-WsusUpdate -Approval Approved -Status Any |
    Select-Object -Property * |
    Export-Csv -Path '.\WSUS01-decom-20260812\approval-history.csv' -NoTypeInformation

# Expected: one row per approved update, thousands on a server of any age.
# Failure mode: an empty file usually means the console filter defaults were inherited,
# not that nothing was ever approved. Check the row count before moving on.

Step 2. Watch last contact until nothing reports

This is the straggler detector, and it is the step that catches what your Group Policy work missed. A descoped machine stops reporting to WSUS. A machine that goes on reporting is a machine that still has the pin, and the reasons are the ones the cutover sheet enumerates: a computer object that never made it out of the filter group, a Kerberos ticket that was never renewed, a second policy object nobody found, a preference item that tattooed, or an agent rewriting the value. It does not matter which. What matters is that the machine is telling you, in the only language it has, that it still thinks WSUS01 is its update source.

In the console this is the Computers node with the status filter set to Any, reading the Last Status Report column. From the command line it is a report you can keep.

Get-WsusComputer -All |
    Select-Object FullDomainName, IPAddress, OSDescription, LastReportedStatusTime, LastSyncTime |
    Sort-Object LastReportedStatusTime -Descending |
    Export-Csv -Path '.\WSUS01-decom-20260812\client-inventory.csv' -NoTypeInformation

# Anything that has checked in during the last 48 hours is a straggler.
Get-WsusComputer -ComputerUpdateStatus Any -FromLastReportedStatusTime (Get-Date).AddHours(-48) |
    Select-Object FullDomainName, LastReportedStatusTime

Expected output on a finished migration: the first command produces a full inventory, and the second produces nothing. Anything the second command returns goes back through the cutover procedure before you continue. Give this at least a week of watching rather than a single afternoon, because a machine that is powered off for a fortnight is not a machine that has been cut over, and it will come back looking for a server that is no longer there.

A machine still reporting to WSUS is not a reporting problem. It is a machine whose source you did not actually change, telling you so.


Step 3. The gate in front of the irreversible part

Step 2 proves that nothing is talking to WSUS01. This step proves the positive: that every machine has a working source somewhere else. The two are not the same claim, and the difference is exactly where estates get hurt, because a machine can stop reporting to WSUS by breaking rather than by moving.

patchinstallationresources
| where type has 'patchinstallationresults' and type !has 'softwarepatches'
| extend p = parse_json(properties)
| extend machineId = tostring(split(tolower(id), '/patchinstallationresults')[0])
| summarize arg_max(todatetime(p.startDateTime), *) by machineId
| project machineId,
          started = todatetime(p.startDateTime),
          status  = tostring(p.status),
          source  = tostring(p.patchServiceUsed)
| join kind=rightouter (
    resources
    | where type in~ ('microsoft.compute/virtualmachines','microsoft.hybridcompute/machines')
    | project machineId = tolower(id), machine = name
  ) on machineId
| project machine, started, status, source
| order by started asc

Expected output: every machine in the estate appears, every one has a recent successful run, and the source value differs from the value that machine reported in its pre-cutover baseline. Compare against those saved baselines rather than against an expected string, for the reason the cutover sheet sets out at length: the documented enumeration and the documented prose do not agree on what a directly sourced Windows machine reports, and I am not going to print a value I cannot stand behind. A machine that appears with a null started is a machine with no installation record inside the retention window, and that is a stop rather than a footnote.

Everything after this point is one way. Do not proceed on a partial pass.


Step 4. Take the last two artefacts off the box

The content path is configured at install time and is frequently not where the installer suggested, so read it rather than typing it. The inventory travels; the content does not.

# Read the configured content location. If this value is absent on your build,
# read the path from the console's server configuration instead of guessing.
$contentDir = (Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Update Services\Server\Setup').ContentDir
$contentDir

Get-ChildItem -Path $contentDir -Recurse -File |
    Measure-Object -Property Length -Sum |
    Select-Object @{n='Files';e={$_.Count}}, @{n='GB';e={[math]::Round($_.Sum/1GB,1)}} |
    Export-Csv -Path '.\WSUS01-decom-20260812\content-inventory.csv' -NoTypeInformation

# Back up the Group Policy Object that is about to be deleted. The backup folder name
# is generated, so read it from the returned object.
$b = Backup-GPO -Name '<your WSUS GPO>' -Path '.\WSUS01-decom-20260812'
"Backup id: $($b.Id)"

Step 5. Retire the Group Policy object first

The policy goes before the server, never after. A machine that somehow still carries the pin when the role disappears is a machine pointed at a service that no longer answers, and it will fail scans quietly rather than falling back to Microsoft. Unlink the object from every organisational unit it touches, leave it unlinked for one full refresh cycle so that anything unexpected surfaces while the server still exists to be pointed back at, then delete it and its now empty filter group.

Get-ADGroupMember -Identity 'SEC-Srv-WSUS-Pinned'    # expected: no output at all

Remove-GPLink -Name '<your WSUS GPO>' -Target 'OU=Servers,DC=catsnackjack,DC=com'
# wait one refresh cycle, confirm nothing broke, then:
Remove-GPO      -Name '<your WSUS GPO>'
Remove-ADGroup  -Identity 'SEC-Srv-WSUS-Pinned'

Step 6. Remove the role, the database, the site and the content

This is the practiced sequence, and I want to be clear about its provenance one more time: it is the convergent method of people who have done this, maintained and republished over several years, not a Microsoft procedure. It is in this order because each step leaves the next one able to complete.

  1. Enumerate the installed features rather than trusting a list, because a Windows Internal Database install and a SQL Server install do not carry the same set.
  2. Remove the role and its role services.
  3. Delete the database, either the SUSDB files under the Windows Internal Database data directory or, on a SQL install, by detaching and removing it from the instance.
  4. Remove the WSUS Administration site and the WsusPool application pool from IIS, which role removal does not always take with it.
  5. Delete the program folder and the content folder. This is the reclaim.
  6. Restart.
# 1. What is actually installed here?
Get-WindowsFeature -Name UpdateServices* | Where-Object Installed | Select-Object Name

# 2. Remove exactly that set. On a WID install this typically resolves to:
Remove-WindowsFeature -Name UpdateServices,UpdateServices-DB,UpdateServices-RSAT,UpdateServices-API,UpdateServices-UI

# 3. Windows Internal Database files.
Get-ChildItem 'C:\Windows\WID\Data' -Filter 'SUSDB*'
Remove-Item    'C:\Windows\WID\Data\SUSDB*' -Force

# 4. IIS leftovers.
Import-Module WebAdministration
Get-Website | Where-Object { $_.Name -like '*WSUS*' } | Remove-Website
Remove-WebAppPool -Name 'WsusPool'

# 5. Program folder and content. $contentDir came from step 4.
Remove-Item 'C:\Program Files\Update Services' -Recurse -Force
Remove-Item $contentDir -Recurse -Force

# 6.
Restart-Computer

Failure modes worth knowing before you meet them. Role removal usually demands a restart of its own and will report success while leaving files locked until it happens, so if the content folder refuses to delete, restart and try again rather than reaching for anything more forceful. The Windows Internal Database instance is itself deprecated, with Microsoft stating it will be removed from Windows in a future release, so if the database files stay locked the service holding them is the one to stop. And if you find IIS bindings or a certificate that were configured for the WSUS site rather than by it, those belong to whoever configured them and are not yours to remove on this change.


Step 7. Validate, end to end

The end to end test is not on WSUS01. It is the estate’s next full patch cycle completing with the server gone, because that is the only thing that proves nothing was quietly depending on it.

# On WSUS01, after the restart: the role and its listeners are gone.
Get-WindowsFeature -Name UpdateServices* | Select-Object Name, Installed
Get-Service -Name WsusService -ErrorAction SilentlyContinue    # expected: no output
Get-Website | Select-Object Name, State
Get-PSDrive C | Select-Object Used, Free                        # the reclaim, measured

# From any server that used to be a client: nothing resolves to the old host.
Test-NetConnection -ComputerName 'wsus01' -Port 8530            # expected: failure

Then wait for the next scheduled run on every ring and re-run the step 3 query. Every machine present, every run succeeded, no change in source. That is the decommission validated, and it is worth the wait, because the failure this catches is a machine that was fine while the server existed for reasons nobody had written down.

One loose end that is not on the box. Whatever hostname the pin used, check that nothing else resolves to it and that no firewall rule, proxy exception or monitoring check still references it. Those outlive servers routinely, and a monitoring alert for a machine that was deliberately deleted is how a decommission acquires a reputation for having gone badly.


Worked example: WSUS01 is its own last client

The machine that catches people is the server itself. WSUS01 is domain joined, so it received the same Group Policy as everything else and has spent its life pointed at itself for updates. It is therefore a WSUS client, it appears in its own console, and it has to go through the source cutover like any other machine before any of this sheet applies to it. It is also Windows Server 2016, which puts it in the dual scan exposed set alongside the domain controllers, and it is by construction the last member of the filter group, which means the group cannot reach empty until the server has cut itself over.

Decision for WSUS01ValueWhy
RingLast, on its own, after every other ringIt is the source for everything else until it is not. Cutting it early buys nothing and removes your ability to see who is still reporting.
Source cutoverFull procedure from the cutover sheetIt is an ordinary domain member with the pin. Nothing about hosting the role changes how it receives policy.
Dual scan guardApplies. Windows Server 2016Same exposure as the domain controllers. No deferral policy may reach it at any point.
Microsoft Update opt inDecided explicitly, recorded either wayOnce the role is gone it is an ordinary server that still needs patching until the day it is switched off.
Order of operationsCut it over, prove it, then decommission the roleA server sourcing from a role it is about to delete is the one genuinely circular dependency in this whole project.

What has no rollback

Steps 1 through 5 are reversible. Approval rules can be re-enabled, synchronisation rescheduled, the Group Policy object restored from the backup taken in step 4 and relinked, and the filter group rebuilt from the client inventory. Step 6 is not reversible in any sense worth the word. Rebuilding a WSUS server from scratch and resynchronising it is a new deployment that happens to have the same hostname, with none of the approval history and none of the content, and the second half of that sentence is why step 4 exists.

That asymmetry is the whole reason the gate in step 3 stands where it does. Verification after an irreversible step tells you something is wrong once the afternoon has already been spent. If you find yourself tempted to run step 6 with one machine still unaccounted for, the honest move is to leave the server switched on and idle for another month. It costs a rack unit and some electricity. The alternative costs a rebuild.

A WSUS server left running one month too long costs almost nothing. One removed a week too early costs a rebuild with no history in it.


Where this goes next

What is left after step 7 is a Windows Server 2016 machine with a role removed and a lot of free disk, and the calendar has an opinion about it. Windows Server 2016 reaches end of extended support on 12 January 2027. Whatever this box does next, it does for a defined number of months, and the honest options are the same three that apply to the other three 2016 machines in this estate: extend, upgrade or replatform. Which of those is even available depends on how the server licences are held, which is a licensing question rather than a patching one and is the subject of the article on what all of this now costs.

Worth noting on the way out, because it is the cleanest illustration of why this project was worth running at all: in July 2026 Microsoft posted a service degradation covering WSUS synchronisation across Windows Server 2012 through 2025, resolved on 20 July with a service side mitigation that restored newly built servers and a support article of manual metadata cleanup steps for existing ones. That incident is closed. The pattern it demonstrates is not, and the machine that would have been on the receiving end of the next one is now switched off.


Azure Update Manager
‹ Previous: [AUM 5.1] Build Sheet: The Ring-by-Ring Source Cutover
Next: [AUM 6] The Meter and the Waivers: What Patching Costs Now