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"' 

WMI subclasses

One of my colleague provided the following screenshot

and asked where he could have found that “ChangeStartMode” method.

Get-CimInstance -Class CIM_Service -Filter "Name='w32time'"|
Get-Member -MemberType Methods

Notice that he asked for instances from the CIM_Service class and got an Win32_Service instance back.
You may wonder why.

  • What’s the difference between CIM_Service and Win32_Service class ?

Win32_service is actually a subclass of the CIM_Service class.
CIM_ based classes are “schema” classes in WMI while Win32_ classes inherit from them (source)

  • How could you find that information using PowerShell?
([wmiclass]"CIM_Service").GetSubclasses() | 
Select Name,Methods | 
ft -AutoSize

There’s a child class named Win32_BaseService that inherits from the CIM_Service schema class.

If I get the subclasses from it, I can finally see the Win32_Service class.

([wmiclass]"Win32_BaseService").GetSubclasses() | 
Select Name,Methods | 
ft -AutoSize

If I want to see the methods, I should simply get them from the Win32_Service class.
In this case, you use Get-CIMClass cmdlet instead of the Get-CIMInstance cmdlet.

Get-CimClass -ClassName Win32_Service | 
Select -Expand CimClassMethods | ft

Bonus: If you want to see the MOF defintion of a WMI class, you can do:

([wmiclass]'Win32_Service').gettext('mof') -replace '\;',"`r"

VideoLan Player (vlc) update status

When I launch the Videolan Player (vlc), I get the following update prompt whenever a new version is available.

I was actually using the following detection based on the target link of the “last” version redirect:

$foundLatestOnlineVersion = $latestOnlineVer  = $null
$HT = @{
 Uri = 'http://download.videolan.org/pub/videolan/vlc/last' ;
 UseBasicParsing = $true ;
 ErrorAction = 'SilentlyContinue'
}
if ( 
 ($latestOnlineVer = (Invoke-WebRequest @HT).Links | 
 Where href -match '\.sha256$')
) { 
 $foundLatestOnlineVersion = (
  [regex]'vlc-(?<version>\d{1,2}\.\d{1,2}\.\d{1,2}(\.\d{1,2})?)\..*\.sha256'
 ).Match($latestOnlineVer.href).Groups | Select -Last 1 -Expand Value
 Write-Verbose -Message "Found latest VLC version online: $foundLatestOnlineVersion" -Verbose
}

As you can see my version detection approach fails. It shows 2.2.5.1 instead of 2.2.6 😦

I started looking around what vlc does to check when a new version has been released.

Fortunately, I found their updater source code on this page: https://github.com/videolan/vlc/blob/master/src/misc/update.c

The interesting line is where they show how they build the target status url based on the platform.

define UPDATE_VLC_STATUS_URL "http://update.videolan.org/vlc/status" UPDATE_OS_SUFFIX

I tried to do the same in PowerShell but the reply from their web server is an octet-stream

I can quickly convert the bytes array into something meaningful like this:

$HT.Uri = 'http://update.videolan.org/vlc/status-win-x64'
[char[]][int[]](Invoke-WebRequest @HT).Content  -join ''

Yeah, you can do this, it’s quick and dirty πŸ˜›

But, it would be nice to get an object returned instead of a multi line string.
I’ll use the char 10 (a line feed) as a separator.
The first line is the version, the second line the URL, the third line the title and the rest is the full text.

(Invoke-WebRequest @HT).Content | 
ForEach-Object -Begin {
 $Version = $URI = $Title = $null
 $i = 0
 $sb  = New-Object System.Text.StringBuilder
} -Process {
 if ($_ -eq 10) {
  $i++
  Switch ($i) {
   1 {
    $Version = $sb.ToString() ;
    break ;
   }
   2 {
    $URI = $sb.ToString() ;
    break ;
   }
   3 {
    $Title = $sb.ToString() ;
    break ;
   }
   4 {
    $Text = $sb.ToString() ;
    break ;
   }
   default {}
  }

  # reset the string builder
  $sb  = New-Object System.Text.StringBuilder
 } else {
  $null = $sb.Append([char][int]$_)
 }
} -End {
 [PSCustomObject]@{
  Version = [version]$Version ;
  URI = [uri]$URI ;
  Title = $Title ;
  Text = $Text ;
 }
} | fl

