Apply Windows Updates Via a Chef Recipe


Using Chef (or any other remote management tool for that matter – like Windows Remote Management or PowerShell Remoting) to apply Windows updates to a remote system is difficult because some of the Windows Update methods will not work when executed from a remote connection, even if you’re using Administrator level credentials (this is apparently a feature, not a bug). To get around this, the Chef recipe must launch the update commands via a task in Task Scheduler. This can be done by configuring the Task Scheduler task to call the Chef recipe via the local ‘chef-client’ utility.

In this example I’m creating a task to ‘run once’, but since it’s in the past, this task will never get executed on its own. Then I’m manually launching the newly created task, which just calls the Chef Client to run my InstallWindowsUpdates cookbook (recipe: default.rb).

Create/Execute Task via PowerShell Remoting Example:

Invoke-Command -ComputerName <server name> -Credential <admin credentials> -ScriptBlock { cmd /c "schtasks /Create /RU System /RL HIGHEST /F /TR ""c:\opscode\chef\bin\chef-client.bat -o InstallWindowsUpdates"" /TN ChefInstallUpdates /SC Once /ST 00:00 2>&1" }
Invoke-Command -ComputerName <server name> -Credential <admin credentials> -ScriptBlock { cmd /c "schtasks /run /tn ChefInstallUpdates 2>&1" }

Windows Update Recipe Example (default.rb):

# Cookbook Name:: InstallWindowsUpdates
# Recipe:: default
# Author(s):: Otto Helweg

# Configures Windows Update automatic updates
powershell_script "install-windows-updates" do
  guard_interpreter :powershell_script
  # Set a 2 hour timeout
  timeout 7200
  code <<-EOH
    Write-Host -ForegroundColor Green "Searching for updates (this may take up to 30 minutes or more)..."

    $updateSession = New-Object -com Microsoft.Update.Session
    $updateSearcher = $updateSession.CreateupdateSearcher()
      $searchResult =  $updateSearcher.Search("Type='Software' and IsHidden=0 and IsInstalled=0").Updates
      eventcreate /t ERROR /ID 1 /L APPLICATION /SO "Chef-Cookbook" /D "InstallWindowsUpdates: Update attempt failed."
      $updateFailed = $true

    if(!($updateFailed)) {
      foreach ($updateItem in $searchResult) {
        $UpdatesToDownload = New-Object -com Microsoft.Update.UpdateColl
        if (!($updateItem.EulaAccepted)) {
        $Downloader = $UpdateSession.CreateUpdateDownloader()
        $Downloader.Updates = $UpdatesToDownload
        $UpdatesToInstall = New-Object -com Microsoft.Update.UpdateColl
        $Title = $updateItem.Title
        Write-host -ForegroundColor Green "  Installing Update: $Title"
        $Installer = $UpdateSession.CreateUpdateInstaller()
        $Installer.Updates = $UpdatesToInstall
        $InstallationResult = $Installer.Install()
        eventcreate /t INFORMATION /ID 1 /L APPLICATION /SO "Chef-Cookbook" /D "InstallWindowsUpdates: Installed update $Title."

      if (!($searchResult.Count)) {
        eventcreate /t INFORMATION /ID 999 /L APPLICATION /SO "Chef-Cookbook" /D "InstallWindowsUpdates: No updates available."
      eventcreate /t INFORMATION /ID 1 /L APPLICATION /SO "Chef-Cookbook" /D "InstallWindowsUpdates: Done Installing Updates."
  action :run