Scan for updates from different sources and compare results

Context: I’m working with PowerShell version 2.0 on a Windows 2008 R2 server connected to Internet, managed by an internal WSUS.

Goal: I want to perform two scans to get results from WSUS and Microsoft Update to compare them.
I just want to see Important updates and not optional updates

Reminder :

  • I’ve already shown how to quickly perform a scan and hide some updates on PowerShell Magazine, here
  • I’ve also already shown on Windows 8.x how to get updates from the Windows Store, here

Foreword: To be able to get updates from Microsoft Update, your Windows Update Agent (WUA) must ‘opt-in’.
This can be achieved with the following code:

# Opt-in to MU            
$UpdateSvc = New-Object -ComObject Microsoft.Update.ServiceManager            
$UpdateSvc.AddService2("7971f918-a847-4430-9279-4a52d1efe18d",7,"")            
$UpdateSvc.QueryServiceRegistration("7971f918-a847-4430-9279-4a52d1efe18d") | fl *            
$UpdateSvc.Services | Where { $_.Name -eq "Microsoft Update"}

Step 1: Modify the Get-WindowsUpdate function I presented on PowerShell Magazine by adding a FromMU parameter

Function Get-WindowsUpdate {
 
    [Cmdletbinding()]
    Param(
        [Parameter()]
        [system.Boolean]$FromMU=$false
    )
 
    Process {
        try {
            Write-Verbose "Getting Windows Update"
            $Session = New-Object -ComObject Microsoft.Update.Session           
            $Searcher = $Session.CreateUpdateSearcher() 
            
            if($fromMU) {
                $Searcher.ServiceID = '7971f918-a847-4430-9279-4a52d1efe18d'
                $Searcher.SearchScope =  1 # MachineOnly
                $Searcher.ServerSelection = 3 # Third Party                  
            }
            $Criteria = "IsInstalled=0 and DeploymentAction='Installation' or IsPresent=1 and DeploymentAction='Uninstallation' or IsInstalled=1 and DeploymentAction='Installation' and RebootRequired=1 or IsInstalled=0 and DeploymentAction='Uninstallation' and RebootRequired=1"           
            $SearchResult = $Searcher.Search($Criteria)          
            $SearchResult.Updates
        } catch {
            Write-Warning -Message "Failed to query Windows Update because $($_.Exception.Message)"
        }
    }
}

Only 3 properties of the Searcher object have been defined inside the if/else block.
These properties can also be discovered by reading the WindowsUpdate.log.

The modified function can be tested like this:

Get-WindowsUpdate | Out-GridView
Get-WindowsUpdate -FromMU:$true | Out-GridView

Problem: The MU scan mixes both Important and optional updates although the WUA is configured like this:
(do not give me recommended updates the same way I receive important updates)

Notice also that there’s no property from the previous Out-GridView window that would help distinguish between these “kinds” of updates.
I find also ambiguous the difference between “Optional” updates and “Recommended” updates.
To avoid confusion, let’s treat “optional” updates as updates that you can see in the “Optional” view of the Windows Update GUI.

Step 2: Find out what updates are optional vs. important

$UpdatesFromMU = Get-WindowsUpdate -FromMU:$true 
$Results = $UpdatesFromMU | ForEach-Object {
    $_ | Add-Member -MemberType ScriptProperty -Name isImportant -Value ({
        if (-not($this.BrowseOnly) -and $this.AutoSelectOnWebSites) {
            $true
        } else { 
            $false
        }
    }) -Force -PassThru |
    Add-member -MemberType ScriptProperty -Name isOptional -Value ({
        -not($this.isImportant)
    }) -Force -PassThru
}

As you can see, I’ve chosen to add script properties on the fly.

Let’s check if this corresponds to an interactive scan with the GUI

$Results | Where { $_.isImportant } | Measure
$Results | Where { $_.isOptional }  |  Measure



There’s one additional optional update that is included in the scan made with PowerShell.
In my case, it’s Microsoft .NET Framework 4 for Windows Server 2008 R2 x64-based Systems (KB982671)

Step 3: Get more info about updates

This step is more experimental. As the built-in description property of the update isn’t enough descriptive (it doesn’t tell you what it fixes), I’ll use the MoreInfoUrls property and extract the title from the raw html returned by an http request.
I don’t have the handy new Invoke-WebRequest cmdlet provided by PowerShell version 3.0, so I’ll use the old method.

$Results | Where { $_.isImportant } | 
ForEach-Object { 
    $update = $_
    $TitleLine = $pageTitle = $null
    try {
        $TitleLine = (new-object Net.Webclient).DownloadString([system.Uri]$($_.MoreInfoUrls )) -split '\<' | 
        Select-string -Pattern "\:pagetitle\=" -ErrorAction Stop | Select -Expand Line
        if ($TitleLine) {
            $pageTitle = @(([regex]'.*\="(?<Title>.*)"\s/\>').Matches(($TitleLine).ToString())) | 
            Select -Last 1 -Expand Groups | 
            Select -Last 1 -Expand Value
            $_ | Add-Member -MemberType NoteProperty -Name "Page Title" -Value $pageTitle -Force -PassThru -ErrorAction Stop
        }
    } catch {
        Write-Warning -Message "Failed for $($update.Title) because $($_.Exception.Message)"
        $update | Add-Member -MemberType NoteProperty -Name "Page Title" -Value $($update.Description) -Force -PassThru
    }        
} | 
Select Title,@{
    l='Category';e={$_.Categories | Where { -not($_.Parent) } | Select -Expand Name}
},'Page Title' | 
Out-GridView

Step 4: scan for Updates from WSUS and Microsoft Update and compare results

I can now export results from the scan performed against Microsoft Update by just replacing the above Out-GridView cmdlet by the following code:

Export-Csv -Path "$($home)\Documents\scan.results.$((Get-date).toString('yyyy-MM-dd-HHmm')).MU.csv"

To get results from WSUS, I do:

#
# scan from WSUS
#
$UpdatesFromWSUS = Get-WindowsUpdate
$Results = $UpdatesFromWSUS | ForEach-Object {
    $_ | Add-Member -MemberType ScriptProperty -Name isImportant -Value ({
        if (-not($this.BrowseOnly) -and $this.AutoSelectOnWebSites) {
            $true
        } else { 
            $false
        }
    }) -Force -PassThru |
    Add-member -MemberType ScriptProperty -Name isOptional -Value ({
        -not($this.isImportant)
    }) -Force -PassThru
}
$Results | Where { $_.isImportant } | Measure
$Results | Where { $_.isOptional }  |  Measure
# > no need to distinguish between the 2 kinds 🙂

$Results | 
ForEach-Object { 
    $update = $_
    $TitleLine = $pageTitle = $null
    try {
        $TitleLine = (new-object Net.Webclient).DownloadString([system.Uri]$($_.MoreInfoUrls )) -split '\<' | 
        Select-string -Pattern "\:pagetitle\=" -ErrorAction Stop | Select -Expand Line
        if ($TitleLine) {
            $pageTitle = @(([regex]'.*\="(?<Title>.*)"\s/\>').Matches(($TitleLine).ToString())) | 
            Select -Last 1 -Expand Groups | 
            Select -Last 1 -Expand Value
            $_ | Add-Member -MemberType NoteProperty -Name "Page Title" -Value $pageTitle -Force -PassThru -ErrorAction Stop
        }
    } catch {
        Write-Warning -Message "Failed for $($update.Title) because $($_.Exception.Message)"
        $update | Add-Member -MemberType NoteProperty -Name "Page Title" -Value $($update.Description) -Force -PassThru
    }        
} | 
Select Title,@{
    l='Category';e={$_.Categories | Where { -not($_.Parent) } | Select -Expand Name}
},'Page Title' | 
Export-Csv -Path "$($home)\Documents\scan.results.$((Get-date).toString('yyyy-MM-dd-HHmm')).WSUS.csv"

To compare results, I do:

Compare-Object -Property Title -IncludeEqual -ReferenceObject (
    Import-CSV (
        Get-ChildItem "$($home)\Documents\scan.results.*.MU.csv" | 
        Sort LastWriteTime | Select -Last 1
    )
) -DifferenceObject (
    Import-CSV (
        Get-ChildItem "$($home)\Documents\scan.results.*.WSUS.csv" | 
        Sort LastWriteTime | Select -Last 1
    )
) 

Enjoy 😀

Advertisements

2 thoughts on “Scan for updates from different sources and compare results

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s