Last week, I was working on the final bits of a release pipeline for Business Central in Azure DevOps and all that was left was to import a couple of RapidStart packages in an automated (PowerShell) fashion.
The only (future-proof) automated way of doing this is by using the automation APIs as described here.
Seems do-able, right…?

For the ones who’re not into reading the documentation (like me :D), we’ll need to do 4 API calls to get the job done:
– Create a record in the Configuration Package table
– Upload the RapidStart package
– Import the RapidStart package
– Apply the RapidStart package

Apart from the fact that we need to do 4 API calls, there’s some undocumented stuff going on behind the scenes that’s definitely worth mentioning.
To be more specific, some of the API calls trigger asynchronous tasks that are executed by the task scheduler in Business Central at a later point in time, meaning that subsequent API calls will fail with a bad request status code if called directly.

I’ll go through the 4 API calls one by one, but first, we need to understand why automation APIs are so important these days.

P.S. all the code is available on my GitHub!

Automation APIs

If I remember well, NAV 2013 was the first version that got delivered with automation tools, PowerShell cmdlets to be more precise.
With almost every new NAV version we got a couple of new cmdlets, but this has slowly come to an end because of the introduction of the Business Central SaaS offering and C/SIDE’s retirement.
In other words, it was time for something new and automation APIs are the only way to automate things for BC SaaS.

Authentication

I’m not going to talk about authentication in this blog, the way you authenticate depends on the type of environment you’re using.
I’ll be using windows authentication and a local Docker container, for BC SaaS you’ll need to use OAuth or basic auth with a web service access key.

Creating the Configuration Package record

It all starts with creating a record in the configuration package table, if you import a package through the client this record is created for you but we’ll need to do it by hand.
For all the API calls we need a company id, let’s retrieve a company first:

Now that we have the company id, we can create the Configuration Package called ‘MyPackage’:

Please note that I explicitly specify the ContentType parameter of the Invoke-RestMethod function, if you omit this parameter you’ll get the confusing error below:
{“error”:{“code”:”BadRequest”,”message”:”Cannot create an instance of an interface.”}}

Always make sure that the package code matches with the package code in your RapidStart file, otherwise the import and apply actions won’t work!

Uploading the package

This is where it becomes a little bit more tricky, the API call to upload the package will be finished before you can make the API call to import it, so we’ll need to give the service tier some time to process the package.
Since there’s no way to retrieve the progress of the upload we have no other option than to wait for a couple of seconds.
The package will be uploaded to the content field in the Tenant Config. Package File table so that it can be retrieved during import.

The API call looks like this:

Importing the package

After we’ve given the upload some time to process we can make the call to import the package, the import takes the package from the Tenant Config. Package File table and fills the RapidStart related tables so that everything’s ready for the apply step.

Here’s the API call:

The annoying this is that this API call seems a bit unstable, there are situations where the API returns an error (can’t repro it at the moment) but the import is started anyways.
Calling the API twice with -ErrorAction SilentlyContinue did the job for me. If the import is already started by the first invocation the API will return an error, which is fine.
The API call doesn’t perform the import itself, it only creates a scheduled task that takes care of the import and then returns a success status code.

Importing a package might take a while (depends on package size) so we’ll need to know when it’s finished before we invoke the apply action.
The only way (AFAIK) is to poll the package entity until the ImportStatus has reached ‘Completed’ or ‘Error’.
To not overload the service tier with unnecessary requests, it’s best to only poll every X number of seconds and implement a timeout so that you don’t end up with an infinite loop.

Applying the package

The call to apply the package is almost the same as the call to import the package, it’s just a different bound action we need to call:

Applying happens asynchronously as well, so we’ll need to use the polling mechanism again to now monitor the ApplyStatus of the package instead of the ImportStatus.
If the import fails, you have no idea about the cause, you only know the number of errors (numberOfErrors property on the package entity) that occurred which forces you to log in to the system to figure out what went wrong.
In my opinion, it would be a great enhancement to also return the error details so actions can be taken in an automated matter.

All the code is available on my GitHub, I didn’t take the time to write nice and clean PowerShell functions and wrap them in a module but feel free to reuse the things you need!