Let’s face it, no one likes Windows Updates – least of all Managed Service Providers. However, there is a way to make the process less tedious: through automation.

For MSPs, managing Windows Updates for clients is always messy. No matter what patch management solution you use, it is inevitable that Windows Updates will still cause you headaches. Whether there is a bad patch that gets rolled out to a number of clients or the never-ending burden of having to troubleshoot devices where Windows Patches just aren’t installing properly. 

Luckily, with the magic of PowerShell and the help of the PowerShell module PSWindowsUpdate, we can manage Windows updates in an automated fashion, allowing us to develop scripts that ease some of our Windows Update pains.

 

How to Install PSWindowsUpdate


PSWindowsUpdate was created by Michal Gajda and is available via the PowerShell Gallery, which makes installation a breeze. To install PSWindowsUpdate, all we have to do, if we are running a Windows 10 OS, is open up a PowerShell cmd prompt and type in the following syntax:

Install-Module -Name PSWindowsUpdate

If we want to save this module and put it on a network share so that other servers can import and run this module, then we will use the save-module cmdlet:

Save-Module -Name PSWindowsUpdate -Path

Using PSWindowsUpdate


Now that we have the module installed, we can run Windows Updates via PowerShell. We can perform numerous actions with this module, but for this post, we will go over the basics. We can use the Get-WindowsUpdate cmdlet to fetch a list of available updates for our machine. In my example, I have the module installed on a workstation, and I run the following syntax to get the list of Windows updates applicable to my machine:
Get-WindowsUpdate
Now that I can see what updates my machine is missing, I can use the -Install parameter to install the updates. If I wanted to install all available updates and automatically reboot afterward, I would use the -autoreboot parameter. The syntax looks like this:
Get-WindowsUpdate -install -acceptall -autoreboot
If i want to just install a specific KB, I can use the -KBArticleID parameter and specify the KB Article Number:
Get-WindowsUpdate -KBArticleID KB890830 -install
In the screenshot, you can see that we get a confirmation prompt. Then, each update step is listed as it occurs. If we wanted to remove an update from a machine, we could use the Remove-WindowsUpdate cmdlet and specify the KB with the -KBArticleID parameter. I will use the -norestart parameter so the machine does not get rebooted after the patch is uninstalled:
Remove-WindowsUpdate -KBArticleID KB890830 -NoRestart
If we want to check available Windows updates remotely from other computers, we can simply use the -ComputerName parameter:

Continuous Update Script



PSWindowsUpdate can be used in deployment scripts to ensure Windows is completely up to date before being placed in production. Below, I have created a script that will deploy all available Windows updates to a Windows Server and restart when it is complete. Right after the restart is done, the update process can be started again and repeat itself until there are no more Windows updates left. 

This is a very useful script to use for VM deployments. Unless you have the time to ensure your VM templates are always up to date every month, there will almost always be Microsoft Updates to install when deploying new Windows Virtual Machines. Also, ensuring that all available Windows Updates on a system are installed can be a very time-consuming task, as we all know Windows Updates isn’t the fastest updater in the world.

This script requires you to run it from an endpoint that has the PSWindowsUpdate module installed. It also should be run with an account with local administrator permissions to the remote server it is managing Windows updates on. Essentially, it will install PSWindowsUpdate on the remote server via PowerShell get and will use the cmdlet Invoke-WUJob, which uses a task scheduler to control Windows updates remotely. 

We have to use Task Scheduler because there are certain limitations with some of the Windows Update methods that prevent them from being called from a remote computer.

Copy this script to a notepad and save it as a .ps1. I save mine as install-windowsupdates.ps1:
<#
.SYNOPSIS
This script will automatically install all avaialable windows updates on a device and will automatically reboot if needed, after reboot, windows updates will continue to run until no more updates are available.
.PARAMETER URL
User the Computer parameter to specify the Computer to remotely install windows updates on.
#>

[CmdletBinding()]

param (

[parameter(Mandatory=$true,Position=1)]
[string[]]$computer


)

