Modules/businessdev.ALbuild.Marketplace/Public/Submit-BcMarketplaceApp.ps1

function Submit-BcMarketplaceApp {
    <#
    .SYNOPSIS
        Submits an AL app to Microsoft Marketplace (AppSource) end to end.
 
    .DESCRIPTION
        Orchestrates a Marketplace submission: authenticates (Partner Center and product-ingestion
        scopes), resolves the product by name, uploads the app (and library apps) and creates a
        submission, waits for validation, and optionally promotes it to live. Requires a valid
        ALbuild license.
 
    .PARAMETER ProductName
        The Marketplace product (offer) name. OPTIONAL - defaults to the main app's manifest name.
        Provide it only to override (e.g. when the offer name differs from the app name).
 
    .PARAMETER AppPath
        A folder of built .app files, or a single .app file. The main app and its library (dependency)
        apps are selected automatically and TEST apps are excluded - so a task can just point at the
        build output folder without naming a specific, version-stamped file.
 
    .PARAMETER LibraryAppPath
        Optional additional folder(s) or .app file(s) whose apps are submitted as library apps, for offers
        whose main app and library app are built by different pipelines/repositories and so arrive as
        separate release artifacts (e.g. Address Validation + its Extension License library). Test apps are
        excluded. Only valid with -AppPath.
 
    .PARAMETER AppFile
        (Legacy) An explicit main .app file. Prefer -AppPath. Ignored when -AppPath is given.
 
    .PARAMETER LibraryAppFile
        Optional dependent/library .app files (only used with the legacy -AppFile).
 
    .PARAMETER TenantId
        Azure AD tenant id for the Partner Center service principal.
 
    .PARAMETER ClientId
        Service principal client id.
 
    .PARAMETER ClientSecret
        Service principal client secret.
 
    .PARAMETER PublisherId
        Numeric publisher/Seller ID (Partner Center > Settings > Account settings > Identifiers). Required
        by the Partner Center ingestion API (sent as the 'x-ms-publisherId' header).
 
    .PARAMETER AutoPromote
        Promote to live after successful validation.
 
    .PARAMETER TimeoutMinutes
        Validation timeout. Default 30.
 
    .EXAMPLE
        Submit-BcMarketplaceApp -AppPath .\output -TenantId $tenantId -ClientId $clientId -ClientSecret $clientSecret -PublisherId '1234567' -AutoPromote
 
        Submits the main app and its library apps from the build output folder and promotes the submission to live once it validates.
 
    .OUTPUTS
        PSCustomObject with ProductId, SubmissionId, State, Substate, Promoted.
    #>

    [CmdletBinding(SupportsShouldProcess, DefaultParameterSetName = 'Path')]
    [OutputType([PSCustomObject])]
    param(
        [string] $ProductName,
        [Parameter(Mandatory, ParameterSetName = 'Path')] [string] $AppPath,
        [Parameter(ParameterSetName = 'Path')] [string[]] $LibraryAppPath = @(),
        [Parameter(Mandatory, ParameterSetName = 'Legacy')] [string] $AppFile,
        [Parameter(ParameterSetName = 'Legacy')] [string[]] $LibraryAppFile = @(),
        [Parameter(Mandatory)] [string] $TenantId,
        [Parameter(Mandatory)] [string] $ClientId,
        [Parameter(Mandatory)] [object] $ClientSecret,
        [Parameter(Mandatory)] [string] $PublisherId,
        [switch] $AutoPromote,
        [int] $TimeoutMinutes = 30
    )

    Assert-ALbuildLicensed -Feature 'Marketplace'

    # Resolve the apps to submit. -AppPath (folder or file) auto-selects the main app + library apps and
    # drops test apps; -AppFile is the explicit legacy path.
    if ($PSCmdlet.ParameterSetName -eq 'Path') {
        $resolveArgs = @{ Path = $AppPath }
        if (@($LibraryAppPath).Count -gt 0) { $resolveArgs['LibraryPath'] = $LibraryAppPath }
        $set = Resolve-BcMarketplaceAppSet @resolveArgs
        $AppFile = $set.MainApp.File
        $LibraryAppFile = @($set.LibraryApps | ForEach-Object { $_.File })
        if (-not $ProductName) {
            $ProductName = $set.MainApp.Name
            Write-ALbuildLog "No product name supplied; using the main app's manifest name '$ProductName'."
        }
    }
    elseif (-not $ProductName) {
        # Legacy path with no explicit name: derive it from the app's manifest.
        $ProductName = (Expand-BcAppFile -Path $AppFile).Name
        Write-ALbuildLog "No product name supplied; using the app's manifest name '$ProductName'."
    }
    if (-not $ProductName) { throw 'Could not determine the Marketplace product name (no -ProductName and no readable app manifest).' }

    # Product lookup uses the Graph product-ingestion scope; the submission uses the Partner Center scope
    # (which requires the PublisherId / x-ms-publisherId header).
    $graphAuth = New-BcMarketplaceAuthContext -TenantId $TenantId -ClientId $ClientId -ClientSecret $ClientSecret -Scope 'https://graph.microsoft.com/.default' -PublisherId $PublisherId
    $partnerAuth = New-BcMarketplaceAuthContext -TenantId $TenantId -ClientId $ClientId -ClientSecret $ClientSecret -Scope 'https://api.partner.microsoft.com/.default' -PublisherId $PublisherId

    Write-ALbuildLog "Resolving product '$ProductName' ..."
    $products = @(Get-BcMarketplaceProduct -AuthContext $graphAuth -Name $ProductName)
    if ($products.Count -eq 0) { throw "Marketplace product '$ProductName' was not found (product name = the offer 'alias')." }
    if ($products.Count -gt 1) { throw "Multiple products match '$ProductName' ($(($products | ForEach-Object { $_.alias }) -join ', ')); use a more specific name." }
    $product = $products[0]
    Write-ALbuildLog "Product '$($product.alias)' ($($product.id))."

    if (-not $PSCmdlet.ShouldProcess($product.alias, 'Create and validate Marketplace submission')) { return }

    # New-BcMarketplaceSubmission uploads the package(s), creates the preview submission, waits for
    # validation and (with -AutoPromote) promotes to live - the proven Publish-AppSourceApp flow.
    return New-BcMarketplaceSubmission -AuthContext $partnerAuth -ProductId $product.id `
        -AppFile $AppFile -LibraryAppFile $LibraryAppFile -AutoPromote:$AutoPromote -TimeoutMinutes $TimeoutMinutes
}