Public/Get-MSCatalogUpdate.ps1
function Get-MSCatalogUpdate { [CmdletBinding(DefaultParameterSetName = 'Search')] [OutputType([MSCatalogUpdate[]])] param ( #region Parameters [Parameter(Mandatory = $false, HelpMessage = "Filter updates by architecture")] [ValidateSet("All", "x64", "x86", "arm64")] [string] $Architecture = "All", [Parameter(Mandatory = $false, HelpMessage = "Sort in descending order")] [switch] $Descending, [Parameter(Mandatory = $false, HelpMessage = "Exclude .NET Framework updates")] [switch] $ExcludeFramework, [Parameter(Mandatory = $false, HelpMessage = "Filter updates from this date")] [DateTime] $FromDate, [Parameter(Mandatory = $false, HelpMessage = "Format for the results")] [ValidateSet("Default", "CSV", "JSON", "XML")] [string] $Format = "Default", [Parameter(Mandatory = $false, HelpMessage = "Only show .NET Framework updates")] [switch] $GetFramework, [Parameter(Mandatory = $false, HelpMessage = "Search through all available pages")] [switch] $AllPages, [Parameter(Mandatory = $false, HelpMessage = "Include dynamic updates")] [switch] $IncludeDynamic, [Parameter(Mandatory = $false, HelpMessage = "Include file names in the results")] [switch] $IncludeFileNames, [Parameter(Mandatory = $false, HelpMessage = "Include preview updates")] [switch] $IncludePreview, [Parameter(Mandatory = $false, HelpMessage = "Filter updates from the last N days")] [int] $LastDays, [Parameter(Mandatory = $false, HelpMessage = "Filter updates with maximum size")] [double] $MaxSize, [Parameter(Mandatory = $false, HelpMessage = "Filter updates with minimum size")] [double] $MinSize, [Parameter(Mandatory = $true, ParameterSetName = 'OS', HelpMessage = "Operating System to search updates for")] [ValidateSet("Windows 11", "Windows 10", "Windows Server")] [string] $OperatingSystem, [Parameter(Mandatory = $false, HelpMessage = "Select specific properties to display")] [string[]] $Properties, [Parameter(Mandatory = $true, ParameterSetName = 'Search', Position = 0, HelpMessage = "Search query for Microsoft Update Catalog")] [string] $Search, [Parameter(Mandatory = $false, HelpMessage = "Unit for size filtering (MB or GB)")] [ValidateSet("MB", "GB")] [string] $SizeUnit = "MB", [Parameter(Mandatory = $false, HelpMessage = "Sort results by specified field")] [ValidateSet("Date", "Size", "Title", "Classification", "Product")] [string] $SortBy = "Date", [Parameter(Mandatory = $false, HelpMessage = "Use strict search with exact phrase matching")] [switch] $Strict, [Parameter(Mandatory = $false, HelpMessage = "Filter updates until this date")] [DateTime] $ToDate, [Parameter(Mandatory = $false, HelpMessage = "Filter by update type")] [ValidateSet( "Security Updates", "Updates", "Critical Updates", "Feature Packs", "Service Packs", "Tools", "Update Rollups", "Cumulative Updates", "Security Quality Updates", "Driver Updates" )] [string[]] $UpdateType, [Parameter(Mandatory = $false, ParameterSetName = 'OS', HelpMessage = "OS Version/Release (e.g., 22H2, 21H2, 23H2)")] [string] $Version #endregion Parameters ) begin { #region Initialization # Ensure MSCatalogUpdate class is available if (-not ('MSCatalogUpdate' -as [type])) { $classPath = Join-Path $PSScriptRoot '..\Classes\MSCatalogUpdate.Class.ps1' if (Test-Path $classPath) { . $classPath } else { throw "MSCatalogUpdate class file not found at: $classPath" } } $ProgressPreference = "SilentlyContinue" $Updates = @() $MaxResults = 1000 #endregion Initialization #region Query Building # Build search query based on parameters $searchQuery = if ($PSCmdlet.ParameterSetName -eq 'OS') { switch ($OperatingSystem) { "Windows 10" { if ($Version) { if ($UpdateType -contains "Cumulative Updates") { "Cumulative Update for Windows 10 Version $Version" } else { "Windows 10 Version $Version" } } else { if ($UpdateType -contains "Cumulative Updates") { "Cumulative Update for Windows 10" } else { "Windows 10" } } } "Windows 11" { if ($Version) { if ($UpdateType -contains "Cumulative Updates") { "Cumulative Update for Windows 11 Version $Version" } else { "Windows 11 Version $Version" } } else { if ($UpdateType -contains "Cumulative Updates") { "Cumulative Update for Windows 11" } else { "Windows 11" } } } "Windows Server" { if ($Version) { if ($UpdateType -contains "Cumulative Updates") { "Cumulative Update for Microsoft Server Operating System, Version $Version" } else { "Microsoft Server Operating System, Version $Version" } } else { if ($UpdateType -contains "Cumulative Updates") { "Cumulative Update for Microsoft Server Operating System" } else { "Microsoft Server Operating System" } } } default { if ($Version) { if ($UpdateType -contains "Cumulative Updates") { "Cumulative Update for $OperatingSystem $Version" } else { "$OperatingSystem $Version" } } else { if ($UpdateType -contains "Cumulative Updates") { "Cumulative Update for $OperatingSystem" } else { "$OperatingSystem" } } } } } else { $Search } Write-Verbose "Search query: $searchQuery" #endregion Query Building } process { try { #region Search Preparation # Prepare search query $EncodedSearch = switch ($true) { $Strict { [uri]::EscapeDataString('"' + $searchQuery + '"') } $GetFramework { [uri]::EscapeDataString("*$searchQuery*") } default { [uri]::EscapeDataString($searchQuery) } } # Initialize catalog request $Uri = "https://www.catalog.update.microsoft.com/Search.aspx?q=$EncodedSearch" $Res = Invoke-CatalogRequest -Uri $Uri $Rows = $Res.Rows #endregion Search Preparation #region Pagination # Handle pagination if ($AllPages) { $PageCount = 0 while ($Res.NextPage -and $PageCount -lt 39) { # Microsoft Catalog limit is 40 pages $PageCount++ $PageUri = "$Uri&p=$PageCount" $Res = Invoke-CatalogRequest -Uri $PageUri $Rows += $Res.Rows } } #endregion Pagination #region Base Filtering # Apply base filters with improved logic $Rows = $Rows.Where({ $title = $_.SelectNodes("td")[1].InnerText.Trim() $classification = $_.SelectNodes("td")[3].InnerText.Trim() $include = $true # Basic exclusion filters if (-not $IncludeDynamic -and $title -like "*Dynamic*") { $include = $false } if (-not $IncludePreview -and $title -like "*Preview*") { $include = $false } # Framework filtering: handle GetFramework and ExcludeFramework parameters if ($GetFramework) { # If GetFramework is specified, only keep Framework updates if (-not ($title -like "*Framework*")) { $include = $false } } elseif ($ExcludeFramework) { # If ExcludeFramework is specified, exclude Framework updates if ($title -like "*Framework*") { $include = $false } } # OS and Version specific filtering if ($PSCmdlet.ParameterSetName -eq 'OS') { if ($OperatingSystem -eq "Windows Server") { # For Server, look for "Microsoft server" or similar patterns if (-not ($title -like "*Microsoft*Server*" -or $title -like "*Server Operating System*")) { $include = $false } } else { # For other OS types, use the standard pattern if (-not ($title -like "*$OperatingSystem*")) { $include = $false } } if ($Version -and -not ($title -like "*$Version*")) { $include = $false } } # Update type filtering if ($UpdateType) { $hasMatchingType = $false foreach ($type in $UpdateType) { switch ($type) { "Security Updates" { # In the Classification column if ($classification -eq "Security Updates") { $hasMatchingType = $true } } "Cumulative Updates" { # In the title, look for "Cumulative Update" if ($title -like "*Cumulative Update*") { $hasMatchingType = $true } } "Critical Updates" { # In the Classification column if ($classification -eq "Critical Updates") { $hasMatchingType = $true } } "Updates" { # In the Classification column if ($classification -eq "Updates") { $hasMatchingType = $true } } "Feature Packs" { # In the Classification column if ($classification -eq "Feature Packs") { $hasMatchingType = $true } } "Service Packs" { # In the Classification column if ($classification -eq "Service Packs") { $hasMatchingType = $true } } "Tools" { # In the Classification column if ($classification -eq "Tools") { $hasMatchingType = $true } } "Update Rollups" { # In the Classification column if ($classification -eq "Update Rollups") { $hasMatchingType = $true } } "Security Quality Updates" { # Combines security and quality if (($classification -eq "Security Updates") -and ($title -like "*Quality Update*")) { $hasMatchingType = $true } } "Driver Updates" { # For drivers if ($title -like "*Driver*") { $hasMatchingType = $true } } default { if ($title -like "*$type*") { $hasMatchingType = $true } } } if ($hasMatchingType) { break } } if (-not $hasMatchingType) { $include = $false } } $include }) #endregion Base Filtering #region Architecture Filtering # Apply architecture filter with improved logic if ($Architecture -ne "all") { $Rows = $Rows.Where({ $title = $_.SelectNodes("td")[1].InnerText.Trim() switch ($Architecture) { "x64" { $title -match "x64|64.?bit|64.?based" -and -not ($title -match "x86|32.?bit|arm64") } "x86" { $title -match "x86|32.?bit|32.?based" -and -not ($title -match "64.?bit|arm64") } "arm64" { $title -match "arm64|ARM.?based" } } }) } #endregion Architecture Filtering #region Create Update Objects # Create MSCatalogUpdate objects with improved error handling $Updates = $Rows.Where({ $_.Id -ne "headerRow" }).ForEach({ try { [MSCatalogUpdate]::new($_, $IncludeFileNames) } catch { Write-Warning "Failed to process update: $($_.Exception.Message)" $null } }) | Where-Object { $null -ne $_ } #endregion Create Update Objects #region Apply Filters # Apply date filters if ($FromDate) { $Updates = $Updates.Where({ $_.LastUpdated -ge $FromDate }) } if ($ToDate) { $Updates = $Updates.Where({ $_.LastUpdated -le $ToDate }) } if ($LastDays) { $CutoffDate = (Get-Date).AddDays(-$LastDays) $Updates = $Updates.Where({ $_.LastUpdated -ge $CutoffDate }) } # Apply size filters if ($MinSize -or $MaxSize) { $Multiplier = if ($SizeUnit -eq "GB") { 1024 } else { 1 } $Updates = $Updates.Where({ $size = [double]($_.Size -replace ' MB$','') $meetsMin = -not $MinSize -or $size -ge ($MinSize * $Multiplier) $meetsMax = -not $MaxSize -or $size -le ($MaxSize * $Multiplier) $meetsMin -and $meetsMax }) } #endregion Apply Filters #region Sorting and Output # Apply sorting $Updates = switch ($SortBy) { "Date" { $Updates | Sort-Object LastUpdated -Descending:$Descending } "Size" { $Updates | Sort-Object { [double]($_.Size -replace ' MB$','') } -Descending:$Descending } "Title" { $Updates | Sort-Object Title -Descending:$Descending } "Classification" { $Updates | Sort-Object Classification -Descending:$Descending } "Product" { $Updates | Sort-Object Products -Descending:$Descending } default { $Updates } } # Display result summary Write-Host "`nSearch completed for: $searchQuery" Write-Host "Found $($Updates.Count) updates" if ($Updates.Count -ge $MaxResults) { Write-Warning "Result limit of $MaxResults reached. Please refine your search criteria." } # Format and return results switch ($Format) { "Default" { if ($Properties) { $Updates | Select-Object $Properties } else { $Updates } } "CSV" { if ($Properties) { $Updates | Select-Object $Properties | ConvertTo-Csv -NoTypeInformation } else { $Updates | ConvertTo-Csv -NoTypeInformation } } "JSON" { if ($Properties) { $Updates | Select-Object $Properties | ConvertTo-Json } else { $Updates | ConvertTo-Json } } "XML" { if ($Properties) { $Updates | Select-Object $Properties | ConvertTo-Xml -As String } else { $Updates | ConvertTo-Xml -As String } } } #endregion Sorting and Output } catch { Write-Warning "Error processing search request: $($_.Exception.Message)" } } end { $ProgressPreference = "Continue" } } |