Modules/businessdev.ALbuild.Marketplace/Public/New-BcMarketplaceSubmission.ps1

function New-BcMarketplaceSubmission {
    <#
    .SYNOPSIS
        Uploads an app package and creates a Marketplace submission (Partner Center ingestion).
 
    .DESCRIPTION
        Uploads the app (and optional library apps) to the product's package collection and creates
        a submission. The package upload uses the ingestion API: POST /packages to obtain a blob
        upload URL, PUT the file to that URL, then POST /submissions to create the submission. The
        package resource type is supplied via -ResourceType so it is explicit (no hidden default).
 
    .PARAMETER AuthContext
        Auth context (Partner Center scope).
 
    .PARAMETER ProductId
        The product id.
 
    .PARAMETER AppFile
        The main .app file to submit.
 
    .PARAMETER LibraryAppFile
        Optional dependent/library .app files to include.
 
    .PARAMETER ResourceType
        The ingestion package resource type for BC extension packages.
 
    .OUTPUTS
        The created submission object.
    #>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', '',
        Justification = 'ResourceType is consumed inside the package-upload script block (closure).')]
    [CmdletBinding(SupportsShouldProcess)]
    param(
        [Parameter(Mandatory)] [PSCustomObject] $AuthContext,
        [Parameter(Mandatory)] [string] $ProductId,
        [Parameter(Mandatory)] [string] $AppFile,
        [string[]] $LibraryAppFile = @(),
        [string] $ResourceType = 'Dynamics365BusinessCentralAddOnExtensionPackage'
    )

    if (-not (Test-Path -LiteralPath $AppFile)) { throw "App file not found: '$AppFile'." }
    $cleanProductId = $ProductId -replace '^product/', ''
    $headers = @{ Authorization = "Bearer $($AuthContext.AccessToken)"; 'Content-Type' = 'application/json' }
    $baseUrl = "https://api.partner.microsoft.com/v1.0/ingestion/products/$cleanProductId"

    $uploadPackage = {
        param([string] $file)
        $body = @{ resourceType = $ResourceType; fileName = [System.IO.Path]::GetFileName($file) } | ConvertTo-Json
        $package = Invoke-RestMethod -Uri "$baseUrl/packages" -Method Post -Headers $headers -Body $body -ErrorAction Stop
        $sasUri = $package.fileSasUri
        if (-not $sasUri) { throw "Package upload for '$file' did not return an upload URL." }
        $bytes = [System.IO.File]::ReadAllBytes((Resolve-Path -LiteralPath $file).ProviderPath)
        Invoke-RestMethod -Uri $sasUri -Method Put -Headers @{ 'x-ms-blob-type' = 'BlockBlob' } -Body $bytes -ContentType 'application/octet-stream' -ErrorAction Stop | Out-Null
        Write-ALbuildLog "Uploaded package '$([System.IO.Path]::GetFileName($file))'."
        return $package
    }

    if (-not $PSCmdlet.ShouldProcess($cleanProductId, "Create submission for $(Split-Path $AppFile -Leaf)")) { return }

    & $uploadPackage $AppFile | Out-Null
    foreach ($lib in $LibraryAppFile) { & $uploadPackage $lib | Out-Null }

    $submission = Invoke-RestMethod -Uri "$baseUrl/submissions" -Method Post -Headers $headers -Body '{}' -ErrorAction Stop
    Write-ALbuildLog -Level Success "Created submission $($submission.id)."
    return $submission
}