Run-AlPipeline

WARNING: Very boring long blog post ahead…

Run-AlPipeline is a new function in BcContainerHelper. It has been in preview for a number of releases while being worked on, and a number of partners have already tried to use it. My apologies for changing things under your feet, but to my defense – I did write that the function was in preview.

With version 1.0.8 of BcContainerHelper, Run-AlPipeline is ready for real-life usage… (I think)

Goal

The goal of this function is that you can use the function as a build mechanism for most PTEs (Per Tenant Extensions) and for some AppSource apps. The function does not make any attempt to allow base app modifications, on-premises apps nor to support older versions of Business Central or NAV.

You might be able to make it work for a lot of different things, but then again, you might find yourself fighting assumption because the function is targeting Business Central online only.

You can run the Run-AlPipeline function on any machine, with Docker installed and BcContainerHelper 1.0.8 or later. The function doesn’t require any metadata, no settings files, no environment variables, all properties are transferred in parameters. This means that this function can be used locally, on Azure DevOps, in Github actions or on Gitlab.

So, even though the function is called Run-AlPipeline, there isn’t a pipeline definition anywhere that gets run, it is an end 2 end thing.

Sample run

Before going in detail with all parameters of the Run-AlPipeline function, let me show a sample run. I have cloned this repo https://dev.azure.com/businesscentralapps/BingMaps to my local box, after which I run this:

Run-AlPipeline `
    -pipelineName "BingMaps" `
    -licenseFile "c:\temp\license.flf" `
    -baseFolder "C:\Users\freddyk\Documents\GitHub\BusinessCentralApps\BingMaps" `
    -appFolders @("app") `
    -testFolders @("test") `
    -installTestFramework `
    -enablePerTenantExtensionCop `
    -enableUICop

This function causes an output, which you can watch here.

Examining the output will give you an idea of what this is. It will create a container, compile the apps specified, publish the apps, run the tests and remove the container.

But this is of course the simplest of runs. You can have multiple app folders, which then would be compiled and published in order of dependencies. You can have dependent apps, that needs to be installed, you can have previous versions of the apps and want to test the upgrade process and so on and so on.

Real life

The function is already used in all my pipelines, including the Hello World: https://dev.azure.com/businesscentralapps/HelloWorld and the AppSource version of Hello World: https://dev.azure.com/businesscentralapps/HelloWorld.AppSource.

The AppSource Version is setup for AppSourceCop. breaking change validation etc. and the PTE version is setup for Per Tenant Extension Cop and the stuff needed for PTE.

Please excuse the very long list of boring parameters…

Parameters (in order of likelihood to be used)

-pipelineName

The name of the pipeline or project.

-artifact

The description of which artifact to use. This can either be a URL (from Get-BcArtifactUrl) or in the format storageAccount/type/version/country/select/sastoken, where these values are transferred as parameters to Get-BcArtifactUrl. Default value is ///us/current, which will give you the current public US version.

-licenseFile

Unless you have a PTE with no tests, no will need a licensefile. Using the test framework will require a license file and so will your appSource app. The license file does not have to reside inside the base folder and can be a secure url to a license file, which then will be downloaded and used. The recommended way is to have this secure url in an Azure KeyVault for use locally and in pipelines.

-baseFolder

The baseFolder serves as the base Folder for all other parameters including a path (appFolders, testFolders, testResultFile, outputFolder and packagesFolder). The base folder doesn’t need to be the immediate parent of subfolders, but this folder is shared with the container, responsible for building and testing the app and the container needs access to all these folders. You should not root level folders as folder sharing with containers tend to get slower if the folder shared contains a high number of files and directories.

-appFolders

appFolders is an array of folders, containing app sourcecode that needs to be compiled and published. It can also be a comma-seperated string of app folders, the function will auto-convert to an array.

Run-AlPipeline will sort the app Folders after dependencies and will compile, sign and publish the dependent apps first and the depending apps last.

-testFolders

testFolders is an array of folders, containing test app sourcecode that needs to be compiled and published. It can also be a comma-seperated string of test app folders, the function will auto-convert to an array.

Run-AlPipeline will sort the test app Folders after dependencies and will compile, publish and test the dependent apps first and the depending apps last.

-installApps

installApps is an array of third party .app or .zip files. It can also be a comma-separated string of .app or .zip files, which will be converted into an array. Apps will be installed in the order they appear in the array. A .zip file may contain multiple .app files and they will be sorted and installed dependencies first (for each .zip file). If the apps in the .zip file are runtime packages, they will be installed in alphabetic order.

The apps provided in installApps are apps, on which your apps depend. Apps that needs to be installed before compiling and publishing your apps.

Default is an empty array and as such no third party apps will be installed.

-previousApps

previousApps is an array of .app or .zip files. All .zip files are unzipped and all app files are sorted by dependencies. Runtime packages are not allowed. The apps in this list are previous versions of the apps you are compiling and will be used for two things.

