Finding the Real "Last Patched" Day (Interim Version)

Published: 2022-05-03
Last Updated: 2022-05-04 01:57:14 UTC
by Rob VandenBrink (Version: 1)
0 comment(s)

I've been using a PowerShell script since forever that enumerates the patch dates across an AD domain.  Yesterday I found a use case where it was broken.

I found a number of servers that received an update last month for Windows Update itself (KB5011570: Servicing stack update for Windows 10, version 1607 and Server 2016: March 8, 2022).  However, they did not install The April or in some cases even the March cumulative updates.  In the graphic below, you'll see that "Servicing stack update" KB5011570, first in the list, with February's Cumulative update (KB5010359) as SECOND.  If I'm measuring patch compliance for a client's domain, or looking for servers that have missed a patch cycle, it's that second patch I'm interested in.

However, my previous script happily enumerated the "last patch date" and found that April update of course, which should not have counted (given what I'm enumerating for)

So, how to fix this?  The easy answer is "look for the word "Cumulative" in the patch description.  It was at this point that I discovered that the get-hotfix and the gwmi methods of collecting patches run against the local machine, and the actual plain-language text description of the update is NOT kept locally, you need to make a web request using the KB number to get that!  These commands collect the KB number and call it a day.  The web request you woudl make from a browser (more on this later) to collect the "real" description looks like:$kbnum

This collects a full HTML page with a TON of information:

You can parse this until you reach sub-atomic particles, but in my script all I'm interested in is if the word "Cumulative" exists in the patch titles.  We find this in the "outertext" section of the HTML that's returned - which you get from:
$returntable = $WebResponse.ParsedHtml.body.getElementsByTagName("table") | Where {$_.className -match "resultsBorder"}
write-host $returntable.outertext   # only needed for debugging and illustration

Now add a quick check:
if ($returntable.outertext -like "*Cumulative*")

Before I make these calls, I sort the full patch list in descending order, so I can start from the most recent one, looking for the newest patch with that key word "Cumulative".  The final script is in my github at:

And yes, I will be creating a CIS Controls Version 8 set of scripts (sometime soon).

The final script, with these changes is:

$pcs = get-adcomputer -filter * -property Name,OperatingSystem,Operatingsystemversion,LastLogonDate,IPV4Address
$patchinfo = @()
$count = $pcs.count
foreach ($pc in $pcs) {
    # keep total progress count
    write-host "Host" $i "of" $count "is being checked"
    if (Test-Connection -ComputerName $pc.DNSHostName -count 2 -Quiet) {
        # echo the host being assessed (only live hosts hit this print)
        write-host $pc.dnshostname "is up, and is being assessed"
        $tempval = new-object psobject
        $hfs = get-hotfix -computername $pc.dnshostname | sort -descending InstalledOn
        # look only for the latest Cumulative update
        foreach ($hf in $hfs) {
                $kbnum = $hf.hotfixid
                $WebResponse = Invoke-WebRequest "$kbnum"
                $returntable = $WebResponse.ParsedHtml.body.getElementsByTagName("table") | Where {$_.className -match "resultsBorder"}
                # write-host $returntable.outertext    # no need to write this to the screen unless debugging
                if ($returntable.outertext -like "*Cumulative*")  {
                     $lasthf = $hf
        $tempval | add-member -membertype noteproperty -name Name -value $pc.dnshostname
        $tempval | add-member -membertype noteproperty -name PatchDate -value $lasthf.installedon
        $tempval | add-member -membertype noteproperty -name OperatingSystem -value $pc.OperatingSystem
        $tempval | add-member -membertype noteproperty -name OperatingSystemVersion -value $pc.OperatingSystemVersion
        $tempval | add-member -membertype noteproperty -name IpAddress -value $pc.IPV4Address
        $tempval | add-member -membertype noteproperty -name LastLogonDate -value $pc.LastLogonDate
        $patchinfo += $tempval
$patchinfo | export-csv -path ./patchdate.csv


This script is by no means done!  This is a quick-and-dirty "how can I get the info really quickly" script, since I needed it before planning a series of updates.  There's definitely an API for this, and Microsoft has also published a graphql approach (which is WAY too complicated for what I'm collecting)

I also found a nice module in PowershellGallery "PSWindowsUpdate", but it doesn't seem to work 100% yet - the get-wuhistory command hangs fairly consistently.  (  Once that's fixed though it'll be a good way to go!.  

The WindowsUpdateProvider module from Microsoft also looks great, but is native to W11 and S2019 - something version agnostic that doesn't need to be installed would be ideal.  Lots of us run scripts like this on customer servers, so you can't depend on a specific OS version, and installing additional tools that the client hasn't approved is also generally frowned upon  ..

What I'm really looking for is a a good, clear PowerShell call using a supported API for this, which doesn't require an third party module and isn't OS or PowerShell version-specific.  My google-fu isn't finding this today, so for now I have the approach above - but I certainly have not given up.  If anyone can point me at such a thing, I'll gladly update the script above and repost it when it's finished.  The method I've described above works, but will only work until some dev at Microsoft decides to change that Update Catalog results page.

Enjoy! (and a follow up to come soon!)

Rob VandenBrink

0 comment(s)

Some Honeypot Updates

Published: 2022-05-03
Last Updated: 2022-05-03 12:05:36 UTC
by Johannes Ullrich (Version: 1)
0 comment(s)

Yesterday I made live some updates to our honeypot. The honeypot has gained popularity, and users have adapted it to different platforms. We continue to work on it, and significant updates will hopefully be ready soon, but for now, I released an update fixing some bugs and, most of all, updated some of the instructions. Thanks to our undergraduate interns for helping with this.

1 - Cowrie Update

We use cowrie to collect telnet and ssh logs (thanks, Michel Oosterhof, for maintaining this great tool). To avoid breaking any dependencies with our end of the log processing, we include a bundled version of cowrie vs. just pulling the most current one from Github. 

2 - Updated Raspberry Pi Installation

The preferred way to install our honeypot is a Raspberry Pi. The Raspberry Pi project has released an "Imager" tool to streamline the initial install experience. The "Imager" tool allows you to create a customized image with ssh enabled, a custom username and password, and upload ssh keys. You first had to create an image and later log in to the Pi to make these adjustments in the past.

3 - Improved Ubuntu Version

Raspberry Pis are in short supply right now. Another popular alternative to installing the honeypot is to use a virtual machine running Ubuntu. Earlier versions of the honeypot did not install the firewall rules correctly, leading to missed logs. This has been fixed now. Note that the honeypot install will not preserve any firewall rules you may have had before installing the honeypot.

4 - Azure Install

Again thanks to our interns, the honeypot now works better with Azure. We always sort of supported AWS (it is just a VM, after all), but now, you may use Azure.

The update should apply automatically if you have automatic updates enabled. If not, you will receive an email reminding you to update manually in a few days. Please report any bugs via GitHub.


Johannes B. Ullrich, Ph.D. , Dean of Research,

0 comment(s)
ISC Stormcast For Tuesday, May 3rd, 2022


Diary Archives