← Back to blog

Stop Windows Update From Auto-Restarting Your PC

ยท By

Keep Windows Update working, but stop Windows from deciding that right now is a great time to reboot your computer.

Stop Windows automatic restarts after updates using registry policies, Task Scheduler and PowerShell

Windows rebooted my machine again to install an update. No permission, no useful warning at the moment I actually needed it, just a fresh desktop where all of my open windows used to be.

Yes, I know using open windows as a reminder system is not exactly a formal productivity methodology. I also do not care. They are my windows, on my computer, and I want to decide when the machine gets restarted.

I do still want Windows Update. Security updates are important, and I am not trying to turn updates off. I simply want Windows to download and install what it needs, then wait until I choose Restart.

The approach

This script takes a belt-and-suspenders approach. It does four things:

  • Sets the Windows Update policy to use scheduled installation behavior.
  • Enables NoAutoRebootWithLoggedOnUsers, which tells Windows not to automatically restart after an update while a user is signed in.
  • Disables Update Orchestrator scheduled tasks whose names contain Reboot or Restart.
  • Creates two SYSTEM scheduled tasks of its own so the settings are re-applied at startup and every 15 minutes.

The reason for the last step is simple: Windows Update has a habit of recreating or re-enabling things it believes it owns. Rather than trusting the setting to remain untouched forever, the script periodically checks again.

Important: this is intended for a personally managed Windows PC. If your computer is controlled by an employer, domain policy, Intune, WSUS or another management system, those policies can conflict with local settings. Microsoft also documents deadline-based restart behavior that can affect how update restart policies behave.

Download and install

Download the PowerShell script, then run it once. It will elevate itself and install the persistent copy and scheduled tasks.

Download NoWindowsAutoRestart.ps1

From PowerShell, assuming the file is in Downloads:

powershell.exe -NoProfile -ExecutionPolicy Bypass -File "$env:USERPROFILE\Downloads\NoWindowsAutoRestart.ps1"

After installation, the script keeps its persistent copy here:

C:\ProgramData\NoWindowsAutoRestart\NoWindowsAutoRestart.ps1

What gets changed

The script writes the following Windows Update policy values under:

HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU
  • AUOptions = 4
  • NoAutoRebootWithLoggedOnUsers = 1
  • AlwaysAutoRebootAtScheduledTime = 0

Microsoft currently documents NoAutoRebootWithLoggedOnUsers for both Windows 10 and Windows 11 when automatic updates use option 4. The AlwaysAutoRebootAtScheduledTime setting is a legacy setting on Windows 11, but leaving it disabled also covers Windows 10 behavior.

The scheduled-task guard

The script creates these two tasks and runs them as SYSTEM:

  • NoWindowsAutoRestart-Boot, runs every time Windows starts.
  • NoWindowsAutoRestart-Guard, runs every 15 minutes.

Each pass also looks under \Microsoft\Windows\UpdateOrchestrator\ and disables tasks whose names contain Reboot or Restart.

This task-disabling portion is intentionally more aggressive than Microsoft's normal supported update-management policies. Task names can also change between Windows builds, which is why the script searches by name rather than assuming one exact task exists forever.

The complete script

#Requires -Version 5.1
param(
    [switch]$EnforceOnly
)

$ErrorActionPreference = 'Stop'

$InstallDir      = Join-Path $env:ProgramData 'NoWindowsAutoRestart'
$InstalledScript = Join-Path $InstallDir 'NoWindowsAutoRestart.ps1'
$BootTaskName    = 'NoWindowsAutoRestart-Boot'
$GuardTaskName   = 'NoWindowsAutoRestart-Guard'
$LogFile         = Join-Path $InstallDir 'NoWindowsAutoRestart.log'

function Test-IsAdministrator {
    $identity  = [Security.Principal.WindowsIdentity]::GetCurrent()
    $principal = New-Object Security.Principal.WindowsPrincipal($identity)
    return $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
}

function Write-Log {
    param([string]$Message)
    if (-not (Test-Path $InstallDir)) {
        New-Item -ItemType Directory -Path $InstallDir -Force | Out-Null
    }
    $stamp = Get-Date -Format 'yyyy-MM-dd HH:mm:ss'
    Add-Content -Path $LogFile -Value "[$stamp] $Message"
}

