Quick tip: What new products and classifications have been added in the past 30 days to WSUS?

Two days ago a PM.org list member asked the following question:

When you see the notice “X new products and Y new classifications have been added in the past 30 days”, how do you know which ones are new when you click on the link.

The only answer was a piece of PowerShell code 😀 that another list member pointed (here)

# Get new classifications
(Get-WsusServer).GetUpdateClassifications(
 "$((Get-Date).AddDays(-31))",
 "$(Get-Date)"
) | Select Title,Description, Releasenotes, arrivaldate

# Get what new products were added over the month
(Get-WsusServer).GetUpdateCategories(
 "$((Get-Date).AddDays(-31))",
 "$(Get-Date)"
) |  
Select Title,Id,arrivaldate | 
ft -AutoSize

Enjoy!

Advertisements

About KB4093118

The KB4093118 page has just been updated and states:

Note:

  • This update replaces update 4100480, Windows kernel update for CVE-2018-1038.
  • Resync is required to get newer revision of this KB for WSUS environment

What changed?

  • Before a WSUS sync:
  • (Get-WsusServer).SearchUpdates('2018-04 Security Monthly Quality Rollup for Windows 7 for x64-based Systems (KB4093118)') | 
    Select PublicationState,@{l='UpdateId';e={$_.Id.UpdateId}},
    @{l='RevisionNumber';e={$_.Id.RevisionNumber}},CreationDate,isApproved,isSuperseded | 
    ft -AutoSize
    

  • What’s the content of the patch
  • (Get-WsusServer).SearchUpdates('2018-04 Security Monthly Quality Rollup for Windows 7 for x64-based Systems (KB4093118)') |
    ForEach-Object {
     $_.GetInstallableItems().Files| 
     Select Name,Modified,Type,@{l='Size';e={'{0:N2} MB'-f($_.TotalBytes/1MB)}},
     @{l='SHA1Hash';e={-join ($_.Hash | ForEach-Object { '{0:X2}' -f $_ })}},
     @{l='URI';e={$_.OriginURI.ToString()}}
    }
    

  • What was synchronized?
  • Using the code from this post,

    $SyncUpdates | Select Title,
    @{l='UpdateId';e={$_.Id.UpdateId}},
    @{l='RevisionNumber';e={$_.Id.RevisionNumber}},
    CreationDate,PublicationState,isApproved,isDeclined,isSuperseded,has* | 
    Out-GridView
    

  • After a WSUS sync:
  • What’s the content of the new patch
  • Notice that a file PCIClearStaleCache.exe has been added and the other files didn’t change (same hashes).

  • What has the new patch superseded?
  • Using this MSDN page to find out the UpdateRelationship value

    [Microsoft.UpdateServices.Administration.UpdateRelationship]::UpdatesSupersededByThisUpdate.value__ -eq 6
    

    It’s possible to list what this new patch has susperseded:

    (Get-WsusServer).SearchUpdates('2018-04 Security Monthly Quality Rollup for Windows 7 for x64-based Systems (KB4093118)') |
    Where-Object { -not($_.isSuperseded)} |
    ForEach-Object {
     $_ | Add-Member -MemberType ScriptProperty -Name UpdatesSupersededByThisUpdate -Value {
     ($this.GetRelatedUpdates(6)).Title
     } -Force -PassThru 
    } | Select -Expand UpdatesSupersededByThisUpdate
    

Conclusion:
It seems for sure that Microsoft has added another binary in the new patch to address the NIC card issue:

Fingers crossed 🙄

Find file names and URL of Windows 10 feature updates on WSUS

This question about how to find file names for Windows 10 feature updates on WSUS was raised this morning or last night on the PM.org list because Windows 10 1709 (CB = Current branch) was declared “CBB”, current branch for business (sorry, this is the old wording). In other words, the 1709 branch went from Semi-Annual Channel (Targeted) deployment state to a broader one called just “Semi-Annual Channel” (SAC)….

If I’m not clear, here’s the official release cadence, names and explanation:

… this question was raised because a 4th ISO is also expected…

and another list member asked how to get this info.

Abbodii replied the following:

If you are interested in these XML files as well, here are the links:

In a business environment, you’re more likely to find a WSUS server and it appears that you can also get the file names (ESD images) and their source location using PowerShell like this:

(Get-WsusServer).SearchUpdates(
'Feature update to Windows 10, version 1709, en-us'
) | 
ForEach-Object { 
 Write-Verbose "Looking at update id $($_.Id.UpdateId) revision: $($_.Id.RevisionNumber)" -Verbose
 $_.GetInstallableItems() | 
 ForEach-Object {
  $_.Files | 
  ForEach-Object {
   [PsCustomObject]@{
    URI = $_.OriginUri.OriginalString
    FileName = $_.OriginUri.Segments[-1]
   }
  }
 }
} | Select FileName

This also means that you can approve only one feature updates if you only have for example x64 based Windows 10 clients to target.
With the above code, you know the update id and you can now do:

$WXtargetgroup = (Get-WsusServer).GetComputerTargetGroups() |
Where Name -eq 'Windows 10 x64'

(Get-WsusServer).SearchUpdates(
'Feature update to Windows 10, version 1709, en-us'
) | Where-Object { 
 $_.Id.UpdateId -eq '7c803efa-be06-4481-9f8c-889afb34faf8' 
} | 
ForEach-Object -Process {
 Write-Verbose -Message "Approving $($_.Title)" -Verbose        
 $_.Approve(
  [Microsoft.UpdateServices.Administration.UpdateApprovalAction]::Install,
  $WXtargetgroup
 )
}

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 😎

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 😎