Security change on December 1, 2026: TLS 1.2 will become the minimum standard for 365 business Print Agent. Details and system requirements
365 business development
PowerShell Module

RuntimePackages module

Module: businessdev.ALbuild.RuntimePackages  •  Tier: Licensed  •  Cmdlets: 7

The RuntimePackages module builds signed Business Central runtime packages for on-premise distribution — one per supported platform version — using a batched, parallel and incremental engine. See the Runtime packages deep dive. This is a licensed feature, enforced at runtime by Assert-ALbuildLicensed.

Licensing

Cmdlets in this module are part of a licensed tier and call Assert-ALbuildLicensed on entry. Without a valid ALbuild license they fail with clear remediation guidance. See Licensing & tiers.

Cmdlets in this module

CmdletDescription
Build-BcRuntimePackagesBuilds runtime packages for one or more apps across Business Central platform versions (licensed).
Get-BcRuntimePackageBuildPlanDetermines which Business Central platform versions still need a runtime package built.
Get-BcRuntimeWorkSetPlans outstanding runtime-package work, grouped by BC platform version across all products.
Invoke-BcRuntimeFactoryBuilds runtime packages for a slice of platform versions, several containers at a time.
New-BcIndirectNuGetPackageCreates an "indirect" NuGet package that maps platform versions to runtime packages.
New-BcRuntimePackageGenerates a runtime package for a published app from a Business Central container.
Publish-BcRuntimePackagePublishes a runtime NuGet package to a feed (licensed).

Build-BcRuntimePackages

Builds runtime packages for one or more apps across Business Central platform versions (licensed).

Implements the batched/incremental runtime-package engine: for each target platform version it creates a single container, publishes all apps into it, generates each app's runtime package and packs a runtime NuGet package (id '<publisher>.<name>.runtime-<appversion>', package version = platform version, resolvable by platform version), then removes the container. One container per version (not per app x version) minimises spin-ups; running this per version on separate agents provides the parallel dimension. A valid ALbuild license is required.

Syntax

PowerShellCode
Build-BcRuntimePackages -App <Object[]> -PlatformVersion <String[]> [-Type <String>] [-Country <String>] [-Credential <PSCredential>] [-OutputFolder <String>] [-DockerExecutable <String>]

Parameters

ParameterTypeRequiredDescription
-AppObject[]YesThe apps to process. Each item: @{ AppFile; Name; Publisher; Version }.
-PlatformVersionString[]YesPlatform versions to build for (typically from Get-BcRuntimePackageBuildPlan).
-TypeStringNoArtifact type: OnPrem (default) or Sandbox. Allowed values: OnPrem, Sandbox. Default: 'OnPrem'.
-CountryStringNoArtifact country. Default 'w1'. Default: 'w1'.
-CredentialPSCredentialNoContainer admin credential.
-OutputFolderStringNoOutput root for the runtime apps/packages. Default: ./runtime-output. Default: (Join-Path (Get-Location) 'runtime-output').
-DockerExecutableStringNoThe Docker executable to use (default 'docker'). Default: 'docker'.

Output

PSCustomObject per produced runtime package (Version, App, RuntimeApp, NuGetPackage).


Get-BcRuntimePackageBuildPlan

Determines which Business Central platform versions still need a runtime package built.

Given the candidate platform versions, the app's minimum supported version, and the set of already-built versions, returns the versions that need building - the "incremental" part of the batched runtime-package engine. Versions below the app's minimum, or already built, are excluded.

Syntax

PowerShellCode
Get-BcRuntimePackageBuildPlan -ArtifactVersion <String[]> [-MinimumVersion <String>] [-AlreadyBuilt <String[]>]

Parameters

ParameterTypeRequiredDescription
-ArtifactVersionString[]YesCandidate platform versions (e.g. from Find-BcArtifactUrl ... -Select All).
-MinimumVersionStringNoThe app's minimum supported version (typically app.json 'application'). Versions whose major is below this minimum's major are skipped. Default 0.0.0.0 (no minimum). Default: '0.0.0.0'.
-AlreadyBuiltString[]NoVersions already built (present in Blob/feed) which should be skipped.

Output

System.String - the platform versions to build, ascending.

Examples

Example 1

PowerShellCode
Get-BcRuntimePackageBuildPlan -ArtifactVersion $versions -MinimumVersion '25.0.0.0' -AlreadyBuilt $built

