Manage Windows Update installations using Windows PowerShell

In most domains Windows Update are controlled by Group Policy and Windows Server Update Services (WSUS). For client computers, its common to download and install updates automatically. For servers, we want to control the installation of Windows Updates inside a scheduled maintenance window, and hence the Windows Update settings are configured not to automatically install updates.

If there are a few servers to manage, it wont be that time consuming to log on to each server with Remote Desktop and do the Windows Update installations manually. In larger environments however, this wont be an option, it must be automated in some way. While enterprise environments typically invest in a commercial product like BigFix for patch management, this might be overkill or too expensive for environments smaller than an enterprise.

To manage Windows Update in an automated way we can access the Windows Update Agent API using a COM-object called Microsoft.Update.Session. Using the New-Object cmdlet in Windows PowerShell, its easy to work with this COM-object. Based on this COM-object and portions of James ONeills functions for managing Windows Update, Ive written a PowerShell-script called Invoke-WindowsUpdate.

This script is intended to be used to download and install Windows Updates on servers. It runs like expected when invoked from a local computer, however, invoking the script using PowerShell Remoting like I was planning turned out to be problematic: Invoke-Command -ComputerName ServerA -FilePath 'C:ps-scriptsWindows UpdateInvoke-WindowsUpdate.ps1'

This will return the following error message:
Exception calling "CreateUpdateDownloader" with "0" argument(s): "Access is denied. (Exception from HRESULT: 0x80070005 E_ACCESSDENIED))"

This issue doesnt seem to be related to PowerShell, as there are several others reporting the same problem in other languages like VBScript.

The common workaround is to schedule the script to run as a scheduled task running as SYSTEM. Ive chosen to use this approach and to use PowerShell Remoting to invoke the scheduled task to run the script. An example:

$servers = Get-Content 'C:ps-scriptsWindows UpdateBulkA.txt'
foreach ($server in $servers) {
Invoke-Command -Computer $server -ScriptBlock {schtasks /run /tn "PowerShell - download and install Windows Updates"}
}

To create the scheduled task I would recommend you to use Group Policy Preferences.
A few sample screenshots from my lab setup:

image

image

image

image

Although its possible to invoke the script on all servers in the domain at once using i.e. the Active Directory-module for PowerShell to get the server names, I would recommend to break down the installations in several bulks. This way you can control that all domain controllers doesnt go offline at the same time and so on.

As you can see in the script its also possible to enable reporting to e-mail or file (HTML) to a central location, in addition to control whether the servers should reboot if required.
Planned improvements include nicer reports, Windows Update settings in the reports and if possible make the script work without having to use scheduled tasks. Suggestions for other improvements are always welcome.