function Set-Dword {
    param(
        [string]$Path,
        [string]$Name,
        [int]$Value
    )

    if (-not (Test-Path $Path)) {
        New-Item -Path $Path -Force | Out-Null
    }

    New-ItemProperty `
        -Path $Path `
        -Name $Name `
        -Value $Value `
        -PropertyType DWord `
        -Force | Out-Null
}

function Enforce-NoAutoRestart {
    $wu = 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate'
    $au = Join-Path $wu 'AU'

    # Microsoft-documented Windows Update restart controls.
    Set-Dword -Path $au -Name 'AUOptions'                         -Value 4
    Set-Dword -Path $au -Name 'NoAutoRebootWithLoggedOnUsers'    -Value 1
    Set-Dword -Path $au -Name 'AlwaysAutoRebootAtScheduledTime'  -Value 0

    # Extra protection: disable Update Orchestrator tasks whose names
    # explicitly indicate reboot/restart. Task names vary by Windows build.
    $rebootTasks = Get-ScheduledTask -ErrorAction SilentlyContinue |
        Where-Object {
            $_.TaskPath -eq '\Microsoft\Windows\UpdateOrchestrator\' -and
            $_.TaskName -match '(?i)(reboot|restart)'
        }

    foreach ($task in $rebootTasks) {
        try {
            Disable-ScheduledTask -InputObject $task -ErrorAction Stop | Out-Null
            Write-Log "Disabled scheduled task: $($task.TaskPath)$($task.TaskName)"
        }
        catch {
            Write-Log "Could not disable task $($task.TaskPath)$($task.TaskName): $($_.Exception.Message)"
        }
    }

    Write-Log 'Windows automatic-restart policy enforced.'
}

# First manual run elevates itself.
if (-not (Test-IsAdministrator)) {
    if (-not $PSCommandPath) {
        throw 'Save this script as a .ps1 file before running it.'
    }

    $arg = "-NoProfile -ExecutionPolicy Bypass -File `"$PSCommandPath`""
    if ($EnforceOnly) {
        $arg += ' -EnforceOnly'
    }

    Start-Process -FilePath 'powershell.exe' -Verb RunAs -ArgumentList $arg
    exit
}

New-Item -ItemType Directory -Path $InstallDir -Force | Out-Null

# A normal manual run installs the persistent copy and Scheduled Tasks.
if (-not $EnforceOnly) {
    $sourcePath = [IO.Path]::GetFullPath($PSCommandPath)
    $destPath   = [IO.Path]::GetFullPath($InstalledScript)

    if ($sourcePath -ne $destPath) {
        Copy-Item -LiteralPath $sourcePath -Destination $InstalledScript -Force
    }

    $powershell = Join-Path $env:SystemRoot 'System32\WindowsPowerShell\v1.0\powershell.exe'
    $taskCommand = "`"$powershell`" -NoProfile -NonInteractive -ExecutionPolicy Bypass -File `"$InstalledScript`" -EnforceOnly"

    # Re-apply at every boot.
    & schtasks.exe /Create `
        /TN $BootTaskName `
        /SC ONSTART `
        /RU SYSTEM `
        /RL HIGHEST `
        /TR $taskCommand `
        /F | Out-Null

    # Re-apply every 15 minutes in case Windows Update recreates/re-enables a reboot task.
    & schtasks.exe /Create `
        /TN $GuardTaskName `
        /SC MINUTE `
        /MO 15 `
        /RU SYSTEM `
        /RL HIGHEST `
        /TR $taskCommand `
        /F | Out-Null

    Write-Log "Installed persistent script at $InstalledScript"
    Write-Log "Created Scheduled Tasks: $BootTaskName and $GuardTaskName"
}

Enforce-NoAutoRestart

if (-not $EnforceOnly) {
    Write-Host ''
    Write-Host 'Installed and enforced.' -ForegroundColor Green
    Write-Host "Persistent script: $InstalledScript"
    Write-Host "Startup task:      $BootTaskName"
    Write-Host "Guard task:        $GuardTaskName"
    Write-Host "Log:               $LogFile"
    Write-Host ''
    Write-Host 'Manual Restart and Shut down still work normally.'
}

Verify that it is installed

Check the scheduled tasks:

Get-ScheduledTask -TaskName "NoWindowsAutoRestart*"

Check the Windows Update policy values:

Get-ItemProperty "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU" |
    Select-Object AUOptions, NoAutoRebootWithLoggedOnUsers, AlwaysAutoRebootAtScheduledTime

You should see:

AUOptions                       : 4
NoAutoRebootWithLoggedOnUsers  : 1
AlwaysAutoRebootAtScheduledTime: 0

Logs

The script logs what it changes here:

C:\ProgramData\NoWindowsAutoRestart\NoWindowsAutoRestart.log

What this does not disable

Your normal Restart and Shut down commands still work. This is not intended to make the machine incapable of rebooting. The goal is to remove Windows Update's ability to casually make that decision for you.

There is one RDP wrinkle worth knowing: Microsoft says only an active Remote Desktop session counts as a signed-in user for the NoAutoRebootWithLoggedOnUsers policy. A disconnected RDP session does not receive the same protection from that policy.

Why not just use Active Hours?

Active Hours are useful if all you want is a quieter update schedule. They are not what I wanted. They still define a window in which Windows is allowed to decide that a restart is appropriate.

I want a much simpler rule: install the update, tell me a restart is required, then wait.

Microsoft references