Get-BcRuntimeWorkSet

Plans outstanding runtime-package work, grouped by BC platform version across all products.

Turns "which (product, app version, platform version) runtime packages are still missing?" into an ordered, sliceable plan. It is a pure function - no feed, blob or artifact calls - so the whole planning policy is unit-testable and a run can be inspected before a single container starts.

WHY GROUPING BY PLATFORM VERSION IS THE WHOLE POINT A runtime package is produced server-side by a running BC service tier, and Microsoft only guarantees it on the exact platform version it was produced on - so the (product x platform) matrix is irreducible. What IS reducible is the container count. Measured on build 27197, provisioning a container costs 238-564 s depending on the major, while the value-adding work per product is a constant ~80 s. Iterating platform-versions-per-product, as one pipeline per product does, pays that provisioning cost once per product; iterating products-per-platform-version pays it once, full stop. For a 12-product catalogue over ~192 versions that is ~2 300 container starts against 192.

It also removes a failure mode rather than working around it: several products depend on other products in the same catalogue (Extension License, 365 business API). Per-product pipelines had to fetch those dependencies' runtime packages from blob storage and SKIP the platform version when they were not there yet. Built in one container in dependency order, the dependency is simply present - it was built moments earlier, in the same container.

LANES A product release invalidates the entire matrix at once, and a full rebuild is hours of work no matter how it is scheduled. Customers, however, pull the newest majors first. -FastLaneMinMajor splits the plan so those versions are built and published first, and the long tail follows behind without holding the release up.

SLICES Each slice is the unit of work for one agent job. -MaxVersionsPerSlice bounds how long a single job runs, which matters because a job holds one of the organisation's few parallel slots for its whole lifetime - and because a job that dies loses only its in-flight version, not the run.

Syntax

PowerShellCode
Get-BcRuntimeWorkSet -Product <Object[]> -PlatformArtifact <Object[]> [-ExistingPackage <String[]>] [-ExistingFeedPackage <String[]>] [-SkipPlatformVersion <String[]>] [-FastLaneMinMajor <Int32>] [-Lane <String>] [-MaxVersionsPerSlice <Int32>]

Parameters

ParameterTypeRequiredDescription
-ProductObject[]YesThe catalogue, as objects with: Name - display name (used in logs and the per-app result). AppId - app.json 'id'; keys the blob layout <appId>/<platformVersion>/<file>. AppVersion - the released version to build runtime packages for. MinimumVersion - app.json 'application'; platform versions below it are not applicable. Compared as a whole version, so a '17.1.0.0' minimum really does exclude 17.0.x. DependsOn - OPTIONAL names of other catalogue products this one needs installed first. Projects - OPTIONAL, passed through untouched for the worker. Country - OPTIONAL, passed through untouched.
-PlatformArtifactObject[]YesCandidate platform versions as objects with PlatformVersion, ArtifactUrl and Country. Country is part of the grouping key, not decoration: a BC artifact carries ONE localisation, so a container built from the 'de' artifact cannot produce a runtime package for a product that ships against 'w1'. In the current catalogue Banking, ERiC and Sanction Screen build against 'de' and the rest against 'w1', so a platform version yields one container per country in use - still one per country instead of one per product.
-ExistingPackageString[]NoRelative blob paths that already exist ('<appId>/<platformVersion>/<file>.app'). A (product, platform version) pair whose file is already there is reported as skipped rather than rebuilt, which is what makes a re-run after a failure cheap and a scheduled sweep close to free.
-ExistingFeedPackageString[]NoPairs of '<appId>/<platformVersion>' whose NuGet package is already on the feed. OPTIONAL, and it changes the meaning of 'already done': when supplied, a pair counts as done only when the blob file AND the feed package exist. Without it, idempotency rests on blob storage alone - and blob and feed can drift. The checkpoint uploads to blob first and pushes to the feed second, and a failed push is caught and warned about rather than fatal (one bad push must not lose a whole slice). The next run then sees the blob file, skips the version, and the NuGet package stays missing for good. Reversing the order would only move the hole to the other side. The version part is normalised to four parts before comparing, because NuGet normalises it away on publish: platform version 27.10.53179.0 is listed by the feed as '27.10.53179', while 28.3.52162.52222 keeps all four. Comparing the strings as they come back would match nothing for every version with a zero revision - and 'nothing on the feed' means rebuild the whole matrix. Measured on the live feed: Address Validation reports 130 three-part versions. Pass nothing when the run does not publish to a feed; the behaviour is then exactly as before.
-SkipPlatformVersionString[]NoPlatform versions to exclude outright - Microsoft occasionally publishes an artifact that cannot produce a working container, and one bad version must not stall the catalogue.
-FastLaneMinMajorInt32NoPlatform majors >= this go into the FastLane; everything else into the LongTail. 0 (default) disables the split and puts everything in a single 'All' lane. Default: 0.
-LaneStringNoWhich lane to return: All (default), FastLane or LongTail. Allowed values: All, FastLane, LongTail. Default: 'All'.
-MaxVersionsPerSliceInt32NoMaximum platform versions per slice. Default 12. Default: 12.

