Building PowerShell Tools for MSPs: Automating Windows Updates

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 your using, 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 there is 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.

Automating Windows Updates for MSPs

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 off 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 now run Windows Updates via PowerShell. There are a numerous amount of actions we can perform 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 updates that are available 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 and afterward, I would use the -autoreboot parameter and the syntax would look 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 and 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 make sure Windows is completely up to date before it is placed in production. Below I have created a script that will deploy all available windows updates to a Windows Server and restart when complete. Right after the restart is done, the update process can be started again and repeats 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 are almost always going to be Microsoft Updates to install when deploying new Windows Virtual Machines. Also, the process of 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 that you run it from an endpoint that has the PSWindowsUpdate module installed, it also should be run with an account that has local administrator permissions to the remote server that 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 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 is 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? Let us know in the comments section below!

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!

Get a 30-day trial of Altaro VM Backup for MSPs

Manage all your customer VM backups from a single cloud console, on a monthly subscription. Try Altaro VM Backup for MSPs for 30 days - no strings attached!

Share this post

Not a DOJO Member yet?

Join thousands of other IT pros and receive a weekly roundup email with the latest content & updates!

119 thoughts on "Building PowerShell Tools for MSPs: Automating Windows Updates"

  • ovais humayun says:

    thanks for the great script , if i have to run this for all the computers in a text file how can i do that

    • To be honest I should have made the script with multiple devices in mind. In the near future, I will release an improved modified version of this which will be able to do multiple computers from the parameter. In the mean time, give this a try. Just replace the -path with a list of your computers, make sure there is no extra white space at the end of the list:

      $computers = Get-content -Path c:\listofcomputers.txt

      Foreach ($computer in $computers){PowerShell “C:\users\Administrator\Documents\install-windowsupdates.ps1” -computer $computer}

      Let me know if that works. Thanks for the comment!

      • Bee says:

        When I throw that into a powershell script and try to run it, i get this error.

        “\\domain.local\dfs\filepath\serverupdates.ps
        1 : The module ‘“’ could not be loaded. For more information, run ‘Import-Module “’.
        At line:1 char:1
        + “\\domain.local\dfs\filepath …
        + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
        + CategoryInfo : ObjectNotFound: (“\\domain.local\…rverupdates.ps1:String) [], CommandNotFoundException
        + FullyQualifiedErrorId : CouldNotAutoLoadModule

        • The workstations will need access to the internet in able to install the PSWindowsUpdate PowerShell module. Try running install-module PSWIndowsUpdate on one of the workstations and see what error you’re getting.

      • Bee says:

        Never mind on my last comment but here is what I wrote to have it loop through all of the servers in a list and open a new powershell console for them.
        I couldn’t figure out how to get a workflow to call another powershell script and pass the parameters into the PS script so I know this isn’t the cleanest/best practice but works if you have a beefier computer and/or not too many computers in your list.

        $computers = Get-content -Path \\somepath\serverlist.txt

        Foreach ($computer in $computers)
        {
        Start-Process powershell -argument “\\somepath\serverupdates.ps1 -computer $computer” -PassThru
        }

        • Yes this work well! Also, if your using Powershell 7 you can make use of the new -Parallel parameter for ForEach and run the script on multiple machines at a time.

          • Piyush says:

            This script is awesome work however when i try to use it on multiple devices i am getting a error message on few, mentioned as below.
            The term ‘Install-PackageProvider’ is not recognized as the name of a cmdlet, function, script file, or operable program. Check the
            spelling of the name, or if a path was included, verify that the path is correct and try again.
            + CategoryInfo : ObjectNotFound: (Install-PackageProvider:String) [], CommandNotFoundException
            + FullyQualifiedErrorId : CommandNotFoundException

            The term ‘install-module’ is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling
            of the name, or if a path was included, verify that the path is correct and try again.
            + CategoryInfo : ObjectNotFound: (install-module:String) [], CommandNotFoundException
            + FullyQualifiedErrorId : CommandNotFoundException

            And because of these errors it does not move forward.

          • What OS are you running this against? The error messages indicate that the OS is missing Install-PackageProvider, you may need to install WMF 5.1 on the OS if it is older: https://www.microsoft.com/en-us/download/details.aspx?id=50395.

      • psychozoic says:

        hi! any updates?

        • Script has been updated! Multiple servers work now. Check it out: .\install-windowsupdates.ps1 -computer “server1″,”server2″,”server3″,”server4”

      • Daniel says:

        have u updatet your script for many Clients in a system?
        It would be plenty of help!
        Thank You!

        • This script has been updated! You can now run updates against multiple servers by using: .\install-windowsupdates.ps1 -computer “server1″,”server2″,”server3″,”server4”.

  • William says:

    I get a ‘Get-Content : Cannot find path \\computername\c$\pswindowsupdate.log because it does not exist’ error …

    Please advise.

  • Jason says:

    Is there a way to make this all work on the local computer?

    • The script I created is intended to be used from a remote endpoint. However, you could easily do this on a local computer by installing the PSWindowsUpdate module on the local machine:

      Install-Module -Name PSWindowsUpdate

      Then create some sort of scheduled task (either by Powershell or manually) that runs the following PowerShell commands on start up:

      Get-WindowsUpdate -install -acceptall -autoreboot

      This would cause the local machine to run windows updates continuously and reboot after install until there are no more windows updates available.

  • Darren says:

    Hi there,

    I’ve been hacking your script and have it working great for my needs.

    One question.

    I have a few servers in a DMZ (on same domain as script) with ICMP disabled. These servers fail as the script just doesn’t recognise they are online.

    Is there anyway to fix this?

    Cheers

    • If these servers are VMs on a hyper-visor then you are in luck. You would have to edit the script a bit and the VMs in the DMZ would need internet access, but for VMware you can use the invoke-vmscript commandlet that would run the commands straight on the VM regardless of network connectivity, it uses VMware Tools to issue the commands.

      If you are using Hyper-V check out powershell direct.

  • Charlie Cho says:

    Thanks for the script, Luke! It is very helpful. Do you know if the PS module allows for a method to skip installation of Silverlight or Silverlight related updates?

    • You can modify get-windowsupdate to not incllude specific KB’s by using the -NotKBArticleID parameter. So it would look someting like: get-windowsupdate -NotKBArticleID ‘KB982861’

      Look through “help get-windowsupdate -full” to see what else you can do with the windows updates, it’s pretty neat.

  • Daniel Dillard says:

    I am having trouble with the Execution policy. What would be the best practice to use this script and temporarily bypass the policy? Is there no way to do that while running it as a remote command? Most of my research points to this.

    If so, is there a way to run this without changing the executionpolicy to bypass or remotesigned permenantly?

    • You can do this when you execute a PowerShell script by running the following command, just use -file and input the path to the script you want to run without execution policy: powershell.exe -executionpolicy bypass -file “script.ps1”

  • BasWas says:

    This script is gold! Was trying to make the -RunNow work with -Autoreboot. But your script does it all including progress.
    Thanks for sharing!!

  • suraj shinde says:

    Hey,

    The script runs fine till searching for updates but it gives error while invoke-WUJob section.

    “The term Invoke-WUJob is not recognized as the name of cmdlet, function, script file…….”

    PSWindowsUpdate version is 2.1.1.2

    • Looks like your having an issue with the PSWindowsUpdate module not being present inside the Powershell session that the script is running in. Try a Get-Command invoke-wujob to see if the module is there.

  • Chris says:

    Hi – Many thanks for the awesome script. Unfortunately when I try to update multiple computers the retrieval of the WindowsUpdate.log fails (looks like the script is appending each computer name to the UNC path, as below:

    Currently processing the following update:
    Get-Content : Cannot find path ‘\\alcyone dardanos kerberos proteus\c$\PSWindowsUpdate.log’ because it does not exist.

    How can I go about fixing this?

    Kind Regards
    Chris

  • ahmed jehanzeb says:

    Hi Luke,
    Thanks for the script! Is it possible to modify this script to include ‘Important’ or Critical’ Windows updates only ?
    Thanks!

    • I believe you can. If you use Get-Windowsupdate -severity you can specify the patches according to their severity types. Use help get-windowsupdate -full to look at all the different options.

  • Kye says:

    Hi,

    This module works great but it doesn’t seem to restart automatically on a local machine?

    Any help would be appreciated.

  • Diego says:

    Is it possible to install a individual update in multiple remote servers?

    • Yes, you just have to modify the Get-WindowsUpdate -AcceptAll -Install to Get-WindowsUpdate -KBArticleID KB890830 -install and change the -KBArticleID to whatever KB you want to install.

  • Enigma says:

    HI,

    Is there any way, we can choose specific update to install. Instead of installing all the updates ?

  • Donnie says:

    If the remote machine is on the domain, can we use a domain admin account to execute the script?

  • vishal Dhavale says:

    Hi sir its good script but can i run workgroup PC.

  • Gustavo Lopes says:

    I get a access denied but my machines are in the same domain and it’s running under the administrator account, follow the details:

    + CategoryInfo : NotSpecified: (:) [Invoke-WUJob], UnauthorizedAccesException
    + FullyQualifiedErrorId : System.UnauthorizedAccesException,PSWindowsUpdate.InvokeWUJob

    • That error looks like your getting an unauthorized access error from the local machine your running the script on. Make sure your running the script through an administrative Powershell console.

  • josh says:

    When I run the following script, I don’t see the kb as having been installed even though the script runs without error?

    Set-ExecutionPolicy Bypass -Scope Process -Force
    Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force # https://dzone.com/articles/install-nuget-packages-in-visual-studio
    Set-PSRepository -Name “psgallery” -InstallationPolicy Trusted # https://www.addictivetips.com/windows-tips/add-a-trusted-repository-in-powershell-windows-10/
    Install-Module -Name PSWindowsUpdate -RequiredVersion 2.1.0.1
    Import-Module PSWindowsUpdate
    Get-WUInstall -KBArticleID KB4512501 -AcceptAll -IgnoreReboot

    • You have -IgnoreReboot at the end of Get-WUInstall, this may be causing the KB to not show as installed until the machine is completely rebooted. Test on a machine without the -IgnoreReboot parameter and see if that fixes it.

  • Rohit Kumar says:

    Still waiting for the multiple servers script 🙂 I have been editing mine but its not looking and running the best.
    Would really appreciate it if you could work on the multiple server script. I tried the foreach with the parallel option but it didnt work. After reading more about it, it only works for ForEach-Object command 🙁

    Thanks

    • ani says:

      This is a function, So you can create a variable which takes multiple servers and use in -computername

  • Jeremy McGee says:

    I’m having an issue when I try to use Get-WUInstall via a WINRM session, when using Get-WUInstall -computername or when using Invoke-WUJob -computer. I am getting an 0x80070005 error. The script does work on some of the servers that I run it on, they are for the most part configured the same. Get-WUInstall -Install -AutoReboot -AcceptAll does work on the machines that have this error, if I run the command locally in RDP. Any thoughts? Thanks a ton for this!

    DEBUG: 10/14/2019 3:44:26 PM CmdletStart: Get-WUInstall
    DEBUG: 10/14/2019 3:44:29 PM ParameterSetName: Default
    DEBUG: 10/14/2019 3:44:30 PM Set pre search criteria: IsInstalled = 0
    DEBUG: 10/14/2019 3:44:31 PM Set pre search criteria: IsHidden = 0
    DEBUG: 10/14/2019 3:44:32 PM Search criteria is: IsInstalled = 0 and IsHidden = 0
    DEBUG: 10/14/2019 3:44:33 PM SERVERNAME: Connecting…
    DEBUG: 10/14/2019 3:44:34 PM Module version: 2.1.1.2
    DEBUG: 10/14/2019 3:44:35 PM Dll version: 2.0.6995.28496
    DEBUG: 10/14/2019 3:44:36 PM UpdateSessionObj mode: Local
    DEBUG: 10/14/2019 3:44:38 PM ServiceManagerObj mode: Local
    DEBUG: 10/14/2019 3:44:39 PM Try Default. Set source of updates to Windows Server Update Service
    VERBOSE: SERVERNAME (10/14/2019 3:44:40 PM): Connecting to Windows Server Update Service (http://wsus.COMPANY.org:8530) server. Please wait…
    VERBOSE: Found [3] Updates in pre search criteria
    DEBUG: 10/14/2019 3:44:41 PM 2019-10 Servicing Stack Update for Windows Server 2012 R2 for x64-based Systems (KB4521864)
    DEBUG: 10/14/2019 3:44:42 PM Update was not filtered
    DEBUG: 10/14/2019 3:44:44 PM 2019-10 Security Only Quality Update for Windows Server 2012 R2 for x64-based Systems (KB4519990)
    DEBUG: 10/14/2019 3:44:45 PM Update was not filtered
    DEBUG: 10/14/2019 3:44:46 PM 2019-10 Security Monthly Quality Rollup for Windows Server 2012 R2 for x64-based Systems (KB4520005)
    DEBUG: 10/14/2019 3:44:47 PM Update was not filtered
    VERBOSE: Found [3] Updates in post search criteria
    DEBUG: 10/14/2019 3:44:48 PM Show update to accept: 2019-10 Servicing Stack Update for Windows Server 2012 R2 for x64-based Systems (KB4521864)
    DEBUG: 10/14/2019 3:44:49 PM Accepted
    DEBUG: 10/14/2019 3:44:50 PM Show update to accept: 2019-10 Security Only Quality Update for Windows Server 2012 R2 for x64-based Systems (KB4519990)
    DEBUG: 10/14/2019 3:44:51 PM Accepted
    DEBUG: 10/14/2019 3:44:52 PM Show update to accept: 2019-10 Security Monthly Quality Rollup for Windows Server 2012 R2 for x64-based Systems (KB4520005)
    DEBUG: 10/14/2019 3:44:53 PM Accepted

    X ComputerName Result KB Size Title
    – ———— —— — —- —–
    1 SERVERNAME Accepted KB4521864 10MB 2019-10 Servicing Stack Update for Windows Server 2012 R2 for x64-based Systems (KB4521864)
    1 SERVERNAME Accepted KB4519990 32MB 2019-10 Security Only Quality Update for Windows Server 2012 R2 for x64-based Systems (KB4519990)
    1 SERVERNAME Accepted KB4520005 478MB 2019-10 Security Monthly Quality Rollup for Windows Server 2012 R2 for x64-based Systems (KB4520005)
    VERBOSE: Accepted [3] Updates ready to Download
    DEBUG: 10/14/2019 3:44:55 PM Show update to download: 2019-10 Servicing Stack Update for Windows Server 2012 R2 for x64-based Systems (KB4521864)
    Access is denied. (Exception from HRESULT: 0x80070005 (E_ACCESSDENIED))
    + CategoryInfo : NotSpecified: (:) [Get-WindowsUpdate], UnauthorizedAccessException
    + FullyQualifiedErrorId : System.UnauthorizedAccessException,PSWindowsUpdate.GetWindowsUpdate

    • Jeremy,

      The error message is showing “Access is denied” which indicates that there is either some sort of permissions issue with the account running the script or some sort of security block. Since the command works locally, it could also be some sort of 3rd party Anti Virus as well blocking remote execution.

  • Ilia says:

    I run the script against windows 2016 server inside our domain. It runs without any errors. It found 4 updates. Then for 24 hours the script is only saying

    “Currently processing the following update:
    1 IGBGSOFTE… Accepted KB2267602 113MB Security Intelligence Update for …”

    If I check the log file, it says:

    X ComputerName Result KB Size Title
    – ———— —— — —- —–
    1 IGBGSOFTE… Accepted KB4103720 1GB 2018-05 Cumulative Update for Win…
    1 IGBGSOFTE… Accepted KB890830 37MB Windows Malicious Software Remova…
    1 IGBGSOFTE… Accepted KB4521858 12MB 2019-10 Servicing Stack Update fo…
    1 IGBGSOFTE… Accepted KB2267602 113MB Security Intelligence Update for …

    Furthermore, the get-wujob returns:
    ComputerName Name Action
    ———— —- ——
    Servern… PSWindowsUpdate import-module PSWindowsUpdate; Get-WindowsUpdate -AcceptAll -Install | Out-File C:\PSWindowsUpdate.log
    It seems that the updates are accepted but the download and installation process didn`t start.
    Do you have idea what`s wrong?

    • This is most likely a limitation of Windows Updates. Sometimes their download servers have issues. When running into this, are you able to manually run windows updates and install the updates still?

      • Neztach says:

        I would bet this is a firewall issue. On Windows Server 2016 and up, if Windows firewall service is stopped, updates will timeout and never work.

        implement some variant of this at the beginning of your script

        $firewallStatus = Get-Service -Name ‘MpsSvc’
        If ($firewallStatus.Status -eq ‘Running’){
        <>
        } else {
        (Get-WmiObject -Class win32_service -Filter “name=’MpsSvc'”).startservice()
        start-sleep 1
        $check = Get-Service -Name ‘MpsSvc’
        If ($check.Status -eq ‘Running’){
        <>
        } else {
        Write-Host “Can’t start firewall service. Start svc and try again”
        Break
        }
        }

        then somewhere in the script wrap-up, if the script enabled the firewall, then it should disable as a standard cleanup task.

  • Robert Olsén says:

    Hi!
    A question about the

    do {
    [..]
    }until ( $installednumber -eq $updatenumber)

    What if a update fails to install? Then you will never get out of the loop, right?

    • Hi Robert,

      I’ve added in a timeout counter that can be modified. Right now after 2 hours it will time out if stuck inside the loop. Also added “failed number” of updates to count for updates failing. Thanks!

  • Andrew O'Brien says:

    Luke, brilliant article and great script – thank you for sharing this. I’m running this successfully across our domain, but frequently get errors at the end of the script “No MSFT_ScheduledTask found with property ‘TaskName’ equal to ‘PSWindowsUpdate’. I can see that line 82 is where the unregister command is, but can’t see where the scheduled task is actually created – lines 44 to 48 seem to be where it should be?

    • Hi Andrew,

      The scheduled task gets created from the Invoke-WUjob command. There are limitations with the Windows OS that prevent remotely running windows updates, so the only way to get some sort of remote windows updates is to remotely create a scheduled task with PowerShell to run the windows updates locally. Not sure why that task is erroring sometimes, could be that there were no windows updates needed.

  • Xavier says:

    Hello Luke,

    I really love the concept of that script and I thank you for sharing it with us. Unfortunately, I have been unsuccessful running it in my environment. We have computers on our domain as well as computers belonging to a workgroup. However, all computers are all on the same subnet.

    Whenever, I try to run the script, either to update the Domain computers or the workgroup ones, I get the following error: Connecting to remote server machinename failed with the following error message: The client cannot connect to the destination specified in the request. Verify that the service on the destination is running and is accepting requests.

    Is there anything that I am doing wrong. I have tried several things such as using the following statements:
    winrm set winrm/config/client/auth ‘@{Basic=”true”}’
    winrm set winrm/config/client ‘@{AllowUnencrypted=”true”}’
    on both the server in charge of launching the updates and the destination machines. I have also tried Set-ExecutionPolicy unrestricted
    but all my attempts remained unsuccessful.
    Would you have any idea what I am doing wrong?

    • Hi Xavier, you may be running into WSMan trusted host issues with your non domain joined workstations. You can try adding the computers your updating to your server’s trusted hosts like this:
      #set trusted host on Source Endpoint to remotely connect to servers
      $trustedhosts = (Get-item WSMan:\localhost\Client\TrustedHosts).value
      If($trustedhosts)
      {
      (Get-item WSMan:\localhost\Client\TrustedHosts).value + “,$computer”
      }
      else
      {
      $trustedhosts = $computer
      }
      Set-Item WSMan:\localhost\Client\TrustedHosts –Value $trustedhosts -Force

      Then add a cleanup step at the end to remove that computer from trusted hosts for security:
      #remove computer from trusted hosts for cleanup
      $trustedhosts = ((Get-item WSMan:\localhost\Client\TrustedHosts).value).replace(“$computer”,””).TrimEnd(“,”)
      Set-Item WSMan:\localhost\Client\TrustedHosts –Value $trustedhosts -Force

      Let me know if that helps. Thanks!

  • Mike Pinkston says:

    noob here. I am getting an error when running the script and not having much luck troubleshooting the error.

    At C:\temp\NewWindowsUpdate.ps1:7 char:1
    + [CmdletBinding()]
    + ~~~~~~~~~~~~~~~~~
    Unexpected attribute ‘CmdletBinding’.
    At C:\temp\NewWindowsUpdate.ps1:8 char:1
    + param (
    + ~~~~~
    Unexpected token ‘param’ in expression or statement.
    + CategoryInfo : ParserError: (:) [], ParentContainsErrorRecordException
    + FullyQualifiedErrorId : UnexpectedAttribute

  • James says:

    This script is really useful, thanks for sharing.

    How would you advise dealing with errors, a lot of the VMs I run this on are always close to being completely full and this stops updates from working a lot of the time. It looks like the script just carries on ad infinitum, what’s going to be the best way to make the script bail out and try the next VM?

    • Hi James,

      It might be worth it to just make sure your VMs have adequate space to run Windows updates before running this script instead of trying to modify the script to work around this issue. But, you could add a portion in there to check for available disk space on the VM and if its under 2GB do nothing.

      -Thanks!

  • Andrew Carey says:

    Hi there,

    I’m having issues installing the module. I receive the following error.


    PS C:\WINDOWS\system32> Install-Module -Name PSWindowsUpdate
    WARNING: Source Location ‘https://www.powershellgallery.com/api/v2/package/PSWindowsUpdate/2.1.1.2’ is not valid.
    PackageManagement\Install-Package : Package ‘PSWindowsUpdate’ failed to download.
    At C:\Program Files\WindowsPowerShell\Modules\PowerShellGet\1.0.0.1\PSModule.psm1:1809 char:21
    + … $null = PackageManagement\Install-Package @PSBoundParameters
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : ResourceUnavailable: (C:\Users\andrew…owsUpdate.nupkg:String) [Install-Package], Exception
    + FullyQualifiedErrorId : PackageFailedInstallOrDownload,Microsoft.PowerShell.PackageManagement.Cmdlets.InstallPackage”

    Can you advise whats going wrong?

    Thanks in advance

    • Hey Andrew, try updating your NuGet to the latest version. There were some faulty releases of NuGet that caused this error. Open up an administrative PowerShell console and run: install-packageprovider nuget -force. Then re-launch the PowerShell console.

Leave a comment

Your email address will not be published. Required fields are marked *