Public/Get-MSCatalogUpdate.ps1

function Get-MSCatalogUpdate {
    <#
        .SYNOPSIS
        Query catalog.update.micrsosoft.com for available updates.

        .DESCRIPTION
        Given that there is currently no public API available for the catalog.update.micrsosoft.com site, this
        command makes HTTP requests to the site and parses the returned HTML for the required data.

        .PARAMETER Search
        Specify a string to search for.

        .PARAMETER Strict
        Force a Search paramater with multiple words to be treated as a single string.

        .PARAMETER AllPages
        By default this command returns the first page of results from catalog.update.micrsosoft.com, which is
        the latest 25 updates matching the search term. If you specify this switch the command will instead
        return all pages of results. This can result in a significant increase in the number of HTTP requests
        to the catalog.update.micrsosoft.com endpoint.

        .EXAMPLE
        Get-MSCatalogUpdate -Search "Cumulative for Windows Server, version 1903"

        .EXAMPLE
        Get-MSCatalogUpdate -Search "Cumulative for Windows Server, version 1903" -AllPages
    #>

    
    [CmdLetBinding()]
    param (
        [Parameter(
            Mandatory = $true,
            Position = 0
        )]
        [String] $Search,

        [Parameter(
            Mandatory = $false,
            Position = 1
        )]
        [Switch] $Strict,

        [Parameter(
            Mandatory = $false,
            Position = 2
        )]
        [Switch] $AllPages,

        [Parameter(DontShow)]
        [String] $Method = "Get",

        [Parameter(DontShow)]
        [String] $EventArgument,

        [Parameter(DontShow)]
        [String] $EventTarget,

        [Parameter(DontShow)]
        [String] $EventValidation,

        [Parameter(DontShow)]
        [String] $ViewState,

        [Parameter(DontShow)]
        [String] $ViewStateGenerator
    )

    try {

        if ($Method -eq "Post") {
            $ReqBody = @{
                "__EVENTARGUMENT" = $EventArgument
                "__EVENTTARGET" = $EventTarget
                "__EVENTVALIDATION" = $EventValidation
                "__VIEWSTATE" = $ViewState
                "__VIEWSTATEGENERATOR" = $ViewStateGenerator
            }
        }
        $UriSearch = [Uri]::EscapeUriString($Search)
        $Params = @{
            Uri = "https://www.catalog.update.microsoft.com/Search.aspx?q=$UriSearch"
            Method = $Method
            Body = $ReqBody
            ContentType = "application/x-www-form-urlencoded"
            UseBasicParsing = $true
        }
        $Results = Invoke-WebRequest @Params

        $HtmlDoc = [HtmlAgilityPack.HtmlDocument]::new()
        $HtmlDoc.LoadHtml($Results.RawContent.ToString())
        $NextPage = $HtmlDoc.GetElementbyId("ctl00_catalogBody_nextPage")
        $EventArgument = $HtmlDoc.GetElementbyId("__EVENTARGUMENT")[0].Attributes["value"].Value
        $EventValidation = $HtmlDoc.GetElementbyId("__EVENTVALIDATION")[0].Attributes["value"].Value
        $ViewState = $HtmlDoc.GetElementbyId("__VIEWSTATE")[0].Attributes["value"].Value
        $ViewStateGenerator = $HtmlDoc.GetElementbyId("__VIEWSTATEGENERATOR")[0].Attributes["value"].Value
        $Table = $HtmlDoc.GetElementbyId("ctl00_catalogBody_updateMatches")
        $Rows = $Table.SelectNodes("tr")

        $Output = foreach ($Row in $Rows[1..($Rows.Count - 1)]) {
            $Cells = $Row.SelectNodes("td")
            $Title = $Cells[1].innerText.Trim()
            if ($Strict) {
                if ($Title -like "*$Search*") {
                    [PSCustomObject] @{
                        PSTypeName = "MSCatalogUpdate"
                        Title = $Title
                        Products = $Cells[2].innerText.Trim()
                        Classification = $Cells[3].innerText.Trim()
                        LastUpdated = (Invoke-ParseDate -DateString $Cells[4].innerText.Trim())
                        Version = $Cells[5].innerText.Trim()
                        Size = $Cells[6].SelectNodes("span")[0].InnerText
                        SizeInBytes = [Int] $Cells[6].SelectNodes("span")[1].InnerText 
                        Guid = $Cells[7].SelectNodes("input")[0].Id
                    }
                }
            } else {
                [PSCustomObject] @{
                    PSTypeName = "MSCatalogUpdate"
                    Title = $Title
                    Products = $Cells[2].innerText.Trim()
                    Classification = $Cells[3].innerText.Trim()
                    LastUpdated = (Invoke-ParseDate -DateString $Cells[4].innerText.Trim())
                    Version = $Cells[5].innerText.Trim()
                    Size = $Cells[6].SelectNodes("span")[0].InnerText
                    SizeInBytes = [Int] $Cells[6].SelectNodes("span")[1].InnerText 
                    Guid = $Cells[7].SelectNodes("input")[0].Id
                }
            }
        }
        $Output | Sort-Object -Property LastUpdated -Descending

        # If $NextPage is $null then there are more pages to collect.
        if ($null -eq $NextPage) {
            if ($AllPages) {
                $NextParams = @{
                    Search = $Search
                    AllPages = $true
                    EventArgument = $EventArgument
                    EventTarget = 'ctl00$catalogBody$nextPageLinkText'
                    EventValidation = $EventValidation
                    ViewState = $ViewState
                    ViewStateGenerator = $ViewStateGenerator
                    Method = "Post"
                }
                Get-MSCatalogUpdate @NextParams
            }
        }
    } catch {
        $NoResults = $HtmlDoc.GetElementbyId("ctl00_catalogBody_noResultText")
        if (-not ($null -eq $NoResults)) {
            Write-Warning "$($NoResults.InnerText)'$Search'"
        } else {
            throw $_
        }
    }
}