If you have enabled AppSourceCop, the previous version of the app you are compiling will be placed in the packages folder and information about this app is placed in appSourceCop.json to signal to the compiler to check for breaking changes, which are not allowed in AppSource apps.

The previous versions of the apps will also be installed before the newly compiled apps will be installed and the newly compiled apps will perform an upgrade instead of an installation.

-enableCodeCop

Include this switch to include Code Cop Rules during compilation.

-enableAppSourceCop

Only relevant for AppSource apps. Include this switch to include AppSource Cop during compilation.

If previousApps is defined, then the appSourceCop will also check for breaking changes compared to the old version. If AppSourceCopMandatoryAffixes or AppSourceCopSupportedCountries is defined, then that will also be added to the AppSourceCop validation.

Apps submitted for validation will be required to pass AppSourceCop with previousApps and the reserved affixes for the publisher given in app.json.

-enablePerTenantExtensionCop

Only relevant for Per Tenant Extensions. Include this switch to include Per Tenant Extension Cop during compilation.

-enableUICop

Include this switch to include UI Cop during compilation.

-AppSourceCopMandatoryAffixes

Only relevant for AppSource Apps when AppSourceCop is enabled. This needs to be an array (or a string with comma separated list) of affixes used in the app.

-AppSourceCopSupportedCountries

Only relevant for AppSource Apps when AppSourceCop is enabled. This needs to be an array (or a string with a comma seperated list) of supported countries for this app.

-installTestFramework

Include this switch to include the test framework in the container before compiling apps and test apps. The Test Framework includes the following apps: Microsoft Any, Microsoft Library Assert, Microsoft Library Variable Storage and Microsoft Test Runner.

-installTestLibraries

Include this switch to include the test libraries in the container before compiling apps and test apps. The Test Libraries includes all the Test Framework apps and the following apps: Microsoft System Application Test Library and Microsoft Tests-TestLibraries

-installPerformanceToolkit

Include this switch to install test Performance Test Toolkit. This includes the apps from the Test Framework and the Microsoft Business Central Performance Toolkit app

Currently there is no good way of running performance tests using PowerShell. We will be working on adding this functionality in order to allow people to run performance regression tests during CI or daily builds.

-doNotRunTests

Include this switch to indicate that you do not want to execute tests. Test Apps will still be published and installed, test execution can later be performed from the UI.

-testResultsFormat

In what format do you want the testResults file. Possible values are XUnit or JUnit. Both formats are XML based test result formats. JUnit is default and contains some properties about memory and installed apps, which due to format limitations cannot be included in XUnit. This parameter is ignored if doNotRunTests is included.

-testResultsFile

Filename in which you want the test results to be written. Default is TestResults.xml, meaning that test results will be written to this filename in the base folder. This parameter is ignored if doNotRunTests is included.

If you do not include any test folders or if you specify -doNotRunTests, then test execution will be skipped and the file will be deleted.

-containerName

This is the containerName going to be used for the build/test container. If not specified, the container name will be the pipeline name followed by -bld. For builds on build agents, there is really no need to specify a containerName. For local builds, where you might want to keep the container after the pipeline has finished, you might want to specify this.

-imageName

The value of imageName can have two meanings. If you specify imageName and set the artifact parameter to an empty string, then Run-AlPipeline will use this imageName as a docker image and run that. Since the artifact parameter is defaulted to ///us/current, you will need to set it to “” in order to use the image.

If you specify imageName AND artifact, then Run-AlPipeline will (as New-BcImage does) check that the image specified by imageName is made on the artifact specified and if that is the case, it will run the image. If the image doesn’t exist, it will be build and next time around running the same function, it will reuse the image.

The default value of imageName is “my”, which means that in the above example, it will create an image called my:sandbox-17.0.17126.17434-us-mt and run that. The tag is type-version-country followed by -mt if multitenancy. The default value is fine for self-hosted agents or local computers. If you are using Azure Hosted agents, which doesn’t benefit from cached images, set imageName to an empty string.

Over time a lot of artifacts and images will be downloaded and build and you can use Flush-ContainerHelperCache -KeepDays 7 to remove all artifacts and images that hasn’t been used for the last 7 days.

-appBuild

Build number for build. Will be stamped into the build part of the app.json version number property.

-appRevision

Revision number for build. Will be stamped into the revision part of the app.json version number property.

-credential

These are the credentials used for the container. If not provided, the Run-AlPipeline function will generate a random password and use that. The generated password will be printed out during pipeline execution. If you use -keepContainer to keep the container after pipeline execution, it is highly recommended that you specify credentials yourself.

-codeSignCertPfxFile

A secure url to a code signing certificate for signing apps. Apps will only be signed if useDevEndpoint is NOT specified.

