Here is something I built from scratch. The idea was I wanted a way to query the latest MS updates for server patching.
One of the caveats I ran into is that some updates are classified differently, so I created an array of URL with different searched.
In my source below I search for the latest server updates, Office 2016 & office 2019 updates, MS Edge Updates, and Microsoft Defender Updates.
The script also checks all pages to grab all results; however there is a limitation of 1000 results per search string.
For my needs the code worked well and I was able to validate these updates against the list I have from SCCM.
CLS
Clear-Variable AllLines -Force -Confirm:$False -ErrorAction SilentlyContinue
$URLS = @"
https://www.catalog.update.microsoft.com/Search.aspx?q=$(Get-Date -Format "yyyy-MM")
https://www.catalog.update.microsoft.com/Search.aspx?q=office%202016
https://www.catalog.update.microsoft.com/Search.aspx?q=office%202019
https://www.catalog.update.microsoft.com/Search.aspx?q=edge
https://www.catalog.update.microsoft.com/Search.aspx?q=Microsoft%20Defender%20Antivirus
"@.Split("`n").Trim()
#Global Variables
#$URL = "https://www.catalog.update.microsoft.com/Search.aspx?q=$(Get-Date -Format "yyyy-MM")"
#$URL = "https://www.catalog.update.microsoft.com/Search.aspx?q=2025"
$allUpdates = @()
$AllLines = @()
foreach($URL in $URLS)
{
#Reset page count for each URL
$pageNumber = 0
Do
{
#Clear Variables
Clear-Variable response, MainContent, nextPageLink -Force -Confirm:$False -ErrorAction SilentlyContinue
$response = Invoke-WebRequest -Uri "$url&p=$pageNumber"
$MainContent = $response.ParsedHtml.getElementById('ctl00_catalogBody_updateMatches') | Select-Object -ExpandProperty innerHTML
$AllLines += $MainContent | ForEach-Object {
$lines = $_ -split "`n"
foreach ($line in $lines)
{
$line
}
}
# Check for next page
$nextPageLink = $response.ParsedHtml.getElementById('ctl00_catalogBody_nextPageLink')
$hasNextPage = $nextPageLink -ne $null
if ($hasNextPage)
{
$pageNumber++
}
#If we have reached the last page process the data
if($NULL -eq $nextPageLink)
{
#Make sure AllLines isn't NULL
if($AllLines)
{
#Grab all the table rows with the data we want
foreach($TR in ((Select-String -InputObject $AllLines -Pattern '<TR id=[a-zA-Z0-9-]+_R\d+.*?>.*?<\/TR>' -AllMatches).Matches).Value)
{
#Clear Variables each iteration
Clear-Variable C1, C2, C3, C4, C5, C6 -Force -Confirm:$False -ErrorAction SilentlyContinue
$C1 = ([regex]::Match($TR, '<TD id=[a-zA-Z0-9-]+_C1_R\d+[^>]*><A[^>]*>(.*?)<\/A><\/TD>', [System.Text.RegularExpressions.RegexOptions]::Singleline)).Groups[1].Value.Trim()
$C2 = ([regex]::Match($TR, '<TD id=[a-zA-Z0-9-]+_C2_R\d+[^>]*>(.*?)<\/TD>', [System.Text.RegularExpressions.RegexOptions]::Singleline)).Groups[1].Value.Trim()
$C3 = ([regex]::Match($TR, '<TD id=[a-zA-Z0-9-]+_C3_R\d+[^>]*>(.*?)<\/TD>', [System.Text.RegularExpressions.RegexOptions]::Singleline)).Groups[1].Value.Trim()
$C4 = ([regex]::Match($TR, '<TD id=[a-zA-Z0-9-]+_C4_R\d+[^>]*>(.*?)<\/TD>', [System.Text.RegularExpressions.RegexOptions]::Singleline)).Groups[1].Value.Trim()
$C5 = ([regex]::Match($TR, '<TD id=[a-zA-Z0-9-]+_C5_R\d+[^>]*>(.*?)<\/TD>', [System.Text.RegularExpressions.RegexOptions]::Singleline)).Groups[1].Value.Trim()
$C6 = ([regex]::Match($TR, '<SPAN[^>]*>(.*?)<\/SPAN>')).Groups[1].Value.Trim()
#Calculate date
$Date = (Get-Date -Format "MM")
if($Date -lt 10)
{
$Month = $Date.Replace("0","")
}
#Skip Drivers and only get Newest Updates, Skip win10 & 11
if(($C3 -notlike "*Driver*") -AND ($C4 -like "*$($($Month)+"/*/"+$(Get-Date -Format "yyyy"))*") -AND ($C2 -notlike "*Windows 10*") -AND ($C2 -notlike "*Windows 11*"))
{
$allUpdates += [PSCustomObject] @{
Title = $C1
Products = $C2
Classification = $C3
LastUpdated = $C4
Version = $C5
Size = $C6
}
}
}
}
}
}while($hasNextPage)
}
$allUpdates