Core module
Module: businessdev.ALbuild.Core • Tier: Free • Cmdlets: 23
The Core module is the foundation of ALbuild. It provides machine-level configuration, structured logging, license enforcement, version mathematics, the project configuration reader (albuild.json), build-order resolution for multi-root workspaces, race-safe build versioning, and the readable error formatter every other module relies on.
Cmdlets in this module
| Cmdlet | Description |
|---|---|
Assert-ALbuildLicensed | Throws unless a valid ALbuild license is present for the given feature. |
Assert-ALbuildModuleComplete | Verify an installed businessdev.ALbuild version folder is complete and loadable. |
Clear-ALbuildCache | Prunes old, unused ALbuild cache content to reclaim disk space on a build agent. |
Compare-BcVersion | Compares two Business Central version strings. |
ConvertTo-BcVersion | Converts a version string into a normalised 4-part [version]. |
Expand-BcAppFile | Extracts a Business Central .app/.runtime.app package and reads its manifest. |
Format-BcErrorMessage | Reduces a captured PowerShell error rendering to its core, human-readable message. |
Get-ALbuildCacheLockName | Returns the global mutex name that guards a cache folder against concurrent write/removal. |
Get-ALbuildConfig | Returns the effective ALbuild machine/runtime configuration. |
Get-ALbuildProjectConfig | Loads the effective ALbuild project configuration for an app folder. |
Get-BcContainerHostShare | Returns the host folder shared into a Business Central container (mapped to C:\run\my). |
Get-BcProjectBuildOrder | Discovers the AL projects under a repository root and orders them by inter-project dependency. |
Install-ALbuildModuleVersion | Atomically install ONE businessdev.ALbuild version into a module root, serialised across processes. |
Invoke-ALbuildProcess | Runs an external process, capturing stdout/stderr/exit code, with optional retry. |
Invoke-BcBuildVersionStamp | Stamps a build version into app.json and atomically claims a build branch for it. |
Move-ALbuildDirectory | Renames a directory into its final place, retrying the transient Windows lock that makes an otherwise-atomic publish fail at random. |
New-BcBuildBranch | Commits the working tree to a build/<version> branch and (optionally) pushes it. |
Repair-ALbuildModule | Make a module root healthy: reinstall the target version if it is broken, park any other broken versions for post-mortem, and prune old ones. |
Set-ALbuildConfig | Sets one or more ALbuild configuration values. |
Test-ALbuildLicense | Verifies an ALbuild commercial license against the licensing service. |
Test-BcPlatform | Reports (or asserts) the host's capability to run Business Central container operations. |
Test-BcVersionInRange | Tests whether a version satisfies a NuGet-style version range. |
Write-ALbuildLog | Writes a structured ALbuild log message. |
Assert-ALbuildLicensed
Throws unless a valid ALbuild license is present for the given feature.
The single licensing gate that licensed cmdlets call on entry (e.g. Marketplace, OnPrem, the runtime-package engine, cross-feed transitive resolution and AL validation). Free features never call it. If the license is invalid - or a trial has expired - it throws a terminating error with remediation guidance. An imminent trial expiry logs a warning but does not block.
Syntax
Code
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
-Feature | String | Yes | Name of the licensed feature requesting access (used in messages/telemetry). |
-TenantId | String | No | Optional tenant override (defaults as Test-ALbuildLicense). Default: $env:System_CollectionId. |
-TenantName | String | No | Optional tenant name override. Default: $env:System_CollectionUri. |
Output
None. Throws on failure.
Examples
Example 1
Code
Assert-ALbuildModuleComplete
Verify an installed businessdev.ALbuild version folder is complete and loadable.
The 2026-08 outage was a single .ps1 file lost during a concurrent, non-atomic Install-Module: the version still LOOKED installed, so every task imported a module that could not load (0x80131047). This check catches exactly that - a version folder that is present but missing a file.
It is cheap enough to run on every task (the steady-state path is: read the '.albuild-complete' marker, re-count the module's PowerShell files, compare) yet strict enough to notice a lost file:
- '<Path>\businessdev.ALbuild.psd1' exists and parses (Import-PowerShellDataFile).
- Every path in the manifest's NestedModules exists under <Path>.
- The '.albuild-complete' marker exists and its recorded PowerShell-file count + psd1 hash match the folder as it is now (a lost/added/edited .ps1|.psm1|.psd1 changes the count or the hash).
A folder with no marker is treated as INCOMPLETE (never trusted) - so a version installed by the old, marker-less bootstrap is re-verified/repaired on first use rather than trusted forever.
Syntax
Code
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
-Path | String | Yes | The version folder to verify, e.g. '...\Modules\businessdev.ALbuild\2.18.2'. |
-PassThru | switch | No | Return a findings object instead of throwing: { Complete, Path, Version, Reason, ExpectedCount, ActualCount }. Without it, an incomplete folder throws with the reason. |
Output
Nothing (throws on failure), or a PSCustomObject with -PassThru.
Clear-ALbuildCache
Prunes old, unused ALbuild cache content to reclaim disk space on a build agent.
Build agents accumulate large caches that ALbuild never trims on its own: the extracted artifact/symbol cache (ArtifactCacheFolder), the dependency package cache (PackageCacheFolder) and the Business Central artifact cache (BcArtifactCacheFolder) - the last two of which grow a new sub-folder per BC version and per package, unbounded. This cmdlet removes cache entries that have not been written to within a retention window, keeping recent ones.
The cache locations are read from Get-ALbuildConfig, so it prunes exactly the folders the tasks use (including any relocated to a roomier drive). Each cache's immediate child entries are the pruning unit; an entry is removed when its last-write age exceeds -KeepDays, except the newest -KeepLatest entries per cache are always retained as a safety floor. Age is measured by LastWriteTime (last-access time is unreliable - Windows disables it by default).
Supports -WhatIf/-Confirm (it is destructive), reports the space reclaimed per cache, and logs every removal so a scheduled cleanup is auditable from the pipeline log. With -IncludeDocker it also prunes unused Docker images and stopped containers older than the same window.
Syntax
Code
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
-KeepDays | Int32 | No | Retention window in days. Cache entries not written within this many days are removed. Default 30. Default: 30. |
-KeepLatest | Int32 | No | Always keep at least this many most-recently-written entries per cache, regardless of age (a safety floor so a cache is never emptied). Default 0. Default: 0. |
-Cache | String | No | Which caches to prune: All (default), Artifacts, Packages or BcArtifacts. Allowed values: All, Artifacts, Packages, BcArtifacts. Default: 'All'. |
-IncludeDocker | switch | No | Also run 'docker image prune' (unused images) and 'docker container prune' (stopped containers) filtered to the same -KeepDays window. Skipped with a note if the docker CLI is unavailable. Images carrying any -ProtectLabel are excluded - see that parameter. |
-ProtectLabel | String[] | No | Docker image labels that must NEVER be pruned by -IncludeDocker. Default 'albuild.image', which is the label New-BcImage stamps on the version-specific BC image cache. Why this is not optional: 'docker image prune -a' removes ALL unused images, not just dangling ones, and New-BcContainer invokes this cmdlet automatically when free disk drops below its floor. Without the exclusion, the one cmdlet that runs when the disk is tight would delete the image cache - which is largest, and most valuable, at exactly that moment. Each deleted image then costs a multi-minute rebuild. Retention of the image cache belongs to Optimize-BcImageCache, which evicts by least-recent-use against a budget and never touches an image a run has pinned. Pass an empty array to opt out (for a deliberate full reclaim). Default: @('albuild.image'). |
Output
PSCustomObject: per-cache Removed count / FreedBytes / FreedText plus a TotalFreedBytes/Text.
Examples
Example 1
Code
Example 2
Code
Compare-BcVersion
Compares two Business Central version strings.
Normalises both operands with ConvertTo-BcVersion and compares them, returning -1, 0 or 1 (reference less than, equal to, or greater than difference).
Syntax
Code
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
-ReferenceVersion | String | Yes | The left-hand version. |
-DifferenceVersion | String | Yes | The right-hand version. |
Output
System.Int32
Examples
Example 1
Code
ConvertTo-BcVersion
Converts a version string into a normalised 4-part [version].
Business Central versions are four-part (Major.Minor.Build.Revision). This helper accepts partial versions (e.g. "25", "25.1") and pads them to four parts, and tolerates NuGet/SemVer pre-release or build-metadata suffixes (e.g. "1.0.0-beta+sha") by using only the numeric core for comparison.
Syntax
Code
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
-InputObject | String | Yes | The version string to convert. Accepts pipeline input. |
-Strict | switch | No | When set, an empty or non-numeric value throws instead of returning 0.0.0.0. |
Output
System.Version
Examples
Example 1
Code
Example 2
Code
Expand-BcAppFile
Extracts a Business Central .app/.runtime.app package and reads its manifest.
A BC .app file is a ZIP archive preceded by a small binary header. This function locates the ZIP payload (by its local-file-header signature), extracts it, and parses the embedded NavxManifest.xml to return the app's identity and dependencies - all without a running container. Namespace/casing differences are tolerated by matching element local-names.
A runtime package (an ISV's protected on-prem distribution) is not encrypted, only lightly obfuscated: after an 8-byte '.NEA' marker at offset 40, the ZIP is scrambled with an RC4 keystream using a fixed, public 6-byte key. This is de-obfuscated in-memory so a runtime package's real id/name/version is read on the host, exactly like an ordinary .app. (The .app runtime container format is documented by the community project SimonOfHH/D365BCAppHelper.)
Syntax
Code
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
-Path | String | Yes | Path to the .app or .runtime.app file. |
-DestinationPath | String | No | Optional folder to extract into. Defaults to a unique temp folder (returned as ExtractedPath); the caller owns cleanup. |
Output
PSCustomObject with Id, Name, Publisher, Version, Application, Platform, Dependencies, ManifestPath, ExtractedPath, Manifest (xml).
Examples
Example 1
Code
Format-BcErrorMessage
Reduces a captured PowerShell error rendering to its core, human-readable message.
Error text captured from a child process or an in-container command arrives wrapped in
PowerShell's error-view scaffolding - the offending source line, the '~~~~' underline,
'At line
Syntax
Code
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
-Text | String | No | The captured error text. |
Output
System.String
Get-ALbuildCacheLockName
Returns the global mutex name that guards a cache folder against concurrent write/removal.
ALbuild caches are shared by every build running on a host - several agents on the same server, and (since the runtime factory) several worker threads inside a single agent job. Two operations must never overlap on the same cache entry:
- Get-BcArtifact EXTRACTING into it, and
- Clear-ALbuildCache REMOVING it.
Both sides serialise on a machine-global mutex, and a mutex only serialises anything if both sides compute the SAME name. That is the entire reason this helper exists: the name used to be an inline expression in Get-BcArtifact, so the prune path had no way to agree with it without copying the expression - and a copied hash silently stops matching the moment either copy is touched. A prune that no longer matches does not fail loudly; it deletes a folder out from under a reader, and the build fails much later with a misleading "package ... could not be found".
The name is derived from the folder path so that unrelated cache entries never contend: SHA-256 over the lower-cased path, hex, truncated to 32 characters (well inside the 260-character limit on a mutex name), prefixed 'Global\albuild-artifact-' so it spans sessions and services. Lower-casing matters because Windows paths are case-insensitive: 'C:\alb' and 'c:\ALB' are the same folder and must yield the same lock.
The 'Global' prefix requires no elevation for the creating account, but a mutex created by one user is not writable by another by default; agents on a host run as the same service account, so this is not a constraint in practice.
Syntax
Code
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
-Path | String | Yes | The cache folder the lock protects. Does not need to exist - callers lock BEFORE creating it. |
Output
System.String: the fully-qualified mutex name.
Examples
Example 1
Code
Get-ALbuildConfig
Returns the effective ALbuild machine/runtime configuration.
Machine-scoped tooling settings (cache folders, the AL Tool store, retry, telemetry, licensing) - NOT the committed, per-workspace project settings (country, artifact type, test runner, ...), which are loaded with Get-ALbuildProjectConfig.
Builds a [ALbuildConfig] instance by merging, in increasing precedence:
- Built-in defaults (the ALbuildConfig constructor)
- The PER-USER config file, if present (legacy location)
- The MACHINE-WIDE config file, if present
- In-memory overrides set with Set-ALbuildConfig
The machine file deliberately outranks the per-user one. These are properties of the SERVER - which drive holds the artifact cache, where the AL Tool lives - so an administrator's system-wide setting must not be silently undone by a file in some account's profile. That precedence is what makes the setting trustworthy on a shared build agent; a developer box can still keep a personal file, it just loses against an explicit machine setting.
ALBUILD_CONFIG names one explicit file and replaces the layering entirely.
Why it matters: the per-user file used to be the only store, so the agent account could hold 'G:\alb' in its own profile while an administrator on the same machine read the built-in default 'C:\alb' - two different caches on one server, with nothing pointing out the split. Both halves of that trap are now closed: the machine file is the canonical store, and a config that exists only per-user is reported as such instead of passing for a machine setting.
The merged instance is cached for the session; use -Refresh to rebuild it (for example after editing a config file on disk).
Syntax
Code
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
-Name | String | No | Optional single setting name to return instead of the whole object. |
-Refresh | switch | No | Rebuilds the cached configuration from defaults + files + overrides. |
-Source | switch | No | Returns where the configuration comes from instead of the values: the candidate files, whether they exist, and which settings each one contributes. Use it when a value is not what you expect - the answer is almost always "a different file than you think". |
Output
ALbuildConfig, the requested setting value, or (with -Source) a PSCustomObject describing the resolution.
Examples
Example 1
Code
Example 2
Code
Example 3
Code
Get-ALbuildProjectConfig
Loads the effective ALbuild project configuration for an app folder.
Builds an [ALbuildProjectConfig] by merging, in increasing precedence:
- Built-in defaults (the ALbuildProjectConfig constructor)
- The workspace-root config file (when -WorkspaceRoot is given and differs from the app folder) - the shared settings for the whole repository
- The app-folder config file - per-app overrides (e.g. a different country or test runner for a test app in a multi-root workspace)
Each config file is 'albuild.json' (canonical) or, as a deprecated fallback, the V1 'pipeline.config' (parsed with a deprecation warning). This is the single project config surface: one file per workspace and, optionally, one per app folder.
Syntax
Code
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
-AppFolder | String | Yes | The app folder whose effective configuration is requested. |
-WorkspaceRoot | String | No | The workspace root that holds the shared config. When omitted (or equal to -AppFolder), only the app-folder config is applied. |
Output
ALbuildProjectConfig.
Examples
Example 1
Code
Get-BcContainerHostShare
Returns the host folder shared into a Business Central container (mapped to C:\run\my).
Getting files into a container with 'docker cp' does not work for a running hyperv-isolated container (the Docker Desktop default on Windows client hosts) - "filesystem operations against a running Hyper-V container are not supported". Instead the container is created with a host folder bind-mounted to C:\run\my; writing a file to that host folder makes it appear inside the container. This returns the per-container host folder, chosen for the environment:
- Azure DevOps agent: under AGENT_TEMPDIRECTORY (job-scoped, writable, auto-cleaned).
- Windows host: C:\ProgramData\ALbuild<container> (shared, like BcContainerHelper).
- other: a temp folder (BC containers are Windows-only, so this is a fallback).
New-BcContainer mounts this folder; Copy-BcFileToContainer writes into it; Remove-BcContainer cleans it up. The path is deterministic from the container name so every step agrees on it.
Syntax
Code
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
-Name | String | Yes | Container name. |
Output
System.String - the host folder path.
Get-BcProjectBuildOrder
Discovers the AL projects under a repository root and orders them by inter-project dependency.
Finds every app.json under -Path (recursively, excluding symbol/package/output folders), reads each project's id/name/publisher/version and its declared dependency ids, drops any project whose folder leaf name is listed in -ExcludeProjects, then returns the projects topologically sorted so that a project always comes after the in-repo projects it depends on (e.g. 'app' before 'test'/'migration'). Dependencies on apps that are not part of the repository are ignored for ordering - they come from feeds or are already in the container. Throws on a dependency cycle.
This is the single source of "which projects, in what order" for the multi-root build: the ResolveDependencies, CompileApp and PublishApp tasks all walk this list.
Syntax
Code
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
-Path | String | No | Repository root to search, a single app folder, or a single app.json. Default: current location. Default: (Get-Location).Path. |
-ExcludeProjects | String[] | No | Folder leaf names to exclude from the build (case-insensitive), e.g. @('migration'). |
Output
PSCustomObject per project, in build order: Folder, Name, Id, Publisher, Version, AppJsonPath, RepoDependencyIds.
Examples
Example 1
Code
Install-ALbuildModuleVersion
Atomically install ONE businessdev.ALbuild version into a module root, serialised across processes.
The safe replacement for Install-Module, which expands straight into the destination and is NOT
atomic - the 2026-08 outage was a file lost when concurrent agents (separate processes, one shared
account/module root) ran Install-Module into the same folder at once.
This:
- takes a machine-wide mutex keyed by the module root (agents are separate processes, so a per-process lock is useless) - §4.2;
- re-checks under the lock (the common contended case is that another job already did the work);
Save-Modules into a private staging folder ON THE SAME VOLUME as the module root, so the final publish is an atomic rename (a reader sees either the old folder or the new one, never a half-written one) - §4.1;- structurally verifies the staged copy and writes the '.albuild-complete' marker (the count+hash fingerprint Assert-ALbuildModuleComplete later checks);
- parks a pre-existing but BROKEN target as '<version>.defekt-<timestamp>' (never deletes it - §4.5/R8) and moves the verified copy into place;
- prunes old versions to -KeepVersions inside the same lock (§4.5).
Syntax
Code
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
-Version | String | Yes | The exact version to install (e.g. '2.18.2'). |
-ModuleRoot | String | No | The module root to install into (…\Modules). Defaults to this edition's CurrentUser module root (WindowsPowerShell vs PowerShell - they differ, which is itself a documented trap). |
-Repository | String | No | PowerShellGet repository name. Default 'PSGallery'. Default: 'PSGallery'. |
-KeepVersions | Int32 | No | Prune to this many newest complete versions after a successful install (0 = do not prune). '.defekt-' folders are always exempt. Default 3. Default: 3. |
-MutexTimeoutSeconds | Int32 | No | Max seconds to wait for the install lock. Default 600. Default: 600. |
-Force | switch | No | Reinstall even if the target already verifies as complete. |
Output
PSCustomObject { Version; Path; Action ('present'|'installed'); ModuleRoot }.
Invoke-ALbuildProcess
Runs an external process, capturing stdout/stderr/exit code, with optional retry.
A reliable wrapper around System.Diagnostics.Process that:
- captures stdout and stderr without dead-locking (asynchronous reads);
- treats a configurable set of exit codes as success;
- optionally retries on failure with a fixed back-off (for transient I/O);
- throws a terminating, descriptive error on final failure unless -PassThru is used. Argument handling is dual-target: ArgumentList is used on PowerShell 7+, with a quoted fallback for Windows PowerShell 5.1.
Syntax
Code
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
-FilePath | String | Yes | The executable to run. |
-Arguments | String[] | No | Arguments passed to the executable (array form; no manual quoting required). |
-WorkingDirectory | String | No | Working directory for the process. |
-SuccessExitCodes | Int32[] | No | Exit codes considered successful. Default: 0. Default: @(0). |
-RetryCount | Int32 | No | Number of additional attempts on failure. Default: 0 (no retry). Default: 0. |
-RetryDelaySeconds | Int32 | No | Delay between attempts. Default: 5. Default: 5. |
-PassThru | switch | No | Return the result object even on failure instead of throwing. |
-StreamOutput | switch | No | Echo the process's stdout to the host line-by-line as it is produced (still captured in the returned StdOut), so long-running children show live progress instead of appearing all at once when they exit. Stderr is captured but not echoed live. |
Output
PSCustomObject with ExitCode, StdOut, StdErr, Success, Attempts.
Examples
Example 1
Code
Invoke-BcBuildVersionStamp
Stamps a build version into app.json and atomically claims a build branch for it.
The pipeline-level versioning step. It combines version stamping (Set-BcAppVersion) and build branch creation (New-BcBuildBranch) into one operation that is safe when several builds run in parallel - the classic race where two simultaneous jobs compute the same version.
Concurrency is solved with a branch-push compare-and-swap: the build branch name encodes the version, so pushing it (without --force) is an atomic claim on the remote. If the push is rejected because the branch already exists (another build claimed that version), the version is incremented and the claim is retried, up to -MaxAttempts. Any other push failure throws.
With -NoBuildBranch the version is written but nothing is committed or pushed (used by the local pipeline runner so a developer's run never claims a version or touches the remote). An explicit -Version is stamped as-is and claimed once (a conflict throws rather than retrying).
Syntax
Code
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
-Path | String | No | Repository root to search for app.json files, an app folder, or a single app.json. Default: the current location. Default: (Get-Location).Path. |
-RepositoryRoot | String | No | The git working tree. Defaults to -Path when it is a folder, otherwise its parent. |
-Schema | String | No | Token schema for computing the version (see Set-BcAppVersion). Default 'major.minor.increment.0'. Default: 'major.minor.increment.0'. |
-SeedFromBuildBranch | switch | No | Consume the version from the latest build branch instead of claiming a new one (release/CD use). The release pipeline checks out the source branch, where app.json carries the product Major.Minor but a placeholder Build (e.g. 1.1.0.0). This reads the highest build/<Major>.<Minor>.* already on the remote (the CI build, e.g. 1.1.588.0), applies -Schema to it, and stamps every app to the result - e.g. schema 'latest.latest.latest.build-id' -> 1.1.588.<BuildId>. Read-only on the remote (no branch is claimed or pushed, so no Contribute permission is needed). If no matching build branch exists, it warns and falls back to the app.json-derived version. Schema-only. |
-Version | String | Yes | An explicit version (overrides -Schema); stamped as-is and claimed once. |
-BuildId | String | No | Value for the 'build-id' schema token. Defaults to BUILD_BUILDID, or '0'. |
-OnlyUpdateOnChangedSource | switch | No | Only stamp apps whose folder changed in the last commit; if nothing changed, no branch is claimed. |
-BranchPrefix | String | No | Build branch name prefix. Default 'build/'. Default: 'build/'. |
-Remote | String | No | Remote to claim the branch on. Default 'origin'. Default: 'origin'. |
-MaxAttempts | Int32 | No | Maximum claim attempts before giving up. Default 99. Default: 99. |
-NoBuildBranch | switch | No | Stamp only; do not commit, branch or push (dry-run for local runs). |
-UserName | String | No | git identity name for the commit. Default 'ALbuild CI'. Default: 'ALbuild CI'. |
-UserEmail | String | No | git identity email for the commit. Default 'albuild@365businessdev.com'. Default: 'albuild@365businessdev.com'. |
Output
PSCustomObject: Version, Branch, Attempts, Claimed, Committed, Apps.
Examples
Example 1
Code
Example 2
Code
Move-ALbuildDirectory
Renames a directory into its final place, retrying the transient Windows lock that makes an otherwise-atomic publish fail at random.
ALbuild publishes everything expensive the same way: write into a staging folder on the same volume, then rename it into place, so a reader never sees a half-written result. The rename itself is atomic - but ISSUING it is not reliable on Windows. A file that was written moments ago can still be held briefly by a virus scanner, the search indexer or a lazily-closed handle, and Directory.Move then fails with "Access to the path ... is denied" on the SOURCE.
That is not theoretical. Hunting a reported test flake across repeated local runs showed the same failure at three different call sites - Get-BcArtifact ('.staging'), Get-BcArtifactSymbolFolder ('.albsym-') and Install-ALbuildModuleVersion ('.albuild-staging-') - roughly once every six runs. On a build agent that is not a flaky test, it is a failed artifact download or a failed module install, from a condition that would have passed a moment later.
So the rename is retried with a growing delay. Two cases are deliberately NOT retried:
- The destination already exists. That means a concurrent publisher won the race, which is a state the callers already handle (keep theirs, or replace a partial one). Retrying would only delay that decision by the whole backoff budget.
- Anything that is not a lock: a missing source, a cross-volume move, an invalid path. Those do not get better by waiting, and hiding them behind seconds of retries makes them harder to diagnose, not easier.
When every attempt fails the error names the source, the destination, the number of attempts and the original message, so a build log says what actually happened instead of "access denied".
Syntax
Code
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
-Path | String | Yes | The staging directory to publish. |
-Destination | String | Yes | The final directory name. Must be on the same volume for the rename to be atomic. |
-RetryCount | Int32 | No | How many attempts in total (default 5). With the default delay that spans about 3 seconds. Default: 5. |
-InitialDelayMilliseconds | Int32 | No | Delay before the second attempt; it doubles each time (default 100 -> 100/200/400/800 ms). Default: 100. |
Examples
Example 1
Code
New-BcBuildBranch
Commits the working tree to a build/<version> branch and (optionally) pushes it.
Creates (or resets) a branch named "<BranchPrefix><Version>" at the current HEAD, stages all changes, commits them when there is something to commit, and pushes the branch to the remote. This is the carrier of the versioned sources produced by a build; the parallel-build lock is handled separately by Invoke-BcBuildVersionStamp.
Pushing requires that the checkout step persisted credentials (e.g. Azure DevOps 'persistCredentials: true'). A push that is rejected because the branch already exists with divergent history is reported as a non-terminating conflict (the returned object's Pushed is $false and a warning is logged) rather than thrown, so a caller doing its own concurrency control can react. A push rejected because the identity lacks contribute permission (Azure DevOps TF401027 / HTTP 403) is likewise non-terminating (Pushed $false, PermissionDenied $true), so the caller can fall back to a stamp-only build. Any other push failure (auth, missing remote, network) throws.
Syntax
Code
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
-Version | String | Yes | The version that names the branch. |
-RepositoryRoot | String | No | The git working tree. Default: the current location. Default: (Get-Location).Path. |
-BranchPrefix | String | No | Prefix for the branch name. Default 'build/'. Default: 'build/'. |
-CommitMessage | String | No | Commit message. Default "Build <Version>". |
-UserName | String | No | git author/committer name set locally for the commit. Default 'ALbuild CI'. Default: 'ALbuild CI'. |
-UserEmail | String | No | git author/committer email set locally for the commit. Default 'albuild@365businessdev.com'. Default: 'albuild@365businessdev.com'. |
-Remote | String | No | The remote to push to. Default 'origin'. Default: 'origin'. |
-Force | switch | No | Force-push the branch (overwrites an existing remote branch). Use with care. |
-NoPush | switch | No | Create and commit the branch locally without pushing (used by the local pipeline runner). |
Output
PSCustomObject: Branch, Committed, Pushed, Conflict, PermissionDenied.
Examples
Example 1
Code
Repair-ALbuildModule
Make a module root healthy: reinstall the target version if it is broken, park any other broken versions for post-mortem, and prune old ones.
The recovery half of the integrity design (§4.4/§4.5). Given a target version:
- (re)install it atomically if it is missing or fails verification (Install-ALbuildModuleVersion re-checks and only rewrites a broken/absent folder);
- park every OTHER version that fails Assert-ALbuildModuleComplete as '<v>.defekt-<timestamp>' (renamed, never deleted - R8), so a poisoned side-by-side copy can't be imported and is kept for analysis;
- prune healthy old versions to -KeepVersions.
Syntax
Code
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
-Version | String | Yes | The version to guarantee is present and complete (e.g. '2.18.2'). |
-ModuleRoot | String | No | Module root to repair. Default: this edition's CurrentUser root. |
-Repository | String | No | Repository for the reinstall. Default 'PSGallery'. Default: 'PSGallery'. |
-KeepVersions | Int32 | No | Newest versions to retain when pruning. Default 3. Default: 3. |
Output
PSCustomObject { Version; Path; Installed; Parked [string[]]; Pruned [string[]] }.
Set-ALbuildConfig
Sets one or more ALbuild configuration values.
Applies overrides on top of the current configuration. By default the override lives for the session only; with -Persist it is written to a config file so it survives future sessions.
-Persist writes the MACHINE-WIDE file by default. These are properties of the server - which drive holds the artifact cache, where the AL Tool lives - and every account has to agree on them. Writing them per user is what let one server carry two artifact caches: the agent account had 'G:\alb' in its own profile while an administrator read the built-in default 'C:\alb'.
Only settings that DIFFER from the built-in defaults are written. The file therefore stays a short, readable statement of what this machine does differently, and a value ALbuild changes in a later release (a licensing endpoint, a retry count) still reaches the agent instead of being frozen by a file that once captured every key.
Syntax
Code
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
-Settings | Hashtable | Yes | A hashtable of setting name/value pairs to apply. |
-Persist | switch | No | Persists the configuration to the file for -Scope. |
-Scope | String | No | Machine (default) - the shared file under the common application-data folder ('C:\ProgramData\ALbuild\config.json'). Requires elevation on Windows, which is the point: a machine-wide setting should take an administrator. User - the per-user file under the roaming application-data folder. Ranks BELOW the machine file when both exist (see Get-ALbuildConfig), so use it for a personal developer box, never to configure a build agent. ALBUILD_CONFIG, when set, overrides both. Allowed values: Machine, User. Default: 'Machine'. |
Output
System.Collections.Specialized.OrderedDictionary (the updated configuration).
Examples
Example 1
Code
Example 2
Code
Test-ALbuildLicense
Verifies an ALbuild commercial license against the licensing service.
Free-tier functionality performs no license check. Licensed features call this to verify a tenant's license via the 365 business development licensing service. The result is cached per tenant for the session. Network/service failures do NOT throw - they return an object with IsValid = $false and a Reason - so that license enforcement is an explicit decision of the caller (see Assert-ALbuildLicensed), never an accidental side effect.
Two offline accommodations exist for trusted agents that cannot always reach the service:
- ALBUILD_LICENSE_KEY - when this environment variable is set, the tenant is authorized without contacting the service (set it as a secret on agents in locked-down networks).
- Grace window - a successful verification is cached to disk; if the service is later unreachable, that cached result keeps the feature working for LicenseGraceDays days (config, default 14). An explicit invalid/expired response is never graced (the cache is cleared), so a revoked license cannot keep working offline.
Syntax
Code
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
-TenantId | String | No | The Azure DevOps organization / collection id. Defaults to $env$env:System_CollectionId. |
-TenantName | String | No | The Azure DevOps organization / collection URI. Defaults to $env$env:System_CollectionUri. |
-Refresh | switch | No | Bypass the per-tenant session cache and re-query the service. |
Output
PSCustomObject with IsValid, Status, IsTrial, ExpiresOn, TenantId, TenantName, Reason.
Examples
Example 1
Code
Test-BcPlatform
Reports (or asserts) the host's capability to run Business Central container operations.
Business Central container images are Windows-only, so container operations require a Windows host with a reachable Docker CLI. This function detects the platform and Docker availability and returns a capability object. With -Require it throws a clear, actionable terminating error when the prerequisites are not met (used by container cmdlets to gate execution off-platform).
Syntax
Code
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
-Require | switch | No | Throw if the host cannot run BC containers (not Windows, or Docker CLI unavailable). |
-Quiet | switch | No | Return a single boolean (CanRunContainers) instead of the capability object. |
Output
PSCustomObject with IsWindows, HasDocker, CanRunContainers, PSEdition, OSDescription; or System.Boolean when -Quiet is used.
Examples
Example 1
Code
Example 2
Code
Test-BcVersionInRange
Tests whether a version satisfies a NuGet-style version range.
Implements NuGet version-range semantics (https://learn.microsoft.com/nuget/concepts/package-versioning#version-ranges):
1.0 minimum, inclusive ( x >= 1.0 ) [1.0] exact ( x == 1.0 ) [1.0,) minimum, inclusive ( x >= 1.0 ) (1.0,) minimum, exclusive ( x > 1.0 ) (,1.0] maximum, inclusive ( x <= 1.0 ) (,1.0) maximum, exclusive ( x < 1.0 ) [1.0,2.0] inclusive both ends [1.0,2.0) inclusive low, exclusive high (1.0,2.0) exclusive both ends
A missing endpoint is treated as unbounded regardless of bracket style. Versions are normalised to four parts via ConvertTo-BcVersion.
Syntax
Code
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
-Version | String | Yes | The version to test. |
-Range | String | Yes | The NuGet version range expression. |
Output
System.Boolean
Examples
Example 1
Code
Write-ALbuildLog
Writes a structured ALbuild log message.
Emits a consistently formatted log line. When running inside Azure DevOps (TF_BUILD set), warnings and errors additionally emit the corresponding "##vso[task.logissue]" logging commands so they surface in the pipeline summary. Verbose and Debug levels honour the caller's $VerbosePreference / $DebugPreference instead of writing to the host.
Syntax
Code
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
-Message | String | No | The text to log. Accepts pipeline input and may be empty (blank line). |
-Level | String | No | Severity: Verbose, Debug, Information (default), Success, Warning or Error. Allowed values: Verbose, Debug, Information, Success, Warning, Error. Default: 'Information'. |
Examples
Example 1
Code
Example 2
Code


