Windows Update Agent with PoSH – Part III

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.

Windows Update Agent with PoSH – Part II

Let’s clean this script up a bit.  There were some uncessary line feeds, had a couple typos, needed some asthetic cleanup, and result codes were numerical, etc.  Let’s stick some comments in and change up the results display so that results are displayed using “spoken language” versus result codes.

The script has a group of 6 basic groups of tasks:

  • Show WUA version
  • List applicable updates (creates an initializes a WU session)
  • Download the applicable updates and list them one by one
  • Prep for installation (create the collection of updates to install)
  • Confirm installation.  Install if Yes.  Exit if No.  Exit with message if any other key pressed.
  • Error Handling

In essence, all this script does is reproduce the WU process as if you were going to the Windows Update website.  The only difference is that it downloads all applicable updates and prompts for confirmation for the installation.

#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
}

So, now we have results that make more sense.  Anything with an error is red.  Anything that succeeded with issues is yellow.  Succeess will be green and any other status is the default font color.

This is the K.I.S.S. approach.  We could create some functions and shorten this up.  I like a clean and simple approach.  It’s easy to read, there aren’t a bunch of abbreviations like I find in a lot of examples posted out on the internet of PoSH code.

There is an error in this script.  Did you see it?  Next we’ll look at where this is and at reboot options and other simple things we can do with WUA using PoSH.

A Simple VB to PowerShell Script Conversion (Windows Update Agent with PoSH)

During a discussion, the question arose about doing Windows Update from PowerShell (PoSH). A simple “I can’t see why not” was my response.

One of the powerful applications of PoSH, especially for the future, is the ability to more easily create deployment scripts for deploying new Windows NOS systems into the data center. As virtual computing becomes more mainstream, scripting the deployment makes a repeatable build a reality. It also ensures that the ability to rebuild a system quickly in the event of a disaster situation is also a reality.

My original objective was just to write some simple code and install 1 Windows Update.  I quickly found many examples but was left with way too many questions.

After just a minute or so of googl’ing, I ran across this MSDN post:

Searching, Downloading, and Installing Updates (Windows)

The script seemed simple enough.  So, my objective changed to converting this almost word-for-word from VB to PoSH.

Only a few slight changes have been made to the original script. The script’s important bits are pretty much the same as in VB, just converted to use PoSH.

#PowerShell script converted from VB Script here: http://msdn.microsoft.com/en-us/library/aa387102(VS.85).aspx
cls
$AgentInfo = new-object -comObject Microsoft.Update.AgentInfo
$WuaVersion = $AgentInfo.GetInfo("ProductVersionString")
Write-Host "Windows Update Manager Version: $WuaVersion"
Write-Host "`n"
$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"
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
}
Write-Host "`n"
Write-Host "Downloading updates... `n"
$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"
$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"
$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()
	Write-Host -noNewLine "Installation results: " $installationResult.Resultcode
	Write-Host " Reboot Required? " $installationResult.RebootRequired
	Write-Host "`n"
	for ([System.Int32]$i = 0; $i -lt $updatesToInstall.count; $i ++ ) {
		Write-Host "Installation Result for: " $updatesToInstall.Item($i).Title "`t" $installationResult.GetUpdateResult($i).ResultCode
	}
	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
}