Modules/businessdev.ALbuild.OnPrem/Public/Publish-BcPerTenantExtension.ps1
|
function Publish-BcPerTenantExtension { <# .SYNOPSIS Deploys a per-tenant extension to a Business Central environment via the automation API (licensed). .DESCRIPTION Uploads and schedules a per-tenant extension using the Business Central automation API extensionUpload entity: create an upload record, PATCH the .app binary into it, then trigger the upload action. A bearer access token for the environment is required. .PARAMETER AutomationBaseUrl The automation API base URL, e.g. https://api.businesscentral.dynamics.com/v2.0/{tenant}/{environment}/api/microsoft/automation/v2.0 .PARAMETER CompanyId The company id (GUID) in the environment. .PARAMETER AppFile Path to the .app file. .PARAMETER AccessToken OAuth2 bearer token for the environment. .PARAMETER SchemaSyncMode Schema sync mode: Add (default) or ForceSync. #> [CmdletBinding(SupportsShouldProcess)] param( [Parameter(Mandatory)] [string] $AutomationBaseUrl, [Parameter(Mandatory)] [string] $CompanyId, [Parameter(Mandatory)] [string] $AppFile, [Parameter(Mandatory)] [string] $AccessToken, [ValidateSet('Add', 'ForceSync')] [string] $SchemaSyncMode = 'Add' ) Assert-ALbuildLicensed -Feature 'OnPrem' if (-not (Test-Path -LiteralPath $AppFile)) { throw "App file not found: '$AppFile'." } if (-not $PSCmdlet.ShouldProcess($AutomationBaseUrl, "Publish PTE $(Split-Path $AppFile -Leaf)")) { return } $base = $AutomationBaseUrl.TrimEnd('/') $headers = @{ Authorization = "Bearer $AccessToken" } $companySegment = "companies($CompanyId)/extensionUpload" # 1. Create the upload record (with the requested schema sync mode). $createBody = @{ schemaSyncMode = $SchemaSyncMode } | ConvertTo-Json $upload = Invoke-RestMethod -Uri "$base/$companySegment" -Method Post -Headers ($headers + @{ 'Content-Type' = 'application/json' }) -Body $createBody -ErrorAction Stop $etag = $upload.'@odata.etag' $uploadId = $upload.systemId # 2. PATCH the binary content into the upload record. $bytes = [System.IO.File]::ReadAllBytes((Resolve-Path -LiteralPath $AppFile).ProviderPath) $patchHeaders = $headers + @{ 'If-Match' = $etag; 'Content-Type' = 'application/octet-stream' } Invoke-RestMethod -Uri "$base/$companySegment($uploadId)/extensionContent" -Method Patch -Headers $patchHeaders -Body $bytes -ErrorAction Stop | Out-Null # 3. Trigger the upload/installation action. Invoke-RestMethod -Uri "$base/$companySegment($uploadId)/Microsoft.NAV.upload" -Method Post -Headers ($headers + @{ 'If-Match' = $etag }) -ErrorAction Stop | Out-Null Write-ALbuildLog -Level Success "Scheduled per-tenant extension '$(Split-Path $AppFile -Leaf)' ($SchemaSyncMode)." } |