PS: Remotely updating devices
I recently had to force a collection of PCs to update - they were configured using Windows Update for Business, all the policies and settings were telling them when to update and how, yet they just hadn't - whether there was something on the UI that the primary user was just ignoring, I'm not sure. Anyway they were stuck on Windows 10 2004, and on the July update.
As they're all configured for WUfB there wasn't anything I could realistically do through Config Manager, besides maybe run these steps as a PowerShell Script and push out that way. Instead I decided to look at PSWindowsUpdate. In this post I'll go through what I did, and share the scripts I used. My aim here was to get the rogue devices patched and updated to 21H1. I did still use Config Manager to help with this task - to wake devices using either the Client Notification > Wake, or the Recast Right Click Tools Wake on LAN feature. I'm not going to go into all the features of PSWindowsUpdate in any detail, there are plenty of good posts on the Internet about this already which can be found with a quick search.
Installing PSWindowsUpdate
The first step here was installing the PSWindowsUpdate module on the device I wanted to manage things from. While you can pass a Credentials parameter to these commands I found it much easier to just run the PowerShell window as an admin user (which has admin privileges on all target devices). I've assumed that in the scripts and not included a Credential parameter. We also need an array of computers that we wish to update.
Install-Module PSWindowsUpdate -Force
Import-Module PSWindowsUpdate
$Computers = @("desktop-1","desktop-2")
PSWindowsUpdate has a variety of commands for managing Windows Updates. I will be using Invoke-WUJob (formerly known as Invoke-WUInstall) and Install-WindowsUpdate. The other command to note is Get-WindowsUpdate, which will show you the available updates for the target machine.
Initially here I was planning on just running Install-WindowsUpdate and giving it an array of computer names, but you can't install Windows Updates remotely - not even if you open a PSSession and try there, you just get Access Denied. That's where Invoke-WUJob comes into play.
Invoke-WUJob will create a scheduled task on the target device(s), set to run the command specified, and it runs as SYSTEM and immediately. It's also supposed to install PSWindowsUpdate if it's not already on the target device, although this didn't work for me - hence the first part of the script. This will check if the module is already installed on each device, if not then install NuGet and PSWindowsUpdate with no prompts.
Invoke-Command ($Computers) {
If ($null -eq (Get-Module -Name PSWindowsUpdate -ListAvailable) ) {
Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force
Install-Module PSWindowsUpdate -Force
Import-Module PSWindowsUpdate
}
}
Determine Windows Version
Now that we've got the module installed on the devices, we could have a look and see which version of Windows they are currently running. This will help us identify targets for the feature update. Unfortunately this won't show you the patch level, but you can work out which version you are running.
$Results = @()
foreach ($Computer in $Computers) {
$Version = Invoke-Command ($Computer) { [Environment]::OSVersion } -ErrorAction SilentlyContinue
if ($null -eq $Version) {
$Results += [PSCustomObject]@{
"ComputerName" = $Computer
"Version" = "(Device offline)"
}
} else {
$Results += [PSCustomObject]@{
"ComputerName" = $Computer
"Version" = $Version.Version.ToString()
}
}
}
$Results
This script will grab the OSVersion data from each computer and creates a PSCustomObject containing the computer name and the version string. If the device is offline, or the Invoke-Command fails for some reason, it'll put "(Device offline)" in the custom object. Finally we output the array of custom objects to give this kind of output:
You can see from the output that desktop-1 is already running 21H1, and desktop-2 is stuck on 2004. The version numbers can be found on the Microsoft Docs.
Pushing the Updates
Next step is to install the updates. Here I've gone with installing all the available updates, and allowing reboots. You can alter this by passing different parameters to Install-WindowsUpdate:
Invoke-WuJob -ComputerName $Computers -Script { ipmo PSWindowsUpdate; Install-WindowsUpdate -AcceptAll -AutoReboot | Out-File "C:\Windows\PSWindowsUpdate.log"} -RunNow -Confirm:$false -Verbose -ErrorAction Ignore
- AcceptAll: Accept all available updates
- AutoReboot: Reboot the device automatically if an update requires it
- IgnoreReboot: Don't auto reboot the device
- UpdateID: Specify the KB you want to install
This will run through all the computers, create a scheduled task on each and run powershell.exe -Command "ipmo PSWindowsUpdate; Install-WindowsUpdate -AcceptAll -AutoReboot | Out-File C:\Windows\PSWindowsUpdate.log" immediately as SYSTEM.
We can monitor the output if we wish, by viewing C:\Windows\PSWindowsUpdate.log on any of the devices, ideally in CMTrace or a similar tool which allows for "live viewing" a file as it updates. The output will show the computer name, status, KB number, size and title of each update - they'll list three times once it's completed, as shown in the screenshot:
You can monitor whether they have completed or not with the Get-WUJob cmdlet:
Get-WUJob -ComputerName $Computers
This will give you a table showing the computer name, the task name (PSWindowsUpdate), and the task action. When this list is empty, then it's all completed.
Once you're done, you can run the version check script again, and hopefully find all devices are now on 21H1. In my case I had to run the Invoke-WUJob bit a second time, to put 21H1 on.
Update: Off the back of this I realised I could use Scripts within Config Manager to query the windows version - there are other ways to do this but I find the device data in CM tends to lag behind. Simply create a script under Software Library > Scripts:
([Environment]::OSVersion).Version.ToString()
and run that on your target devices. You will then get script output showing the counts for each version found:
This can further be tweaked to show how many are on 21H1, and the computer names of those which are not:
$Version = ([Environment]::OSVersion).Version.ToString()
if ($Version -ne "10.0.19043.0") {
Write-Host "$Env:Computername $Version"
} else {
Write-Host "$Version"
}