Save to My DOJO
Table of contents
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!
Not a DOJO Member yet?
Join thousands of other IT pros and receive a weekly roundup email with the latest content & updates!
145 thoughts on "Building PowerShell Tools for MSPs: Automating Windows Updates"
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!
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.
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.
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.
hi! any updates?
Script has been updated! Multiple servers work now. Check it out: .\install-windowsupdates.ps1 -computer “server1″,”server2″,”server3″,”server4”
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”.
I get a ‘Get-Content : Cannot find path \\computername\c$\pswindowsupdate.log because it does not exist’ error …
Please advise.
This looks like a permissions issue. The account that is running the script needs to be a local administrator of the remote machine. Also it needs to be domain joined.
if the remote machine is on the domain can it use a domain admin account instead of a local admin account?
Absolutely!
If the remote machine is on the domain, can we use a domain admin account to execute to script?
Yes this will work perfectly.
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.
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.
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.
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”
This script is gold! Was trying to make the -RunNow work with -Autoreboot. But your script does it all including progress.
Thanks for sharing!!
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.
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
Typically that error is due to an access issue. Make sure that the account that is running the script has access to the c$ share of that machine.
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.
Hi,
This module works great but it doesn’t seem to restart automatically on a local machine?
Any help would be appreciated.
The -autoreboot parameter should restart the machine if it is needed after installing the update.
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.
HI,
Is there any way, we can choose specific update to install. Instead of installing all the updates ?
Yes, you just need to know the KB number. So the syntax would look like: Get-WindowsUpdate -KBArticleID KB890830 -install
If the remote machine is on the domain, can we use a domain admin account to execute the script?
Absolutely! The remoting actually works better for domain joined machines.
I’m getting this error though “‘Get-Content : Cannot find path \\computername\c$\pswindowsupdate.log because it does not exist’
Hi sir its good script but can i run workgroup PC.
Since the script uses the c$ share, it won’t work properly on Workgroup joined machines.
I have found a way to make this work for Workgroup machines.
Awesome, glad to hear it. What did you end up doing for authentication? Use the same local account credentials for each server?
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.
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.
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
This is a function, So you can create a variable which takes multiple servers and use in -computername
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.
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?
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.
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!
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.
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!
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
Hi Mike,
What process are you following to run this script? It looks like your getting some formatting errors, maybe missing lines?
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!
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.
Hi Thanks for the script Luke, not sure if by design or not but the log gets wiped as it doesn’t have the append switch. This meant I could only see the last update applied, not sure if this happens every time, or just if there is an update after a reboot.
I also found I had to add some registry key creation to the script to enable tls 1.2 on older .net installs so I could install nuget. I added checks for the reg keys, nuget already being installed, as well as the pswindowsupdate module to avoid unnecessary steps on future runs
Hi Ed! This is great feedback! I’ll be sure to share it with Luke directly for the next update to this post sometime in the future! Thanks!
Hi,
If I use the script for multiple computers, will it run in parallel, or finish each server before going to the next?
This script is designed to run on one machine at a time, however, you could modify it or pair it with some other scripting to fill either use-case you mentioned. For example, take a look at parallel for each if you want to run it on multiple machines at once.
Love you script Luke. Thank you so much for this tutorial!
Is there a way to install only important updates and not the optional ones?
Hi Gilbert! There is a way to target specific groups of updates. Take a look at the help for the cmdlet and you should see the sorting options.
Hi Luke, thanks for writing this script.
I have been using it for a little over a year now and it has been really helpful for automating the preparation of Citrix Gold Image VMs.
I have noticed a couple of problems that I have managed to find fixes for, so I thought I would feed back here.
The main problem I had was that the script was never finishing. The ” }until($updates -eq $null)” on line 101 was not triggering. I changed this line to “}until($updatenumber -eq 0)” and that seems to have resolved the problem for me.
I also added a check for the Task scheduler task so that the script doesn’t try to remove it if it doesn’t exist.
Great feedback Bryan! Thanks! I’ll make sure to provide this to Luke for his next update!
Very good script!
On some servers I get this error message after the script has searched for updates:
(10,44):StartBoundary:2021-05-19T15.35.38
+ CategoryInfo : NotSpecified: (:) [Invoke-WUJob], COMException
+ FullyQualifiedErrorId : System.Runtime.InteropServices.COMException,PSWindowsUpdate.InvokeWUJob
I can’t find out what the issue is. Do you know?
Is there anything in the Windows Update Log files? Typically this type of error is caused by a firewall, connectivity issues, or the Windows Update service itself being in an inconsistent state. Do updates run manually with no issues on these systems?
I’m trying to use this on standalone laptops. I was able to manually run the “Get-WindowsUpdate -install -acceptall -autoreboot” command in Powershell and it rebooted after the first round of were updates installed. However, after rebooting nothing happens since I manually ran the commands. I need to have a script that resumes checking for updates when using a standalone PC. How can I make this work on a standalone computer so that it continually runs and reboots until all the updates have been installed? I have minimal experience with scripting so please forgive me question if it’s not clear. Thank
Hi Adam! Did you try running the script contained within the post? That script should do exactly what you’re looking for. Let us know if the script isn’t working for you!
Hi there,
the script installs PSWindowsUpdate on the remote machine every time, regardless of whether it is already installed or not. Is this correct? This slows down the hole update-prccess. Can this be turned off? THX
Hi Cyberdott! Good feedback here. Yes, the -force parameter on the lines that install the PSWindowsUpdate package on the remote machine causes the package to be installed each time. You could add logic to the script to check for the PSWindowsUpdate Package first if needed. I’ll reach out to the script author with the feedback so it gets updated during the next update. Thanks!
Line |
7 | [CmdletBinding()]
| ~~~~~~~~~~~~~~~~~
| Unexpected attribute ‘CmdletBinding’.
Any idea why this fails? Have Nuget, PowershellGet, PackageManager…
Regards 🙂
I suspect this may be a typo during the copy/past process perhaps. Can you verify the script that you’re executing is the same as contained in the post?
Randomly came across this script and decided to give it ago, seems to work fine but is extremely slow, would be amazing if it ran on several servers from a text file in parallel, its taking me around 20mins per server with only 3 small updates.
unregister scheduled task is not present on windows 2008, you need a Get-wmiobject check for operating system link Microsoft Windows Server 201* and only unregister if on OS higher than 2008.
2008 you need to schtask delete.
unsure if its the timers but it takes far longer to run this script than it does to run the command locally.
Hi Alex! Thanks for the info on 2008 servers! Super helpful!. In terms of running the script in parallel, take a look at the parallel for each feature in Powershell. Pairing it with this script should allow you to run it against multiple machines at once.
Hi Luke,
First of all Thanks for the script it works when i install nuget and pswindowsupdate on the machine by hand.
But installing Nuget and pswindowsupdate over the script does not work and it’s just throwing errors.
i also need this command to make the installation work manually
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
PS C:\Windows\system32> PowerShell “C:\Users\xx.xx-adm\Documents\install-windowsupdates.ps1″ -computer bs01
WARNING: MSG:UnableToDownload «https://go.microsoft.com/fwlink/?LinkID=627338&clcid=0x409» «»
WARNING: Unable to download the list of available providers. Check your internet connection.
WARNING: Unable to download from URI ‘https://go.microsoft.com/fwlink/?LinkID=627338&clcid=0x409’ to ”.
No match was found for the specified search criteria for the provider ‘NuGet’. The package provider requires
‘PackageManagement’ and ‘Provider’ tags. Please check if the specified package has the tags.
+ CategoryInfo : InvalidArgument: (Microsoft.Power…PackageProvider:InstallPackageProvider) [Install-Pac
kageProvider], Exception
+ FullyQualifiedErrorId : NoMatchFoundForProvider,Microsoft.PowerShell.PackageManagement.Cmdlets.InstallPackagePro
vider
+ PSComputerName : bs01
NuGet provider is required to continue
PowerShellGet requires NuGet provider version ‘2.8.5.201’ or newer to interact with NuGet-based repositories. The NuGet
provider must be available in ‘C:\Program Files\PackageManagement\ProviderAssemblies’ or
‘C:\Users\xx.xx-adm\AppData\Local\PackageManagement\ProviderAssemblies’. You can also install the NuGet provider
by running ‘Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force’. Do you want PowerShellGet to install
and import the NuGet provider now?
[Y] Yes [N] No [?] Help (default is “Y”): Y
WARNING: Unable to download from URI ‘https://go.microsoft.com/fwlink/?LinkID=627338&clcid=0x409’ to ”.
WARNING: Unable to download the list of available providers. Check your internet connection.
No match was found for the specified search criteria for the provider ‘NuGet’. The package provider requires
‘PackageManagement’ and ‘Provider’ tags. Please check if the specified package has the tags.
+ CategoryInfo : InvalidArgument: (Microsoft.Power…PackageProvider:InstallPackageProvider) [Install-Pac
kageProvider], Exception
+ FullyQualifiedErrorId : NoMatchFoundForProvider,Microsoft.PowerShell.PackageManagement.Cmdlets.InstallPackagePro
vider
+ PSComputerName : bs01
No match was found for the specified search criteria and provider name ‘NuGet’. Try ‘Get-PackageProvider
-ListAvailable’ to see if the provider exists on the system.
+ CategoryInfo : InvalidData: (NuGet:String) [Import-PackageProvider], Exception
+ FullyQualifiedErrorId : NoMatchFoundForCriteria,Microsoft.PowerShell.PackageManagement.Cmdlets.ImportPackageProv
ider
+ PSComputerName : bs01
If i install nuget manually i get this:
PS C:\Windows\system32> PowerShell “C:\Users\xx.xx-adm\Documents\install-windowsupdates.ps1″ -computer bs01
WARNING: MSG:UnableToDownload «https://go.microsoft.com/fwlink/?LinkID=627338&clcid=0x409» «»
WARNING: Unable to download the list of available providers. Check your internet connection.
WARNING: Unable to download from URI ‘https://go.microsoft.com/fwlink/?LinkID=627338&clcid=0x409’ to ”.
No match was found for the specified search criteria for the provider ‘NuGet’. The package provider requires ‘PackageManagement’ and ‘Provider’ tags. Please check if the
specified package has the tags.
+ CategoryInfo : InvalidArgument: (Microsoft.Power…PackageProvider:InstallPackageProvider) [Install-PackageProvider], Exception
+ FullyQualifiedErrorId : NoMatchFoundForProvider,Microsoft.PowerShell.PackageManagement.Cmdlets.InstallPackageProvider
+ PSComputerName : bs01
No match was found for the specified search criteria and module name ‘pswindowsupdate’. Try Get-PSRepository to see all available registered module repositories.
+ CategoryInfo : ObjectNotFound: (Microsoft.Power….InstallPackage:InstallPackage) [Install-Package], Exception
+ FullyQualifiedErrorId : NoMatchFoundForCriteria,Microsoft.PowerShell.PackageManagement.Cmdlets.InstallPackage
+ PSComputerName : bs01
The specified module ‘PSWindowsUpdate’ was not loaded because no valid module file was found in any module directory.
+ CategoryInfo : ResourceUnavailable: (PSWindowsUpdate:String) [Import-Module], FileNotFoundException
+ FullyQualifiedErrorId : Modules_ModuleNotFound,Microsoft.PowerShell.Commands.ImportModuleCommand
+ PSComputerName : bs01
Hi Robin! I suspect this was a temporary connectivity issue to the target repo. Can you confirm you’re still having this issue?
Thank you for this, great bit of work.
I’ve got a minor issue wonder if you may have an idea of why?
Only sometimes the Out-File C:\PSWindowsUpdate.log fails to create the file and I’ll then get
VERBOSE: Found [18] Updates in pre search criteria
VERBOSE: Found [18] Updates in post search criteria
Get-Content : Cannot find path ‘\\Server1\c$\PSWindowsUpdate.log’ because it does not exist.
At C:\SLCScripts\WindowsUpdates\InstallWindowsUpdates.ps1:125 char:33
+ … do {$updatestatus = Get-Content \\$c\c$\PSWindowsUpdate.log
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (\\Server1\c$\PSWindowsUpdate.log:String) [Get-Content], ItemNotFoundException
+ FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.GetContentCommand
As I say this only happens occasionally.
I have also taken the liberty of adding a Pending Reboot check, prior to checking for updates, which thought may be of interest.
#check for pending reboot
Invoke-command -computername $c -scriptblock {Get-WUIsPendingReboot | Out-File C:\PendingReboot.log}
$pending = Get-Content \\$c\c$\PendingReboot.log
$check = ([regex]::Matches($pending, “True” ))
If ($pending -eq $check) {
Write-Output “Updates are pending rebooting $c …”
Restart-Computer -ComputerName $c -Force
Break
}
Else
{
Write-Output “No Reboot Pending on $c”
}
Hey Gordan! Thanks for the feedback on this! I’ll make sure Luke sees this for the next time he updates this article! Regarding the error you’re getting. Is that on the same machines every time? Can you confirm the WindowsUpdate.log is present or not when the error is thrown?
Hi Luke
Can i run this script on a looged on machine. so the reboot can be included
Hi Rizwan! The script will handle the reboot for you and can be used on a logged on machine as well!
Hi! I need the script not to reconnect after reboot. Do you have any idea what I can skip?
regards!
Hi David! The script is built with loops to make sure all available updates are applied. In this particular case you should just be able to call the Get-WindowsUpdate cmdlet in the PSWindowsUpdate Module in order to achieve this.
Hi, Thanks for a great script. wondering if i can use it for 600 virtual servers, and then the output of each server installed updates in excel or csv file ?
Will be very much easier for reporting (For that particular month, i mean how many patches got installed / not applicable / or failed status) ? Any possibilities?
Hi Aps! This should be possible. It’s just a matter of capturing the needed information from the log and piping it through something like Out-File. Then you could pair it with a technique like Luke explained in this HTML reporting article.
Total PS noob here. I’ve got PSWindowsUpdate installed on various Windows Server 2016 VMs and a Windows 10 VM I am executing their commands remotely from. I was able to update all of my server VMs successfully using PSWU commands. But, after restarting, a few of the VMs no longer respond to remote commands though the same commands work from the VMs’ consoles.
The problems some servers experience are one of two issues:
1) The command seemingly executes but nothing is returned.
For instance, on my Win10 VM I enter “get-wuhistory -computername SERVER -verbose” and get “Connecting to default for SERVER. Please wait…” No data is returned. I just come back to the PS prompt within a couple of seconds. Executing the command in PS at the server’s console results in the full history of applied Windows updates.
2) I enter the command “get-wuhistory -computername SERVER -verbose” and get the following results
—————————————————–
VERBOSE: Performing the operation “(5/19/2020 8:58:35 AM) Get Windows Update History” on target “SERVER”.
get-wuhistory : CORP-SYS-DC-01: Unknown failure.
At line:1 char:1
+ get-wuhistory -computername SERVER -verbose
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : PermissionDenied: (:) [Get-WUHistory], Exception
+ FullyQualifiedErrorId : Unknown,PSWindowsUpdate.GetWUHistory
—————————————————–
Again, these commands worked fine until I restarted some of the servers. Oddly, the problem affects only a few of my servers.
Hi Alex! this is throwing an access denied error. Have you confirmed the script is running via an account that has administrative access on the target machine?