Thursday, August 22, 2013

Install Windows Updates using a PowerShell script

or: How I stopped worrying and learned to live with Automatic Maintenance


As many of you may already know Windows Server 2012 does not play nice with WSUS/Windows Update. It will accept the download and notify or the download and schedule settings, but that's about where it ends. In other words, it will not honor the Windows Update schedule settings.

This is due to a new feature called Automatic Maintenance, which manages software updates in its own special kind of way and prevents us from forcing a reboot immediately after the updates have been installed.

If you are not yet sure how it works, this posts sums it up pretty well:
http://social.technet.microsoft.com/Forums/windowsserver/en-US/9714c384-ffed-4561-8349-32fd1dace9f9/server-2012-windows-update-group-policy


To work around this behavior and make sure that the updates get installed and the servers immediately rebooted I put together this PowerShell script. Together with the Windows Update policy to just download and notify it should at least ensure that the Windows Updates get installed during the proper server maintenance window.

I'm putting this script up here as-is and leave no guarantees that it's either perfect or will work for you. As always, watch for line breaks. If you find any bugs or have ideas on how to improve it, feel free to leave a comment.


[CmdletBinding()]
Param (
    [switch]$Reboot
)

$UpdateSession = New-Object -ComObject 'Microsoft.Update.Session'
$UpdateSearcher = $UpdateSession.CreateUpdateSearcher()
$SearchResult = $UpdateSearcher.Search("IsInstalled=0 and IsHidden=0")
$RebootRequired = $False

$Install = New-Object -ComObject 'Microsoft.Update.UpdateColl'
for($i=0;$i -lt $SearchResult.Updates.Count;$i++) {
    $Update = $SearchResult.Updates.Item($i)
    Write-Verbose $Update.Title
    if($Update.InstallationBehavior.CanRequestUserInput) {
        Write-Verbose 'CanRequestUserInput True'
        Continue
    }
    if($Update.EulaAccepted -eq $False) {
        Write-Verbose 'Accepting EULA'
        $Update.AcceptEula()
    }
    if($Update.IsDownloaded) {
        Write-Verbose 'IsDownloaded'
        $Install.Add($Update) | Out-Null
        if($Update.InstallationBehavior.RebootBehavior -gt 0) {
            $RebootRequired = $True
        }
    } else {
        Write-Verbose 'NotDownloaded'
    }
}

if($Install.Count -eq 0) {
    Write-Verbose 'No updates to install'
    break
}

$UpdateInstaller = $UpdateSession.CreateUpdateInstaller()
$UpdateInstaller.Updates = $Install
$Results = $UpdateInstaller.Install()

$Date = (Get-Date).ToUniversalTime()
$DateUTC = Get-Date($Date) -Format 'yyyy-MM-dd HH:mm:ss'

$LastSuccessTime = $DateUTC
$LastError = $Results.HResult

Write-Verbose $Results

$RegPath = 'Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\Results\Install'
if(Test-Path -Path $RegPath) {
    $RegKey = Get-ItemProperty -Path $RegPath
    if($RegKey -match 'LastError') {
        Set-ItemProperty -Path $RegPath -Name 'LastError' -Value $LastError
    }
    if($RegKey -match 'LastSuccessTime') {
        Set-ItemProperty -Path $RegPath -Name 'LastSuccessTime' -Value $LastSuccessTime
    }
}

if($RebootRequired -or $Reboot) {
    Write-Verbose 'Rebooting in 10 seconds'
    Start-Sleep -Seconds 10
    Restart-Computer -Force
}


<#
    $Results.ResultCode:

    Return Value    Meaning
    0               Not Started
    1               In Progress
    2               Succeeded
    3               Succeeded With Errors
    4               Failed
    5               Aborted
#>