Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save 0xntpower/246c87d931117dc818b8ebca56a60dcb to your computer and use it in GitHub Desktop.

Select an option

Save 0xntpower/246c87d931117dc818b8ebca56a60dcb to your computer and use it in GitHub Desktop.
Auto-patch, never auto-reboot. A self-verifying Windows 11 Update policy script.

Configure-WindowsUpdate-NoAutoReboot

A PowerShell script that configures Windows 11 (Pro / Enterprise) to automatically install security updates while never automatically rebooting the machine. Designed for long-lived workstations, lab boxes, and VMs where restarts must remain a manual decision.

What it does

The script applies a defensive Windows Update policy via the registry and the Task Scheduler, then verifies every setting was applied correctly.

Update behavior

  • Enables fully automatic download and installation of Windows Updates.
  • Schedules the daily install window at 03:00.
  • Updates that do not require a reboot install immediately.
  • Updates that do require a reboot are installed but left in a pending reboot state until the operator restarts the machine manually.

Reboot suppression

  • Blocks auto-reboot whenever any user session is active.
  • Disables reboot at the scheduled install time.
  • Disables deadline-forced reboots for both quality and feature updates.
  • Disables the EDU restart override.
  • Forces active hours to the widest permitted window (06:00–23:00).
  • Disables the legacy UpdateOrchestrator\Reboot* scheduled tasks where present (older Windows 11 builds only; current builds no longer expose these as standalone tasks and the registry policy alone is sufficient).

Verification

  • After applying the policy the script reads every value back and prints a [PASS] / [FAIL] report.
  • Checks for an existing pending-reboot state and cancels any already-scheduled auto-restart via shutdown /a.
  • Exits with code 0 on full success, 1 on any failure.

Requirements

  • Windows 11 Pro or Enterprise (Home edition does not honour all of the relevant Group Policy values).
  • Windows PowerShell 5.1 or PowerShell 7+.
  • An elevated (Administrator) shell.

Usage

powershell.exe -ExecutionPolicy Bypass -File .\Configure-WindowsUpdate-NoAutoReboot.ps1

The script is idempotent — re-running it is safe and recommended after any major Windows feature update.

Optional: schedule weekly re-assertion

Cumulative updates have, on occasion, reset individual policy values. A weekly self-asserting task closes that gap:

$action  = New-ScheduledTaskAction -Execute 'powershell.exe' `
            -Argument '-ExecutionPolicy Bypass -WindowStyle Hidden -File "C:\Scripts\Configure-WindowsUpdate-NoAutoReboot.ps1"'
$trigger = New-ScheduledTaskTrigger -Weekly -DaysOfWeek Sunday -At 4am
$princ   = New-ScheduledTaskPrincipal -UserId 'SYSTEM' -RunLevel Highest
Register-ScheduledTask -TaskName 'Reassert-WUPolicy' -Action $action -Trigger $trigger -Principal $princ -Force

Operational notes

  • Monthly cumulative updates require a reboot. Plan on roughly one manual restart per month after Patch Tuesday.
  • Microsoft Defender signature updates flow through their own channel and never require a reboot. They are always on and unaffected by this configuration.
  • To check for a queued reboot, inspect HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootPending. If present, a previously installed update is awaiting a manual restart.
  • To cancel any scheduled auto-restart on demand, run shutdown /a.

Policy values applied

Key Value Type Purpose
…\WindowsUpdate\AU\NoAutoUpdate 0 DWORD Automatic Updates enabled
…\WindowsUpdate\AU\AUOptions 4 DWORD Auto download + scheduled install
…\WindowsUpdate\AU\ScheduledInstallDay 0 DWORD Install every day
…\WindowsUpdate\AU\ScheduledInstallTime 3 DWORD Install at 03:00
…\WindowsUpdate\AU\NoAutoRebootWithLoggedOnUsers 1 DWORD Suppress reboot when a user is logged on
…\WindowsUpdate\AU\AlwaysAutoRebootAtScheduledTime 0 DWORD Do not reboot at scheduled time
…\WindowsUpdate\SetAutoRestartDeadline 0 DWORD Disable deadline-forced reboots
…\WindowsUpdate\SetAutoRestartRequiredNotificationDismissal 0 DWORD Disable restart-notification lock
…\WindowsUpdate\SetEDURestart 0 DWORD Disable EDU restart override
…\WindowsUpdate\SetActiveHours 1 DWORD Enforce active hours
…\WindowsUpdate\ActiveHoursStart 6 DWORD Active hours begin at 06:00
…\WindowsUpdate\ActiveHoursEnd 23 DWORD Active hours end at 23:00

All keys live under HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate.

Reverting

To restore default behaviour, remove the policy keys and refresh Group Policy:

Remove-Item 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate' -Recurse -Force
gpupdate /force
``
<#
.SYNOPSIS
Configures Windows 11 Enterprise to auto-install updates without ever auto-rebooting.
.DESCRIPTION
- Enables fully automatic download + scheduled install of Windows Updates.
- Disables every known auto-reboot path: logged-on-user reboots, scheduled-time
reboots, deadline-forced reboots, and the UpdateOrchestrator reboot tasks.
- Verifies all settings after applying and prints a pass/fail report.
.NOTES
Run elevated. Designed for a long-lived VM where you want zero surprise restarts.
#>
#Requires -RunAsAdministrator
[CmdletBinding()]
param()
$ErrorActionPreference = 'Stop'
$script:Failures = @()
function Write-Section($Text) {
Write-Host ""
Write-Host ("=" * 70) -ForegroundColor Cyan
Write-Host $Text -ForegroundColor Cyan
Write-Host ("=" * 70) -ForegroundColor Cyan
}
# Walks each segment of a registry path and creates anything missing.
# More reliable than `New-Item -Force` which has quirks on some PS 5.1 builds.
function Ensure-RegistryKey {
param([string]$Path)
$segments = $Path -split '\\'
$current = $segments[0] # e.g. 'HKLM:'
for ($i = 1; $i -lt $segments.Count; $i++) {
$current = Join-Path $current $segments[$i]
if (-not (Test-Path -LiteralPath $current)) {
New-Item -Path $current -ErrorAction Stop | Out-Null
Write-Host " Created: $current" -ForegroundColor DarkGray
}
}
if (-not (Test-Path -LiteralPath $Path)) {
throw "Failed to create registry key: $Path"
}
}
function Set-RegDWord {
param([string]$Path, [string]$Name, [int]$Value)
Set-ItemProperty -Path $Path -Name $Name -Type DWord -Value $Value -ErrorAction Stop
}
function Test-RegValue {
param([string]$Path, [string]$Name, [int]$Expected, [string]$Description)
try {
$actual = (Get-ItemProperty -Path $Path -Name $Name -ErrorAction Stop).$Name
if ($actual -eq $Expected) {
Write-Host (" [PASS] {0,-55} = {1}" -f $Description, $actual) -ForegroundColor Green
return $true
} else {
Write-Host (" [FAIL] {0,-55} = {1} (expected {2})" -f $Description, $actual, $Expected) -ForegroundColor Red
$script:Failures += "$Description : got $actual, expected $Expected"
return $false
}
} catch {
Write-Host (" [FAIL] {0,-55} = <missing>" -f $Description) -ForegroundColor Red
$script:Failures += "$Description : value missing"
return $false
}
}
# ----------------------------------------------------------------------------
# 1. Apply Windows Update policy
# ----------------------------------------------------------------------------
Write-Section "Step 1: Applying Windows Update policy"
$WU = 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate'
$AU = 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU'
Ensure-RegistryKey -Path $WU
Ensure-RegistryKey -Path $AU
# Automatic updates: download + scheduled install, daily at 03:00
Set-RegDWord $AU 'NoAutoUpdate' 0
Set-RegDWord $AU 'AUOptions' 4
Set-RegDWord $AU 'ScheduledInstallDay' 0
Set-RegDWord $AU 'ScheduledInstallTime' 3
# Block auto-reboot
Set-RegDWord $AU 'NoAutoRebootWithLoggedOnUsers' 1
Set-RegDWord $AU 'AlwaysAutoRebootAtScheduledTime' 0
# Kill deadline-forced reboots
Set-RegDWord $WU 'SetAutoRestartDeadline' 0
Set-RegDWord $WU 'SetAutoRestartRequiredNotificationDismissal' 0
Set-RegDWord $WU 'SetEDURestart' 0
# Active hours (widest allowed: 18h)
Set-RegDWord $WU 'SetActiveHours' 1
Set-RegDWord $WU 'ActiveHoursStart' 6
Set-RegDWord $WU 'ActiveHoursEnd' 23
Write-Host " Registry policy written." -ForegroundColor Gray
# ----------------------------------------------------------------------------
# 2. Disable UpdateOrchestrator reboot tasks (requires SYSTEM context)
# ----------------------------------------------------------------------------
Write-Section "Step 2: Disabling UpdateOrchestrator reboot tasks (SYSTEM context)"
$logPath = 'C:\Windows\Temp\disable-uo-reboot.log'
if (Test-Path $logPath) { Remove-Item $logPath -Force }
$inner = @'
$tasks = @('Reboot','Reboot_AC','Reboot_Battery','Schedule Reboot')
foreach ($t in $tasks) {
try {
Disable-ScheduledTask -TaskPath '\Microsoft\Windows\UpdateOrchestrator\' -TaskName $t -ErrorAction Stop | Out-Null
Add-Content C:\Windows\Temp\disable-uo-reboot.log "$(Get-Date -f s) DISABLED: $t"
} catch {
Add-Content C:\Windows\Temp\disable-uo-reboot.log "$(Get-Date -f s) SKIPPED: $t ($($_.Exception.Message))"
}
}
'@
$b64 = [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($inner))
$action = New-ScheduledTaskAction -Execute 'powershell.exe' -Argument "-NoProfile -EncodedCommand $b64"
$trigger = New-ScheduledTaskTrigger -Once -At (Get-Date).AddSeconds(3)
$princ = New-ScheduledTaskPrincipal -UserId 'SYSTEM' -RunLevel Highest
Register-ScheduledTask -TaskName '_disable_uo_reboot' -Action $action -Trigger $trigger -Principal $princ -Force | Out-Null
Write-Host " Spawned SYSTEM helper task, waiting for completion..." -ForegroundColor Gray
# Wait for the helper task to fire and finish (poll for log)
$deadline = (Get-Date).AddSeconds(20)
while ((Get-Date) -lt $deadline) {
Start-Sleep -Milliseconds 500
if (Test-Path $logPath) {
$lines = (Get-Content $logPath -ErrorAction SilentlyContinue | Measure-Object).Count
if ($lines -ge 1) { Start-Sleep -Seconds 2; break }
}
}
Unregister-ScheduledTask -TaskName '_disable_uo_reboot' -Confirm:$false -ErrorAction SilentlyContinue
if (Test-Path $logPath) {
Get-Content $logPath | ForEach-Object { Write-Host " $_" -ForegroundColor Gray }
} else {
Write-Host " (no log produced; tasks may not exist on this build)" -ForegroundColor Yellow
}
# ----------------------------------------------------------------------------
# 3. Refresh policy
# ----------------------------------------------------------------------------
Write-Section "Step 3: Refreshing group policy"
gpupdate /force | Out-Null
Write-Host " gpupdate complete." -ForegroundColor Gray
# ----------------------------------------------------------------------------
# 4. Verification - registry
# ----------------------------------------------------------------------------
Write-Section "Step 4: Verifying registry policy"
Test-RegValue $AU 'NoAutoUpdate' 0 'AU enabled' | Out-Null
Test-RegValue $AU 'AUOptions' 4 'Auto download + scheduled install' | Out-Null
Test-RegValue $AU 'ScheduledInstallDay' 0 'Install every day' | Out-Null
Test-RegValue $AU 'ScheduledInstallTime' 3 'Install at 03:00' | Out-Null
Test-RegValue $AU 'NoAutoRebootWithLoggedOnUsers' 1 'Block reboot if user logged on' | Out-Null
Test-RegValue $AU 'AlwaysAutoRebootAtScheduledTime' 0 'No auto-reboot at scheduled time' | Out-Null
Test-RegValue $WU 'SetAutoRestartDeadline' 0 'No deadline-forced reboot' | Out-Null
Test-RegValue $WU 'SetAutoRestartRequiredNotificationDismissal' 0 'No restart notif dismissal lock' | Out-Null
Test-RegValue $WU 'SetEDURestart' 0 'No EDU restart override' | Out-Null
Test-RegValue $WU 'SetActiveHours' 1 'Active hours enforced' | Out-Null
Test-RegValue $WU 'ActiveHoursStart' 6 'Active hours start = 06:00' | Out-Null
Test-RegValue $WU 'ActiveHoursEnd' 23 'Active hours end = 23:00' | Out-Null
# ----------------------------------------------------------------------------
# 5. Verification - scheduled tasks
# ----------------------------------------------------------------------------
Write-Section "Step 5: Verifying UpdateOrchestrator reboot tasks"
$uoTasks = @('Reboot','Reboot_AC','Reboot_Battery','Schedule Reboot')
$foundAny = $false
foreach ($t in $uoTasks) {
$task = Get-ScheduledTask -TaskPath '\Microsoft\Windows\UpdateOrchestrator\' -TaskName $t -ErrorAction SilentlyContinue
if ($null -eq $task) {
Write-Host (" [skip] {0,-20} not present on this build" -f $t) -ForegroundColor DarkGray
continue
}
$foundAny = $true
if ($task.State -eq 'Disabled') {
Write-Host (" [PASS] {0,-20} state = Disabled" -f $t) -ForegroundColor Green
} else {
Write-Host (" [FAIL] {0,-20} state = {1}" -f $t, $task.State) -ForegroundColor Red
$script:Failures += "Scheduled task $t is $($task.State), expected Disabled"
}
}
if (-not $foundAny) {
Write-Host " (no UpdateOrchestrator reboot tasks exist on this build - nothing to disable)" -ForegroundColor Yellow
}
# ----------------------------------------------------------------------------
# 6. Pending-reboot sanity check
# ----------------------------------------------------------------------------
Write-Section "Step 6: Pending reboot status"
$pending = Test-Path 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootPending'
if ($pending) {
Write-Host " [WARN] System currently has a pending reboot from prior updates." -ForegroundColor Yellow
Write-Host " Running 'shutdown /a' to cancel any scheduled auto-restart..." -ForegroundColor Yellow
& shutdown /a 2>&1 | Out-Null
Write-Host " Reboot at your discretion to clear the pending state." -ForegroundColor Yellow
} else {
Write-Host " [OK] No pending reboot." -ForegroundColor Green
}
# ----------------------------------------------------------------------------
# Summary
# ----------------------------------------------------------------------------
Write-Section "Summary"
if ($script:Failures.Count -eq 0) {
Write-Host " All checks passed. The VM will auto-install updates and never auto-reboot." -ForegroundColor Green
Write-Host ""
Write-Host " Reminders:" -ForegroundColor Cyan
Write-Host " - Cumulative updates still require a manual reboot (~monthly)." -ForegroundColor Gray
Write-Host " - Defender signature updates flow through their own channel (always on)." -ForegroundColor Gray
Write-Host " - Re-run this script after major Windows updates; MS sometimes re-enables UO reboot tasks." -ForegroundColor Gray
exit 0
} else {
Write-Host " $($script:Failures.Count) check(s) failed:" -ForegroundColor Red
$script:Failures | ForEach-Object { Write-Host " - $_" -ForegroundColor Red }
exit 1
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment