Randomize Windows Updates via PowerShell and PSWindowsUpdate

If you manage a group of computers that have fallen behind on installing Windows Updates, this procedure and a few PowerShell tools may help you less-disruptively install a just a few randomized updates at a time when computers are idle.

Objective

Many of the computers in my environment are used 24/7 by rotating shifts of users. My objective is to sneak in some Windows Updates on a group of computers when they are not in use. Some computers may require as many as 100 outstanding updates. Applying them all at the same time would take a while and create a lengthy “Configuring Windows updates, do not turn off your computer” process on startup after a required reboot.

Idea: Install just a few updates when idle

My idea is to use Group Policy Preferences to create a Scheduled Task that will run a PowerShell script that leverages the Windows Update PowerShell Module to install just a few updates at a time.

Steps:

  • Use Group Policy Preferences to copy Windows Update PowerShell Module from a network share to “%WINDIR%\System32\WindowsPowerShell\v1.0\Modules\PSWindowsUpdate” on each workstation
    (e.g. Group Policy Preferences > Computer Configuration > Preferences > Windows Settings > Files)

  • Use Group Policy Preferences to create a Scheduled Task on each workstation that will Run When Idle my Randomize Windows Updates script on each workstation
    (e.g. Group Policy Preferences > Computer Configuration > Preferences > Windows Settings > Control Panel Settings > Scheduled Tasks)

Prerequisites

I’m going to assume that you know how to use Group Policy Preferences to copy files and create scheduled tasks on one or more computers (hint, see links I noted above).

I’ll also assume that you can figure out how to use Michal Gajda’s great Windows Update PowerShell Module (hint, use Get-Help Get-WUInstall -Full for inline documentation).

You also know how to Unblock-File and Set-ExecutionPolicy to get scripts running. With these prerequisites out of the way, it’s time to introduce my randomizing script.

Randomize Windows Updates via PowerShell script

Here’s my script.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
# Purpose: Use the PowerShell Windows Update to install a few random updates and reboot
# Requirements: Module must be copied to and run on local machine
# Source: https://gallery.technet.microsoft.com/scriptcenter/2d191bcd-3308-4edd-9de2-88dff796b0bc
# Network Share: \\server\Scripts\WindowsUpdates\PSWindowsUpdateScripts\install-random-updates-reboot.ps1
# Implementation: Group Policy could copy module to client, Group Policy could create a Schedule Task to run script when idle
# Author: Jason Pearce created this script (not the module)
# Date: 2016 Feb 2
# Version: 2.1
 
# ####################
# BEGIN Variables
# ####################
 
# Count: Define how many random updates with KB values you want to install
$WUCount = 0
 
# Module Source Path: The network share that contains the PowerShell Windows Update module
$WUModuleSourcePath = '\\server\Scripts\WindowsUpdates\PSWindowsUpdate\*'
 
# Logs Local Path: The local path to write logs
$WULocalLogPath = $ENV:SystemDrive+'\PSLogs'
 
# Logs Network Path: The network path to copy logs as a central repository
$WUNetworkLogPath = '\\server\ActivityLogs\PSWindowsUpdate\Computers\'
 
# Logs Network Shortcut Path: The network path to create shortcuts to local administrative shares
$WUNetworkShortcutPath = '\\server\ActivityLogs\PSWindowsUpdate\LocalPSLogsFolders\'
 
# ####################
# END Variables
# ####################
 
# Copy Module: Copy module to client IF it does not already exist
$WUModuleDestinationPath = $ENV:SystemRoot+'\System32\WindowsPowerShell\v1.0\Modules\PSWindowsUpdate'
IF ( -Not (Test-Path -Path $WUModuleDestinationPath)) {New-Item $WUModuleDestinationPath -ItemType Directory; Copy-Item -Path $WUModuleSourcePath -Destination $WUModuleDestinationPath -Recurse -Force}
 
# Import Module: Import the Powershell Windows Update module
Import-Module PSWindowsUpdate
 
# Date: Get the date and time in a filename friendly format
$WUDate = Get-Date -Format yyyyMMdd-HHmmss
 
# Log Folder: Create a log folder IF it does not already exist
IF ( -Not (Test-Path -Path $WULocalLogPath)) {New-Item -Path $WULocalLogPath -ItemType Directory}
 
