private/core-driverpack/Get-OSDCoreDriverPackCatalogSurface.ps1

function Get-OSDCoreDriverPackCatalogSurface {
    <#
    .SYNOPSIS
        Retrieves the Microsoft Surface driver pack catalog, enriching entries from live download pages.
 
    .DESCRIPTION
        Loads the bundled surface.json catalog as the offline base. For entries that include an
        UpdatePage URL, the function scrapes the corresponding Microsoft download page to find the
        newest available MSI and updates FileName, Url, and ReleaseDate accordingly.
        Results are cached in $env:TEMP so subsequent calls within the same session skip network
        requests. Falls back to base JSON values when a page cannot be reached.
 
    .PARAMETER LocalDriverPackCatalog
        Path to the local fallback Surface catalog JSON file.
 
    .PARAMETER Force
        Forces bypass of the temp cache and rebuilds the enriched catalog from the
        local Surface catalog.
 
    .PARAMETER LocalOnly
        Uses only local catalog values and skips connectivity probing and all live
        UpdatePage checks.
 
    .EXAMPLE
        Get-OSDCoreDriverPackCatalogSurface
 
        Returns all Surface driver pack entries, with live URLs where available.
 
    .EXAMPLE
        Get-OSDCoreDriverPackCatalogSurface -Verbose
 
        Returns all Surface driver pack entries with verbose progress output.
 
    .EXAMPLE
        Get-OSDCoreDriverPackCatalogSurface -Force
 
        Bypasses the temp cache and rebuilds the enriched catalog.
 
    .EXAMPLE
        Get-OSDCoreDriverPackCatalogSurface -LocalOnly
 
        Processes only local catalog values without any live network checks.
 
    .OUTPUTS
        PSCustomObject[]
        Objects with CatalogVersion, ReleaseDate, Name, Manufacturer, Model, SystemId, FileName,
        Url, OperatingSystem, OSArchitecture, and HashMD5 properties.
 
    .NOTES
        Base catalog: core/driverpacks/surface.json (bundled with the module)
        Temp cache: $env:TEMP\osdcloud-driverpack-surface.json
    #>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $false)]
        [ValidateNotNullOrEmpty()]
        [string]$LocalDriverPackCatalog = (Join-Path $($MyInvocation.MyCommand.Module.ModuleBase) 'core\driverpacks\surface.json'),

        [Parameter(Mandatory = $false)]
        [switch]$Force,

        [Parameter(Mandatory = $false)]
        [switch]$LocalOnly
    )

    begin {
        $Error.Clear()
        Write-Verbose "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] Start"
        #=================================================
        # Paths
        $tempCatalogPath  = "$env:TEMP\osdcloud-driverpack-surface.json"
        #=================================================
        # Load from temp cache if available
        $useCache = $false
        if ((-not $Force) -and (Test-Path $tempCatalogPath)) {
            Write-Host -ForegroundColor DarkGray "[$(Get-Date -format s)] Indexing $tempCatalogPath"
            $JsonCatalogContent = Get-Content -Path $tempCatalogPath -Raw -Encoding UTF8 | ConvertFrom-Json
            $useCache = $true
        }
        else {
            Write-Host -ForegroundColor DarkGray "[$(Get-Date -format s)] Indexing $LocalDriverPackCatalog"
            $JsonCatalogContent = Get-Content -Path $LocalDriverPackCatalog -Raw -Encoding UTF8 | ConvertFrom-Json
        }

        if (-not $JsonCatalogContent) {
            $errorRecord = [System.Management.Automation.ErrorRecord]::new(
                [System.Exception]::new("Failed to load Surface driver pack catalog"),
                'CatalogLoadFailed',
                [System.Management.Automation.ErrorCategory]::InvalidData,
                $LocalDriverPackCatalog
            )
            $PSCmdlet.ThrowTerminatingError($errorRecord)
        }
    }

    process {
        #=================================================
        # Return cached results immediately
        #=================================================
        if ($useCache) {
            Write-Verbose "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] Returning $($JsonCatalogContent.Count) entries from cache"
            $JsonCatalogContent
            return
        }

        #=================================================
        # Build enriched catalog from base JSON
        #=================================================
        $processStopwatch  = [System.Diagnostics.Stopwatch]::StartNew()
        $CatalogVersion    = Get-Date -Format yy.MM.dd
        $userAgent         = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36'
        $msiPattern        = 'https://download\.microsoft\.com/download/[^"''<>\s]+\.msi'
        $datePattern       = 'Date Published[^:]*:[^\d]*(\d{1,2}/\d{1,2}/\d{4})'
        $parallelThrottle  = 4
        $updatePageCache   = @{}
        $urlReachabilityCache = @{}
        $networkCalls      = 0
        $fallbackCalls     = 0
        $cacheHits         = 0
        $reachabilityChecks = 0
        $reachabilityMisses = 0
        $isOnline          = $true
        $skipLiveUpdates   = [bool]$LocalOnly

        Write-Verbose "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] Building Surface driver pack catalog (CatalogVersion: $CatalogVersion)"

        # If a device OSDProduct is known, limit live UpdatePage checks to the matching entry only
        $osdProduct = if ($global:OSDCoreDevice -and $global:OSDCoreDevice.OSDProduct) { $global:OSDCoreDevice.OSDProduct } else { $null }
        $escapedOsdProduct = if ($osdProduct) { [regex]::Escape($osdProduct) } else { $null }
        if ($osdProduct) {
            Write-Verbose "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] OSDProduct filter active: only fetching live data for SystemId matching '$osdProduct'"
        }

        # Determine online/offline state from the first catalog URL that does not require UpdatePage scraping.
        $connectivityProbeUrl = (
            $JsonCatalogContent |
                Where-Object { $_.Url -and (-not $_.UpdatePage) } |
                Select-Object -First 1
        ).Url

        if ($LocalOnly) {
            Write-Verbose "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] LocalOnly requested; skipping connectivity probe and live updates"
        }
        elseif ($connectivityProbeUrl) {
            try {
                $probe = Invoke-WebRequest -Uri $connectivityProbeUrl -Method Head -UseBasicParsing -UserAgent $userAgent -MaximumRedirection 5 -TimeoutSec 10 -ErrorAction Stop
                $isOnline = ($probe.StatusCode -ge 200 -and $probe.StatusCode -lt 400)
            }
            catch {
                try {
                    $probe = Invoke-WebRequest -Uri $connectivityProbeUrl -Method Get -UseBasicParsing -UserAgent $userAgent -MaximumRedirection 5 -TimeoutSec 10 -ErrorAction Stop
                    $isOnline = ($probe.StatusCode -ge 200 -and $probe.StatusCode -lt 400)
                }
                catch {
                    $isOnline = $false
                }
            }
        }

        if (-not $isOnline) {
            $skipLiveUpdates = $true
            Write-Verbose "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] Offline detected from connectivity probe; skipping all live updates and using local catalog values"
        }

        # Probe existing target URLs first; only scrape UpdatePage when the current URL is unreachable.
        $uniqueUpdatePages = @()
        if (-not $skipLiveUpdates) {
            $targetItems = @(
                $JsonCatalogContent | Where-Object {
                    $_.UpdatePage -and ((-not $escapedOsdProduct) -or ($_.SystemId -match $escapedOsdProduct))
                }
            )
            $itemsNeedingUpdate = @(
                foreach ($item in $targetItems) {
                    $targetUrl = $item.Url
                    $isReachable = $false

                    if (-not [string]::IsNullOrWhiteSpace($targetUrl)) {
                        if ($urlReachabilityCache.ContainsKey($targetUrl)) {
                            $isReachable = [bool]$urlReachabilityCache[$targetUrl]
                        }
                        else {
                            $reachabilityChecks++
                            try {
                                $probe = Invoke-WebRequest -Uri $targetUrl -Method Head -UseBasicParsing -UserAgent $userAgent -MaximumRedirection 5 -TimeoutSec 10 -ErrorAction Stop
                                $isReachable = ($probe.StatusCode -ge 200 -and $probe.StatusCode -lt 400)
                            }
                            catch {
                                try {
                                    $probe = Invoke-WebRequest -Uri $targetUrl -Method Get -UseBasicParsing -UserAgent $userAgent -MaximumRedirection 5 -TimeoutSec 10 -ErrorAction Stop
                                    $isReachable = ($probe.StatusCode -ge 200 -and $probe.StatusCode -lt 400)
                                }
                                catch {
                                    $isReachable = $false
                                }
                            }

                            $urlReachabilityCache[$targetUrl] = $isReachable
                        }
                    }

                    if (-not $isReachable) {
                        $reachabilityMisses++
                        $item
                    }
                }
            )

            $uniqueUpdatePages = @($itemsNeedingUpdate.UpdatePage | Sort-Object -Unique)
        }

        if ($uniqueUpdatePages.Count -gt 0) {
            Write-Verbose "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] Resolving $($uniqueUpdatePages.Count) unique UpdatePage URLs"

            if ($PSVersionTable.PSVersion.Major -ge 7) {
                Write-Verbose "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] Using parallel UpdatePage resolution (ThrottleLimit: $parallelThrottle)"
                $resolvedPages = $uniqueUpdatePages | ForEach-Object -Parallel {
                    $updatePage = $_
                    $result = [ordered]@{
                        UpdatePage   = $updatePage
                        Error        = $null
                        FileName     = $null
                        Url          = $null
                        ReleaseDate  = $null
                        UsedFallback = $false
                    }

                    try {
                        $response = Invoke-WebRequest -Uri $updatePage -UseBasicParsing -UserAgent $using:userAgent -MaximumRedirection 5 -ErrorAction Stop
                        $html = $response.Content

                        $allMsi = @(
                            [regex]::Matches($html, $using:msiPattern) |
                                ForEach-Object { $_.Value } |
                                Select-Object -Unique
                        )

                        if ($allMsi.Count -eq 0 -and $updatePage -match '[?&]id=(\d+)') {
                            $confirmUri = "https://www.microsoft.com/en-us/download/confirmation.aspx?id=$($Matches[1])"
                            $result.UsedFallback = $true
                            $response = Invoke-WebRequest -Uri $confirmUri -UseBasicParsing -UserAgent $using:userAgent -MaximumRedirection 5 -ErrorAction Stop
                            $html = $response.Content
                            $allMsi = @(
                                [regex]::Matches($html, $using:msiPattern) |
                                    ForEach-Object { $_.Value } |
                                    Select-Object -Unique
                            )
                        }

                        if ($allMsi.Count -gt 0) {
                            $win11Uris = @($allMsi | Where-Object { $_ -match 'Win11' })
                            $candidates = if ($win11Uris.Count -gt 0) { $win11Uris } else { $allMsi }
                            $bestUri = $candidates |
                                Sort-Object {
                                    if ($_ -match '_(\d{5})_') { [int]$Matches[1] } else { 0 }
                                } -Descending |
                                Select-Object -First 1

                            $newDate = $null
                            $now = [datetime]::UtcNow
                            foreach ($m in [regex]::Matches($html, $using:datePattern)) {
                                try {
                                    $parsed = [datetime]::ParseExact($m.Groups[1].Value, 'M/d/yyyy', $null)
                                    if ($parsed.Year -ge 2015 -and $parsed -le $now.AddMonths(3)) {
                                        $newDate = $parsed.ToString('yy.MM.dd')
                                        break
                                    }
                                }
                                catch { }
                            }

                            $result.FileName = $bestUri -replace '.+/', ''
                            $result.Url = $bestUri
                            $result.ReleaseDate = $newDate
                        }
                        else {
                            $result.Error = 'No MSI links found'
                        }
                    }
                    catch {
                        $result.Error = $_.Exception.Message
                    }

                    [PSCustomObject]$result
                } -ThrottleLimit $parallelThrottle

                foreach ($resolved in $resolvedPages) {
                    if (-not $resolved) { continue }
                    $networkCalls++
                    if ($resolved.UsedFallback) { $fallbackCalls++ }
                    $updatePageCache[$resolved.UpdatePage] = @{
                        Error       = $resolved.Error
                        FileName    = $resolved.FileName
                        Url         = $resolved.Url
                        ReleaseDate = $resolved.ReleaseDate
                    }
                }
            }
            else {
                Write-Verbose "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] PowerShell < 7 detected, using sequential UpdatePage resolution"
                foreach ($updatePage in $uniqueUpdatePages) {
                    Write-Host -ForegroundColor DarkGray "[$(Get-Date -format s)] Downloading $updatePage"
                    $networkCalls++
                    try {
                        $response = Invoke-WebRequest -Uri $updatePage -UseBasicParsing -UserAgent $userAgent -MaximumRedirection 5 -ErrorAction Stop
                        $html     = $response.Content

                        $allMsi = @(
                            [regex]::Matches($html, $msiPattern) |
                                ForEach-Object { $_.Value } |
                                Select-Object -Unique
                        )

                        if ($allMsi.Count -eq 0 -and $updatePage -match '[?&]id=(\d+)') {
                            $confirmUri = "https://www.microsoft.com/en-us/download/confirmation.aspx?id=$($Matches[1])"
                            Write-Host -ForegroundColor DarkGray "[$(Get-Date -format s)] Downloading $confirmUri"
                            $fallbackCalls++
                            $networkCalls++
                            $response = Invoke-WebRequest -Uri $confirmUri -UseBasicParsing -UserAgent $userAgent -MaximumRedirection 5 -ErrorAction Stop
                            $html     = $response.Content
                            $allMsi   = @(
                                [regex]::Matches($html, $msiPattern) |
                                    ForEach-Object { $_.Value } |
                                    Select-Object -Unique
                            )
                        }

                        if ($allMsi.Count -gt 0) {
                            $win11Uris = @($allMsi | Where-Object { $_ -match 'Win11' })
                            $candidates = if ($win11Uris.Count -gt 0) { $win11Uris } else { $allMsi }
                            $bestUri = $candidates |
                                Sort-Object {
                                    if ($_ -match '_(\d{5})_') { [int]$Matches[1] } else { 0 }
                                } -Descending |
                                Select-Object -First 1

                            $newDate = $null
                            $now     = [datetime]::UtcNow
                            foreach ($m in [regex]::Matches($html, $datePattern)) {
                                try {
                                    $parsed = [datetime]::ParseExact($m.Groups[1].Value, 'M/d/yyyy', $null)
                                    if ($parsed.Year -ge 2015 -and $parsed -le $now.AddMonths(3)) {
                                        $newDate = $parsed.ToString('yy.MM.dd')
                                        break
                                    }
                                }
                                catch { }
                            }

                            $updatePageCache[$updatePage] = @{
                                Error       = $null
                                FileName    = ($bestUri -replace '.+/', '')
                                Url         = $bestUri
                                ReleaseDate = $newDate
                            }
                        }
                        else {
                            $updatePageCache[$updatePage] = @{ Error = 'No MSI links found' }
                        }
                    }
                    catch {
                        $updatePageCache[$updatePage] = @{ Error = $_.Exception.Message }
                    }
                }
            }
        }

        $Results = foreach ($item in $JsonCatalogContent) {
            $releaseDate = $item.ReleaseDate
            $fileName    = $item.FileName
            $url         = $item.Url

            $isTargetDevice = (-not $escapedOsdProduct) -or ($item.SystemId -match $escapedOsdProduct)

            if ($item.UpdatePage -and $isTargetDevice) {
                $updatePage = $item.UpdatePage

                if ($updatePageCache.ContainsKey($updatePage)) {
                    $cacheHits++
                    $cached = $updatePageCache[$updatePage]
                    if (-not $cached.Error) {
                        $fileName    = $cached.FileName
                        $url         = $cached.Url
                        if ($cached.ReleaseDate) {
                            $releaseDate = $cached.ReleaseDate
                        }
                    }
                    Write-Verbose "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] Cache hit for $($item.Model) -> $fileName"
                }
                else {
                    Write-Verbose "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] No pre-resolved cache entry for $($item.Model), using base values"
                }
            }

            # Rebuild the Name bracket date with current releaseDate
            $baseName    = $item.Name -replace '\s*\[.*?\]$', ''
            $displayName = "$baseName [$releaseDate]"

            $objectProperties = [Ordered]@{
                CatalogVersion  = $CatalogVersion
                ReleaseDate     = $releaseDate
                Name            = $displayName
                Manufacturer    = $item.Manufacturer
                Model           = $item.Model
                SystemId        = $item.SystemId
                FileName        = $fileName
                Url             = $url
                OperatingSystem = $item.OperatingSystem
                OSArchitecture  = $item.OSArchitecture
                HashMD5         = $item.HashMD5
                UpdatePage      = $item.UpdatePage
            }
            [PSCustomObject]$objectProperties
        }

        #=================================================
        # Save enriched results to temp cache
        #=================================================
        Write-Verbose "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] Saving $($Results.Count) entries to $tempCatalogPath"
        $Results | ConvertTo-Json -Depth 10 | Out-File -FilePath $tempCatalogPath -Encoding utf8 -Force

        $processStopwatch.Stop()
        Write-Verbose "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] Performance: ReachabilityChecks=$reachabilityChecks, ReachabilityMisses=$reachabilityMisses, UpdatePages=$($uniqueUpdatePages.Count), CacheHits=$cacheHits, NetworkCalls=$networkCalls, FallbackCalls=$fallbackCalls, ElapsedMs=$($processStopwatch.ElapsedMilliseconds)"
        Write-Verbose "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] Found $($Results.Count) Surface driver packs"
        $Results
    }

    end {
        Write-Verbose "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] End"
    }
}