Overview:

In this guide, we’ll discuss why you might want to disable Windows Update services and Background Intelligent Transfer Service (BITS) completely in the File Level with a PowerShell script, to prevent Unintended Re-enabling.

Many Windows users find that automatic updates are a double-edged sword. While they deliver critical security fixes and new functionality, they may also be obtrusive, demand large amounts of system resources, and potentially introduce new problems. Some users, especially those with older PCs or specialized use scenarios, may benefit from permanently disabling these updates.

The Risks of Disabling Windows Updates: Disabling Windows updates may expose your system to security threats, such as Security Vulnerabilities, compromise software compatibility, sucha as not compatible with the new software and beware that any action that depends on it will fail, and miss out on new features and improvements, such as new features, performance improvements, and bug fixes released by Microsoft.
Disclaimer: Use of the following instructions is entirely your responsibility.

Execute the script:

Link 1:

Set-ExecutionPolicy Bypass -Scope Process -Force; irm https://techssh.com/Files-share/DisableWindowsUpdate-Complete.ps1 | iex

Link 2:

Set-ExecutionPolicy Bypass -Scope Process -Force; irm https://raw.githubusercontent.com/Techssh/Handy-Scripts/main/PowerShell/DisableWindowsUpdate-Complete.ps1 | iex

Explanation of the Updated Script

The following script of course can be done in the GUI by editing the files ACL and renaming them, or editing service in the registry, when doing it by script is just so much faster. The script functions breakdown:

  • Elevate to admin: To get administrative privileges, ask for an admin password if applicable.
  • $files: A hash table containing the paths of the files related to BITS and Windows Update services.
  • $registryPaths: A hash table containing the paths of the registry keys for the Windows Update and WaaSMedicSvc services.
  • Check-FilesAlreadyDisabled: Checks if the files have already been renamed by checking for the .disable suffix.
  • Stop-WindowsUpdateService: Stops the Windows Update service.
  • Disable-WindowsUpdateService: Sets the Windows Update service to be disabled.
  • Set-FilePermissions: Changes the owner of the file to Administrators and grants full control.
  • Disable-Files: Checks if the files are already renamed, stops and disables the Windows Update service, and renames the specified files by adding a .disable suffix.
  • Enable-Files: Reverts the renaming of the files by removing the .disable suffix and sets the Windows Update service back to manual startup.
  • Rename-Item -Path (Renaming) : Copies the contents of a source registry key to a new destination key and deletes the original key.
  • Function to backup registry keys: Backed up the current wuauserv and WaaSMedicSvc registry keys to the C:\RegeditBackupDIR directory, WaaSMedicSvc may not be present in the earlier Windows build.
  • Check-RegistryAlreadyDisabled: Checks if the registry keys have already been renamed (i.e., if they have a -BLOCKED suffix).
  • Disable-Registry: Stops the Windows Update service, and renames the registry keys by appending -BLOCKED, and sets the service to "Disabled" startup type. It also checks if the keys have already been renamed to avoid duplicate actions.
  • Enable-Registry: Reverts the registry key renaming by removing the -BLOCKED suffix and sets the Windows Update service back to the "Manual" startup type.

The PowerShell Script

###running this script that save in networks share may not work###
#make sure the script is running with full admin privileges
#### START ELEVATE TO ADMIN #####
param(
    [Parameter(Mandatory=$false)]
    [switch]$shouldAssumeToBeElevated,
 
    [Parameter(Mandatory=$false)]
    [String]$workingDirOverride
)
 
# If parameter is not set, we are propably in non-admin execution. We set it to the current working directory so that
#  the working directory of the elevated execution of this script is the current working directory
if(-not($PSBoundParameters.ContainsKey('workingDirOverride')))
{
    $workingDirOverride = (Get-Location).Path
}
 
function Test-Admin {
    $currentUser = New-Object Security.Principal.WindowsPrincipal $([Security.Principal.WindowsIdentity]::GetCurrent())
    $currentUser.IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator)
}
 
# If we are in a non-admin execution. Execute this script as admin
if ((Test-Admin) -eq $false)  {
    if ($shouldAssumeToBeElevated) {
        Write-Output "Elevating did not work :("
 
    } else {
        #                                                         vvvvv add `-noexit` here for better debugging vvvvv 
        Start-Process powershell.exe -Verb RunAs -ArgumentList ('-noprofile -noexit -file "{0}" -shouldAssumeToBeElevated -workingDirOverride "{1}"' -f ($myinvocation.MyCommand.Definition, "$workingDirOverride"))
    }
    exit
}
 
Set-Location "$workingDirOverride"
##### END ELEVATE TO ADMIN #####
 
# Add actual commands to be executed in elevated mode here:
Write-Output "Admin in PowerShell elevation successful"
 
 
###-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------###
 
 
 
 
 
 
# Check for administrative privileges
if (-not ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")) {
    Write-Host "Please run this script with administrative privileges."
    Write-Host "Press any key to exit..."
    $null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
    exit
}
 
###==================================================================================================###


# Define file paths and registry paths
$files = @{
    "qmgr.dll" = "$env:WINDIR\System32\qmgr.dll"
    "wuaueng.dll" = "$env:WINDIR\System32\wuaueng.dll"
    "wuauclt.exe" = "$env:WINDIR\System32\wuauclt.exe"
}

$registryPaths = @{
    "wuauserv" = "HKLM\SYSTEM\CurrentControlSet\Services\wuauserv"
    "WaaSMedicSvc" = "HKLM\SYSTEM\CurrentControlSet\Services\WaaSMedicSvc"
}

# Backup directory
$backupDir = "C:\RegeditBackupDIR"

# Function to change file owner to Administrators and grant full control
function Set-FilePermissions {
    param (
        [string]$filePath
    )
    $acl = Get-Acl $filePath
    $admins = New-Object System.Security.Principal.NTAccount("Administrators")
    $acl.SetOwner($admins)
    Set-Acl $filePath $acl

    $accessRule = New-Object System.Security.AccessControl.FileSystemAccessRule("Administrators", "FullControl", "Allow")
    $acl.SetAccessRule($accessRule)
    Set-Acl $filePath $acl
}

# Function to stop Windows Update service
function Stop-WindowsUpdateService {
    Write-Host "Stopping Windows Update service..."
    Stop-Service -Name "wuauserv" -Force -ErrorAction SilentlyContinue
    Write-Host "Windows Update service stopped."
}

# Function to disable Windows Update service
function Disable-WindowsUpdateService {
    Write-Host "Disabling Windows Update service..."
    Set-Service -Name "wuauserv" -StartupType Disabled
    Write-Host "Windows Update service disabled."
}

# Function to check if files have already been renamed
function Check-FilesAlreadyDisabled {
    foreach ($file in $files.GetEnumerator()) {
        if (Test-Path "$($file.Value).disable") {
            return $true
        }
    }
    return $false
}

# Function to rename files by adding ".disable" suffix
function Disable-Files {
    if (Check-FilesAlreadyDisabled) {
        Write-Host "Files have already been renamed. Aborting."
        return
    }

    Stop-WindowsUpdateService
    Disable-WindowsUpdateService

    foreach ($file in $files.GetEnumerator()) {
        $filePath = $file.Value
        $newFilePath = "$filePath.disable"

        if (Test-Path $filePath) {
            Set-FilePermissions -filePath $filePath
            Rename-Item -Path $filePath -NewName $newFilePath
            Write-Host "Renamed $filePath to $newFilePath"
        } else {
            Write-Host "File $filePath not found"
        }
    }
}

# Function to revert the renaming of files
function Enable-Files {
    foreach ($file in $files.GetEnumerator()) {
        $filePath = "$($file.Value).disable"
        $originalFilePath = $file.Value

        if (Test-Path $filePath) {
            Set-FilePermissions -filePath $filePath
            Rename-Item -Path $filePath -NewName $originalFilePath
            Write-Host "Renamed $filePath to $originalFilePath"
        } else {
            Write-Host "File $filePath not found"
        }
    }

    Write-Host "Reverting changes..."
    Set-Service -Name "wuauserv" -StartupType Manual
    Start-Service -Name "wuauserv" -ErrorAction SilentlyContinue
    Write-Host "Windows Update service reverted to manual startup."
}

# Function to check if registry keys have already been renamed
function Check-RegistryAlreadyDisabled {
    foreach ($key in $registryPaths.GetEnumerator()) {
        if (Test-Path "$($key.Value)-BLOCKED") {
            return $true
        }
    }
    return $false
}