Output

PSCustomObject with Slices (Index, Lane, Items[]) and Summary (per-product counts).

Examples

Example 1

PowerShellCode
$plan = Get-BcRuntimeWorkSet -Product $catalogue -PlatformArtifact $artifacts ` -ExistingPackage $blobNames -FastLaneMinMajor 27 -Lane FastLane $plan.Slices | ForEach-Object { Invoke-BcRuntimeFactory -WorkItem $_.Items ... }

Invoke-BcRuntimeFactory

Builds runtime packages for a slice of platform versions, several containers at a time.

Runs the work items from Get-BcRuntimeWorkSet through a pool of worker threads, each owning one BC container for one platform version, and checkpoints every version as soon as it is done.

WHY A POOL INSIDE ONE JOB Throughput here is capped by the organisation's four parallel self-hosted jobs, not by the build host - which has 36 cores and 192 GB of RAM and was measured with ~72 GB still free while four containers ran. Adding pipeline stages cannot get past that cap; adding workers inside a job does, and costs nothing.

WHY CHECKPOINTING Build 27197 built 186 runtime packages and shipped 130 of them. Upload ran once, after the whole loop, so when the run was cancelled ten hours of finished work was thrown away. -OnVersionComplete runs after each platform version, so a cancelled or crashed run loses at most the version still in flight.

The callback runs on the DISPATCHER thread, deliberately. Uploading to blob storage and pushing to a feed needs an Azure PowerShell context, and those context objects are not safe to share across runspaces. Workers produce files; the dispatcher ships them.

PER-APP VISIBILITY Azure DevOps renders a stage as a single icon, and giving every app its own stage would mean a container per (app, platform version) - the arrangement the factory exists to remove. So the per-app result is carried in the artefacts a job can publish instead:

  • the log, one collapsible '##[group]' per platform version with one line per app;
  • -JUnitPath, one test case per app per version for PublishTestResults@2, which is what puts a green/red/skipped entry per app into the Tests tab;
  • -SummaryPath, the app x version matrix for '##vso[task.uploadsummary]'.

Worker output is captured to a per-version file and replayed by the dispatcher when the version finishes, so the log reads as ordered blocks instead of interleaved lines from parallel workers.

Syntax

PowerShellCode
Invoke-BcRuntimeFactory -WorkItem <Object[]> -WorkRoot <String> -OutputFolder <String> [-Throughput <Int32>] [-MemoryLimit <String>] [-ReserveHostGb <Int32>] [-Credential <PSCredential>] [-LicenseFile <String>] [-LegacyLicenseFile <String>] [-Signing <Hashtable>] [-TeardownMode <String>] [-UseImageCache] [-OnVersionComplete <ScriptBlock>] [-LogFolder <String>] [-JUnitPath <String>] [-SummaryPath <String>] [-DockerExecutable <String>] [-NoAzureDevOpsLogging]

Parameters

ParameterTypeRequiredDescription
-WorkItemObject[]YesWork items from Get-BcRuntimeWorkSet (PlatformVersion, ArtifactUrl, Products[]).
-WorkRootStringYesRoot for the private per-worker working copies.
-OutputFolderStringYesRoot of the '<appId>/<platformVersion>/' output layout.
-ThroughputInt32NoRequested worker count. Capped by available memory and disk - see Get-BcRuntimeWorkerCount. Default: 3.
-MemoryLimitStringNoPer-container memory limit. Default: '8G'.
-ReserveHostGbInt32NoHost memory to leave for the agent, the compiler and the OS. Default: 16.
-CredentialPSCredentialNoContainer admin credential.
-LicenseFileStringNoBC licence for the containers.
-LegacyLicenseFileStringNoThe .flf license for platform majors up to 19; BC20 and newer take -LicenseFile. Both are needed because one run spans majors on either side of that change.
-SigningHashtableNoSplat for Invoke-BcAppSigning.
-TeardownModeStringNoFull (default) removes each app and its dependency chain after its runtime package is produced. Allowed values: Full, AppOnly. Default: 'Full'.
-UseImageCacheswitchNoStart containers from the cached version-specific image.
-OnVersionCompleteScriptBlockNoScriptblock invoked on the dispatcher thread with the finished version result - the checkpoint.
-LogFolderStringNoWhere per-version worker logs are written. Defaults to a folder under -WorkRoot.
-JUnitPathStringNoWrite JUnit results here for PublishTestResults@2.
-SummaryPathStringNoWrite the Markdown matrix here for task.uploadsummary.
-DockerExecutableStringNoDocker executable. Default: 'docker'.
-NoAzureDevOpsLoggingswitchNoSuppress the '##vso[...]' logging commands. Tests set this: they run inside a real Azure DevOps job, where a logged issue would mark that job rather than the run under test.

Output

PSCustomObject: Produced, Failed, Skipped, Results[], WorkerCount.


New-BcIndirectNuGetPackage

Creates an "indirect" NuGet package that maps platform versions to runtime packages.

Produces a dependency-only NuGet package whose dependencies are the per-platform-version runtime packages. A consumer resolving against a specific Business Central build pulls the matching runtime package through this single, stable package id.

Syntax

PowerShellCode
New-BcIndirectNuGetPackage -PackageId <String> -Version <String> -RuntimePackage <Object[]> [-Authors <String>] [-Description <String>] [-OutputFolder <String>]

Parameters

ParameterTypeRequiredDescription
-PackageIdStringYesThe indirect package id.
-VersionStringYesThe indirect package version (typically the app version).
-RuntimePackageObject[]YesThe runtime package dependencies, each @{ id = ...; version = ... } (version may be a range).
-AuthorsStringNoPackage authors. Default '365 business development'. Default: '365 business development'.
-DescriptionStringNoPackage description.
-OutputFolderStringNoOutput folder. Default: current directory. Default: (Get-Location).Path.

Output

System.String - the path to the created .nupkg.


New-BcRuntimePackage

Generates a runtime package for a published app from a Business Central container.

Runs Get-NAVAppRuntimePackage inside the container to produce a platform-specific runtime (.runtime.app) for an already-published app, then copies it to the host. Runtime package generation is an NST (server) operation and therefore requires the container.

Syntax

PowerShellCode
New-BcRuntimePackage -Name <String> -AppName <String> -AppPublisher <String> -AppVersion <String> -OutputFolder <String> [-ServerInstance <String>] [-DockerExecutable <String>]

Parameters

ParameterTypeRequiredDescription
-NameStringYesContainer name.
-AppNameStringYesThe published app's name.
-AppPublisherStringYesThe app publisher (used for the output file name).
-AppVersionStringYesThe app version.
-OutputFolderStringYesHost folder to copy the runtime package to.
-ServerInstanceStringNoBC server instance. Default 'BC'. Default: 'BC'.
-DockerExecutableStringNoThe Docker executable to use (default 'docker'). Default: 'docker'.

Output

System.String - the path to the generated runtime .app on the host.


Publish-BcRuntimePackage

Publishes a runtime NuGet package to a feed (licensed).

Pushes a runtime (or indirect) NuGet package to a NuGet v3 feed. A valid ALbuild license is required.

Syntax

PowerShellCode
Publish-BcRuntimePackage -PackagePath <String> -Url <String> -ApiKey <String> [-FailOnConflict]

Parameters

ParameterTypeRequiredDescription
-PackagePathStringYesPath to the .nupkg.
-UrlStringYesThe feed's NuGet v3 service index URL.
-ApiKeyStringYesAPI key / PAT for publishing.
-FailOnConflictswitchNoThrow if the version already exists.

Examples

Example 1

PowerShellCode
Publish-BcRuntimePackage -PackagePath .\pkg.nupkg -Url $feed -ApiKey $key

Last modified on