Modules/businessdev.ALbuild.Feeds/Public/Get-BcUniversalPackage.ps1

function Get-BcUniversalPackage {
    <#
    .SYNOPSIS
        Downloads a named package from an Azure DevOps Universal feed into a folder.
 
    .DESCRIPTION
        Azure DevOps Universal feeds address packages by name and version (unlike NuGet symbol feeds,
        which the dependency resolver queries by app id). This cmdlet downloads such a named package -
        e.g. shared symbol bundles or a configuration package - into -OutputFolder. It is the native
        replacement for the V1 DownloadArtifactsUniversalFeed / DownloadDependenciesUniversalFeed /
        DownloadConfigPackageUniversalFeed tasks.
 
        Download uses the Azure CLI 'az artifacts universal download' (the supported Universal
        Packages client). The Azure CLI with the azure-devops extension must be available; the call
        is routed through Invoke-ALbuildProcess so it is captured and testable. Authenticate either by
        a prior 'az login' or by passing -AccessToken (a PAT), which is exported as
        AZURE_DEVOPS_EXT_PAT for the download only.
 
    .PARAMETER Organization
        The Azure DevOps organization - a full URL (https://dev.azure.com/org) or a bare org name.
 
    .PARAMETER Feed
        The feed name.
 
    .PARAMETER Name
        The Universal package name.
 
    .PARAMETER Version
        The package version, or '*' for the latest. Default '*'.
 
    .PARAMETER OutputFolder
        The folder to download the package contents into (created if missing).
 
    .PARAMETER Project
        Project name for a project-scoped feed. Omit for an organization-scoped feed.
 
    .PARAMETER AccessToken
        A personal access token used for authentication (exported as AZURE_DEVOPS_EXT_PAT).
 
    .PARAMETER AzExecutable
        The Azure CLI executable. Default 'az'.
 
    .EXAMPLE
        Get-BcUniversalPackage -Organization 'https://dev.azure.com/contoso' -Feed 'D365BC' `
            -Name 'shared-symbols' -Version '*' -OutputFolder '.\.alpackages'
 
    .OUTPUTS
        PSCustomObject: Name, Version, OutputFolder, Files.
    #>

    [CmdletBinding(SupportsShouldProcess)]
    [OutputType([PSCustomObject])]
    param(
        [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string] $Organization,
        [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string] $Feed,
        [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [Alias('PackageName')] [string] $Name,
        [string] $Version = '*',
        [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string] $OutputFolder,
        [string] $Project,
        [string] $AccessToken,
        [string] $AzExecutable = 'az'
    )

    if (-not (Get-Command -Name $AzExecutable -ErrorAction SilentlyContinue)) {
        throw "The Azure CLI ('$AzExecutable') was not found. Install it (with the 'azure-devops' extension) or pass -AzExecutable."
    }

    $organizationUrl = if ($Organization -match '^https?://') { $Organization } else { "https://dev.azure.com/$Organization" }

    if (-not (Test-Path -LiteralPath $OutputFolder)) {
        New-Item -Path $OutputFolder -ItemType Directory -Force | Out-Null
    }

    $arguments = @(
        'artifacts', 'universal', 'download',
        '--organization', $organizationUrl,
        '--feed', $Feed,
        '--name', $Name,
        '--version', $Version,
        '--path', $OutputFolder
    )
    if ($Project) { $arguments += @('--project', $Project, '--scope', 'project') }

    if (-not $PSCmdlet.ShouldProcess("$Name $Version", "Download from Universal feed '$Feed'")) {
        return [PSCustomObject]@{ Name = $Name; Version = $Version; OutputFolder = $OutputFolder; Files = @() }
    }

    $restorePat = $false
    $previousPat = $null
    if ($AccessToken) {
        $previousPat = $env:AZURE_DEVOPS_EXT_PAT
        $env:AZURE_DEVOPS_EXT_PAT = $AccessToken
        $restorePat = $true
    }
    try {
        Invoke-ALbuildProcess -FilePath $AzExecutable -Arguments $arguments | Out-Null
    }
    finally {
        if ($restorePat) {
            if ($null -eq $previousPat) { Remove-Item Env:\AZURE_DEVOPS_EXT_PAT -ErrorAction SilentlyContinue }
            else { $env:AZURE_DEVOPS_EXT_PAT = $previousPat }
        }
    }

    $files = @(Get-ChildItem -LiteralPath $OutputFolder -Recurse -File -Include '*.app', '*.rapidstart' -ErrorAction SilentlyContinue |
            Select-Object -ExpandProperty FullName)
    Write-ALbuildLog -Level Success "Downloaded Universal package '$Name' $Version to '$OutputFolder'."

    return [PSCustomObject]@{
        Name         = $Name
        Version      = $Version
        OutputFolder = $OutputFolder
        Files        = $files
    }
}