-codeSignCertPfxPassword

Password for the code signing certificate specified by codeSignCertPfxFile. Apps will only be signed if useDevEndpoint is NOT specified.

-enableTaskScheduler

Include this switch if the Task Scheduler should be running inside the build/test container, as some app features rely on the Task Scheduler.

-assignPremiumPlan

Include this switch if the primary user in Business Central should have assign premium plan, as some app features require premium plan.

-tenant

Normally all deployments, compilations, deployments and tests are running in the default tenant. If you specify a tenant name, a tenant with this name will be created and used for the entire process.

-memoryLimit

MemoryLimit is default set to 8Gb. This is fine for compiling small and medium size apps, but if your have a number of apps or your apps are large and complex, you might need to assign more memory.

-createRuntimePackages

Include this switch if you want to create runtime packages of all apps. The runtime packages will also be signed (if certificate is provided) and copied to artifacts folder.

-buildArtifactFolder

If this folder is specified, the build artifacts will be copied to this folder. All apps to an apps folder. Runtime packages to a runtimepackages folder and test apps to a testapps folder. Test results will also be copied to the build artifacts folder.

-azureDevOps

Include this switch if you want compile errors and test errors to surface directly in Azure Devops pipeline.

-useDevEndpoint

Including the useDevEndpoint switch will cause the pipeline to publish apps through the development endpoint (like VS Code). This should ONLY be used when running the pipeline locally and will cause some changes in how things are done.

When using dev Endpoint, apps will not be placed in the appOutputFolder and the appPackagesFolder is also not used for all symbols. Instead, every app will have it’s own .alPackages folder and symbols will be downloaded there (exactly as VS Code does it). The output will also be placed where VS Code places it.

Normally apps are compiled and placed in the output folder. After all apps are compiled, all apps will be signed, the previous versions will be installed (if specified) and then the newly compiled apps will be published and installed/upgraded. With useDevEndpoint, every app is compile, published and installed one by one.

The biggest advantage of using -useDevEndpoint is, that you can open the app in VS Code and start debugging right away – everything is set for RAD. Remember to specify keepContainer and updateLaunchJson if this is what you want to do.

-updateLaunchJson

Including the updateLaunchJson switch causes the launch.json file in each project to be updated with container information to be able to start debugging right away.

-keepContainer

Including the keepContainer switch causes the container to not be deleted after the pipeline finishes.

-packagesFolder

This parameter is only relevant when NOT using useDevEndpoint. This is the folder (relative to base folder) where symbols are downloaded and compiled apps are placed. This way each app compilation reuses the symbols downloaded by the prior compilation and the compiled app will also be available. Default is .packages and you will likely not need to change this.

-outputFolder

This parameter is only relevant when NOT using useDevEndpoint. This is the folder (relative to base folder) where compiled apps are placed. Default is .output and you will likely not need to change this.

Advanced – the parameters below are typically not changed

-DockerPull

Override function parameter for docker pull. The default scriptblock for this parameter is:

{ Param($imageName)
    docker pull $imageName
}

-NewBcContainer

Override function parameter for New-BcContainer. The default scriptblock for this parameter is:

{ Param([Hashtable]$parameters)
    New-BcContainer @parameters
    Invoke-ScriptInBcContainer $parameters.ContainerName -scriptblock {
        $progressPreference = 'SilentlyContinue'
    }
}

-CompileAppInBcContainer

Override function parameter for Compile-AppInBcContainer. The default scriptblock for this parameter is:

{ Param([Hashtable]$parameters)
    Compile-AppInBcContainer @parameters
}

-PublishBcContainerApp

Override function parameter for Publish-BcContainerApp. The default scriptblock for this parameter is:

{ Param([Hashtable]$parameters)
    Publish-BcContainerApp @parameters
}

-SignBcContainerApp

Override function parameter for Sign-BcContainerApp. The default scriptblock for this parameter is:

{ Param([Hashtable]$parameters)
    Sign-BcContainerApp @parameters
}

-RunTestsInBcContainer

Override function parameter for Run-TestsInBcContainer. The default scriptblock for this parameter is:

{ Param([Hashtable]$parameters)
    Run-TestsInBcContainer @parameters
}

-GetBcContainerAppRuntimePackage

Override function parameter Get-BcContainerAppRuntimePackage. The default scriptblock for this parameter is:

{ Param([Hashtable]$parameters)
    Get-BcContainerAppRuntimePackage @parameters
}

-RemoveBcContainer

Override function parameter for Remove-BcContainer. The default scriptblock for this parameter is:

{ Param([Hashtable]$parameters)
    Remove-BcContainer @parameters
}

That’s it

That is the currently supported parameters in the Run-AlPipeline function.

Take it for a spin and file an issue on https://github.com/microsoft/navcontainerhelper/issues if you encounter things that doesn’t work as expected.

