Get the status of WSUS clients installing the September Cumulative Update

A few days ago, a PM.org list member asked the following question:

Iā€™m trying to find out what the status is on clients installing the September Cumulative Update.

He also reported that he was using WID (Windows Internal Database) and not SQL. He was also struggling with the Microsoft Report Viewer and Microsoft System CLR Types for SQL Server.

I replied that he can achieve this using only #PowerShell and the WSUS API šŸ˜€ but my first try was:

$updateScope = New-Object Microsoft.UpdateServices.Administration.UpdateScope
$updateScope.TextIncludes= '4038777'
(Get-WsusServer).GetUpdateApprovals($updateScope) | 
ForEach-Object {
 $tg = (Get-WsusServer).GetComputerTargetGroup($($_.ComputerTargetGroupId))
 Write-Verbose -Message "Dealing with approval type $($_.Action) on to target group '$($tg.Name)'" -Verbose
 $tg.GetComputerTargets($true) | 
 ForEach-Object {
  $computer = $_
   $State = $computer.GetUpdateInstallationSummary($updateScope)
   [PSCustomObject]@{
    ComputerName = $_.FullDomainName ;
    Unknown = $( if($State.UnknownCount) { $true } else { $false} );
    NotApplicable = $( if($State.NotApplicableCount) { $true } else { $false } );
    NotInstalled = $( if($State.NotInstalledCount) { $true } else { $false } );
    Downloaded = $( if($State.DownloadedCount) { $true } else { $false } );
    Installed = $( if($State.InstalledCount) { $true } else { $false } );
    InstalledPendingReboot = $( if($State.InstalledPendingRebootCount) { $true } else { $false } );
    Failed = $( if($State.FailedCount) { $true } else { $false } );
   }
 }
} | ogv

Well, the above code did the job but performed very poorly. It took more than a minute to display the results for a hundred client computers.

I took the update scope approach with a custom text filter and I’ve been inspired by my previous blog post about WSUS reporting.

I wasn’t satisfied and I believed I took the wrong approach and that there should be one or more efficient ways to get the results.
So, the next morning, I gave it another try and found another way to skin the cat:

$kb = '4038777' | % { (Get-WsusServer).SearchUpdates($_) } | ? Title -match 'Windows 7 for x64-based ' |  ? { $_.IsLatestRevision }
(Get-WsusServer).GetComputerTargetGroups() | 
ForEach-Object {
 $kb.GetUpdateInstallationInfoPerComputerTarget($_)  | 
 ForEach-Object {
  [PSCustomObject]@{
   Computer = (Get-WsusServer).GetComputerTarget($_.ComputerTargetId).FullDomainName
   State = $_.UpdateInstallationState
  }
 }
} | ogv

The above way performs much faster (max 8 seconds for the same 100 computers) and has less code šŸ˜Ž

Advertisements

WSUS (bundled or installable) child updates

This morning there was question on Patchmanagement.org about how to find if the following .Net Rollup update (4019112) has been installed.

The person approved 4019112 but noticed that the WMI Win32_QuickFixEngineering class didn’t list it.
Instead it contained the following updates KB4014504 and KB4014511 that are actually bundled inside the rollup 4019112

The person was also asking if there’s another way to find what child updates an update installs instead of what’s listed on its support page.

Yes, there is one. If you use the WSUS API GetInstallableItems method. It will list what child updates an update will install (more precisely, it will get the directly bundled, non-explicitly-deployable child updates of this update. Creates an installable item on behalf of this update if the update itself has files.)

(Get-WsusServer).SearchUpdates('4019112') |
Out-GridView -PassThru -Title 'Select an update'| 
ForEach-Object { 
    $_.GetInstallableItems() 
} | ft -AutoSize

As you can see the KB4019112 (logically) bundles an update for every supported version of .Net.

Unfortunately, we couldn’t use the GetRelatedUpdates method and its enum UpdatesBundledByThisUpdate that should also list child updates that are bundled in this update.

# what's the numeric value of the UpdatesBundledByThisUpdate enum
[Microsoft.UpdateServices.Administration.UpdateRelationship]::UpdatesBundledByThisUpdate.value__

(Get-WsusServer).SearchUpdates('4019112') | 
ForEach-Object {
 $_ | 
 Add-Member -MemberType ScriptProperty -Name UpdatesBundledByThisUpdate -Value {
  ($this.GetRelatedUpdates(2)).Title
 } -Force -PassThru
} | Select Title,UpdatesBundledByThisUpdate | 
Out-GridView -PassThru | Select -ExpandProperty UpdatesBundledByThisUpdate


As you can see, it doesn’t return anything. Why?

(Get-WsusServer).SearchUpdates('4014504')
(Get-WsusServer).SearchUpdates('4014511')

Well, because the above two searches also don’t return anything.
It means that these updates don’t exist as individual updates in WSUS.

To find out if the .Net rollup 4019112 has been installed on a target computer you can either:

# use the Get-HotFix cmdlet
Get-HotFix -Id 'kb4014504','kb4014508','kb4014511','kb4014514'
# or the  Get-WmiObject cmdlet
Get-WmiObject -Query 'Select * FROM Win32_QuickFixEngineering WHERE HotFixID = "KB4014504" OR HotfixID = "KB4014508" OR HotfixID = "KB4014511" OR HotFixID = "KB4014514"' 

Quick tip: disable WSUS approval rules

I stumbled upon the following article about WSUS and PowerShell a few weeks ago.
After seeing the second point, I realized, it’s nice to verify how “auto-approval” rules are configured but it would be better to know how to enable or disable these rules.

Here’s how to disable rules:


(Get-WsusServer).GetInstallApprovalRules() | 
Out-GridView -Passthru| 
ForEach-Object {
    $r = $_
    try {
        $_.Enabled = $false
        $_.Save()
        Write-Verbose "Successfully disabled $($_.Name)" -Verbose

    } catch {
        Write-Warning "Failed on rule $($r.Name) because $($_.Exception.Message)"
    }
}

With the above code snippet, you first get the out-grid view where you can select rules and then click ok:

I selected the only rule I had and got the following result:

If you want to seem more articles about WSUS from my blog, you can use WSUS tag to list them all šŸ™‚

WSUS synchronization report

The other day I wondered how to get the equivalent of this WSUS synchronization summary and its details.
More precisely I wanted to get the summary of what you see in the mmc snap-in under the synchronizations node:
wsus-sync-01
and the details that you get when you right-click a synchronization and hit ‘Synchronization report’ but I got instead the following message telling me that the report viewer wasn’t installed
wsus-sync-02

After a few minutes, I found the following MSDN page about Reporting Newly Synchronized Updates
I first looked at the GetUpdates method that has many ways to call it.
wsus-sync-03
The msdn article proposes to use the 2nd one where you specify all the 5 arguments:

  • Microsoft.UpdateServices.Administration.ApprovedStates approvedStates,
  • datetime fromArrivalDate,
  • datetime toArrivalDate,
  • Microsoft.UpdateServices.Administration.UpdateCategoryCollection updateCategories,
  • Microsoft.UpdateServices.Administration.UpdateClassificationCollection updateClassifications

where both the updatecategories and updateclassifications are set to $null
I gave it a try but I couldn’t get reliable results šŸ˜¦ even when the two collections (UpdateCategoryCollection and UpdateClassificationCollection) were properly defined and not set to null.
Forget that method, it’s too unpredictable.

I switched to the 3rd method where you just use a single updateScope object as argument instead of the above 5 arguments and finally got the expected reliability šŸ˜€

# Get the last sync info (start and end times)
$lastSync = (Get-WsusServer).GetSubscription().GetLastSynchronizationInfo()

# Create an updatescope object
$UpdateScope = New-Object -TypeName Microsoft.UpdateServices.Administration.UpdateScope

# Set the start time
$UpdateScope.FromArrivalDate = $lastSync.StartTime
# Set the end time
$UpdateScope.ToArrivalDate = $lastSync.EndTime

# Invoke the getupdates method using the update scope object
$SyncUpdates = (Get-WsusServer).GetUpdates($UpdateScope)

Now to get the summary, I just do

$SyncUpdates | 
Group-Object -Property publicationstate -NoElement

wsus-sync-04
To view the details of what was synchronized I do:

$SyncUpdates | Out-GridView
# or 
$SyncUpdates | 
Select Title,SecurityBulletins,UpdateClassificationTitle,PublicationState | 
Sort PublicationState | 
Format-Table -AutoSize

wsus-sync-05

Easy-peasy and as always PowerShell rocks šŸ˜Ž

Testing WSUS server operational status

During the summer, someone asked the following questionwsus-monitoring-question on the WSUS patchmanagement.org mailing list.

I replied and immediately thought that Pester would do the job and quickly showed how he could test if he can connect to the console.

I’ve actually more than a WSUS server to manage. So, I started separating the environmental configuration data from the pester tests code almost the same way Mike F. Robbins did in his recent post where he goes far beyond to what I did.

I think it’s a great idea and here’s what I did in my case to monitor my WSUS server operational status.

To get started, I copied the Pester module on my WSUS server, imported the module and did in the ISE:

# helper to create the required files and folder if not present
New-Fixture -Path  ~/Documents/Pester -Name Test-WSUS
# Put the config data into that file:
psEdit ~/Documents/Pester/Test-WSUS.ps1
# Put the pester code into that file:
psEdit ~/Documents/Pester/Test-WSUS.Tests.ps1

After the first 3 commands, here’s what the ISE console looked liked
pester-new-fixture-wsus-tests

The first file Test-WSUS.ps1 looks like this by default.
It will be used to store my configuration data.
pester-wsus-tests-01

The second file Test-WSUS.Tests.ps1 is where I’ll write the pester tests code
pester-wsus-tests-02

After editing the 1rst file Test-WSUS.ps1 like this:
pester-test-wsus-file (fake data in this case)

…and the 2nd file Test-WSUS.Tests.ps1 like this:

…I’m actually ready to assess the operational readiness of my WSUS configuration by using the following cmdlet:

Invoke-Pester -Script ~/Documents/Pester/Test-WSUS.Tests.ps1

wsus-server-config-invoke-pester

I still feel like a Pester newbie but no doubt that Pester rocks šŸ˜Ž

Import the convenience update into WSUS

My WSUS server runs on a Windows Core edition where I don’t have Internet Explorer installed.

Whatif I want to import the Convenience rollup update for Windows 7 SP1 and Windows Server 2008 R2 SP1 from the catalog?

Here’s the way to go:

On a client computer with a GUI, launch internet explorer with high privileges to be able to install the ActiveX.

$HT = @{
 FilePath = 'C:\Program Files (x86)\Internet Explorer\iexplore.exe' ;
 ArgumentList = 'http://catalog.update.microsoft.com/v7/site/Search.aspx?q=KB3125574' ;
 Verb = 'Runas'
}
Start-Process @HT

MS-catalog-ActiveX-install

Install the ActiveX, add the update to the basket, view your basket, download, browse…
Move the file onto the WSUS server…and check if the file is digitally signed

Get-AuthenticodeSignature -FilePath ~\downloads\Catalog\*\*.msu

The WSUS API has an ImportUpdateFromCatalogSite, documented here on msdn.
WSUS-import-method

I’m missing the UpdateID. Where do I find this Id?

It appears that if you click on the link highlighted below…

MS-catalog-KB-link

…you get on a page where you’ve more details about the update.

Convenience-update-Id

The updateId actually appears in the address bar. šŸ˜€

Now, to import the update into WSUS, I do:

$MSUfile = 'C:\Users\administrator\Downloads\Catalog\Update for Windows 7 for x64-based Systems (KB3125574)\AMD64-all-windows6.1-kb3125574-v4-x64_2dafb1d203c8964239af3048b5dd4b1264cd93b9.msu'
(Get-WsusServer).ImportUpdateFromCatalogSite(
    '49924c88-1b31-4b0f-ad3d-48df9877f385',
    $MSUfile
)
# Find the imported file
(Get-WsusServer).SearchUpdates('3125574') | fl *

The convenience update has been successfully imported and can now be approved.

Easily identify your WSUS updates

I’ve been using some piece of code on WSUS to enrich non-security updates to help me identify what problem they address.

Let’s first have a look at the result as a picture is worth a thousand words šŸ˜€
Enrich-WSUS-updates

Now, let’s see the code

I wanted to share this a long time ago but the initial piece of code broke while Microsoft was upgrading their support.microsoft.com website.
It may broke again of course…