Windows Update Agent with PoSH – Part III
April 22, 2009 Leave a comment
In my last post, I left you hanging with inspecting the PoSH code looking for an error.
Let’s review the code:
#PowerShell script converted from VB Script here: http://msdn.microsoft.com/en-us/library/aa387102(VS.85).aspx cls # Show WUA version $AgentInfo = new-object -comObject Microsoft.Update.AgentInfo $WuaVersion = $AgentInfo.GetInfo("ProductVersionString") Write-Host "Windows Update Manager Version: $WuaVersion" Write-Host "`n" # Create session and look for updates applicable to machine where script is run $updateSession = new-object -comObject Microsoft.Update.Session $updateSearcher = $updateSession.CreateupdateSearcher() Write-Host "Searching for updates..." $searchResult = $updateSearcher.Search("IsInstalled=0 and Type='Software'") [System.Int32]$Results = $searchResult.Updates.Count if ($Results -eq 0) { Write-Host “There are no applicable updates.” return $Results } else { Write-Host “List of applicable items on the machine: $Results” Write-Host “——————————————–” for ([System.Int32]$i = 0; $i -lt $Results; $i ++ ) { $update = $searchResult.Updates.Item($i) Write-Host $update.title } } Write-Host “`n” # List applicable updates Write-Host “Creating collection of updates to download:” $updatesToDownload = New-Object -comObject Microsoft.Update.UpdateColl for ([System.Int32]$i = 0; $i -lt $Results; $i ++ ) { $update = $searchResult.Updates.Item($i) Write-Host “Adding - ” $update.title $updatesToDownload.Add($update) | Out-Null } # Download the applicable updates and list them Write-Host “Downloading updates…” $downloader = $updateSession.CreateUpdateDownloader() $downloader.Updates = $updatesToDownload $downloader.Download() | Out-Null Write-Host “`n” Write-Host “List of downloaded updates:” for ([System.Int32]$i = 0; $i -lt $Results; $i ++ ) { $update = $searchResult.Updates.Item($i) if ($update.IsDownloaded){ Write-Host $update.title ” - is downloaded.” } } Write-Host “`n” # Prep for installation $updatesToInstall = New-Object -comObject Microsoft.Update.UpdateColl Write-Host “Creating collection of downloaded updates to install:” for ([System.Int32]$i = 0; $i -lt $Results; $i ++ ) { $update = $searchResult.Updates.Item($i) if ($update.IsDownloaded){ Write-Host “Adding - ” $update.title ” - to install list.” $updatesToInstall.Add($update) | Out-Null } } Write-Host “`n” # Confirm installation. Install if Yes. Exit if No. Exit with message if any other key pressed. $Response = $(Read-Host “Would you like to install updates now? (Y/N)”) if(($Response -eq “Yes”) -or ($Response -eq “Y”)){ Write-Host “Installing updates…`n” $installer = $updateSession.CreateUpdateInstaller() $installer.Updates = $updatesToInstall $installationResult = $installer.Install() for ([System.Int32]$i = 0; $i -lt $updatesToInstall.count; $i ++ ) { $InstallationResults = $installationResult.GetUpdateResult($i).ResultCode Write-Host “Installation Result for: ” $updatesToInstall.Item($i).Title ” - Results:” switch ($InstallationResults) { 0 {Write-Host Red “Installation of Update Not Started”} 1 {Write-Host “Installation of Update in Progress” } 2 {Write-Host -foregroundcolor Green “Installation of Update Succeeded”} 3 {Write-Host -foregroundcolor Yellow “Installation of Update Succeeded with Errors”} 4 {Write-Host -foregroundcolor Red “Installation of Update Failed”} 5 {Write-Host -foregroundcolor Red “Installation of Update Aborted”} } Write-Host “Reboot Required? ” $installationResult.RebootRequired “`n” } exit } elseif (($Response -eq “No”) -or ($Response -eq “N”)) { exit } else { Write-Host -foregroundcolor Red “`nPlease answer `”[Y]es`” or `”[N]o`” next time!`n” exit } Write-Host “`n” # Error Handling trap{ Write-Host “ERROR: script execution was terminated.`n” $_.Exception.Message break }</pre>
My question was a bit “leading” I suppose. There is no error per se. However, as the script is written, it will list ALL downloads! Do we want ALL updates downloaded and installed? Perhaps not.
We can the script to help with scripted/automated system deployments in a variety of ways (physical as well as virtual deployments). So, we can improve on this script quite dramatically if we look carefully. There are some optional updates we may not want to install.
First, let’s assume we want to create an automated process for deploying systems. In that case, there’s no need to list the updates; we can eliminate the section which begins:
$updatesToDownload = New-Object -comObject Microsoft.Update.UpdateColl
[We can make the same argument for some other portions of the script such as displaying the WU client version being displayed; that can be removed as well.]
Additionally, if you test this script and compare it against a manual Microsoft Update process by using Internet Explorer, you’ll notice a discrepency in the list generated in PoSH and the one shown by Microsoft Update. After a bit of research, I found that if you use WSUS in your internal environment, it works quite differently that what expect to see from Microsoft Update. As I previously mentioned, as this script is written ALL available updates are listed and would be downloaded and installed.
The easiest way around this is to use the AutoSelectOnWebSites property of the IUpdate interface. In the beginning of the script, we created the session. If we take advantage of this by narrowing our download selections to only those which would normally be auto-selected by Microsoft Update [we visit the website], then our downloads will be limited to only those “auto selected”.
But, wait! While we are at it, let’s make sure Windows Firewall is shut down and that we restart the service at the end of the script.
We can also add a reboot option if it is required after an update.
Finally, since this script is intended to be part of an automated deployment process, let’s also create a transcript so we see the results when a reboot is required.
Here is the script. It is what I’m currently use when deploying virtual machines before they are added to our WSUS AD Policy:
function get-datestamp{ $datestamp = "_" + (get-date).tostring("yyyyMMddHHmmss") return $datestamp } function get-FWStatus ($ServiceName) { # Check Windows Firewall service status, stop if running. stop-Service $ServiceName Start-Sleep -seconds 5 Write-Host "Firewall is" $ServiceStatus.status } CLS $ServiceName = "SharedAccess" $ServiceStatus = get-Service $ServiceName if ($ServiceStatus.status -eq "Running" -or $ServiceStatus.status -eq "Started"){ stop-Service $ServiceName } else { do { get-FWStatus $ServiceName } until ($ServiceStatus.Status -eq "Stopped") } $TimeStamp = get-datestamp $WULog = $env:SystemDrive + "\" + $env:Computername + "_WULog" + $TimeStamp + ".log" # Create session and look for updates applicable to machine where script is run Write-Host "Searching for updates...`n" $updateSession = new-object -comObject Microsoft.Update.Session $updateSearcher = $updateSession.CreateupdateSearcher() $searchResult = $updateSearcher.Search("IsInstalled=0 and Type='Software'") [System.Int32]$Results = $searchResult.Updates.Count if ($Results -eq 0 -or $Results -eq $null){ # exit if no updates applicable to this system Write-Host "`nNo updates available`n" exit } # Create collection of items in preparation to download $UpdatesToDownload = New-Object -comObject Microsoft.Update.UpdateColl Write-Host "Preparing to download updates...`n" for ([System.Int32]$i = 0; $i -lt $Results; $i ++ ) { $update = $searchResult.Updates.Item($i) # Search for updates that are auto selected as critical updates by Microsoft Update Website. if ($update.AutoSelectOnWebSites -eq "True"){ $UpdatesToDownload.Add($update) | Out-Null } } if ($UpdatesToDownload.Count -gt 0){ Write-Host "Found" $UpdatesToDownload.Count "updates.`n" } else{ # exit if no updates meet AutoSelectOnWebSites criteria Write-Host "`nNo updates available`n" exit } # Download the applicable updates if collection contains items Write-Host "Downloading updates...`n" if ($UpdatesToDownload){ for ($i = 0; $i -lt $UpdatesToDownload.Count; $i ++ ){ $downloader = $updateSession.CreateUpdateDownloader() $downloader.Updates = $UpdatesToDownload $downloader.Download() | Out-Null Write-Host $UpdatesToDownload.Item($i).Title " ... has been downloaded." } } Write-Host "Updates to install: " $UpdatesToDownload.Count "`n" # Let's log the actual installation actions and results so we know what the results were Start-Transcript -path $WULog | Out-Null # So transcript doesn't get stuck on in case of error, set a flag $LogStatus = "True" # Install updates in the collection Write-Host "Installing updates...`n" $installer = $updateSession.CreateUpdateInstaller() $installer.Updates = $UpdatesToDownload $installationResult = $installer.Install() for ([System.Int32]$i = 0; $i -lt $UpdatesToDownload.count; $i ++ ) { $InstallationResults = $installationResult.GetUpdateResult($i).ResultCode switch ($InstallationResults) { 0 {Write-Host Red "Installation Result for: " $UpdatesToDownload.Item($i).Title " - Results: Update Not Started"} 1 {Write-Host "Installation Result for: " $UpdatesToDownload.Item($i).Title " - Results: Update in Progress" } 2 {Write-Host -foregroundcolor Green "Installation Result for: " $UpdatesToDownload.Item($i).Title " - Results: Succeeded"} 3 {Write-Host -foregroundcolor Yellow "Installation Result for: " $UpdatesToDownload.Item($i).Title " - Results: Succeeded with Errors"} 4 {Write-Host -foregroundcolor Red "Installation Result for: " $UpdatesToDownload.Item($i).Title " - Results: Update Failed"} 5 {Write-Host -foregroundcolor Red "Installation Result for: " $UpdatesToDownload.Item($i).Title " - Results: Update Aborted"} } } #Reboot if necessary if ($installationResult.RebootRequired){ Shutdown /r /t 1 /d P:2:4 }else{ Write-Host "A reboot is not required." # Restart Windows Firewall Start-Service $ServiceName } # make sure to look for transcript flat, stop transcript for normal script exit if (LogStatus -eq "True"){ Stop-Transcript } # Error Handling - make sure to look for transcript flat, stop transcript during exception catch trap{ Write-Host "ERROR: script execution was terminated.`n" $_.Exception.Message if ($LogStatus -eq "True"){ # Restart Windows Firewall Start-Service $ServiceName Stop-Transcript } exit }
One thing I’ve noticed, it could be my imagination, but WU appears to run much slower in PowerShell. It could just be my perception.