Here’s a PowerShell script that can be used to scan for Windows updates, install them and optionally restart the system automatically. I’ve been using this for a few years now on Windows Server 2008 R2 and above without issues.
Please note that I’m not taking credit for all of this – the original script was published by Gregory Strike back in 2011 who adapted it from a VBScript originally created by Microsoft. I took Gregory’s version and added some additional warnings, modified the restart logic and added an email report which includes a description of the status codes.
You can schedule this using Task Scheduler specifying these values in the Actions tab:
Action:
Start a program
Program/Script:
C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe
Add arguments (optional):
c:\scripts\InstallUpdatesAndRestart.ps1 Y Y
The two Y’s at the end of the arguments are parameters for the powershell script – the first Y is to install updates automatically, the second one is to restart automatically if needed.
Note that you need to run this script with elevated privileges – start Windows PowerShell by using the Run as Administrator option if running interactively, or specify the Scheduled Task runs as the SYSTEM account and enable the ‘Run with highest privileges’ option.
# Script: InstallUpdatesAndRestart.ps1
# Author: Gregory Strike, Modified by Dan Tonge
# Website: www.GregoryStrike.com, www.techexplorer.co.uk
# Date: 19-02-2010, 07-06-2015 DT - added warnings, modified reboot logic and added email report with status code lookup
# 17-06-2021 DT - added check for elevated privileges
#
# Information: This script was adapted from the WUA_SearchDownloadInstall.vbs VBScript from Microsoft. It uses the
# Microsoft.Update.Session COM object to query a WSUS server, find applicable updates, and install them.
#
# InstallUpdatesAndRestart.ps1 is a little less verbose about what it is doing when compared to the original VBScript. The
# lines exist in the code below to show the same information as the original but are just commented out.
#
#
# InstallUpdatesAndRestart.ps1 can automatically install applicable updates by passing a Y to the script. The default
# behaviour is to ask whether or not to install the new updates.
#
# Syntax: .\InstallUpdatesAndRestart.ps1 [Install] [Reboot]
# Where [Install] is optional and can be "Y", "Yes", "No" or "N"
# Whether or not to install the updates automatically. If Null, the user will be prompted.
#
# Where [Reboot] is optional and can be "Y", "Yes", "No" or "N", This
# If updates require a reboot, whether or not to reboot automatically. If Null, the user will
# be prompted.
#--------------------------------------------------------------------------------------------------------------------------------------
function Test-IsAdmin {
try {
$identity = [Security.Principal.WindowsIdentity]::GetCurrent()
$principal = New-Object Security.Principal.WindowsPrincipal -ArgumentList $identity
return $principal.IsInRole( [Security.Principal.WindowsBuiltInRole]::Administrator )
} catch {
throw "Failed to determine if the current user has elevated privileges. The error was: '{0}'." -f $_
}
<#
.SYNOPSIS
Checks if the current Powershell instance is running with elevated privileges or not. Function by Andy Arismendi.
.EXAMPLE
PS C:\> Test-IsAdmin
.OUTPUTS
System.Boolean
True if the current Powershell is elevated, false if not.
.LINK
https://stackoverflow.com/questions/9999963/test-admin-rights-within-powershell-script
#>
}
# check script is running with admin rights
if (!(Test-IsAdmin)) {
Write-Host "The current Windows PowerShell session is not running as Administrator. Start Windows PowerShell by using the Run as Administrator option, and then try running the script again." -Fore Red
exit
}
## include the SendEmail PowerShell script
."c:\scripts\powershell\Send-Email.ps1"
## build a list of email recipients
$EmailRecipients = @("user@yourdomain.co.uk","admin@yourdomain.co.uk")
$EmailBody = ""
$UpdateSession = New-Object -Com Microsoft.Update.Session
$UpdateSearcher = $UpdateSession.CreateUpdateSearcher()
$Install = [System.String]$Args[0]
$Reboot = [System.String]$Args[1]
If ($Reboot.ToUpper() -eq "Y" -or $Reboot.ToUpper() -eq "YES"){
Write-Host("")
Write-Host("WARNING: this script will automatically restart this server if Windows updates") -Fore Red
Write-Host("are found and installed.") -Fore Red
Write-Host("")
Write-Host("Press CTRL-C to cancel if you don't want this to happen") -Fore Red
} else {
Write-Host("")
Write-Host("This script will not restart if Windows updates are found and installed.") -Fore Red
Write-Host("")
Write-Host("You should manually resart this server manually if necessary.") -Fore Red
}
Write-Host("")
Write-Host("Searching for applicable updates...") -Fore Green
$SearchResult = $UpdateSearcher.Search("IsInstalled=0 and Type='Software'")
Write-Host("")
Write-Host("List of applicable items on the machine:") -Fore Green
$EmailBody = $EmailBody + "List of applicable items on the machine:"
For ($X = 0; $X -lt $SearchResult.Updates.Count; $X++){
$Update = $SearchResult.Updates.Item($X)
Write-Host( ($X + 1).ToString() + "`> " + $Update.Title)
$EmailBody = $EmailBody + "`n" + ($X + 1).ToString() + "`> " + $Update.Title
}
If ($SearchResult.Updates.Count -eq 0) {
Write-Host("")
Write-Host("There are no applicable updates.")
Write-Host("")
Exit
}
#Write-Host("")
#Write-Host("Creating collection of updates to download:") -Fore Green
$UpdatesToDownload = New-Object -Com Microsoft.Update.UpdateColl
For ($X = 0; $X -lt $SearchResult.Updates.Count; $X++){
$Update = $SearchResult.Updates.Item($X)
#Write-Host( ($X + 1).ToString() + "`> Adding: " + $Update.Title)
$Null = $UpdatesToDownload.Add($Update)
}
Write-Host("")
Write-Host("Downloading Updates...") -Fore Green
$EmailBody = $EmailBody + "`n`nDownloading updates"
$Downloader = $UpdateSession.CreateUpdateDownloader()
$Downloader.Updates = $UpdatesToDownload
$Null = $Downloader.Download()
#Write-Host("")
#Write-Host("List of Downloaded Updates...") -Fore Green
$UpdatesToInstall = New-Object -Com Microsoft.Update.UpdateColl
For ($X = 0; $X -lt $SearchResult.Updates.Count; $X++){
$Update = $SearchResult.Updates.Item($X)
If ($Update.IsDownloaded) {
#Write-Host( ($X + 1).ToString() + "`> " + $Update.Title)
$Null = $UpdatesToInstall.Add($Update)
}
}
If (!$Install){
$Install = Read-Host("Would you like to install these updates now? (Y/N)")
}
## define a function to lookup windows update status codes
Function resResultDescription ($val)
{
Switch ($val){
0 {"Not Started"}
1 {"In Progress"}
2 {"Succeeded"}
3 {"Succeeded With Errors"}
4 {"Failed"}
5 {"Aborted"}
default {"Unknown ($val)"}
}
}
If ($Install.ToUpper() -eq "Y" -or $Install.ToUpper() -eq "YES"){
Write-Host("")
Write-Host("Installing Updates...") -Fore Green
$EmailBody = $EmailBody + "`nInstalling updates"
$Installer = $UpdateSession.CreateUpdateInstaller()
$Installer.Updates = $UpdatesToInstall
$InstallationResult = $Installer.Install()
Write-Host("")
Write-Host("List of Updates Installed with Results:") -Fore Green
$EmailBody = $EmailBody + "`n`nInstallation results:"
For ($X = 0; $X -lt $UpdatesToInstall.Count; $X++){
$ResultDescription = resResultDescription($InstallationResult.GetUpdateResult($X).ResultCode)
Write-Host($UpdatesToInstall.Item($X).Title + ": " + $ResultDescription)
$EmailBody = $EmailBody + "`n" + $ResultDescription + ": " + $UpdatesToInstall.Item($X).Title
}
Write-Host("")
Write-Host("Installation Result: " + $InstallationResult.ResultCode)
Write-Host(" Reboot Required: " + $InstallationResult.RebootRequired)
$ResultDescription2 = resResultDescription($InstallationResult.ResultCode)
$EmailBody = $EmailBody + "`nOverall results: " + $ResultDescription2 + "`nRestart Required: " + $InstallationResult.RebootRequired
If ($InstallationResult.RebootRequired -eq $True){
If (!$Reboot){
$Reboot = Read-Host("Would you like restart now? (Y/N)")
}
If ($Reboot.ToUpper() -eq "Y" -or $Reboot.ToUpper() -eq "YES"){
$ErrorActionPreference = 'Stop'
Write-Host("")
Write-Host("Restarting...") -Fore Green
$EmailBody = $EmailBody + "`n`nRestarting now..."
Send-Email -EmailTo $EmailRecipients -EmailSubject "Restarting after Windows updates installed" -EmailBody $EmailBody
$MyComputerName = [String]$env:computerName.ToLower()
$OperatingSystemObject = Get-WmiObject Win32_OperatingSystem -Comp $MyComputerName -EnableAllPrivileges
try {
$OperatingSystemObject.reboot()
} catch {
Write-Host("Error - couldn't restart using that method, trying another...") -Fore Red
try {
Restart-Computer -Force
} catch {
$e2 = $_.Exception
$EmailErrorBody = "Reboot failure: `n" + $e2
Send-Email -EmailTo $EmailRecipients -EmailSubject "Error while restarting" -EmailBody $EmailErrorBody
throw $e2
} finally {
# Send-Email -EmailTo $EmailRecipients -EmailSubject "Restarting after Windows updates installed" -EmailBody $EmailBody
}
} finally {
# Send-Email -EmailTo $EmailRecipients -EmailSubject "Restarting after Windows updates installed (2)" -EmailBody $EmailBody
}
} else {
#reboot required, but not enabled using script parameters
$EmailBody = $EmailBody + "`n`nFinished."
Send-Email -EmailTo $EmailRecipients -EmailSubject "Windows updates installed - manual restart needed ASAP" -EmailBody $EmailBody
}
}
else {
$EmailBody = $EmailBody + "`n`nFinished."
Send-Email -EmailTo $EmailRecipients -EmailSubject "Windows updates installed - no restart needed" -EmailBody $EmailBody
}
}
Note that this script uses the Send-Email.ps1 script from this post: https://www.techexplorer.co.uk/windows/powershell-script-to-send-email-with-optional-attachments/
Here’s an example email you receive from this script. Note that there are a couple of updates here that failed to install.
From: <servername@yourdomain.co.uk>
Date: Mon, 16 Mar 2020 at 06:03
Subject: Restarting after Windows updates installed
To: <user1@yourdomain.co.uk>, <user2@yourdomain.co.uk>
List of applicable items on the machine:
1> 2019-11 Security Monthly Quality Rollup for Windows Server 2012 R2 for
x64-based Systems (KB4525243)
2> 2020-03 Cumulative Security Update for Internet Explorer 11 for Windows
Server 2012 R2 for x64-based systems (KB4540671)
3> 2020-03 Security Monthly Quality Rollup for Windows Server 2012 R2 for
x64-based Systems (KB4541509)
4> 2020-03 Security Only Quality Update for Windows Server 2012 R2 for
x64-based Systems (KB4541505)
5> 2020-03 Servicing Stack Update for Windows Server 2012 R2 for x64-based
Systems (KB4540725)
Downloading updates
Installing updates
Installation results:
Failed: 2019-11 Security Monthly Quality Rollup for Windows Server 2012 R2
for x64-based Systems (KB4525243)
Succeeded: 2020-03 Cumulative Security Update for Internet Explorer 11 for
Windows Server 2012 R2 for x64-based systems (KB4540671)
Failed: 2020-03 Security Monthly Quality Rollup for Windows Server 2012 R2
for x64-based Systems (KB4541509)
Succeeded: 2020-03 Security Only Quality Update for Windows Server 2012 R2
for x64-based Systems (KB4541505)
Succeeded: 2020-03 Servicing Stack Update for Windows Server 2012 R2 for
x64-based Systems (KB4540725)
Overall results: Succeeded With Errors
Restart Required: True
Restarting now…
This works well if you run it manually. When you try to run it via a Scheduled Task, it doesn’t seem to work. What am I doing wrong?
Thanks for your comment, it’s difficult to know without seeing the specific setup of your task and any error messages you’re getting. I’m not able to provide support on this unfortunately but I’d suggest you check that the scheduled task is created with the following options to ensure the best chance of it being able to install the updates and restart:
I’ve noticed in certain versions of Windows Server there is a bug in the task scheduler which manifests itself if you’ve set the schedule to run on certain days of the week (rather than something simple like ‘daily’) If you don’t set the start date/time to a point in the future, the schedule doesn’t run at all.
To work around this, edit the trigger and ensure the first run date/time is in the future.
Thanks for your suggestion Neil, interesting bug!
Using this script at the moment and work great however, have you got a way to display the progress of the updates downloading and installing?
Hi Jack, not in any great detail. I would usually run this script from a scheduled task rather than interactively and then refer to the resulting email to see what happened.
There are a few lines in the code (120,121 and 128) which you can uncomment to show the list of updates that were downloaded, however I haven’t included a way to show the progress of each download or installation.
A quick searched turned up this page where someone else asked the same question for their own script and the suggested answer looks like it would work. You may be able to take elements from this script to create a modified version of your own that does what you need.
Unfortunately I can’t work on doing that myself at the moment, but will bear it in mind for the future.
Got an issue with the script doing feature updates, specifically 1909 to 21H2 for me. The pc restarts, however, doesn’t do the feature update. Script then starts again and picks the update up again. An ideas
Hi Jack, I’ not sure. I just tested this on a clean install of 1909 and was offered a range of updates including the 20H2 feature update. The status output from the script said that all were successfully installed, but got the same result as you, after restarting it was clear that the feature update did not install.
This could be related to the large difference between the installed 1909 and the target feature update version. This KB5000736 page talks about an enablement package that may help.
The Microsoft-generated log files that get created during a feature update are detailed here and may give you some clues. There’s also lot of detail here on Windows Update troubleshooting.
I would suggest trying a manual update to 21H2 on one machine to see if that also fails, or gives any other detail (e.g. prerequisites missing, drive incompatibility, not enough disk space). The Windows update assistant may help with this.
thank you for the script. I ran it remotely with this command:
Invoke-Command -FilePath C:\Workspace\FAR\Powershell\windowsupdatescript.ps1 -ComputerName $item.IP -Credential $cred