Let me quickly explain this approach. Btw, if you’ve got a better one please share it πŸ™‚
I use the Begin block of the Foreach-Object cmdet to initialize a counter and an empty string builder.
Inside the Process block, I test if the encountered byte is char 10, the line feed separator.
If it is, it means I reached a milestone. I increment the counter to know if it’s the first, second, third or fourth line. Then inside the switch block, based on this counter value, I create temporary variables where I store the result of the string builder.
If the character isn’t 10, I just append it to the string I’m building.
In the End block of the Foreach-Object cmdet, I can now create a single custom object using the temporary variables used in the process block.
This approach is rock solid until they change lines returned when you query the status URL πŸ™‚

What’s the last Sunday?

In October 2012, I published a function to find the last Sunday or another day of a month and based on some comments, I decided to revisit the code and improve it.
I started to improve the discovery of expected values by some parameters like the day, the month and year.

To achieve this I started to use a Dynamic parameter block and had the following code:

I wasn’t satisfied although it worked as expected. It’s hard to read, there are too many variables…

I remember I’ve already seen someone who wrote helper functions to avoid writing so many redundant code in the dynamic parameter block. (I cannot remember who. If you do, please post a link in the comments).

I wrote a quick helper function. I didn’t want to go further in order to keep the complexity quite low.

It’s around 50 lines.
Here’s an example to create a ValidateRange parameter attribute for the “Month” parameter.

Let’s see how this helper function is used in practice:

I first create the Dictionary object in the dynamic parameter block.
I load the helper function.
I then create the 3 dynamic parameters for the day, month and year.
Notice that I’ve also used some hash-table and splatting separated by a semicolon inside the same script block for a better readability.
There are only two variables left my dynamic parameter block ($Dictionary and $HT).

Now, let’s see how this function performs.
I’ve added a new feature where you can actually tell what day position in the month you want.
You can get the First, Second, Third, Fourth or Last.
You might be scratching your head why such options.
Well, let’s take a concrete example to illustrate why.
Let’s say Sunday.
In February 2016, you’ve got 4 Sundays but in April 2017, you’ve got 5.
One of the purpose of this function is to find out the last Sunday in a month to run a scheduled task.
Or you may want to run your task every fourth Sunday in a month.
Sometimes, the fourth Sunday is the last Sunday like it’s in February 2016.

But that’s not true in April 2017

I’ve got another code snippet to demo how this function can be used:


I can display the first, last, fourth Sunday in every month in 2016 and the number of days between the first and last 😎

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 πŸ™‚

(crazy) Tips: editing scripts

I’ve made a typo when invoking a script and was surprised by the result.
I decided to investigate and tried to understand what just happened.
Without further suspense, I did actually append a dot at the end of my script.

Whoaaa, notepad opened the script.
I didn’t get an error saying this file path doesn’t exist.
That was such a fast way to open my brave old notepad that this typo might even become my new way of editing scripts πŸ˜›
I mean, instead of typing notepad c:\test.ps1, I’ll from now on type c:\test.ps1.

To understand what happened, I first traced side by side my surprising command and the one I was supposed to run:

Trace-Command -Name * -Expression { C:\test.ps1 } -PSHost
Trace-Command -Name * -Expression { C:\test.ps1. } -PSHost

At the beginning, everything looks similar

But at some point, it starts doing something else (as you can see below).
It starts examining my PATHEXT environment variable.

To better highlight what I happened, I did:

Trace-Command -Name CommandDiscovery -Expression { C:\test.ps1 } -PSHost
Trace-Command -Name CommandDiscovery -Expression { C:\test.ps1. } -PSHost

In the first case, the CommandDiscovery correctly found that c:\test.ps1 is a script and invoked it.

In the second case, the CommandDiscovery found that c:\test.ps1. is an application and started looking for an application that can handle this extension.

If it finds a MRU list under HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\.ps1\OpenWithList (if you’ve ever double-clicked on .ps1 file), it will use it.

If it’s not the case, it starts looking at this registry key HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\.ps1\OpenWithProgids

and it should point to:

But when it doesn’t find the MRU list or the OpenWithProgids

It writes the OpenWithProgids value in the user hive and set it to Microsoft.PowerShellScript.1

(I’ll over simplify it) It then starts looking for any application listed under this key HKEY_LOCAL_MACHINE\SOFTWARE\RegisteredApplications to find out if .ps1 is listed in their “capabilities”

Then under the same key but under the user hive: HKEY_CURRENT_USER\Software\RegisteredApplications

It then reaches back again what’s in the machine hive to “open” what it detected as a “Microsoft.PowerShellScript.1”

and uses notepad