Business Central Artifacts, they’re ripe for ripping!

In this video, I show how you can access the vast archive of BC artifacts to extract useful information.

https://youtu.be/DWxN32TPk78

In this video, Erik explores the world of Business Central (BC) artifacts — the downloadable components that Microsoft uses to build Docker containers — and demonstrates a creative use case: building a comprehensive translation database by “ripping” translation files from these artifacts. He walks through how artifacts are structured, how to access them via Azure Blob Storage, and shows a C# program that automates the extraction process.

What Are BC Artifacts?

When Microsoft started working with Docker containers for Business Central, they faced a combinatorial explosion: different versions of BC × different localizations × different Windows platforms resulted in an enormous number of container images. The solution, largely attributed to Freddy Kristiansen, was to separate the components. Instead of pre-building every possible container, Microsoft would provide a generic Windows container and layer the Business Central components on top.

These components — the “artifacts” — are stored on Azure and are publicly accessible. When you use New-BCContainer through BC Container Helper, this is exactly what’s happening behind the scenes: artifacts are downloaded and assembled into a container for you.

Exploring the Artifact URLs

Using the Get-BCArtifactUrl command, you get back a URL like:

bcartifacts.azureedge.net/sandbox/<version>/w1

Azure Edge is a content distribution network based on Azure, ensuring fast downloads. If you look at the BC Container Helper source code on GitHub, you can find three artifact URLs defined:

  • bcartifacts — The public artifacts, available to everyone
  • bcinsider — Requires a key and membership in the Insider program
  • bcprivate — Private artifacts

The public URL points to an Azure Blob Storage container, which means you can query it directly using the Azure Blob Storage REST API:

?comp=list&restype=container&prefix=18.3.27240.28243

This returns an XML list of all available blobs matching that prefix. Each blob corresponds to a country version — Austria (AT), Australia (AU), Belgium (BE), Brazil (BR), Canada (CA), and so on. There’s also a “base” version alongside W1.

Sandbox vs. On-Prem

Changing the URL path from /sandbox/ to /onprem/ gives you the on-premises artifacts, which typically have fewer builds per version and a shorter list of country localizations. On-prem artifacts go all the way back to version 10.

For sandbox, a given minor version like 18.2 may have many builds, and the results can span multiple pages indicated by a NextMarker element in the XML response.

What’s Inside an Artifact?

Erik downloaded the Canadian version artifact to examine its contents. The artifact is simply a ZIP file containing:

  • A SQL backup — The demo database
  • A Cronus license file
  • manifest.json — Metadata describing the artifact (country, database name, version, platform version)
  • Configuration packages — RapidStart packages with demo data
  • Applications and source code — ZIP files containing the source code for all apps
  • Language files — Translation files for the base app
  • Hidden extensions — For web service endpoints, API endpoints, etc.

Notably, the artifacts do not contain server executables or runtime files — just the content needed from a Business Central application perspective.

The Use Case: Building a Translation Database

Erik’s specific use case is compelling: he develops apps in English and wants to provide translations into many languages. Rather than spending time (or translation service costs) translating terms that Microsoft has already translated, he harvests Microsoft’s existing translations.

For example, if his app has a field called “Customer No.”, that field already exists in the base app and has been professionally translated into dozens of languages. Using Microsoft’s translations ensures consistency with the rest of the application.

The Ripper: A C# Program

Erik built a C# program that systematically downloads artifacts and extracts all translation data into a local database. Here’s how the process works at a high level:

Step 1: Enumerate Available Blobs

The program queries the Azure Blob Storage API to get a list of all available artifacts for a given version prefix. The outer loop handles pagination via the NextMarker element:

// Load the XML listing of all blobs
XmlDocument master = new XmlDocument();
master.LoadXml(client.DownloadString(url));

// Iterate through each blob entry
foreach (XmlNode xn in master.GetElementsByTagName("Blob"))
{
    string name = // extract name
    string country = // substring after the slash
    string version = // substring before the slash
    
    // Filter for the requested country/version
    if (/* not matching filter */) continue;
    
    // Download the artifact
    byte[] data = client.DownloadData(blobUrl);
    // Process...
}

Step 2: Extract App Files from the Artifact ZIP