# Log Filename: Create a log filename for module, computer name, date, and time
$WULogFile = $WULocalLogPath+'\PSWinUp-'+$ENV:ComputerName+'-'+$WUDate+'.log'
 
# Log Header: Create log file with header and time stamp
$WUNewLine = "`r`n"
$WUNewBreak = "===================="
$WULogHeader = $WUNewBreak + $WUNewLine + "Begin: " + $WUDate + $WUNewLine + $WUNewBreak
 
# Create Shortcut:Create a Shortcut on a network share to the PSLogs folder
$WUTargetFile = '\\'+$ENV:ComputerName+'\C$\PSLogs'
$WUShortcutFile = $WUNetworkShortcutPath+$ENV:ComputerName+'-PSLogsFolder.lnk'
$WUWScriptShell = New-Object -ComObject WScript.Shell
$WUShortcut = $WUWScriptShell.CreateShortcut($WUShortcutFile)
$WUShortcut.TargetPath = $WUTargetFile
$WUShortcut.Save()
 
# List All Updates: Get a list of all updates
$WUAllUpdates = Get-WUList
 
# List Random Updates: Filter updates that do/don't have a KB number, randomize them, output the defined count, then convert list to comma-delimited string
$WURandomUpdatesWithKB = ($WUAllUpdates | Where-Object {$_.KB -like "KB*"} | Select-Object -Expand KB | Get-Random -Count $WUCount) -join ","
$WURandomUpdatesWithoutKB = ($WUAllUpdates | Where-Object {$_.KB -notlike "KB*"} | Select-Object -Expand Title | Get-Random -Count $WUCount) -join ","
 
# Measure Updates: Count how many updates were selected ($_.Count will be used later)
$WUMeasuredAllUpdates = $WUAllUpdates | Measure-Object
$WUMeasuredUpdatesWithKB = $WUAllUpdates | Where-Object {$_.KB -like "KB*"} | Measure-Object
$WUMeasuredUpdatesWithoutKB = $WUAllUpdates | Where-Object {$_.KB -notlike "KB*"} | Measure-Object
 
# IF the correct quantity of random updates are needed, install them. ELSEIF, install all remaining updates if any. ELSE, do nothing and exit.
IF ($WUMeasuredUpdatesWithKB.Count -eq $WUCount)
{
	# IF: If there are X needed random updates, install those X updates.
 
	# Log Header: Write the nice log header
	$WULogHeader | Out-File -FilePath $WULogFile
 
	# Install: Install selected random updates, append to log, and automatically reboot
	Get-WUInstall -KBArticleID "$WURandomUpdatesWithKB" -AcceptAll -AutoReboot -Verbose | Out-File -FilePath $WULogFile -Append
 
	# Copy Log: Try to copy log file to a network share before automatic reboot
	Copy-Item -Path $WULogFile -Destination $WUNetworkLogPath
}
ELSEIF ($WUMeasuredAllUpdates.Count -gt 0)
{
	# ELSEIF: There must be fewer than X needed random updates, so install all updates (even those without a KB value) if they exist.
 
	# Log Header: Write the nice log header
	$WULogHeader | Out-File -FilePath $WULogFile
 
	# Install: Install all remaining updates, append to log, and automatically reboot
	Get-WUInstall -AcceptAll -AutoReboot -Verbose | Out-File -FilePath $WULogFile -Append
 
	# Copy Log: Try to copy log file to a network share before automatic reboot
	Copy-Item -Path $WULogFile -Destination $WUNetworkLogPath
}
ELSE 
{
	# ELSE: No updates (with or without a KB) are needed
	Exit
}

If you don’t care about logging when and what updates are applied, then all you really need are these two lines to install three random (but needed) updates:

1
2
3
4
5
# Randomize: Get all updates by KB number, randomize, output the defined count, then convert list to comma-delimited string
$WURandomUpdatesByKB = (Get-WUList | Select-Object -Expand KB | Get-Random -Count 3) -join ","
 
# Install: Install random updates and automatically reboot
Get-WUInstall -KBArticleID "$WURandomUpdatesByKB" -AcceptAll -AutoReboot -Verbose

Personally, I like logging which updates were (or were not) successfully installed. This gives my Help Desk team the ability to quickly reference what recently changed if an update ended up causing problems.

By installing just a few updates at a time when machines are idle, I’m able to slowly but eventually get a group of computers caught up with their updates, which can then be more easily managed via Windows Server Update Services (WSUS) with the rest of the machines in the environment.