SCRIPT LIBRARY · POWERSHELL
Re-Enable Windows Update with PowerShell: Find Every Registry Block, Not Just One
When Windows Update is greyed out or just won't run, check all the policy values and services that can block it, and put them back the way Windows shipped.
- What it does
- Checks the registry policy values that disable Windows Update access or automatic updates, plus the Windows Update and BITS services. Removes anything that's blocking, resets disabled services to Manual, and returns one row per check.
- Requires
- Windows PowerShell 5.1 (PowerShell 7 works too)
- No modules
- Permissions
- Local administrator or SYSTEM for the machine-wide checks. The optional per-user checks need to run as that user.
- Runs on
- Windows 10/11, Windows Server 2016+
- Tested
- Parse-checked and dry-run with mocked registry and service cmdlets in PowerShell 7.4
Part 3 of the thread Windows Update, untangled
You open Settings, and Windows Update is greyed out, or missing, or says "Some settings are managed by your organization" on a machine nobody manages. Usually one of three things happened: malware turned it off so it could stay put, a "debloat" script from the internet got a little enthusiastic, or a GPO from a previous life left something behind.
The original version of this post was a single line that set DisableWindowsUpdateAccess to 0. It's a real setting, but it's only one of several places Windows Update can be switched off, and Set-ItemProperty throws an error if the policy key doesn't exist in the first place.
This version checks all the usual suspects, shows you which ones are set, and removes them, so they go back to "Not configured" instead of being explicitly set to "allowed." It also looks at the two services that malware and debloat tools like to disable. Run it with -WhatIf first. The report is useful all by itself.
<#
.SYNOPSIS
Finds and removes the registry settings that lock people (or Windows) out of Windows Update.
.DESCRIPTION
Checks the handful of policy values that disable Windows Update access or automatic
updates, plus the Windows Update and BITS services. Anything set to block is removed
(returned to "Not configured"), and disabled services go back to Manual, which is how
Windows ships them. Returns one object per check, so you can see what was wrong even
when you only run it with -WhatIf.
.PARAMETER IncludeCurrentUser
Also check the per-user policy values in HKCU. Only useful when run as the signed-in
user, not as SYSTEM.
.PARAMETER KeepAutoUpdatePolicy
Leave NoAutoUpdate alone. Some ConfigMgr and WSUS setups turn automatic updates
off on purpose.
.PARAMETER SkipServices
Leave service startup types alone.
.EXAMPLE
.\Repair-WindowsUpdateAccess.ps1 -WhatIf
.EXAMPLE
.\Repair-WindowsUpdateAccess.ps1 -IncludeCurrentUser -Verbose
#>
[CmdletBinding(SupportsShouldProcess)]
param(
[switch]$IncludeCurrentUser,
[switch]$KeepAutoUpdatePolicy,
[switch]$SkipServices
)
$wuPolicy = 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate'
$checks = @(
@{ Path = $wuPolicy; Name = 'DisableWindowsUpdateAccess'; Setting = 'Turn off access to all Windows Update features' }
@{ Path = $wuPolicy; Name = 'SetDisableUXWUAccess'; Setting = 'Remove access to use all Windows Update features' }
@{ Path = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\Explorer'; Name = 'NoWindowsUpdate'; Setting = 'Windows Update removed from Start/Settings (machine)' }
@{ Path = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\WindowsUpdate'; Name = 'DisableWindowsUpdateAccess'; Setting = 'Windows Update access disabled (machine)' }
)
if (-not $KeepAutoUpdatePolicy) {
$checks += @{ Path = "$wuPolicy\AU"; Name = 'NoAutoUpdate'; Setting = 'Automatic Updates turned off' }
}
if ($IncludeCurrentUser) {
$checks += @{ Path = 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Policies\Explorer'; Name = 'NoWindowsUpdate'; Setting = 'Windows Update removed (user)' }
$checks += @{ Path = 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Policies\WindowsUpdate'; Name = 'DisableWindowsUpdateAccess'; Setting = 'Windows Update access disabled (user)' }
}
$changed = $false
foreach ($check in $checks) {
$value = (Get-ItemProperty -Path $check.Path -Name $check.Name -ErrorAction SilentlyContinue).($check.Name)
$row = [pscustomobject]@{
Check = $check.Setting
Location = "$($check.Path)\$($check.Name)"
Value = $value
Blocking = ($value -eq 1)
Action = 'None'
}
if ($row.Blocking) {
if ($PSCmdlet.ShouldProcess($row.Location, 'Remove blocking policy value')) {
try {
Remove-ItemProperty -Path $check.Path -Name $check.Name -ErrorAction Stop
$row.Action = 'Removed'
$changed = $true
}
catch {
$row.Action = 'Failed'
Write-Warning "Couldn't remove $($row.Location): $($_.Exception.Message)"
}
}
else { $row.Action = 'WhatIf' }
}
$row
}
if (-not $SkipServices) {
foreach ($name in 'wuauserv', 'BITS') {
$service = Get-Service -Name $name -ErrorAction SilentlyContinue
$row = [pscustomobject]@{
Check = "$name service startup"
Location = "Service:$name"
Value = if ($service) { "$($service.StartType)" } else { 'Missing' }
Blocking = ($service -and "$($service.StartType)" -eq 'Disabled')
Action = 'None'
}
if ($row.Blocking) {
if ($PSCmdlet.ShouldProcess($name, 'Set startup type to Manual')) {
try {
Set-Service -Name $name -StartupType Manual -ErrorAction Stop
$row.Action = 'SetToManual'
$changed = $true
}
catch {
$row.Action = 'Failed'
Write-Warning "Couldn't change $name`: $($_.Exception.Message)"
}
}
else { $row.Action = 'WhatIf' }
}
$row
}
}
# Windows Update reads policy when it starts a scan, so give it a clean start.
if ($changed) {
$wu = Get-Service -Name wuauserv -ErrorAction SilentlyContinue
if ($wu -and $wu.Status -eq 'Running' -and $PSCmdlet.ShouldProcess('wuauserv', 'Restart service')) {
Restart-Service -Name wuauserv -Force -ErrorAction SilentlyContinue
}
Write-Verbose 'Changes made. If a GPO or MDM policy set these values, it will set them again at the next refresh.'
}
Parameters
| Parameter | Type | Default | What it's for |
|---|---|---|---|
-IncludeCurrentUser | switch | — | Also check the per-user policy values in HKCU. Only meaningful when it runs as the signed-in user. |
-KeepAutoUpdatePolicy | switch | — | Don't touch NoAutoUpdate. Use this on WSUS or ConfigMgr machines where automatic updates are off on purpose. |
-SkipServices | switch | — | Only look at the registry. Leave service startup types alone. |
Run it
See what's blocking Windows Update, without changing anything.
.\Repair-WindowsUpdateAccess.ps1 -WhatIfShow only the checks that found a problem.
.\Repair-WindowsUpdateAccess.ps1 -WhatIf | Where-Object BlockingFix everything, including the signed-in user's settings (run it in their session).
.\Repair-WindowsUpdateAccess.ps1 -IncludeCurrentUserOn a ConfigMgr-managed machine, where NoAutoUpdate may be deliberate.
.\Repair-WindowsUpdateAccess.ps1 -KeepAutoUpdatePolicyWhat you'll see
Check Value Blocking Action
----- ----- -------- ------
Turn off access to all Windows Update features 1 True Removed
Remove access to use all Windows Update features False None
Windows Update removed from Start/Settings (m... False None
Windows Update access disabled (machine) False None
Automatic Updates turned off 1 True Removed
wuauserv service startup Disabled True SetToManual
BITS service startup Manual False None
How it works
- Check each known blocker. The main ones live under
HKLM\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate.DisableWindowsUpdateAccesscomes from "Turn off access to all Windows Update features," andSetDisableUXWUAccessfrom "Remove access to use all Windows Update features." Then there'sNoAutoUpdatein theAUsubkey, plus the older Explorer-styleNoWindowsUpdateand a machine-wideDisableWindowsUpdateAccessunderCurrentVersion\Policies. - Remove, don't flip. A value set to 1 gets deleted with
Remove-ItemProperty. Deleting puts the setting back to "Not configured," which is how a clean install looks. Setting it to 0 would still be a policy, just a different one. - Check the services. If
wuauservorBITSis disabled, it's set back to Manual. Both start on demand, and Manual is how Windows ships them. - Report every check. You get a row for each value and service, blocking or not, so the
-WhatIfrun doubles as a diagnosis. - Restart Windows Update if anything changed. The service reads policy at the start of a scan, so a clean restart means the next check for updates sees the new state.
Take it further
- Kick off a scan. After a repair,
UsoClient StartScan(or just clicking Check for updates) confirms the machine can actually reach its update source. - Check where it's pointed. A machine that was "fixed" by pointing it at a WSUS server that no longer exists is still broken. Look at
WUServerandUseWUServerin the same policy key before you close the ticket. - Keep a baseline. Turn the report into a configuration item that flags any device with a blocking value, so you hear about it before the user does.
Things that'll trip you up
- If a policy set it, the policy will set it again. Removing a value that a GPO or Intune profile owns only lasts until the next refresh. If it comes back, run gpresult /h report.html and find out which policy is doing it.
- HKCU as SYSTEM is the wrong HKCU. Deployed through ConfigMgr or a scheduled task, the script runs as SYSTEM, and -IncludeCurrentUser checks SYSTEM's settings, not the user's. For per-user blocks, run it in the user's session.
- Access denied on a service is a bad sign. Local admins can normally change wuauserv and BITS. If they can't, something has changed the service permissions, which is what malware tends to do. Scan the machine properly before you call it fixed.
- It doesn't repair a broken Windows Update. This removes blocks. If updates still fail afterwards, the component store or the update agent itself may be damaged, and that's a job for DISM /RestoreHealth or an in-place upgrade.