On Windows PowerShell and other admin-related topics

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, it`s 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 won`t 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 won`t 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, it`s easy to work with this COM-object. Based on this COM-object and portions of James O`Neill`s functions for managing Windows Update, I`ve 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-scripts\Windows Update\Invoke-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 doesn`t 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. I`ve 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-scripts\Windows Update\BulkA.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:





Although it`s 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 doesn`t go offline at the same time and so on.

As you can see in the script it`s 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.


June 25, 2010 - Posted by | Group Policy, Scripting, Windows PowerShell, Windows Update |


  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.

    Comment by David Moisan | July 3, 2010 | Reply

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

    Comment by Jan Egil Ring | July 4, 2010 | Reply

  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.

    Comment by Andreas P | July 15, 2010 | Reply

  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.

    Comment by eclipsed450` | February 24, 2011 | Reply

    • Hi,

      You mean how to put it back into loadbalancing? How is the configuration for the loadbalancer exposed? Text-file, database, etc?

      Comment by Jan Egil Ring | March 7, 2011 | Reply

      • 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…

        Comment by eclipsed450 | March 7, 2011

      • 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.

        Comment by Jan Egil Ring | March 7, 2011

      • 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!

        Comment by eclipsed450 | March 8, 2011

  5. How can I test out your script? Can I buy it, use it for free?

    Comment by ewen | May 12, 2011 | Reply

    • Hi,
      It`s free – use it on your own risk 🙂 You could download it and run it manually to see how it works.

      Comment by Jan Egil Ring | May 12, 2011 | Reply

  6. 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.

    Comment by Wayne | June 28, 2011 | Reply

  7. […] print driver is added to a print server. The details on how to do this is previously described in this […]

    Pingback by Adding printer drivers from a print server using Windows PowerShell « | July 3, 2011 | Reply

  8. 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

    Comment by Mark Garza | July 5, 2011 | Reply

    • 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

      Comment by Christoph Schudel | December 21, 2011 | Reply

      • 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”

        Comment by Christoph Schudel | December 22, 2011

    • Did anyone reply back with an answer for this because i’m getting the same error?

      Comment by Trent | January 4, 2012 | Reply

  9. 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?

    Comment by Kirk | January 11, 2012 | Reply

  10. 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() }

    Comment by Mike | March 2, 2012 | Reply

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )


Connecting to %s

%d bloggers like this: