In my previous post, I covered how I fixed the OS Update Report script that was incorrectly reporting KB numbers for some servers. The journey didn't end there - I also created a dedicated tool to retrieve and categorize updates from any Windows server. Let me walk you through developing this specialized hotfix reporting tool.
The Need for a Dedicated Hotfix Reporter
While fixing my domain controller update script, I realized I often needed to examine detailed update information for troubleshooting purposes. The standard Get-HotFix
cmdlet provides basic information, but lacks context about update types and detailed descriptions.
I needed a tool that could:
- Target any specific server
- Retrieve the most recent installed updates
- Categorize updates by type (Cumulative, Servicing Stack, etc.)
- Provide enhanced descriptions of each update
- Generate a summary report
Building the Hotfix Reporting Tool
I developed a PowerShell script that uses the same web-lookup strategy from my previous fix, but applied it to a standalone tool. Here's the core structure:
param(
[Parameter(Mandatory=$true)]
[string]$Server,
[string]$ProxyServer = "proxy.bear.local:3129",
[int]$HotfixCount = 15,
[string]$OutputFile = ""
)
This parameter structure allows me to specify any server, with options for proxy configuration, the number of hotfixes to retrieve, and an optional output file for the report.
The Execution Flow
The main execution follows this pattern:
# Main script execution
Write-Host "Retrieving hotfix information from $Server..." -ForegroundColor Green
try {
# Get OS info
$OS = Get-WmiObject -Class Win32_OperatingSystem -ComputerName $Server -ErrorAction Stop
$OSVersion = $OS.Caption
$OSBuild = $OS.Version + " (Build " + $OS.BuildNumber + ")"
# Get hotfixes
$hotfixes = Get-HotFix -ComputerName $Server -ErrorAction Stop |
Sort-Object -Property InstalledOn -Descending |
Select-Object -First $HotfixCount
# Process each hotfix
foreach ($hotfix in $hotfixes) {
# Get enhanced description and determine update type
$enhancedDescription = Get-KBDescription -KBNumber $hotfix.HotFixID
-OSVersion $OSVersion -InstalledOn $hotfix.InstalledOn
$updateType = Get-UpdateType -KBNumber $hotfix.HotFixID -Description
$hotfix.Description
# Add to results
$results += [PSCustomObject]@{
KBNumber = $hotfix.HotFixID
InstalledOn = $hotfix.InstalledOn
UpdateType = $updateType
EnhancedDescription = $enhancedDescription
}
}
# Display and export results
}
catch {
Write-Host "Error: $($_.Exception.Message)" -ForegroundColor Red
}
The Challenge of Update Type Identification
One of the most important aspects of the script was accurately identifying update types. Windows Server updates come in various categories, each serving different purposes:
function Get-UpdateType {
param(
[string]$KBNumber,
[string]$Description,
[string]$EnhancedDescription
)
# Service Stack Updates
if ($KBNumber -eq "KB5054007" -or $KBNumber -eq "KB5054006" -or
$Description -like "*Servicing Stack*" -or $EnhancedDescription -like
"*Servicing Stack*") {
return "Servicing Stack Update"
}
# Cumulative Updates
if ($EnhancedDescription -like "*Cumulative Update*" -or
$Description -like "*Cumulative Update*" -or
($KBNumber -like "*5055*" -and $KBNumber -notlike "*5054*")) {
return "Cumulative Update"
}
# Quality Rollups
if ($EnhancedDescription -like "*Quality Rollup*" -or
$Description -like "*Monthly Quality*") {
return "Monthly Rollup"
}
# .NET Framework Updates
if ($Description -like "*.NET Framework*" -or $EnhancedDescription
-like "*.NET Framework*") {
return ".NET Framework Update"
}
# Default case - Security Update
return "Security Update"
}
This function examines both the KB number and description to determine the update type, allowing me to properly categorize updates in the report.
Web-Based Description Enhancement
The magic behind getting better update descriptions comes from querying Microsoft's support site:
function Get-KBDescription {
param(
[string]$KBNumber,
[string]$OSVersion,
[DateTime]$InstalledOn,
[string]$ProxyServer
)
try {
# Format month and year from install date
$month = $InstalledOn.Month
$year = $InstalledOn.Year
$formattedMonth = $month.ToString("00")
# Configure web client
$webClient = New-Object System.Net.WebClient
if ($ProxyServer) {
$webProxy = New-Object System.Net.WebProxy($ProxyServer)
$webClient.Proxy = $webProxy
}
# Search for the KB on Microsoft's site
$encodedQuery = [System.Web.HttpUtility]::UrlEncode("$KBNumber $OSVersion")
$url = "https://support.microsoft.com/en-us/search/results?query=$encodedQuery"
$html = $webClient.DownloadString($url)
# Process results and extract description
# (pattern matching code omitted for brevity)
# If no match found, create our own description based on patterns
$shortOSName = $OSVersion -replace "Microsoft ", ""
-replace " Standard", "" -replace " Datacenter", ""
if ($OSVersion -like "*Windows Server 2016*") {
if ($KBNumber -like "*5055*") {
return "$year-$formattedMonth Cumulative Update for Windows
Server 2016 for x64-based Systems ($KBNumber)"
}
# Additional pattern matches (not shown here)
}
# Default fallback
return "$year-$formattedMonth Security Update for $shortOSName ($KBNumber)"
}
catch {
Write-Host "Error fetching KB description: $($_.Exception.Message)"
-ForegroundColor Yellow
return "Security Update for Windows ($KBNumber)"
}
}
This function handles a key challenge: if the web lookup fails, it still generates a reasonable description based on the KB number pattern and installation date.
Handling Update Format Variations
A particular challenge emerged when I found that update descriptions varied by Windows Server version:
- Windows Server 2016/2019: "2025-04 Cumulative Update for Windows Server 2016 for x64-based Systems (KB5055521)"
- Windows Server 2012 R2: "2025-03 Security Monthly Quality Rollup for Windows Server 2012 R2 (KB5053887)"
My script needed to handle these format differences. For example, in the Get-KBDescription
function, I included specific formatting logic:
# Windows Server 2016 format
if ($OSVersion -like "*Windows Server 2016*") {
if ($KBNumber -like "*5055*") {
return "$year-$formattedMonth Cumulative Update for Windows Server 2016
for x64-based Systems ($KBNumber)"
}
// Other patterns... (not shown here)
}
# Windows Server 2012 R2 format
if ($OSVersion -like "*Windows Server 2012 R2*") {
if ($KBNumber -like "*5055*") {
return "$year-$formattedMonth Security Monthly Quality Rollup for
Windows Server 2012 R2 ($KBNumber)"
}
// Other patterns (not shown here)
}
Visual Output and Summary Statistics
For effective reporting, I added color-coded output and summary statistics:
# Display results in table format
$results | Format-Table -Property KBNumber, InstalledOn, UpdateType,
EnhancedDescription -AutoSize
# Output summary of update types
$updateTypes = $results | Group-Object -Property UpdateType
Write-Host "`n======= Update Type Summary =======" -ForegroundColor Yellow
foreach ($type in $updateTypes) {
Write-Host "$($type.Name): $($type.Count)" -ForegroundColor Yellow
}
This produces an easy-to-read report with a breakdown of update types:
======= Hotfix Report for grizzlybwar.bear.local =======
OS Version: Microsoft Windows Server 2019 Datacenter
OS Build: 10.0.17763 (Build 17763)
Total Hotfixes: 15
=======================================
KBNumber InstalledOn UpdateType EnhancedDescription
-------- ----------- ---------- -------------------
KB5054007 3/14/2025 12:00:00 AM Servicing Stack 2025-03 Servicing Stack Update
for Windows Server 2019 (KB5054007)
KB5052000 2/15/2025 12:00:00 AM Cumulative Update 2025-02 Cumulative Update for
Windows Server 2019 (KB5052000)
KB5050110 1/27/2025 12:00:00 AM Security Update 2025-01 Security Update for Windows
Server 2019 (KB5050110)
======= Update Type Summary =======
Servicing Stack Update: 1
Cumulative Update: 5
Security Update: 9
================================
CSV Export Capability
For archiving and documentation purposes, I added CSV export functionality:
# Export to CSV if OutputFile is specified
if ($OutputFile -ne "") {
$results | Export-Csv -Path $OutputFile -NoTypeInformation
Write-Host "Results exported to $OutputFile" -ForegroundColor Green
}
Challenges and Lessons Learned
Developing this tool taught me several important lessons:
- KB Patterns Are Complex: Microsoft's KB numbering scheme follows patterns, but they're not always consistent. KB5054007 is a Servicing Stack Update, while KB5055521 is a Cumulative Update, despite the similar numbering.
- Network Operations Need Fallbacks: When querying external websites, always have a fallback mechanism. My script uses pattern-based naming when web queries fail.
- Windows Server Version Differences Matter: Different Windows Server versions use different update naming conventions. Windows Server 2016/2019 follow one pattern, while Windows Server 2012 R2 follows another.
- Error Handling Is Crucial: The script includes comprehensive error handling to ensure it doesn't crash when encountering connectivity issues or unexpected server responses.
- Troubleshooting update-related issues
- Documenting server state for compliance
- Verifying that critical updates are installed
- Understanding the update history of problematic servers
This tool complements my fixed DC OS Update Report script, which still handles the broader task of reporting on all domain controllers in my environment. Together, they provide a comprehensive view of my Windows update landscape.