23 thoughts on “Manage Windows Update installations using Windows PowerShell

  1. Rats! I was using James’ functions and hoping that you’d gotten around that remoting problem.

    I get around it in an ugly fashion: The machines I need to update in that way are XP machines. It’s well known that Vista/Seven Task Scheduler is not compatible with XP Task Scheduler.

    So, you copy the task scheduler command-line EXE and its associated DLL from a XP machine.

    Then you have a batch file (Task Scheduler is one of those apps that really doesn’t go well with PowerShell as far as parsing is considered) that I run on my network server on demand.

    And all it does is run a variation of James’ script on three particular machines. They display content over the air so I prefer to run the updates when there’s no programming.

    I have to think the COM object involved is not allowing remote activation, probably by design.

  2. Thanks for sharing your experience. Ive also starting to believe the COM-object doesnt allow remote activation by design. Maybe another (also ugly) workaround could be using psexec.exe from Windows Sysinternals?

  3. Great Script.
    I ran into one little bit.
    It did not install a ServicePack on a Windows 2008 maschine. The UI from the ServicePack came up and wanted the EULA accepted and so on.
    Otherwise i have not encountered any Problems.

  4. Great post, thanks for sharing! I have one question regarding this: our servers are behind a loadbalancer, and we have a script that takes them out of it when it’s going down for a reboot – which would be easy to add to this – however, can you think of any way to have it put back in via script when this is all done? We wouldn’t want it added in a startup script, because there may be a time when we reboot and don’t want it back in. We can’t set a follow-up scheduled task, because we may be putting it back in before it’s done with the updates.

      • Moreso how to run a premade exe that handles the ‘in’/’out’ of the servers in the loadbanacer. Your script seems it would handle the ‘out’ fine, as that could just be added to this — however, I was wondering about when the server is done getting the updates and has rebooted, if there would be a way to catch that in some sort of script so that it could be put back ‘in’ the loadbalancer…

        • You could set up a ping monitor script and add/remove servers to the loadbalancer based on that. However, what you want is probably to add/remove them based on the availability of the services they provide, and that is more trickier to accomplish with scripting.

      • So, I could fire use ServerA to initiate your script to be run locally on ServerB, but in the same token, waiting for it to get some sort of return so that it can send the ‘up’ signal. I’ll see if it can make that happen :-) thanks!

  5. Very nice and it works (maybe too well) on my test machine – in the present configuration it grabs every update listed.

    Is there any way to modify the script to install updates sorted by priority? For example, I want to install everything that’s classified as Critical, Important, Moderate, or Low, but not “Unknown”. The “Unknown” updates seem to be things like IE9, SP1, and so forth that requires user intervention/major system changes.

  6. Pingback: Adding printer drivers from a print server using Windows PowerShell « blog.powershell.no

  7. Was getting the following error when running code locally. Any ideas?

    Invoke-WindowsUpdate.ps1:98 char:30
    + $Result= $downloader.Download <<<< ()
    + CategoryInfo : NotSpecified: (:) [], MethodInvocationException
    + FullyQualifiedErrorId : ComMethodTargetInvocation

    • I exerienced the same behaviour.

      In oder to work I needed to change the line

      from $Result= $downloader.Download()
      to $Result= $downloader.Download

      note the missing parenteses at the end of the command.
      Now it works.

      I run it on a Windows 7 Machine 32 Bit

      • My previous “solution” is wrong.

        In oder that the line “$Result= $downloader.Download()” works the whole script needs to be run in “elevated admin rights”. This means the script needs to be launched with “run as administrator”

  8. Script is great! When I run on my servers I get a report that it ran, but no updates were found. If I run Windows Update manually I show that there are updates needed. I don’t see any errors though. Am I missing something?

  9. I found that an exception would be thrown if the download had a EULA that needed an action.
    The following addition to the loop on line 102 of Invoke-WindowsUpdate.ps1 solved that problem:

    if ( $_.EulaAccepted -eq 0 ) { $_.AcceptEula() }

  10. Will this script work for systems that use WSUS? IOW, will it just use the default Registry settings for update server?

    I’d like to use it in the capacity to update the desktops across the network. We have a significant number of desktops that run processes overnight and therefore can’t be rebooted.

    We have WSUS set to approve updates and push them to the clients, but not install.

    Once a month, during our scheduled maintenance window, I’d like to script the update. WOL the PCs, reboot them, install updates, reboot, install updates, reboot, etc until the updates are complete, reboot one more time.

    This would also overcome the issue with the EULA, as that is accepted on the WSUS level.

  11. Hi!

    Is this still maintained?

    Is there a way to disable the installation of optional updates? Or to block specific updates? I keep getting Bing Desktop in the updates, which stops the update process.

  12. I’ve been using the script to help stage computers that we sell to customers. I did find a significant problem: Windows Update installs the Bing Desktop (Optional Update). This stops the update process in the middle because you have to confirm some settings for Bing Desktop to install. In fact, I don’t want Optional Updates to install at all.

    Is there a way to code that into the script?

  13. I have been using your script in a 100 server environment and it is a great help. Thanx. Solved 1 error in the reported state of updates by changing these lines:

    $Global:counter=0 # (start counter at 0 not at -1)

    $Report = $installer.updates |
    Select-Object -property Title,EulaAccepted,@{Name=’Result';expression={$ResultCode[$installationResult.GetUpdateResult($Global:Counter).resultCode ] }},@{Name=’Reboot required';expression={$installationResult.GetUpdateResult($Global:Counter++).RebootRequired }} |
    ConvertTo-Html # (increase the counter only in the second expression, not in both expressions)

    This way the list of updates gets the right values for colums ‘Result’ and ‘Reboor required’.

  14. Pingback: Poor Man’s Automated Windows Updates | #DeepThoughts