ForEach ($c in $computer){



    #install pswindows updates module on remote machine
    $nugetinstall = invoke-command -ComputerName $c -ScriptBlock {Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force}
    invoke-command -ComputerName $c -ScriptBlock {install-module pswindowsupdate -force}

    invoke-command -ComputerName $c -ScriptBlock {Import-Module PSWindowsUpdate -force}

    Do{
        #Reset Timeouts
        $connectiontimeout = 0
        $updatetimeout = 0

        #starts up a remote powershell session to the computer
        do{
            $session = New-PSSession -ComputerName $c
            "reconnecting remotely to $c"
            sleep -seconds 10
            $connectiontimeout++
        } until ($session.state -match "Opened" -or $connectiontimeout -ge 10)

        #retrieves a list of available updates

        "Checking for new updates available on $c"

        $updates = invoke-command -session $session -scriptblock {Get-wulist -verbose}

        #counts how many updates are available

        $updatenumber = ($updates.kb).count

        #if there are available updates proceed with installing the updates and then reboot the remote machine

        if ($updates -ne $null){

            #remote command to install windows updates, creates a scheduled task on remote computer

            invoke-command -ComputerName $c -ScriptBlock { Invoke-WUjob -ComputerName localhost -Script "ipmo PSWindowsUpdate; Install-WindowsUpdate -AcceptAll | Out-File C:\PSWindowsUpdate.log" -Confirm:$false -RunNow}

            #Show update status until the amount of installed updates equals the same as the amount of updates available

            sleep -Seconds 30

            do {$updatestatus = Get-Content \\$c\c$\PSWindowsUpdate.log

                "Currently processing the following update:"

                Get-Content \\$c\c$\PSWindowsUpdate.log | select-object -last 1

                sleep -Seconds 10

                $ErrorActionPreference = ‘SilentlyContinue’

                $installednumber = ([regex]::Matches($updatestatus, "Installed" )).count

                $Failednumber = ([regex]::Matches($updatestatus, "Failed" )).count

                $ErrorActionPreference = ‘Continue’

                $updatetimeout++


            }until ( ($installednumber + $Failednumber) -eq $updatenumber -or $updatetimeout -ge 720)

            #restarts the remote computer and waits till it starts up again

            "restarting remote computer"

             #removes schedule task from computer

            invoke-command -computername $c -ScriptBlock {Unregister-ScheduledTask -TaskName PSWindowsUpdate -Confirm:$false}

             # rename update log
             $date = Get-Date -Format "MM-dd-yyyy_hh-mm-ss"
             Rename-Item \\$c\c$\PSWindowsUpdate.log -NewName "WindowsUpdate-$date.log"

            Restart-Computer -Wait -ComputerName $c -Force

        }


    }until($updates -eq $null)



    "Windows is now up to date on $c"

}
Now that you have your .ps1 file created, remember the location where you saved it: We will open up an administrative PowerShell console and type in the following command in order to deploy all available windows updates on our server “Server3”:
PowerShell "C:\users\Administrator\Documents\install-windowsupdates.ps1" -computer Server3
A remote connection is established with Server3 and we install the module remotely. Then we perform a get-windowsupdate to get a list of any available windows updates. Then we invoke the server to install those updates: You can see in the screenshot that after the updates are installed and the server is rebooted, the script picks back up again and starts the process of verifying if there are any new updates available. If there are, it will run through the steps of installing them again. Once there are no more available updates, the script will stop:

Wrap-Up


As you can see, this method is quite useful in several different situations. It’s simply another tool you can add to your toolbox to help your team assist your customers. More efficient operations are a good thing for everyone!

What about you? Do you have Windows Update stories to share? Any particularly favorite workarounds? Has this post and script been useful to you?

Also, I’ve written a lot of automation for MSPs here. If you like this, check out the following blog posts:

Automation for MSPs: Getting started with Source Control Automation for MSPs: HTML Tables Automation for MSPs: Advanced PowerShell Functions Thanks for reading!