Hey, Scripting Guy! How Can I List All Updates That Have Been Added to a Computer?

Hey, Scripting Guy! How Can I List All Updates That Have Been Added to a Computer?

  • Comments 13
  • Likes

Hey, Scripting Guy! Question

Hey, Scripting Guy! We have Windows Update configured on all our workstations at work. Automatically, the workstations check for new updates, and download and install the updates that are required. This happens without user intervention, or from intervention on the part of our small and overworked IT department. Here is the problem. The pointy-headed boss (PHB) came floating into our office late Friday afternoon and asked "How do you know all the workstations are up to date?" I said, because we have Windows Update configured on all the workstations." He said, "Can you display a list of all the updates that have ever been applied to that computer?" I said, "D’oh!" So, Scripting Guy, can you hook me up with a script to list all the updates that have been added to a computer?

- JU

SpacerHey, Scripting Guy! Answer

Hi JU,

I just hate not having an answer when the PHB comes floating by. Usually, I just make something up, because I figure he will not really know the difference anyway. Luckily, Windows Update has an API we can use to obtain information regarding updates that have been applied to the system.

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.

The script we wrote is called Get-MicrosoftUpdates.ps1 and it is seen here (you can also see a VBScript version of today's script):.

Function Get-MicrosoftUpdates
{ 
  Param(
        $NumberOfUpdates,
        [switch]$all
       )
  $Session = New-Object -ComObject Microsoft.Update.Session
  $Searcher = $Session.CreateUpdateSearcher()
  if($all)
    {
      $HistoryCount = $Searcher.GetTotalHistoryCount()
      $Searcher.QueryHistory(1,$HistoryCount)
    }
  Else { $Searcher.QueryHistory(1,$NumberOfUpdates) }
} #end Get-MicrosoftUpdates

Get-MicrosoftUpdates

The first thing we do in the script is create a function named Get-MicrosoftUpdates. To create a function in Windows PowerShell, we use the Function keyword, assign a name, and open and close the script block by using two curly brackets. The basic pattern is seen here:

Function FunctionName
{
# Function code goes here
} #end of the function

Here is the portion of our script that creates the function and assigns a name to it:

Function Get-MicrosoftUpdates
{

Now we create two command parameters. To do this, we use the Param keyword. The pattern we use to include parameters in a function is the same pattern as the one we use for a script. We use the Param keyword, open a set of parentheses, separate each parameter with a comma, and then close the set of parentheses. This pattern is seen here:

Param(Parametername, additionalParameterName)

In this script we use two parameters. The first parameter is called –NumberOfUpdates. The value supplied to this parameter when calling the function will be stored in the $NumberOfUpdates variable. The second parameter is a switched parameter named –all. A switched parameter does not receive a value when it is used—rather, it only has effect when it is present. When the –all parameter is not present, the script does not return all the updates. The parameter section of the Get-MicrosoftUpdates function is seen here:

Param(
        $NumberOfUpdates,
        [switch]$all
       )

It is time to create an instance of the Microsoft Update Session object (technically known as the IUpdateSession interface). The Microsoft Update Session object is a COM object, and can be created by using the New-Object cmdlet with the –ComObject parameter. It is not necessary to place quotation marks around the program ID Microsoft.Update.Session when using the New-Object cmdlet. We store the returned object in the $session variable, as seen here:

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

The Session object has a number of methods and properties available, which are listed in Table 1.

Table 1 Members of the Microsoft Update Session object
Name Type Definition

CreateUpdateDownloader

Method

IUpdateDownloader CreateUpdateDownload

CreateUpdateInstaller

Method

IUpdateInstaller CreateUpdateInstaller ()

CreateUpdateSearcher

Method

IUpdateSearcher CreateUpdateSearcher ()

CreateUpdateServiceManager

Method

IUpdateServiceManager2 CreateUpdateSer

QueryHistory

Method

IUpdateHistoryEntryCollection QueryHis

ClientApplicationID

Property

string ClientApplicationID () {get} {s

ReadOnly

Property

bool ReadOnly () {get}

UserLocale

Property

uint UserLocale () {get} {set}

WebProxy

Property

IWebProxy WebProxy () {get} {set}

Now we need to create an UpdateSearcher object. To create an Update Searcher object, we use the CreateUpdateSearcher method from the Session object. We store the Searcher object in the variable $Searcher as seen here:

$Searcher = $Session.CreateUpdateSearcher()

Then we need to determine if the function was called with the –all switch or not. If the function was called with the –all switch, the $all variable will be present. If it was not used, the $all variable will not exist. We use the if statement to determine if the $all variable is present. An example of using the if statement to test for the presence of a variable is seen here:

PS C:\> $all = $true
PS C:\> if($all) {'$all was found'}
$all was found

If we want to see if the $all variable is not present, we can use the not (!) operator as shown here:

PS C:\> $all = $false
PS C:\> if(!$all) {'$all was not found'}
$all was not found

In our script, we use the if statement and look for the $all variable. If we find it, we use the GetTotalHistoryCount property from the Searcher object to retrieve the total number of updates that have been applied to the system. We need this information because the QueryHistory method of the Searcher object needs a starting point and an ending point. If we want to get all the updates, we start at one and go until we have reached the total number of updates applied to the system. This is seen here:

if($all)
    {
      $HistoryCount = $Searcher.GetTotalHistoryCount()
      $Searcher.QueryHistory(1,$HistoryCount)
    }

If the script was not run with the –all switched parameter, we will be retrieving a specific number of updates from the system. The total number of updates to be retrieved is passed to the function via the –NumberOfUpdates parameter and will be stored in the $NumberOfUpdates variable. This section of the script is seen here:

Else { $Searcher.QueryHistory(1,$NumberOfUpdates) }
} #end Get-MicrosoftUpdates

To retrieve all the update information, you call the Get-MicrosoftUpdates function with the –all parameter:

Get-MicrosoftUpdates –all

When we call the function with the –all parameter, we are treated with an output similar to the one seen here:

Image of the output produced by the script

 

If we call the function with the –NumberOfUpdates parameter set to 1, we retrieve the most recent update that was applied to the system. The syntax for this is seen here:

Get-MicrosoftUpdates –NumberOfUpdates 1

When we do this, we are treated with the output seen here:

Image of the output produced by the script

 

If we decide we would like to have a text file that contains all of the update information, we only need to use the redirection arrows as shown here when we call the function:

Get-MicrosoftUpdates -all >> c:\fso\updates.txt ; notepad c:\fso\updates.txt

Well, JU, that is all there is to querying the update history for Windows Update. If you were to compare the VBScript version and the Windows PowerShell version, you would notice we receive a nice bonus when using Windows PowerShell in that we do not need to use a wscript.echo kind of line for each of the properties in which we are interested. We used the extra lines of code to create a function and to allow the ability to choose how many updates to report. Hope you have a great day, and we will see you tomorrow as Windows Update Week continues. Until then, peace.

 

Ed Wilson and Craig Liebendorfer, Scripting Guys

Your comment has been posted.   Close
Thank you, your comment requires moderation so it may take a while to appear.   Close
Leave a Comment
  • How would it be possible then to use the output generated to further script in Powershell to apply new updates to specific workgroups, or specific worksations to update them?

  • very interesting as always!

    thanks!

    btw what if no update was ever installed and we choose to show a defined "number" of updates?

    can you provide a win32com python example?

    I am stuck at the iteration over the QueryHistory result...

    <pre>

    updateHistory = updateSearcher.QueryHistory(1, 1)

    for item in updateHistory.Update:

       print repr(item)

       print item.Date

    </pre>

    it fails with AttributeError or index out of range...

  • @mark

    be sure to have at least one update installed

    &&

    use 0 as a base for QueryHistory (i.e. updateHistory = updateSearcher.QueryHistory(0, 1))

  • Hi,

    I have around 132 Microsoft  Security Patches,26 office patches,2 .Net updates installed in my PC.

    But when checked the history count in the above function it shows 92 updates only.

    Is there any thing else to list all the installed updates.

  • This looked perfect for something I now need to do .. Sadly, it doesn't run properly for me ...?

    Exception calling "QueryHistory" with "2" argument(s): "Exception from HRESULT: 0x80240007"
    At C:\temp\Get-MicrosoftUpdates.ps1:14 char:10
    + Else { $Searcher.QueryHistory(1,$NumberOfUpdates) }
    +
    + CategoryInfo : NotSpecified: (:) [], MethodInvocationException
    + FullyQualifiedErrorId : ComMethodTargetInvocation

  • Here's an optimized script:

    $Session = New-Object -ComObject "Microsoft.Update.Session"
    $Searcher = $Session.CreateUpdateSearcher()
    $historyCount = $Searcher.GetTotalHistoryCount()
    $Searcher.QueryHistory(0, $historyCount) | Select-Object Date,
    @{name="Operation"; expression={switch($_.operation){
    1 {"Installation"}; 2 {"Uninstallation"}; 3 {"Other"}}}},
    @{name="Status"; expression={switch($_.resultcode){
    1 {"In Progress"}; 2 {"Succeeded"}; 3 {"Succeeded With Errors"};
    4 {"Failed"}; 5 {"Aborted"}
    }}}, Title | Export-Csv -NoType "$Env:userprofile\Desktop\Windows Updates.csv"

    Also if you want to run scripts on Windows 8 or 2008 R2, you'll need to right click on PowerShell and select "Run as administrator", execute the following and answer "y" pressing enter:
    Set-ExecutionPolicy -ExecutionPolicy RemoteSigned

    Info from: http://technet.microsoft.com/library/hh847748.aspx

  • Nice, thanks Stu

  • It seems that the Param $NumberofUpdates doesn't work if we leave off the -all switch in this function. How do you properly use $NumberofUpdates as a parameter when calling Get-MicrosoftUpdates?

  • Couple of points if you are trying to get a list of all the updates on the machine:

    1. Calling the UpdateSearcher as described above will only give you a list of updates installed by WindowsUpdate process itself. So if a user installs an update by downloading through IE, that update will not appear here.

    2. This method will only show updates that were installed after the computer was first started. So, if the computer has an OEM install of Windows with pre-installed updates, those updates will not appear on this list.

    3. Hotfixes will not appear on this list, I am not sure why. To get a list of those just call "hotfix" from PowerShell.

    4. There is an off-by-one bug in the above script (already mentioned by mork). The list of updates is 0 indexed so
    $Searcher.QueryHistory(1,$HistoryCount)
    should be
    $Searcher.QueryHistory(0,$HistoryCount)

  • The script is running fine against the local server, looking for a way to run against a list of servers remotely

  • To check on a remote computer you use invoke-command and if you wish to check for a particular update you can filter the output:

    Invoke-Command -computername "RemotePC" -ScriptBlock {

    Function Get-MicrosoftUpdates
    {
    Param(
    $NumberOfUpdates,
    [switch]$all
    )
    $Session = New-Object -ComObject Microsoft.Update.Session
    $Searcher = $Session.CreateUpdateSearcher()
    if($all)
    {
    $HistoryCount = $Searcher.GetTotalHistoryCount()
    $Searcher.QueryHistory(0,$HistoryCount)
    }
    Else { $Searcher.QueryHistory(0,$NumberOfUpdates) }
    } #end Get-MicrosoftUpdates

    Get-MicrosoftUpdates -All | where {$_.Title -like "*KB2743555*"}

    }

  • To run it against a computer list load the text file and add a loop. Just pass the $PC variable to the -computername parameter:

    $file = Get-Content c:\PCs.txt
    foreach ($PC in $file) {

    Invoke-Command -computername $PC -ScriptBlock {

    Function Get-MicrosoftUpdates
    {
    Param(
    $NumberOfUpdates,
    [switch]$all
    )
    $Session = New-Object -ComObject Microsoft.Update.Session
    $Searcher = $Session.CreateUpdateSearcher()
    if($all)
    {
    $HistoryCount = $Searcher.GetTotalHistoryCount()
    $Searcher.QueryHistory(0,$HistoryCount)
    }
    Else { $Searcher.QueryHistory(0,$NumberOfUpdates) }
    } #end Get-MicrosoftUpdates

    Get-MicrosoftUpdates -All | where {$_.Title -like "*KB2743555*"} | select Title

    }
    }

  • Change this:
    $Session = New-Object -ComObject Microsoft.Update.Session

    To this:
    $Session = [activator]::CreateInstance([type]::GetTypeFromProgID("Microsoft.Update.Session",$computername))

    This will allow you to remotely query for updates on the system without the need to use remoting if it is not enabled in your environment.