Public/Functions/MicrosoftCatalog.ps1

function Get-MsUpCat {
    [CmdletBinding(DefaultParameterSetName = 'Search')]
    #[OutputType([MSCatalogUpdate[]])]
    #[OutputType([MsUpCat[]])]
    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 ('MsUpCat' -as [type])) {
            $classPath = Join-Path $PSScriptRoot '..\Classes\MsUpCat.Class.ps1'
            if (Test-Path $classPath) {
                . $classPath
            }
            else {
                throw "MsUpCat 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 {
                        [MsUpCat]::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 but Silent if $Update variable or piped is used Fixes#23
            $IsUpdate = ($MyInvocation.Line -match '^\s*\$update\s*=')
            $IsPiped = ($PSCmdlet.MyInvocation.PipelineLength -gt 1)

            if (-not $IsUpdate -and -not $IsPiped) {
                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"
    }
}

function Get-MsUpCatUpdate {
    [CmdLetBinding()]
    param (
        [ValidateSet('Windows 11','Windows 10','Windows Server','Windows Server 2016','Windows Server 2019','Windows Server 2022')]
        [Alias('OperatingSystem')]
        [string]$OS = 'Windows 11',

        [ValidateSet('x64','x86')]
        [Alias('Architecture')]
        [string]$Arch = 'x64',

        [ValidateSet('22H2','21H2','21H1','20H2',2004,1909,1903,1809,1803,1709,1703,1607,1511,1507)]
        [string]$Build = '22H2',

        [ValidateSet('LCU','SSU','DotNetCU')]
        [string]$Category = 'LCU',

        [System.Management.Automation.SwitchParameter]$Insider,

        [System.Management.Automation.SwitchParameter]$ListAvailable
    )
    #=================================================
    # MSCatalog PowerShell Module
    # Ryan-Jan
    # https://github.com/ryan-jan/MSCatalog
    # This excellent work is a good way to gather information from MS
    # Catalog
    #=================================================
    if (!(Get-Module -ListAvailable -Name MSCatalog)) {
        Install-Module MSCatalog -Force -SkipPublisherCheck
    }
    #=================================================
    # Make sure the Module was installed first
    #=================================================
    if (Test-MicrosoftUpdateCatalog) {
        if (Get-Module -ListAvailable -Name MSCatalog -ErrorAction Ignore) {
            #=================================================
            # Details
            #=================================================
            Write-Verbose -Verbose "OperatingSystem: $OS"
            Write-Verbose -Verbose "Architecture: $Arch"
            Write-Verbose -Verbose "Category: $Category"
            #=================================================
            # Build
            #=================================================
            if ($OS -eq 'Windows 10') {
                Write-Verbose -Verbose "Build: $Build"
                $SearchString = "$OS $Build $Arch"
            }
            elseif ($OS -eq 'Windows Server') {
                Write-Verbose -Verbose "Build: $Build"
                $SearchString = "$OS $Build $Arch"
            }
            else {
                $SearchString = "$OS $Arch"
            }
            #=================================================
            # Category
            #=================================================
            if ($Category -eq 'SSU') {
                $SearchString = "$SearchString Servicing Stack Update"
            }
            if ($Category -eq 'LCU') {
                $SearchString = "$SearchString Cumulative Update"
            }
            if ($Category -eq 'DotNetCU') {
                $SearchString = "$SearchString Framework"
            }
            Write-Verbose -Verbose "SearchString: $SearchString"
            #=================================================
            # Go
            #=================================================
            $CatalogUpdate = Get-MSCatalogUpdate -Search $SearchString -SortBy "Title" -AllPages -Descending |`
            Sort-Object LastUpdated -Descending |`
            Select-Object LastUpdated,Classification,Title,Size,Products,Guid
            #=================================================
            # Exclude
            #=================================================
            $CatalogUpdate = $CatalogUpdate | Where-Object {$_.Title -notmatch 'arm64'}
            #=================================================
            # OperatingSystem
            #=================================================
            if ($OS -eq 'Windows 10') {
                $CatalogUpdate = $CatalogUpdate | Where-Object {$_.Title -match 'Windows 10'}
                $CatalogUpdate = $CatalogUpdate | Where-Object {$_.Products -notmatch 'Windows Server'}
            }
            if ($OS -eq 'Windows Server') {
                $CatalogUpdate = $CatalogUpdate | Where-Object {$_.Products -eq 'Windows Server, version 1903 and later'}
            }
            if ($OS -eq 'Windows Server 2016') {
                $CatalogUpdate = $CatalogUpdate | Where-Object {$_.Products -eq 'Windows Server 2016'}
            }
            if ($OS -eq 'Windows Server 2019') {
                $CatalogUpdate = $CatalogUpdate | Where-Object {$_.Products -eq 'Windows Server 2019'}
            }
            #=================================================
            # Category
            #=================================================
            if ($Category -eq 'SSU') {
                #Do nothing
            }
            if ($Category -eq 'LCU') {
                $CatalogUpdate = $CatalogUpdate | Where-Object {$_.Title -notmatch '.NET'}
                $CatalogUpdate = $CatalogUpdate | Where-Object {$_.Title -notmatch 'Dynamic Cumulative Update'}
            }
            if ($Category -eq 'DotNetCU') {
                $CatalogUpdate = $CatalogUpdate | Where-Object {$_.Title -match "Framework"}
            }
            if ($Insider) {
                Write-Verbose -Verbose "Insider and Preview Updates: True"
            }
            else {
                Write-Verbose -Verbose "Insider and Preview Updates: False"
                $CatalogUpdate = $CatalogUpdate | Where-Object {$_.Title -notmatch 'Preview'}
                $CatalogUpdate = $CatalogUpdate | Where-Object {$_.Products -notmatch 'Insider'}
            }
            #=================================================
            # ListAvailable
            #=================================================
            if ($ListAvailable) {
                #Do Nothing
            }
            else {
                $CatalogUpdate = $CatalogUpdate | Select-Object -First 1
            }
            #=================================================
            Write-Output $CatalogUpdate
            #=================================================
        }
        else {
            Write-Host -ForegroundColor DarkGray "Save-MsUpCatUpdate: Could not install required PowerShell Module MSCatalog"
        }
    }
    else {
        Write-Host -ForegroundColor DarkGray "Save-MsUpCatUpdate: Could not reach https://www.catalog.update.microsoft.com/"
    }
    #=================================================
}
function Invoke-MSCatalogParseDate {
    param (
        [String] $DateString
    )

    $Array = $DateString.Split("/")
    Get-Date -Year $Array[2] -Month $Array[0] -Day $Array[1]
}
function Save-MsUpCatDriver {
    [CmdletBinding(DefaultParameterSetName = 'ByPNPClass')]
    param (
        [System.String]$DestinationDirectory,

        [Parameter(ParameterSetName = 'ByHardwareID')]
        [System.String[]]$HardwareID,

        [Parameter(ParameterSetName = 'ByPNPClass')]
        [ValidateSet('DiskDrive','Display','Net','SCSIAdapter','SecurityDevices','USB')]
        [System.String]$PNPClass
    )
    #=================================================
    if (!($DestinationDirectory)) {
        Write-Host -ForegroundColor DarkGray 'Set the DestinationDirectory parameter to download the Drivers'
    }
    else {
        if (!(Test-Path $DestinationDirectory)){
            New-Item -Path $DestinationDirectory -ItemType Directory -Force | Out-Null
        }
    }
    #Grab OSDCloud USB Flash Drive Info
    $OSDCloudUSB = Get-Volume.usb | Where-Object {($_.FileSystemLabel -match 'OSDCloud') -or ($_.FileSystemLabel -match 'BHIMAGE')} | Select-Object -First 1
    if ($OSDCloudUSB){
        $MSUpCatDriversOSDCloudUSBPath =  "$($OSDCloudUSB.DriveLetter):\OSDCloud\MsUpCatDrivers"
    }
    #=================================================
    # MSCatalog PowerShell Module
    # Ryan-Jan
    # https://github.com/ryan-jan/MSCatalog
    # This excellent work is a good way to gather information from MS
    # Catalog
    #=================================================
<# if (!(Get-Module -ListAvailable -Name MSCatalog)) {
        Install-Module MSCatalog -Force -SkipPublisherCheck -ErrorAction Ignore
    } #>

    #=================================================
    #$HardwareIDPattern = 'VEN_([0-9a-f]){4}&DEV_([0-9a-f]){4}&SUBSYS_([0-9a-f]){8}'
    $HardwareIDPattern = 'v[ei][dn]_([0-9a-f]){4}&[pd][ie][dv]_([0-9a-f]){4}'
    $SurfaceIDPattern = 'mshw0[0-1]([0-9]){2}'

    if (Test-MicrosoftUpdateCatalog) {
        #=================================================
        # ByPNPClass
        #=================================================
        if ($PSCmdlet.ParameterSetName -eq 'ByPNPClass') {
            $Params = @{
                ClassName = 'Win32_PnpEntity' 
                Property = 'Name','Description','DeviceID','HardwareID','ClassGuid','Manufacturer','PNPClass'
            }
            $Devices = Get-CimInstance @Params

            if ($Devices) {
                if ($PNPClass -match 'Display') {
                    $Devices = $Devices | Where-Object {($_.Name -match 'Video') -or ($_.PNPClass -match 'Display')}
                }
                elseif ($PNPClass -match 'Net') {
                    $Devices = $Devices | Where-Object {($_.Name -match 'Network') -or ($_.PNPClass -match 'Net')}
                }
                elseif ($PNPClass) {
                    $Devices = $Devices | Where-Object {$_.PNPClass -match $PNPClass}
                }
                else {
                    #All Devices
                }
            }

            if ($Devices) {
                if ($PNPClass) {
                    Write-Verbose "Devices were found for PNPClass $PNPClass"
                }
                
                foreach ($Item in $Devices) {
                    $FindHardwareID = $null
    
                    #See if DeviceID matches the pattern
                    # Write-Host -ForegroundColor DarkGray "DeviceID: $($Item.DeviceID)"
                    $FindHardwareID = $Item.DeviceID | Select-String -Pattern $HardwareIDPattern -AllMatches | Select-Object -ExpandProperty Matches | Select-Object -ExpandProperty Value

                    if (-not ($FindHardwareID)) {
                        if ($Item.HardwareID) {
                            Write-Verbose "HardwareID: $($Item.HardwareID[0])"
                            $FindHardwareID = $Item.HardwareID[0] | Select-String -Pattern $HardwareIDPattern -AllMatches | Select-Object -ExpandProperty Matches | Select-Object -ExpandProperty Value
                        }
                    }

                    if ($FindHardwareID) {
                        # Write-Verbose "Searching: $FindHardwareID"
                        $SearchString = "$FindHardwareID".Replace('&',"`%26")
                        try {
                            $WindowsUpdateDriver = Get-MsUpCat -Search "22H2+$PNPClass+$SearchString" -Descending | Select-Object LastUpdated,Title,Version,Size,Guid -First 1 -ErrorAction Ignore
                            
                            if (-not ($WindowsUpdateDriver)) {
                                $WindowsUpdateDriver = Get-MsUpCat -Search "21H2+$PNPClass+$SearchString" -Descending | Select-Object LastUpdated,Title,Version,Size,Guid -First 1 -ErrorAction Ignore
                            }
                            if (-not ($WindowsUpdateDriver)) {
                                $WindowsUpdateDriver = Get-MsUpCat -Search "Vibranium+$PNPClass+$SearchString" -Descending | Select-Object LastUpdated,Title,Version,Size,Guid -First 1 -ErrorAction Ignore
                            }
                            if (-not ($WindowsUpdateDriver)) {
                                $WindowsUpdateDriver = Get-MsUpCat -Search "1903+$PNPClass+$SearchString" -Descending | Select-Object LastUpdated,Title,Version,Size,Guid -First 1 -ErrorAction Ignore
                            }
                            if (-not ($WindowsUpdateDriver)) {
                                $WindowsUpdateDriver = Get-MsUpCat -Search "1809+$PNPClass+$SearchString" -Descending | Select-Object LastUpdated,Title,Version,Size,Guid -First 1 -ErrorAction Ignore
                            }
                            if (-not ($WindowsUpdateDriver)) {
                                $WindowsUpdateDriver = Get-MsUpCat -Search "$SearchString" -Descending | Select-Object LastUpdated,Title,Version,Size,Guid -First 1 -ErrorAction Ignore
                            }
        
                            if ($WindowsUpdateDriver.Guid) {
                                if ($Item.Name -and $Item.PNPClass) {
                                    Write-Host -ForegroundColor Cyan "$($Item.PNPClass) $($Item.Name)"
                                }
                                elseif ($Item.Name) {
                                    Write-Host -ForegroundColor Cyan "$($Item.Name)"
                                }
                                else {
                                    Write-Host -ForegroundColor Cyan $Item.DeviceID
                                }
                                Write-Host -ForegroundColor DarkGray "HardwareID: $FindHardwareID"
                                Write-Host -ForegroundColor DarkGray "SearchString: $SearchString"
            
                                Write-Host -ForegroundColor DarkGray "$($WindowsUpdateDriver.Title) version $($WindowsUpdateDriver.Version)"
                                Write-Host -ForegroundColor DarkGray "Version $($WindowsUpdateDriver.Version) Size: $($WindowsUpdateDriver.Size)"
                                Write-Host -ForegroundColor DarkGray "Last Updated $($WindowsUpdateDriver.LastUpdated)"
                                Write-Host -ForegroundColor DarkGray "UpdateID: $($WindowsUpdateDriver.Guid)"
            
                                if ($DestinationDirectory) {
                                    $DestinationPath = Join-Path $DestinationDirectory $WindowsUpdateDriver.Guid
                                    #If OSDCloud USB Attached, Check for Driver in Cache and Copy ro Local Cache
                                    if ($OSDCloudUSB){
                                        $USBCachePath = Join-Path $MSUpCatDriversOSDCloudUSBPath $WindowsUpdateDriver.Guid
                                        if (Test-Path $USBCachePath){
                                            Write-Host -ForegroundColor DarkGray "Driver already expanded at $USBCachePath, copying to $DestinationPath"
                                            Copy-Item -Path $USBCachePath -Destination $DestinationPath -Recurse -Force
                                        }
                                    }
                                    #Check if Driver is already Local Cache
                                    if (Test-Path $DestinationPath) {
                                        Write-Host -ForegroundColor DarkGray "Driver already expanded at $DestinationPath"
                                    }
                                    #Download if not already found in Local Cache
                                    else {
                                        Write-Host -ForegroundColor DarkGray "Downloading and expanding to $DestinationPath"
                                        $WindowsUpdateDriverFile = Save-UpdateCatalog -Guid $WindowsUpdateDriver.Guid -DestinationDirectory $DestinationPath
                                        if ($WindowsUpdateDriverFile) {
                                            expand.exe "$($WindowsUpdateDriverFile.FullName)" -F:* "$DestinationPath" | Out-Null
                                            Remove-Item $WindowsUpdateDriverFile.FullName | Out-Null
                                        }
                                        else {
                                            Write-Host -ForegroundColor DarkGray "Save-MsUpCatDriver: Could not find a Driver for this HardwareID"
                                        }
                                    }
                                }
                            }
                            else {
                                Write-Host -ForegroundColor DarkGray "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] No Results: $($Item.Name) $FindHardwareID"
                                #Write-Host -ForegroundColor DarkGray "HardwareID: $FindHardwareID"
                                #Write-Host -ForegroundColor DarkGray "SearchString: $SearchString"
                                #Write-Host -ForegroundColor DarkGray "Save-MsUpCatDriver: Could not find a Windows Update GUID"
                            }
                        }
                        catch{
                            Write-Host -ForegroundColor DarkGray "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] Unable to get Driver for Hardware component"
                        }   
                    }
                    else {
                        Write-Verbose "DeviceID: $($Item.DeviceID)"
                        #Write-Host -ForegroundColor DarkGray "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] No Results: $FindHardwareID"
                    }
                }
            }
        }
        #=================================================
        # ByHardwareID
        #=================================================
        if ($PSCmdlet.ParameterSetName -eq 'ByHardwareID') {
            foreach ($Item in $HardwareID) {
                Write-Verbose "Save-MsUpCatDriver: ByHardwareID"
                Write-Verbose $Item

                $WindowsUpdateDriver = $null

                #See if DeviceID matches the pattern
                $FindHardwareID = $Item | Select-String -Pattern $HardwareIDPattern | Select-Object -ExpandProperty Matches | Select-Object -ExpandProperty Value

                if (-not ($FindHardwareID)) {
                    $FindHardwareID = $Item | Select-String -Pattern $SurfaceIDPattern | Select-Object -ExpandProperty Matches | Select-Object -ExpandProperty Value
                }
    
                if ($FindHardwareID) {
                    $SearchString = "$FindHardwareID".Replace('&', "`%26")

                    try {
                        Write-Verbose "Save-MsUpCatDriver Search: 23H2 $SearchString"
                        $WindowsUpdateDriver = Get-MsUpCat -Search "23H2+$SearchString" -Descending | Select-Object LastUpdated, Title, Version, Size, Guid -First 1 -ErrorAction Ignore
                    }
                    catch {
                        <#Do this if a terminating exception happens#>
                    }
                    if (-not ($WindowsUpdateDriver)) {
                        try {
                            Write-Verbose "Save-MsUpCatDriver Search: 22H2 $SearchString"
                            $WindowsUpdateDriver = Get-MsUpCat -Search "22H2+$SearchString" -Descending | Select-Object LastUpdated, Title, Version, Size, Guid -First 1 -ErrorAction Ignore
                        }
                        catch {
                            <#Do this if a terminating exception happens#>
                        }
                    }
                    if (-not ($WindowsUpdateDriver)) {
                        try {
                            Write-Verbose "Save-MsUpCatDriver Search: 21H2+$SearchString"
                            $WindowsUpdateDriver = Get-MsUpCat -Search "21H2+$SearchString" -Descending | Select-Object LastUpdated, Title, Version, Size, Guid -First 1 -ErrorAction Ignore
                        }
                        catch {
                            <#Do this if a terminating exception happens#>
                        }
                    }
                    if (-not ($WindowsUpdateDriver)) {
                        try {
                            Write-Verbose "Save-MsUpCatDriver Search: Vibranium+$SearchString"
                            $WindowsUpdateDriver = Get-MsUpCat -Search "Vibranium+$SearchString" -Descending | Select-Object LastUpdated, Title, Version, Size, Guid -First 1 -ErrorAction Ignore
                        }
                        catch {
                            <#Do this if a terminating exception happens#>
                        }
                    }
                    if (-not ($WindowsUpdateDriver)) {
                        try {
                            Write-Verbose "Save-MsUpCatDriver Search: 1903+$SearchString"
                            $WindowsUpdateDriver = Get-MsUpCat -Search "1903+$SearchString" -Descending | Select-Object LastUpdated, Title, Version, Size, Guid -First 1 -ErrorAction Ignore
                        }
                        catch {
                            <#Do this if a terminating exception happens#>
                        }
                    }
                    if (-not ($WindowsUpdateDriver)) {
                        try {
                            Write-Verbose "Save-MsUpCatDriver Search: 1809+$SearchString"
                            $WindowsUpdateDriver = Get-MsUpCat -Search "1809+$SearchString" -Descending | Select-Object LastUpdated, Title, Version, Size, Guid -First 1 -ErrorAction Ignore
                        }
                        catch {
                            <#Do this if a terminating exception happens#>
                        }
                    }

                    if ($WindowsUpdateDriver.Guid) {
                        Write-Host -ForegroundColor Cyan "$Item $($WindowsUpdateDriver.Title)"
                        Write-Host -ForegroundColor DarkGray "UpdateID: $($WindowsUpdateDriver.Guid)"
                        Write-Host -ForegroundColor DarkGray "Size: $($WindowsUpdateDriver.Size) Last Updated $($WindowsUpdateDriver.LastUpdated)"

                        #Write-Host -ForegroundColor DarkGray "HardwareID: $FindHardwareID"
                        #Write-Host -ForegroundColor DarkGray "SearchString: $SearchString"
    
                        #Write-Host -ForegroundColor DarkGray "$($WindowsUpdateDriver.Title) version $($WindowsUpdateDriver.Version)"
                        #Write-Host -ForegroundColor DarkGray "Version $($WindowsUpdateDriver.Version) Size: $($WindowsUpdateDriver.Size)"
                        #Write-Host -ForegroundColor DarkGray "Last Updated $($WindowsUpdateDriver.LastUpdated)"
    
                        if ($DestinationDirectory) {
                            $DestinationPath = Join-Path $DestinationDirectory $WindowsUpdateDriver.Guid
                            #If OSDCloud USB Attached, Check for Driver in Cache and Copy ro Local Cache
                            if ($OSDCloudUSB){
                                $USBCachePath = Join-Path $MSUpCatDriversOSDCloudUSBPath $WindowsUpdateDriver.Guid
                                if (Test-Path $USBCachePath){
                                    Write-Host -ForegroundColor DarkGray "Driver already expanded at $USBCachePath, copying to $DestinationPath"
                                    Copy-Item -Path $USBCachePath -Destination $DestinationPath -Recurse -Force
                                }
                            }
                            if (Test-Path $DestinationPath) {
                                Write-Host -ForegroundColor DarkGray "Driver already expanded at $DestinationPath"
                            }
                            else {
                                Write-Host -ForegroundColor DarkGray "Downloading and expanding to $DestinationPath"
                                $WindowsUpdateDriverFile = Save-UpdateCatalog -Guid $WindowsUpdateDriver.Guid -DestinationDirectory $DestinationPath
                                if ($WindowsUpdateDriverFile) {
                                    expand.exe "$($WindowsUpdateDriverFile.FullName)" -F:* "$DestinationPath" | Out-Null
                                    Remove-Item $WindowsUpdateDriverFile.FullName | Out-Null
                                }
                                else {
                                    Write-Host -ForegroundColor DarkGray "Save-MsUpCatDriver: Could not find a Driver for this HardwareID"
                                }
                            }
                        }
                    }
                    else {
                        Write-Host -ForegroundColor DarkGray "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] No Results: $FindHardwareID"
                    }
                }
                else {
                    Write-Host -ForegroundColor DarkGray "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] No Results: $FindHardwareID"
                }
            }
        }
        #=================================================
        # Sync Back to OSDCloudUSB
        #=================================================
        if ($Global:OSDCloud.SyncMSUpCatDriverUSB -eq $true){
            if ($OSDCloudUSB){
                #Get Size of Cached Drivers in Local Drive Cache
                if (Test-Path $DestinationDirectory){
                    $MsUpCatDriverCacheSizeGB = (Get-ChildItem $DestinationDirectory -Recurse | Measure-Object -Property Length -Sum).Sum /1GB
                    #Get Free Space on OSDCloud USB Drive (with buffer)
                    $OSDCloudUSBFree = ($OSDCloudUSB.SizeRemainingGB - 5) #Free Space with 5GB Buffer
                    #If enough Free Space, cache files on Flash Drive
                    if ($MsUpCatDriverCacheSizeGB -lt $OSDCloudUSBFree){
                        $Source      = $DestinationDirectory #Yes this can seem confusing, remember Destination is where the Drivers were downloaded orginially
                        $Destination = $MSUpCatDriversOSDCloudUSBPath #OSDCloud Flash Drive cache folder

                        Write-Host -ForegroundColor Cyan "Syncing MS Update Catalog Drivers to OSDCloud USB Cache"
                        Write-Host -ForegroundColor DarkGray "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] Transfering $([Math]::Round($MsUpCatDriverCacheSizeGB,2)) GB of MS Update Drivers to $Destination"
                        Invoke-Exe robocopy $Source $Destination *.* /s /ndl /nfl /njh /njs
                    }
                    else {
                        Write-Host -ForegroundColor DarkGray "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] Not enough Free Space on OSDCloudUSB to sync drivers"
                        Write-Host -ForegroundColor DarkGray "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] Requires $([Math]::Round(($MsUpCatDriverCacheSizeGB + 5),2)) GB, but only $($OSDCloudUSB.SizeRemainingGB) GB available"
                    }
                }
            }
            else {
                # Write-Host -ForegroundColor DarkGray "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] OSDCloudUSB not detected to sync drivers back to, skipping sync"
            }
        }
    }
}
function Save-MsUpCatUpdate {
    [CmdLetBinding()]
    param (
        [ValidateSet('Windows 10','Windows Server','Windows Server 2016','Windows Server 2019')]
        [Alias('OperatingSystem')]
        [string]$OS = 'Windows 11',

        [ValidateSet('x64','x86')]
        [Alias('Architecture')]
        [string]$Arch = 'x64',

        [ValidateSet('22H2','21H2','21H1','20H2',2004,1909,1903,1809,1803,1709,1703,1607,1511,1507)]
        [string]$Build = '22H2',

        [ValidateSet('LCU','SSU','DotNetCU')]
        [string]$Category = 'LCU',

        [ValidateSet('Preview')]
        [string[]]$Include,

        [string]$DestinationDirectory = "$env:TEMP\MsUpCat",

        [System.Management.Automation.SwitchParameter]$Latest
    )
    #=================================================
    # MSCatalog PowerShell Module
    # Ryan-Jan
    # https://github.com/ryan-jan/MSCatalog
    # This excellent work is a good way to gather information from MS
    # Catalog
    #=================================================
    if (!(Get-Module -ListAvailable -Name MSCatalog)) {
        Install-Module MSCatalog -Force -SkipPublisherCheck
    }
    #=================================================
    # Make sure the Module was installed first
    #=================================================
    if (Test-MicrosoftUpdateCatalog) {
        if (Get-Module -ListAvailable -Name MSCatalog -ErrorAction Ignore) {
            #=================================================
            # Details
            #=================================================
            Write-Verbose -Verbose "OperatingSystem: $OS"
            Write-Verbose -Verbose "Architecture: $Arch"
            Write-Verbose -Verbose "Category: $Category"
            #=================================================
            # Category
            #=================================================
            if ($Category -eq 'LCU') {
                $SearchString = "Cumulative Update $OS"
            }
            if ($Category -eq 'SSU') {
                $SearchString = "Servicing Stack Update $OS"
            }
            if ($Category -eq 'DotNetCU') {
                $SearchString = "Framework $OS"
            }
            if ($OS -eq 'Windows 10') {
                Write-Verbose -Verbose "Build: $Build"
                $SearchString = "$SearchString $Build $Arch"
            }
            elseif ($OS -eq 'Windows Server') {
                Write-Verbose -Verbose "Build: $Build"
                $SearchString = "$SearchString $Build $Arch"
            }
            else {
                $SearchString = "$SearchString $Arch"
            }
            #=================================================
            # Go
            #=================================================
            $CatalogUpdate = Get-MSCatalogUpdate -Search $SearchString -SortBy "Title" -AllPages -Descending |`
            Sort-Object LastUpdated -Descending |`
            Select-Object LastUpdated,Classification,Title,Size,Products,Guid
            #=================================================
            # Exclude
            #=================================================
            $CatalogUpdate = $CatalogUpdate | Where-Object {$_.Title -notmatch 'arm64'}
            $CatalogUpdate = $CatalogUpdate | Where-Object {$_.Title -notmatch 'Dynamic'}
            #=================================================
            # OperatingSystem
            #=================================================
            if ($OS -eq 'Windows 10') {
                $CatalogUpdate = $CatalogUpdate | Where-Object {$_.Title -match 'Windows 10'}
                $CatalogUpdate = $CatalogUpdate | Where-Object {$_.Products -notmatch 'Windows Server'}
                if ($Category -eq 'LCU') {
                    #$CatalogUpdate = $CatalogUpdate | Where-Object {$_.Title -match "Cumulative Update for Windows 10 Version $Build"}
                }
                if ($Category -eq 'SSU') {
                    #$CatalogUpdate = $CatalogUpdate | Where-Object {$_.Title -match "Servicing Stack Update for Windows 10 Version $Build"}
                }
            }
            if ($OS -eq 'Windows Server') {
                $CatalogUpdate = $CatalogUpdate | Where-Object {$_.Products -eq 'Windows Server, version 1903 and later'}
            }
            if ($OS -eq 'Windows Server 2016') {
                $CatalogUpdate = $CatalogUpdate | Where-Object {$_.Products -eq 'Windows Server 2016'}
            }
            if ($OS -eq 'Windows Server 2019') {
                $CatalogUpdate = $CatalogUpdate | Where-Object {$_.Products -eq 'Windows Server 2019'}
            }
            #=================================================
            # Category
            #=================================================
            if ($Category -eq 'LCU') {
                $CatalogUpdate = $CatalogUpdate | Where-Object {$_.Title -notmatch '.NET'}
            }
            if ($Category -eq 'DotNetCU') {
                $CatalogUpdate = $CatalogUpdate | Where-Object {$_.Title -match "Framework"}
            }
            if ($Include -contains 'Preview') {
                Write-Verbose -Verbose "Include Preview Updates: True"
            }
            else {
                Write-Verbose -Verbose "Include Preview Updates: False"
                $CatalogUpdate = $CatalogUpdate | Where-Object {$_.Title -notmatch 'Preview'}
            }
            $CatalogUpdate = $CatalogUpdate | Where-Object {$_.Products -notmatch 'Insider'}
            #=================================================
            # Select
            #=================================================
            if ($Latest.IsPresent) {
                $CatalogUpdate = $CatalogUpdate | Select-Object -First 1
            }
            else {
                $CatalogUpdate = $CatalogUpdate | Out-GridView -Title 'Select a Microsoft Update to download' -PassThru
            }
            #=================================================
            # Download
            #=================================================
            foreach ($Update in $CatalogUpdate) {
                Save-UpdateCatalog -Guid $Update.Guid -DestinationDirectory $DestinationDirectory
            }
            #=================================================
        }
        else {
            Write-Host -ForegroundColor DarkGray "Save-MsUpCatUpdate: Could not install required PowerShell Module MSCatalog"
        }
    }
    else {
        Write-Host -ForegroundColor DarkGray "Save-MsUpCatUpdate: Could not reach https://www.catalog.update.microsoft.com/"
    }
    #=================================================
}