private/Step-BuildBootInstallWinPEAppAzCopy.ps1

#Requires -PSEdition Core

function Step-BuildBootInstallWinPEAppAzCopy {
    <#
    .SYNOPSIS
        Downloads, caches, and injects AzCopy into the mounted WinPE image
 
    .DESCRIPTION
        Creates or reuses the microsoft-azcopy cache and, when the corresponding
        architecture directory is absent, resolves and downloads both amd64 and arm64
        AzCopy archives. Downloads use curl.exe when available and otherwise use
        Invoke-WebRequest. Download failures produce warnings.
 
        The function searches the cache for AzCopy.exe matching global
        BuildMedia.Architecture, copies each match into Windows\System32 in the mounted
        image, and appends AzCopy to global BuildMedia.InstalledApps for each copy.
 
    .EXAMPLE
        PS> Step-BuildBootInstallWinPEAppAzCopy
 
        Populates the AzCopy cache and injects the selected architecture into WinPE.
 
    .INPUTS
        None. This function does not accept pipeline input.
 
    .OUTPUTS
        None. Native download output is silent and web requests write directly to files.
 
    .NOTES
        Author: David Segura
        Company: Recast Software
        Version: 0.1.0
        Date: 2026-08-28
 
        Requires global BuildMedia with Architecture, MountPath, and InstalledApps
        properties; global OSDeployModule.BootImage.winpeapps.azcopy; and script
        OSDeployCorePath. Requires network access for cache misses and Expand-Archive.
        Modifies the shared cache, mounted image, and global InstalledApps collection.
    #>

    [CmdletBinding()]
    param ()

    $Architecture = $global:BuildMedia.Architecture
    $MountPath = $global:BuildMedia.MountPath
    $WinPEAppsPath = Join-Path $Script:OSDeployCorePath 'cache' 'winpe-apps'
    $azcopyConfig = $global:OSDeployModule.BootImage.winpeapps.azcopy

    $CacheAzCopy = Join-Path $WinPEAppsPath 'microsoft-azcopy'

    if (-not (Test-Path -Path $CacheAzCopy)) {
        Write-Host -ForegroundColor DarkGray "[$(Get-Date -format s)] [INFO] Creating AzCopy cache: $CacheAzCopy"
        New-Item -Path $CacheAzCopy -ItemType Directory -Force | Out-Null
    }
    else {
        Write-Host -ForegroundColor DarkGray "[$(Get-Date -format s)] [INFO] Using AzCopy cache: $CacheAzCopy"
    }

    # Download amd64 version
    if (-not (Test-Path "$CacheAzCopy\amd64")) {
        $Uri = $azcopyConfig.amd64
        try {
            $DownloadUri = (Invoke-WebRequest -Uri $Uri -Method Head -ErrorAction SilentlyContinue).BaseResponse.RequestMessage.RequestUri.AbsoluteUri
            if ($DownloadUri) {
                $FileName = if ($DownloadUri -match 'filename%3D([^&]+)') { [System.Uri]::UnescapeDataString($Matches[1]) } else { Split-Path $DownloadUri -Leaf }
                if (-not (Test-Path "$CacheAzCopy\$FileName")) {
                    Write-Host -ForegroundColor DarkGray "[$(Get-Date -format s)] [INFO] Downloading AzCopy amd64: $FileName"
                    $downloadPath = Join-Path $CacheAzCopy $FileName
                    if (Get-Command 'curl.exe' -ErrorAction SilentlyContinue) {
                        & curl.exe --silent --location --retry 5 --continue-at - --output $downloadPath $DownloadUri
                    }
                    else {
                        Invoke-WebRequest -UseBasicParsing -Uri $DownloadUri -OutFile $downloadPath
                    }
                    Expand-Archive -Path $downloadPath -DestinationPath "$CacheAzCopy\amd64" -Force
                }
            }
        }
        catch {
            Write-Warning "[$(Get-Date -Format s)] Failed to download AzCopy amd64: $_"
        }
    }

    # Download arm64 version
    if (-not (Test-Path "$CacheAzCopy\arm64")) {
        $Uri = $azcopyConfig.arm64
        try {
            $DownloadUri = (Invoke-WebRequest -Uri $Uri -Method Head -ErrorAction SilentlyContinue).BaseResponse.RequestMessage.RequestUri.AbsoluteUri
            if ($DownloadUri) {
                $FileName = if ($DownloadUri -match 'filename%3D([^&]+)') { [System.Uri]::UnescapeDataString($Matches[1]) } else { Split-Path $DownloadUri -Leaf }
                if (-not (Test-Path "$CacheAzCopy\$FileName")) {
                    Write-Host -ForegroundColor DarkGray "[$(Get-Date -format s)] [INFO] Downloading AzCopy arm64: $FileName"
                    $downloadPath = Join-Path $CacheAzCopy $FileName
                    if (Get-Command 'curl.exe' -ErrorAction SilentlyContinue) {
                        & curl.exe --silent --location --retry 5 --continue-at - --output $downloadPath $DownloadUri
                    }
                    else {
                        Invoke-WebRequest -UseBasicParsing -Uri $DownloadUri -OutFile $downloadPath
                    }
                    Expand-Archive -Path $downloadPath -DestinationPath "$CacheAzCopy\arm64" -Force
                }
            }
        }
        catch {
            Write-Warning "[$(Get-Date -Format s)] Failed to download AzCopy arm64: $_"
        }
    }

    # Copy AzCopy to WinPE
    Get-ChildItem -Path "$CacheAzCopy\$Architecture" -Recurse -Include 'AzCopy.exe' -ErrorAction SilentlyContinue | ForEach-Object {
        Write-Host -ForegroundColor DarkGray "[$(Get-Date -format s)] [INFO] Injecting AzCopy.exe into WinPE"
        Copy-Item $_.FullName -Destination "$MountPath\Windows\System32" -Force
        $global:BuildMedia.InstalledApps += 'AzCopy'
    }
}