Architecture & modules
ALbuild is a single PowerShell package, businessdev.ALbuild, composed internally of use-case nested modules that ship and version together as one package. The Azure DevOps extension, the VS Code extension, the CLI and the MCP server are thin shells on top of it.
Code
All four surfaces are thin shells over the same module. The CLI and the MCP server added the cross-platform and AI-agent entry points, but none of them re-implement container, compile or coverage logic. The behaviour is identical wherever you drive it from.
The one deliberate exception: AL Probe
albuild probe is not a shell over the PowerShell module. It is an AL
interpreter written in C# and hosted inside the CLI: a lexer, parser and tree-walking evaluator
that reads your .al source directly and evaluates expressions and procedure calls in-process.
That is the whole point of it. Every other surface answers "what does Business Central do?" by asking Business Central, which costs a container and minutes. Probe answers a narrow slice of the same question in well under a second, with no Docker and no server, so an agent can check a piece of logic as often as it likes.
The cost of a second engine is the risk of the two disagreeing, so probe is built to refuse rather
than guess: anything it cannot model exactly (Commit, Random, unsupported CalcFormula kinds,
Format format strings) returns a refusal, not an approximation. A
differential harness runs the same AL through both engines and
turns any disagreement into a failing test. Probe is a fast pre-check, never a replacement for
running the real tests.
The nested modules
| Area (nested module) | Purpose | Tier |
|---|---|---|
businessdev.ALbuild.Core | Config, logging, telemetry, licensing, helpers, version math, project config, build order, version stamping | Free |
businessdev.ALbuild.Containers | Artifacts, Docker container lifecycle, Traefik v3, certificates | Free |
businessdev.ALbuild.Apps | Compile (al tool / container), publish/install/sign/test, XLIFF | Free (except AppSource validation) |
businessdev.ALbuild.Feeds | Feed providers + the transitive dependency resolver, NuGet packaging | Free / Licensed |
businessdev.ALbuild.RuntimePackages | Batched / parallel / incremental runtime-package engine | Licensed |
businessdev.ALbuild.Marketplace | AppSource / Partner Center ingestion | Licensed |
businessdev.ALbuild.OnPrem | On-prem publish/install/upgrade, PTE, Dev | Licensed |
businessdev.ALbuild.Environments | Demo/dev environment provisioning | Licensed |
businessdev.ALbuild.Pipeline | Local pipeline runner | Free |
Licensing tiers are enforced at runtime (via Assert-ALbuildLicensed), independent of packaging, every cmdlet ships in the one package, and the licensed ones check on entry. See Licensing & tiers.
Naming conventions
- Cmdlet nouns use the
Bcprefix for Business Central operations (New-BcContainer,Invoke-BcCompiler) andALbuildfor ALbuild's own tooling, configuration, logging and licensing (Get-ALbuildConfig,Write-ALbuildLog,Assert-ALbuildLicensed). - Every public cmdlet ships comment-based help; the cmdlet reference pages are generated from it.
Two kinds of configuration
ALbuild deliberately separates per-repository settings from per-machine settings:
albuild.json (project config) | Get-/Set-ALbuildConfig (machine config) | |
|---|---|---|
| Scope | Per repository / per app folder | Per machine / per user |
| Committed to source control? | Yes | No |
| Controls | Country, artifact type, BC version, selection, test runner, dependency feeds | Artifact/package cache folders, retry, telemetry, licensing |
| Reference | Project configuration | Get-ALbuildConfig / Set-ALbuildConfig |
The machine configuration file (config.json)
The per-machine settings live in a JSON file the module reads automatically (so the CLI, the Azure DevOps tasks and the MCP server all honour it). You normally manage it with cmdlets rather than editing it by hand:
Code
-Persist writes the file; without it the change lasts only for the current session.
These are machine settings, so they are stored machine-wide by default, in two layers:
| Layer | File | Precedence |
|---|---|---|
Machine (-Scope Machine, the default) | C:\ProgramData\ALbuild\config.json | Wins |
User (-Scope User) | %APPDATA%\ALbuild\config.json | Below the machine file |
Writing the machine file requires an elevated session on Windows, which is deliberate: a
build agent's cache location should not be something one account can change out from under another.
Without elevation you get an explicit message naming the scope, and can fall back to
-Scope User for a developer box.
Why the machine layer exists
The per-user file used to be the only store, and it caused a silent split on build servers: the agent account had one cache folder in its own profile while an administrator on the same machine read the built-in default and saw a completely different cache. Nothing announced the divergence. If you still have a per-user file from that era, remember it now ranks below the machine file.
ALBUILD_CONFIG still names one explicit file and then replaces the layering entirely. It is
meant for tests and for pinning a configuration in an isolated run, not as the normal way to
configure an agent:
Code
It is plain JSON, so you can also drop the file in place directly:
Code
Settings and their defaults:
| Setting | Default | Purpose |
|---|---|---|
BaseFolder | C:\alb on Windows, ~/.local/share/ALbuild elsewhere | Root for the cache folders below. Windows deliberately uses a short path at the system-drive root: paths inside a BC artifact reach ~245 characters on their own, and the Azure DevOps task runs under Windows PowerShell 5.1, which enforces the 260-character MAX_PATH limit hard. A deeper per-user base intermittently pushed artifact extraction over it. |
ArtifactCacheFolder | …\ALbuild\artifacts | Host-side extracted BC artifacts (compile symbols). |
PackageCacheFolder | …\ALbuild\packages | Downloaded NuGet / dependency packages. |
BcArtifactCacheFolder | C:\bcartifacts.cache | Persistent cache shared into every container at C:\dl, so the BC artifact is downloaded once per host and reused by later containers. BcContainerHelper-compatible. |
ProcessRetryCount / ProcessRetryDelaySeconds | 3 / 5 | Retry policy for external processes. |
TelemetryEnabled / TelemetryConnectionString | false / (empty) | Opt-in usage telemetry. |
LicensingBaseUrl / LicenseAppId / LicenseGraceDays | (service) / (app id) / 14 | Licensing service for the paid tier (offline grace window in days). |
Self-hosted agents: stop re-downloading the artifact
Container creation otherwise downloads the full BC artifact (multiple GB) inside every new container. Set the artifact cache to a roomy, persistent drive once — as a machine environment variable:
Code
or in config.json (BcArtifactCacheFolder). The first container on the host fills it; every later container reuses it. Precedence: New-BcContainer -ArtifactCacheFolder → ALBUILD_BCARTIFACT_CACHE → the config value (-NoArtifactCache disables it). Microsoft-hosted agents are ephemeral, so there's nothing to reuse — leave the default.
Readable errors everywhere
Every surface reduces failures to a single, clean message via Format-BcErrorMessage (the VS Code extension mirrors it as an ALBUILD-ERROR: summary), instead of dumping a raw PowerShell error. Full detail is always available in the underlying log / output channel.
Platform support
- Container operations (
*-BcContainer, test toolkit, in-container compile/test) require a Windows host with a running Docker engine. They fail fast with a clear message on other platforms, enforced byTest-BcPlatformandTest-BcDocker. - Container-less operations: compilation via Microsoft's cross-platform
aldotnet tool, dependency resolution, signing, Marketplace/SaaS REST and XLIFF, run on any platform with PowerShell 7. albuild probeneeds neither Docker nor PowerShell nor a Business Central installation. It is pure .NET inside the CLI and runs anywhere the CLI runs.


