Modules/businessdev.ALbuild.OnPrem/Public/Publish-BcOnPremApp.ps1

function Publish-BcOnPremApp {
    <#
    .SYNOPSIS
        Publishes an AL app to an on-premises Business Central server instance (licensed).
 
    .DESCRIPTION
        Publishes the .app to the given server instance using the Business Central management
        cmdlets, optionally synchronising and installing it. Run on a host where the BC management
        module is available (the BC server or a deployment agent). Requires a valid ALbuild license.
 
    .PARAMETER ServerInstance
        The BC server instance name.
 
    .PARAMETER AppFile
        Path to the .app file.
 
    .PARAMETER SkipVerification
        Publish without signature verification.
 
    .PARAMETER Sync
        Synchronise after publishing.
 
    .PARAMETER Install
        Install after synchronising.
 
    .PARAMETER SyncMode
        Add (default), Clean, Development or ForceSync.
 
    .PARAMETER Scope
        Global (default) or Tenant.
 
    .PARAMETER Tenant
        Tenant. Default 'default'.
    #>

    [CmdletBinding(SupportsShouldProcess)]
    param(
        [Parameter(Mandatory)] [string] $ServerInstance,
        [Parameter(Mandatory)] [string] $AppFile,
        [switch] $SkipVerification,
        [switch] $Sync,
        [switch] $Install,
        [ValidateSet('Add', 'Clean', 'Development', 'ForceSync')] [string] $SyncMode = 'Add',
        [ValidateSet('Global', 'Tenant')] [string] $Scope = 'Global',
        [string] $Tenant = 'default'
    )

    Assert-ALbuildLicensed -Feature 'OnPrem'
    if (-not (Test-Path -LiteralPath $AppFile)) { throw "App file not found: '$AppFile'." }
    $leaf = Split-Path -Path $AppFile -Leaf
    if (-not $PSCmdlet.ShouldProcess($ServerInstance, "Publish $leaf")) { return }

    # Publish and capture the server's app record via -PassThru. We deliberately do NOT read the app
    # identity with 'Get-NAVAppInfo -Path' - that file-reading parameter set is not reliably available
    # across on-prem BC versions; the record returned by Publish-NAVApp is authoritative for
    # Name/Publisher/Version. When the exact Name+Publisher+Version is already published, Publish-NAVApp
    # writes a non-terminating error and returns nothing, which we treat as "already present".
    $published = Publish-NAVApp -ServerInstance $ServerInstance -Path $AppFile -Scope $Scope -SkipVerification:$SkipVerification -PassThru -ErrorAction SilentlyContinue
    if (-not $published) {
        Write-ALbuildLog -Level Information "'$leaf' is already published on '$ServerInstance' in this version; nothing to do."
        return
    }

    # Re-read the full tenant-specific record: adds AppId and IsInstalled, which the sync / install /
    # upgrade flow below needs (and which are keyed by AppId + Publisher, not just Name).
    $info = Get-NAVAppInfo -ServerInstance $ServerInstance -Name $published.Name -Publisher $published.Publisher -Version $published.Version -Tenant $Tenant -TenantSpecificProperties
    Write-ALbuildLog -Level Information "Published $($info.Name) $($info.Version) (AppId $($info.AppId)) to '$ServerInstance'."

    if (-not $Sync) { return }
    if ($info.IsInstalled) {
        Write-ALbuildLog -Level Information "$($info.Name) $($info.Version) is already installed on '$ServerInstance'; nothing to sync or install."
        return
    }

    Sync-NAVApp -ServerInstance $ServerInstance -Name $info.Name -Publisher $info.Publisher -Version $info.Version -Mode $SyncMode -Tenant $Tenant -Force -ErrorAction Stop

    if (-not $Install) {
        Write-ALbuildLog -Level Success "Published and synced $($info.Name) $($info.Version) on '$ServerInstance'."
        return
    }

    # Install vs. upgrade: if any OTHER version of this app id is already published, the freshly
    # published one is an upgrade - run the data upgrade, then unpublish the superseded version(s).
    # Otherwise it is a first-time install.
    $priorVersions = @(Get-NAVAppInfo -ServerInstance $ServerInstance -AppId $info.AppId -Publisher $info.Publisher -Tenant $Tenant -TenantSpecificProperties |
            Where-Object { $_.Version -ne $info.Version })

    if ($priorVersions.Count -eq 0) {
        Install-NAVApp -ServerInstance $ServerInstance -AppId $info.AppId -Publisher $info.Publisher -Version $info.Version -Tenant $Tenant -ErrorAction Stop
        Write-ALbuildLog -Level Success "Installed $($info.Name) $($info.Version) on '$ServerInstance'."
    }
    else {
        Start-NAVAppDataUpgrade -ServerInstance $ServerInstance -AppId $info.AppId -Publisher $info.Publisher -Version $info.Version -Tenant $Tenant -ErrorAction Stop
        foreach ($old in $priorVersions) {
            Unpublish-NAVApp -ServerInstance $ServerInstance -AppId $info.AppId -Publisher $info.Publisher -Version $old.Version -ErrorAction Stop
            Write-ALbuildLog -Level Information "Unpublished superseded $($info.Name) $($old.Version) from '$ServerInstance'."
        }
        Write-ALbuildLog -Level Success "Upgraded $($info.Name) to $($info.Version) on '$ServerInstance'."
    }
}