Modules/businessdev.ALbuild.Containers/Public/Get-BcArtifact.ps1

function Get-BcArtifact {
    <#
    .SYNOPSIS
        Downloads and caches a Business Central artifact (application + platform packages).
 
    .DESCRIPTION
        Downloads the application package for the given artifact URL and, unless suppressed, the
        matching platform package (the same URL with the country replaced by 'platform'). Each
        package is a ZIP; it is extracted into the artifact cache and reused on subsequent calls.
 
    .PARAMETER ArtifactUrl
        The artifact URL (see Find-BcArtifactUrl).
 
    .PARAMETER CacheFolder
        Root cache folder. Defaults to the configured ArtifactCacheFolder.
 
    .PARAMETER IncludePlatform
        Also download/extract the platform package. Default: $true.
 
    .PARAMETER Force
        Re-download even if a cached copy exists.
 
    .EXAMPLE
        Get-BcArtifact -ArtifactUrl (Find-BcArtifactUrl -Country w1 -Select Latest)
 
    .OUTPUTS
        PSCustomObject with ApplicationPath, PlatformPath, Version, Country.
    #>

    [CmdletBinding()]
    [OutputType([PSCustomObject])]
    param(
        [Parameter(Mandatory, Position = 0, ValueFromPipeline)]
        [ValidateNotNullOrEmpty()]
        [string] $ArtifactUrl,

        [string] $CacheFolder,

        [bool] $IncludePlatform = $true,

        [switch] $Force
    )

    process {
        if (-not $CacheFolder) { $CacheFolder = Get-ALbuildConfig -Name 'ArtifactCacheFolder' }
        $info = Get-BcArtifactVersion -ArtifactUrl $ArtifactUrl

        $downloadAndExtract = {
            param([string] $url, [string] $targetFolder, [bool] $force)
            if ((Test-Path -LiteralPath $targetFolder) -and -not $force) {
                # Cached marker file indicates a complete extraction.
                if (Test-Path -LiteralPath (Join-Path $targetFolder '.albuild-complete')) { return $targetFolder }
                Remove-Item -LiteralPath $targetFolder -Recurse -Force -ErrorAction SilentlyContinue
            }
            $tempZip = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), [System.Guid]::NewGuid().ToString() + '.zip')
            try {
                Write-ALbuildLog "Downloading artifact $url ..."
                $attempt = 0
                while ($true) {
                    $attempt++
                    try { Invoke-WebRequest -Uri $url -OutFile $tempZip -UseBasicParsing -TimeoutSec 600 -ErrorAction Stop; break }
                    catch {
                        if ($attempt -ge 3) { throw "Failed to download artifact '$url': $($_.Exception.Message)" }
                        Start-Sleep -Seconds (5 * $attempt)
                    }
                }
                New-Item -Path $targetFolder -ItemType Directory -Force | Out-Null
                Expand-Archive -LiteralPath $tempZip -DestinationPath $targetFolder -Force
                Set-Content -LiteralPath (Join-Path $targetFolder '.albuild-complete') -Value (Get-Date -Format 'o') -Encoding UTF8
                return $targetFolder
            }
            finally {
                if (Test-Path -LiteralPath $tempZip) { Remove-Item -LiteralPath $tempZip -Force -ErrorAction SilentlyContinue }
            }
        }

        $versionFolder = Join-Path $CacheFolder ($info.Type) | Join-Path -ChildPath $info.Version.ToString()
        $appFolder = & $downloadAndExtract $ArtifactUrl (Join-Path $versionFolder $info.Country) ([bool]$Force)

        $platformFolder = $null
        if ($IncludePlatform) {
            $platformUrl = $ArtifactUrl -replace "/$([regex]::Escape($info.Country))$", '/platform'
            $platformFolder = & $downloadAndExtract $platformUrl (Join-Path $versionFolder 'platform') ([bool]$Force)
        }

        return [PSCustomObject]@{
            ApplicationPath = $appFolder
            PlatformPath    = $platformFolder
            Version         = $info.Version
            Country         = $info.Country
            Type            = $info.Type
        }
    }
}