I will update the HOL with a more detailed description on how to use this end 2 end.

Note that if the container doesn’t start correctly it probably isn’t caused by the run-alpipeline function. Only file an issue if you can create containers using New-BcConatiner – and it doesn’t work with Run-AlPipeline.

If you cannot get New-BcContainer working – when Run-AlPipeline also won’t work.

You can always create an Azure VM using http://aka.ms/getbc and use that as agent or development machine.

Enjoy

Freddy Kristiansen
Technical Evangelist

18 thoughts on “Run-AlPipeline

  1. Hi Freddy,

    Thanks for your continuous improvement in your PowerShell module, I love the idea to have less scripts file and one process that handle most of our needs.
    Following the evolution of your Hello World and Bing Map I appreciate more the changes. I’ve noted that you are doing releases to Storage and that Deploy-App.ps1 file disappeared, can I ask you to anticipate what’s going on with releases? Will you cover also releases on-Premise?
    Considering that we have large international projects for multiple countries and licalizations, do you think would be possible for Microsoft to offer a worldwide license that contains all licalizations to allow us to use one license for all pipelines? I hope this make sense for you as well

    Like

  2. Pingback: Run-AlPipeline | Pardaan.com

  3. hey, this is a really cool function, but i can’t see your example, i get the error “UnauthorizedRequestException” when i try to open your hello world repo

    Like

  4. Hi Freddy
    Is it possible to use the -installApps parameter with the folder path to you app? For example “dependency/dbas/output/app/”. This is the folder path to the app file which is downloaded from the pipeline. The full value for the local agent folder is “D:\a\1\s\dependency\dbas\output\app/xxxx.app”). Our app is depending on this app.

    Like

  5. Hi Freddy,
    I tried to enable CodeCop, PerTenantExtensionCop, etc… but I didn’t find a parameter where pass the file (included in the repository) “app.ruleset.json” to modify the logic of some checks.
    The app.ruleset.json file is in the repository and in settings.json there is the “al.ruleSetPath” parameter correctly set. Did I miss something?

    Thank you

    Like

  6. Hi Freddy,
    Great addition, this really simplify our pipeline management.

    I still have a quick question, is there any plan to allow Publish-PerTenantExtensionApps to publish on Dev End point in the same way as VSCode do when we publish manually?
    We are working on App Source extensions already published on Global Tenant and I can’t find how to deploy automatically through release pipelines toward our tests sandboxes without raising global tenant existence error.
    Or maybe is there a process I missed to do that ?

    Like

  7. Hi Freddy! Great article! I have a problem with the https://dev.azure.com/businesscentralapps/HelloWorld/ repo. When I am running Local-Pipeline.ps1 file, I have an error:

    BcContainerHelper version 2.0.11
    Get Secret LicenseFileSecret
    Get Secret passwordSecret
    baseFolder must be an existing folder
    At C:\Program Files\WindowsPowerShell\Modules\BcContainerHelper\2.0.11\AppHandling\Run-AlPipeline.ps1:274 char:5
    + throw “baseFolder must be an existing folder”
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : OperationStopped: (baseFolder must be an existing folder:String) [], RuntimeException
    + FullyQualifiedErrorId : baseFolder must be an existing folder

    PS D:\HelloWorld\scripts>

    What can be wrong?

    Like

  8. Hello Freddy,
    I am using your HelloWorld Demo Project as a template for my project.
    But i have following issues:
    If i create a container from your script, i get a multi tenancy container.
    Where can i change this? i didn’t find any parameter in RunAlPipeline

    I wanted to use Version: “artifact”: “//18.3.27240.27480/at” and changed it your settings.json
    If i do so, i get following output:
    Determining artifacts to use
    Unable to locate artifacts
    At C:\Program Files\WindowsPowerShell\Modules\BcContainerHelper\2.0.15-
    preview456\AppHandling\Run-AlPipeline.ps1:406
    char:9
    + throw “Unable to locate artifacts”
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : OperationStopped: (Unable to locate artifacts:String) [], RuntimeException
    + FullyQualifiedErrorId : Unable to locate artifacts

    Thank you
    Best regards

    Like

    • Please use https://github.com/microsoft/navcontainerhelper/issues for issues.
      There is a setting in bccontainerhelper.config.json called sandboxContainersAreMultitenantByDefault – set that to false and your sandbox containers are single tenant.
      But… – the version you are asking for is not available as a sandbox artifact.
      if you change //18.3.27240.27480/at to /onprem/18.3.27240.27480/at – then you get the on premises artifacts – and single tenancy.
      You can use Get-BcArtifactUrl to determine the available artifacts.

      Like

  9. Hi Freddy,
    Do you have an example of running Run-AlPipeline for a BC Extension in a cloud Sandbox and installing its dependencies from AppSource?

    Like

Leave a reply to Fabrice Cancel reply