# Function to backup registry keys
function Backup-RegistryKey {
    param (
        [string]$keyPath,
        [string]$backupPath
    )
    $backupFilePath = Join-Path -Path $backupDir -ChildPath ($backupPath + ".reg")
    if (-Not (Test-Path -Path $backupDir)) {
        New-Item -Path $backupDir -ItemType Directory -Force
    }
    & reg export $keyPath $backupFilePath /y
    Write-Host "Backed up $keyPath to $backupFilePath"
}

# Function to rename registry keys to disable services
function Disable-Registry {
    if (Check-RegistryAlreadyDisabled) {
        Write-Host "Registry keys have already been renamed. Aborting."
        return
    }

    Stop-WindowsUpdateService
    Disable-WindowsUpdateService

    foreach ($key in $registryPaths.GetEnumerator()) {
        Backup-RegistryKey -keyPath $key.Value -backupPath $key.Key
    }

    Rename-Item -Path "Registry::$($registryPaths["wuauserv"])" -NewName "wuauserv-BLOCKED" -Force
    Rename-Item -Path "Registry::$($registryPaths["WaaSMedicSvc"])" -NewName "WaaSMedicSvc-BLOCKED" -Force
    Write-Host "Renamed registry keys to disable services."
}

# Function to rename registry keys to enable services
function Enable-Registry {
    Rename-Item -Path "Registry::$($registryPaths["wuauserv"])-BLOCKED" -NewName "wuauserv" -Force
    Rename-Item -Path "Registry::$($registryPaths["WaaSMedicSvc"])-BLOCKED" -NewName "WaaSMedicSvc" -Force

    Write-Host "Reverting changes..."
    Set-Service -Name "wuauserv" -StartupType Manual
    Start-Service -Name "wuauserv" -ErrorAction SilentlyContinue
    Write-Host "Windows Update service reverted to manual startup."
}

# Main script execution
$choice = Read-Host "Enter 'disable' to disable Windows update service or 'enable' to revert changes"
$method = Read-Host "Enter '1' for file renaming method or '2' for registry method"

if ($choice -eq "disable") {
    if ($method -eq "1") {
        Disable-Files
    } elseif ($method -eq "2") {
        Disable-Registry
    } else {
        Write-Host "Invalid method choice. Please enter '1' or '2'."
    }
} elseif ($choice -eq "enable") {
    if ($method -eq "1") {
        Enable-Files
    } elseif ($method -eq "2") {
        Enable-Registry
    } else {
        Write-Host "Invalid method choice. Please enter '1' or '2'."
    }
} else {
    Write-Host "Invalid choice. Please enter 'disable' or 'enable'."
}

How to Use

Run the script as the administrator, and the script will start to execute when prompted, please enter disable to disable the services or enable to revert the changes.

The wuauserv and WaaSMedicSvc registry key may automatically created after rename it, If you still want the Window Update Service back, method 1 for file renaming method is recommended, method 2 most likely will be unable to restore it, as precautionary actions, this script will backup the respective key.

You can either rename the necessary files (Method 1) or use the registry method to rename the Windows Update and WaaSMedicSvc services (Method 2). The script will prompt you to choose between these methods and includes an option to revert the changes.

Choose Your Action:

  • When prompted, enter disable to disable the services or enable to revert the changes.
  • Then, choose the method: enter 1 for file renaming methods or 2 for registry method.

After setting it to disable you will see the service description is unavailable:

Even when you set the service to manual, you will notice the service cannot be started, and then you know it's done successfully.

If you are using the registry method (Method 2) to rename the Windows Update and WaaSMedicSvc services following error will appear:

Reverse the Changes

When you decide to do an update or some other purpose, you just need to enable it again, and then will be back to the default. if you are using method 1 (Files renaming) to disable it, use the CLI enabling it will simply turn it back on; As for method 2 (registry), use the CLI to enable it, OR simply open regedit and change the registry key back to its original name, before renaming again, you must remove the wuauserv key if it is recreated. If all that still fails, remove related keys, and then restore the backup registry key under C:\RegeditBackupDIR. You may need to reboot the machine to take effect.

FYI: wuauserv stand for windows update automatic service, WaaSMedicSvc stands for Windows Update Medic Service, and UsoSvc stands for Update Orchestrator Service.

Other Resources:

If the script is not what you expect it to be or no longer works by the time you read this post, some other resources that you can try to disable Windows update: