March 11th, 2009

Hey, Scripting Guy! How Can I Search For, Download, and Install an Update?

Hey, Scripting Guy! Question

Hey, Scripting Guy! Reporting on whether a Windows Update is installed or not is pretty cool. However, what happens when I determine the update has not been applied? Am I supposed to call the user and talk them through connecting to the Microsoft Update site and choosing the appropriate update? That may work for one or two users, but it does not scale very well. Surely you have a better plan than that? I mean, you are after all the Microsoft Scripting Guy, not the Microsoft Talks the User Through the GUI Guy. You got a script for me?

– NB

SpacerHey, Scripting Guy! Answer

Hi NB,

Well, we are snowed in down here in Charlotte today. It’s a snow day! Woo Hoo! Hey, wait a minute, I work from home, so not much of a snow day after all. Oh well. It looks pretty from my office windows, and the neighbors are in the street with their various snow implements. Looks cold; luckily I am sipping a cup of Constant Comment tea and listening to the Kentucky Headhunters on my Zune. So you want to be able to search for an update, download the update, and install the update? No problem.

This week we will be looking at using the Windows Update API to work with Windows Update. The Windows Update API is documented on MSDN. There is also a good collection of VBScripts that use the Windows Update API in the Script Center Script Repository. For information about manually configuring Windows Update on workstations, refer to this page. These scripts use Windows PowerShell. The Windows PowerShell getting started page has basic information for learning about Windows PowerShell and about downloading Windows PowerShell.

Today’s script is called InstallSoftwareUpdate.ps1 and is seen here (a VBScript version of today’s script can be seen in the Script Center Script Repository):

$UpdateCollection = New-Object -ComObject Microsoft.Update.UpdateColl
$Searcher = New-Object -ComObject Microsoft.Update.Searcher
$Session = New-Object -ComObject Microsoft.Update.Session

$updateID = “f1b1a591-bb75-4b1c-9fbd-03eedb00cc9d” $Result = $Searcher.Search(“UpdateID=’$updateID'”) $Updates = $Result.updates $UpdateCollection.Add($Updates.Item(0)) | out-null

$Downloader = $Session.CreateUpdateDownloader() $Downloader.Updates = $UpdateCollection $Downloader.Download()

$Installer = New-Object -ComObject Microsoft.Update.Installer $Installer.Updates = $UpdateCollection $Installer.Install()

The first thing we need to do is to create an instance of the Microsoft.Update.UpdateColl object. This object is known as the IUpdateCollection interface and it is documented on MSDN. To create the UpdateColl object, we use the New-Object cmdlet and specify the –ComObject parameter and use the Microsoft.Update.UpdateColl program ID. The pattern for creating these objects is seen here:

$VariableToHoldObject = New-Object –ComObject ProgramIDOFTheComObject

We store the returned UpdateColl object in the $UpdateCollection variable as shown here:

$UpdateCollection = New-Object -ComObject Microsoft.Update.UpdateColl

Next we need to create the Searcher object. The Microsoft.Update.Searcher object (also known as the IUpdateSearcher interface) is documented on MSDN. To create the Searcher object, we use the New-Object cmdlet with the –ComObject parameter and supply the Program ID of Microsoft.Update.Searcher. We store the returned Searcher object in the $Searcher variable. This is seen here:

$Searcher = New-Object -ComObject Microsoft.Update.Searcher

The next object we need to create is the Session object. This object, like the other two objects, begins with Microsoft.Update. It also is created with the New-Object cmdlet. Also known as the IUpdateSession interface, it is documented on MSDN. The code that creates the Session object is seen here:

$Session = New-Object -ComObject Microsoft.Update.Session

To install a software update, we need to first search for the update, add the retrieved update to the update collection, download the update, and finally install the update. If we want to install a specific update, we can use the updateID property from the Update Identity object. To easily retrieve updateIDs for software updates that are not yet installed, we can use the ListSoftwareUpdatesNotInstalled.ps1 script (this script is a revision of the ListSoftwareUpdates.ps1 script we looked at yesterday):

$Searcher = New-Object -ComObject Microsoft.Update.Searcher
$results = $searcher.search(“Type=’software’ AND IsInstalled = 0”)
$Results.Updates | 
ForEach-Object { $_.Identity.UpdateID ; “`t” + $_.Title }

We have added the ForEach-Object cmdlet to allow us to return the updateID property from the Update Identity object. We then tab over by using the “`t” and print out the title of the update as well by concatenating it via the plus sign (+). The results are seen here:

Image of the updateID and title of each update

 

After we have found the update we wish to install, we copy the updateID and assign it to the $UpdateID variable. We then use this variable when we call the Search method from the Searcher object. The query we use for the Searcher object is UpdateID = the value stored in the $updateID variable. The Searcher syntax was discussed in yesterday’s “Hey, Scripting Guy!” article.

After the update has been found, it is added to the UpdateCollection by using the Add method. The return code from adding the update to the collection is discarded by piping it to the Out-Null cmdlet. The updateID f1b1a591-bb75-4b1c-9fbd-03eedb00cc9d is the Windows Search 4.0 for Windows XP (KB940157) update. This section of the code is seen here:

$updateID = “f1b1a591-bb75-4b1c-9fbd-03eedb00cc9d”
$Result = $Searcher.Search(“UpdateID=’$updateID'”)
$Updates = $Result.updates
$UpdateCollection.Add($Updates.Item(0)) | out-null

We now need to download the updates. To do this, we use the CreateUpdateDownloader method from the Session object. The resulting download object is stored in the $Downloader variable. The updates from the UpdateCollection are assigned to the Updates property of the Downloader, and the Download method is called. This section of the script is seen here:

$Downloader = $Session.CreateUpdateDownloader()
$Downloader.Updates = $UpdateCollection
$Downloader.Download()

Now it is time to install the update. To do this, we need to first create an instance of the Installer object. The Installer object (also known as the IUpdateInstaller interface) is documented on MSDN. After the Installer object is created, we use the updates stored in the $updateCollection variable and assign it to the Updates property. We then call the Install method as seen here:

$Installer = New-Object -ComObject Microsoft.Update.Installer
$Installer.Updates = $UpdateCollection
$Installer.Install()

After we run the script, this is the output seen on the screen.

Image of the script's output

 

The HResult seen in the preceding image is either the COM or Windows error that was raised during the installation. The ResultCode is an OperationResultCode enumeration value. A ResultCode of 2 means the operation succeeded. The first set of results is returned by the Download operation, and the second set of results is returned by the Install operation. Table 1 documents the result codes.

Table 1 OperationResultCode enumeration values and meanings
Return Value Meaning

0

Not Started

1

In Progress

2

Succeeded

3

Succeeded With Errors

4

Failed

5

Aborted

If a problem occurs during the installation or download, the Installation Results object (IInstallationResult interface) can at times provide information that is useful in troubleshooting the script. An example of such an error can be seen here:

Image of an error that can occur if a problem occurs during installation or download

 

The Installation ResultsHResult property returned from the script is documented on MSDN. To interpret the results, we need to first convert the value to hexadecimal. To do this, we can use the Calculator (Calc.exe) program to make the conversion. I copy the -2145124318 value from the Windows PowerShell console and paste it into Calculator. When you have done so, it looks like what you see here:

Image of using Calculator to convert the value to hexadecimal

 

To convert the HRESULT value to something we can look up, we now need to translate the number into hexadecimal. We check hexadecimal and we also check the Dword radio button as seen here:

Image of checking hexadecimal and DWord on Calculator

 

The resulting number 80240022 can be looked up directly in the MSDN documentation, which dutifully tells us that the operation failed for all the updates. Additional information can be obtained from the System event log. The source of the event log messages will be Windows Update Agent. In the example seen in the final image below, the error that is reported is 0x80246007, which means, as the same MSDN documentation tells us, that the update has not been downloaded.

Image of event properties of error 0x80246007

 

Well, NB, that is about all there is to installing Windows Updates. I think I am going to put on my woolies and go play in the snow. It is a rare event for us to get snow at all, and to have 6 inches of the stuff? That’s truly a snow day. See you tomorrow as Windows Update Week continues. Until then, peace.

 

Ed Wilson and Craig Liebendorfer, Scripting Guys

Author

1 comment

Discussion is closed. Login to edit/delete existing comments.

  • Stig Nielsen

    Hey, Scripting Guy

    I am working on a scripted solution to apply update to servers in out datacenter in a controlled way. I have noticed that a scripted search (and installation) don't update the GUI where a date and time for "Last checked" is displayed. This is on Windows Server 2016 and 2019.
    Previous Windows has the same fault, but there I am able to update the field in Registry to reflect the correct date. In...

    Read more