private/WinPEDrivers/CloudWinPEDriver/Invoke-CloudWinPEDriverDownload.ps1

#Requires -PSEdition Core

function Invoke-CloudWinPEDriverDownload {
    <#
    .SYNOPSIS
        Downloads a WinPE driver file using curl.exe with SHA256 verification.
 
    .DESCRIPTION
        Internal helper used by all per-driver Save-CloudWinPEDriver* functions.
        Uses curl.exe for reliable binary downloads. Skips the download when the
        file already exists and the SHA256 checksum matches. Throws a terminating
        error on download failure or checksum mismatch so callers can catch cleanly.
 
    .PARAMETER Uri
        The download URI.
 
    .PARAMETER DestinationPath
        Full path (including filename) where the file will be saved.
 
    .PARAMETER SearchUri
        Optional referer URI sent with the curl request.
 
    .PARAMETER ExpectedSHA256
        Expected SHA256 hash. When provided and the file already exists with a
        matching hash, the download is skipped entirely.
 
    .PARAMETER Force
        Re-download even if the file already exists and the hash matches.
 
    .OUTPUTS
        [System.IO.FileInfo] The downloaded (or already-cached) file.
    #>

    [CmdletBinding()]
    [OutputType([System.IO.FileInfo])]
    param (
        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [string]$Uri,

        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [string]$DestinationPath,

        [Parameter()]
        [string]$SearchUri,

        [Parameter()]
        [string]$ExpectedSHA256,

        [Parameter()]
        [switch]$Force
    )

    $downloadDir = Split-Path -Path $DestinationPath -Parent
    $destinationLeaf = Split-Path -Path $DestinationPath -Leaf
    $normalizedExpectedSHA256 = $null
    if (-not (Test-Path -Path $downloadDir)) {
        New-Item -ItemType Directory -Path $downloadDir -Force | Out-Null
    }

    if (-not [string]::IsNullOrWhiteSpace($ExpectedSHA256)) {
        $normalizedExpectedSHA256 = ($ExpectedSHA256 -replace '\s+', '').ToUpperInvariant()
    }

    $getVerifiedCachedFile = {
        param([string]$Path)

        if (-not (Test-Path -Path $Path -PathType Leaf)) {
            return $null
        }

        if (-not $normalizedExpectedSHA256) {
            return Get-Item -Path $Path
        }

        $actualHash = (Get-FileHash -Path $Path -Algorithm SHA256).Hash.ToUpperInvariant()
        if ($actualHash -eq $normalizedExpectedSHA256) {
            return Get-Item -Path $Path
        }

        return $null
    }

    # Skip if file already exists and hash matches
    if (-not $Force) {
        $verifiedCachedFile = & $getVerifiedCachedFile $DestinationPath
        if ($verifiedCachedFile) {
            if ($normalizedExpectedSHA256) {
                Write-Verbose "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] '$destinationLeaf' already downloaded and SHA256 verified. Skipping."
            }
            else {
                Write-Verbose "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] '$destinationLeaf' already exists (no checksum to verify). Skipping."
            }

            return $verifiedCachedFile
        }

        if ($normalizedExpectedSHA256) {
            $alternateCachedFile = Get-ChildItem -Path $downloadDir -File -ErrorAction SilentlyContinue |
                Where-Object { $_.FullName -ne $DestinationPath } |
                ForEach-Object {
                    $cachedFile = & $getVerifiedCachedFile $_.FullName
                    if ($cachedFile) {
                        $cachedFile
                    }
                } |
                Select-Object -First 1

            if ($alternateCachedFile) {
                Write-Verbose "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] Reusing cached file '$($alternateCachedFile.Name)' for '$destinationLeaf'."
                Copy-Item -Path $alternateCachedFile.FullName -Destination $DestinationPath -Force
                return Get-Item -Path $DestinationPath
            }

            if (Test-Path -Path $DestinationPath -PathType Leaf) {
                Write-Warning "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] SHA256 mismatch for '$destinationLeaf'. Re-downloading."
            }
        }
    }

    Write-OSDeployWinPEDriversProgress -Message "Source: $Uri"
    Write-OSDeployWinPEDriversProgress -Message "Output: $DestinationPath"
    Write-Verbose "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] Downloading '$(Split-Path $Uri -Leaf)'"

    $curlArgs = @(
        '--location', '--fail', '--retry', '3',
        '--user-agent', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36'
    )
    if ($SearchUri) {
        $curlArgs += '--referer', $SearchUri
    }
    $curlArgs += '--output', $DestinationPath, $Uri

    curl.exe @curlArgs
    if ($LASTEXITCODE -ne 0) {
        $PSCmdlet.ThrowTerminatingError(
            [System.Management.Automation.ErrorRecord]::new(
                [System.IO.IOException]::new("curl.exe failed (exit $LASTEXITCODE) downloading '$Uri'"),
                'DownloadFailed',
                [System.Management.Automation.ErrorCategory]::ConnectionError,
                $Uri
            )
        )
    }

    if ($normalizedExpectedSHA256) {
        $actualHash = (Get-FileHash -Path $DestinationPath -Algorithm SHA256).Hash.ToUpperInvariant()
        if ($actualHash -ne $normalizedExpectedSHA256) {
            Remove-Item -Path $DestinationPath -Force -ErrorAction SilentlyContinue
            $PSCmdlet.ThrowTerminatingError(
                [System.Management.Automation.ErrorRecord]::new(
                    [System.IO.IOException]::new("SHA256 mismatch for '$destinationLeaf'. Expected: $normalizedExpectedSHA256, Actual: $actualHash"),
                    'ChecksumMismatch',
                    [System.Management.Automation.ErrorCategory]::SecurityError,
                    $DestinationPath
                )
            )
        }
        Write-Verbose "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] SHA256 verified for '$destinationLeaf'"
    }

    Get-Item -Path $DestinationPath
}