Each downloaded artifact is a ZIP file held in memory. The program iterates through the ZIP entries looking for .app files:

using (MemoryStream ms = new MemoryStream(data))
using (ZipArchive zip = new ZipArchive(ms))
{
    foreach (ZipArchiveEntry entry in zip.Entries)
    {
        if (entry.FullName.EndsWith(".app"))
        {
            // Process the app file
        }
    }
}

Step 3: Crack Open the App Files

This is where it gets clever. As Erik covered in a previous video about app file dissection, a .app file is really just 40 bytes of header followed by a ZIP file. The program strips the header using Buffer.BlockCopy and opens the inner ZIP:

// Strip the 40-byte app file header
byte[] appContent = new byte[buffer.Length - 40];
Buffer.BlockCopy(buffer, 40, appContent, 0, appContent.Length);

// Now treat the remainder as a ZIP file
using (MemoryStream appStream = new MemoryStream(appContent))
using (ZipArchive appZip = new ZipArchive(appStream))
{
    foreach (ZipArchiveEntry appEntry in appZip.Entries)
    {
        // Look for translation files (.xlf) that aren't the 
        // compiler-generated default (g.xlf)
        if (appEntry.FullName.EndsWith(".xlf") 
            && !appEntry.FullName.EndsWith("g.xlf"))
        {
            // Extract and parse the translation file
        }
    }
}

Inside each app file, you’ll find entries like the NavX manifest, AL source code, entitlements, media resources, and — most importantly — translation (.xlf) files for various languages.

Step 4: Parse Translations and Store Them

Each XLF translation file is parsed using a custom XLF parser. The translations are stored in a LiteDB document database with the following structure:

// Translation record structure
class Translation
{
    public int Id { get; set; }
    public string Source { get; set; }    // English text
    public string Target { get; set; }    // Translated text
    public string Language { get; set; }  // Target language code
    public string Origin { get; set; }    // Where it came from
    public string Index { get; set; }     // Lookup index
    public string Hash { get; set; }      // For fast lookups
}

A hash is generated for each translation to enable quick lookups in the document database.

The Nested Loop Structure

To summarize, the program has three nested loops:

  1. Outer loop: Iterate through all blobs (country/version combinations)
  2. Middle loop: Iterate through all app files within each blob
  3. Inner loop: Iterate through all translations within each app file

Querying the Translation Database

After processing approximately 80% of all language versions, Erik had accumulated nearly a gigabyte of translation data. Querying the database for a phrase like “You are now in the %1 journal” returned 25 translations across various languages.

An interesting observation: the translations reveal real-world localization differences. For instance, Canadian French uses “journal” while Belgian French, France French, and Swiss French use “feuille” — demonstrating why having locale-specific translations matters.

Build Pipeline Integration

Erik’s broader vision is to integrate this translation database into his build pipeline. The source files included show a build configuration that already includes a Translate task:

{
  "Type": "Translate",
  "Settings": {
    "XLFPath": "%APPPATH%\\Translations\\%NAME%.g.xlf",
    "ProductName": "%NAME%"
  }
}

This task, part of Erik’s ALBuild system, can leverage the harvested translation database to automatically populate translations during the build process — reusing Microsoft’s official translations wherever possible and only requiring manual or machine translation for truly new strings.

Other Potential Uses for Artifacts

Beyond translations, Erik mentions several other creative uses for BC artifacts:

  • Harvesting symbols — Extracting symbol files for all versions, useful for tools like the Object Designer
  • Grabbing demo databases — Getting SQL backups for specific versions without spinning up a full container
  • Extracting RapidStart data — Pulling configuration packages for specific country versions
  • Source code analysis — Examining how Microsoft implements specific features across versions

Conclusion

BC artifacts are a rich, publicly accessible treasure trove of Business Central components sitting on Azure Blob Storage. While their primary purpose is to support Docker container creation via BC Container Helper, they can be accessed directly and repurposed for creative use cases. Erik’s translation ripper demonstrates the power of this approach: by systematically downloading and extracting translation files from artifacts across all country versions, he built a comprehensive translation database that can automatically provide consistent, Microsoft-quality translations for custom AL apps. The possibilities extend well beyond translations — anywhere you need structured data from Business Central’s many versions and localizations, artifacts are ripe for ripping.