This blog I wanted to write for a while. But as NDA under Business Central Wave 2 is lifted my hands are untied
Today I want to talk about how to create a script that will spin up Business Central Wave 2 online sandbox for you.
Why?
I can imagine different scenarios. Maybe you want to test your extensions against Wave 2 without docker. Or you want to create a custom Wave 2 sandbox with your data, your extensions and give it to the potential customer. May be online sandboxes are a part of your CI/CD process, or maybe you want to run the script, because it’s so cool to sit and see how the process goes
AAD Authentication
First thing you need to cover – to configure AAD Authentication.
I described that in my previous blog.
So the output from the previous blog, we use here is the authentication key

Create Business Central Cloud SandBox with Admin API
Here is the description of API. But without proper examples, it’s difficult to create the script quickly.
So here we go.
Available Versions
You have a choice: do you want to create a sandbox with the current version, or with a next (preview) version.
To get the list of available versions I created this code. Oh, yes. This is all in a Powershell 🙂
$url ='https://api.businesscentral.dynamics.com'
$authHeaderDA = GetAuthHeader -DAemail $DAemail -DApassword $DApassword -tenantdomain $tenantdomain -appId $appId
Get-BCCloudAvailableVersions -url $url -authHeaderDA $authHeaderDA
function Get-BCCloudAvailableVersions{
param (
[Parameter(Mandatory=$true)]
[string] $url,
[Parameter(Mandatory=$true)]
$authHeaderDA
)
$req = $url + '/v1.2/admin/applications/BusinessCentral/rings/'
$result = Invoke-WebRequest `
-Uri $req `
-Headers @{Authorization=$authHeaderDA} `
-Method Get
$result.Content
}
The result will be
If you notice, the output gives us 2 available versions of Business Central that are available for the sandbox creation: 14.5.35970 (current) and 15.0.36135.0 (next).
Caution!
When preparing this blog I tried to create current sandbox (with API) I got this error.
{“code”:”ResourceDoesNotExist”,”message”:”The resource does not exist”,”target”:””}
2 days of investigations and discussing with support team (big thanks to them, btw) resulted in this answer. Hope I can publish that and it’s not a secret
You are calling an API that tries to create a sandbox environment. Since an application version is not provided, we try to create the sandbox environment on the same Application Version as your production tenant (in this case 14.4.35602.0). However, we cannot create a sandbox tenant on a version that is not currently enabled for new signups. Late last week, we enabled signups on 14.5, which means that new signups are no longer allowed on 14.4. This API likely stopped working for you as soon as we disabled signups on 14.4 last week. In my opinion, this is a bug. I’ll discuss that with my team. In the meantime, you have two possible mitigation:
Use the API that allows ApplicationVersion and RingName to be specified:
>The application version that is currently available is 14.5.35970.0
>The ring name should be PROD
If you really need your environment to be on 14.4, create your environment as a copy of Production. This will ignore the check for whether signups are enabled.
And really I had this situation.
But let’s return to the Wave 2
Get the next available version number
If you want to create a repetitive script, you need to “know” the next version number, cos you should pass it to the create-new-bc-sandbox URL. Otherwise, you can just hardcode it, and the script will work for a while… not long.
$url ='https://api.businesscentral.dynamics.com'
$authHeaderDA = GetAuthHeader -DAemail $DAemail -DApassword $DApassword -tenantdomain $tenantdomain -appId $appId
Get-BCCloudAvailablePreviewVersion -url $url -authHeaderDA $authHeaderDA
function Get-BCCloudAvailablePreviewVersion{
param (
[Parameter(Mandatory=$true)]
[string] $url,
[Parameter(Mandatory=$true)]
$authHeaderDA
)
$supportedVersions = Get-BCCloudAvailableVersions -url $url -authHeaderDA $authHeaderDA | ConvertFrom-Json
$appVersion = $supportedVersions.value | where { $_.ringFriendlyName -eq "Preview" } | Select -ExpandProperty "applicationVersion"
$major = $appVersion.major
$minor = $appVersion.minor
$build = $appVersion.build
$revision = $appVersion.revision
return ("$major.$minor.$build.$revision")
}
The result will be
Caution!
Seems that while preparing this blog, I discovered all possible bugs 🙂
This time applicationVersion was returned in flat format, meaning that it was a text and not a JSON. I reported to support and seems it was resolved. But if you will have that in the future here is the parsing code.
#There where problems in API when appVersion returned in flat format
$major = $appVersion |
Select-String '(?<=major=)\d+' |
Select-Object -Expand Matches |
Select-Object -Expand Value
$minor =$appVersion |
Select-String '(?<=minor=)\d+' |
Select-Object -Expand Matches |
Select-Object -Expand Value
$build = $appVersion |
Select-String '(?<=build=)\d+' |
Select-Object -Expand Matches |
Select-Object -Expand Value
$revision = $appVersion |
Select-String '(?<=revision=)\d+' |
Select-Object -Expand Matches |
Select-Object -Expand Value
return ("$major.$minor.$build.$revision")
Hope you won’t need it.
Create new Wave 2 Sandbox
And now we are at the point when we are ready to create new Wave 2 Sandbox
Clear-Host
$url ='https://api.businesscentral.dynamics.com'
$authHeaderDA = GetAuthHeader -DAemail $DAemail -DApassword $DApassword -tenantdomain $tenantdomain -appId $appId
$sandboxName = 'My-Wave2-Sandbox'
New-BCCloudSandBox -url $url -authHeaderDA $authHeaderDA -sandboxName $sandboxName -nextVersion:$true
function New-BCCloudSandBox {
param (
[Parameter(Mandatory=$true)]
[string] $url,
[Parameter(Mandatory=$true)]
$authHeaderDA,
[Parameter(Mandatory=$true)]
[string] $sandboxName,
[switch] $nextVersion
)
if (! $nextVersion) {
$req = $url + "/v1.2/admin/applications/BusinessCentral/environments/$sandboxName"
} else {
$appVersion = Get-BCCloudAvailablePreviewVersion -url $url -authHeaderDA $authHeaderDA
$req = $url + "/v1.2/admin/applications/BusinessCentral/environments/$sandboxName/$appVersion/PREVIEW"
}
Write-Host "Creating new Sandbox..."
$JSON = @'
{
"type": "Sandbox"
}
'@
try {
$result = Invoke-WebRequest `
-Uri $req `
-Headers @{Authorization=$authHeaderDA} `
-Method PUT `
-Body $JSON -ContentType "application/json"
} catch {
$status = $_.Exception.Response.StatusCode.value__
}
}
The result will be
and in the Admin portal
We could stop here, but we could make the script a bit better, right?
Get the list of installed environments in the tenant
During the spin-up process, or afterwards, we can get the list of installed environments in your tenant with the environment status.
$url ='https://api.businesscentral.dynamics.com'
$authHeaderDA = GetAuthHeader -DAemail $DAemail -DApassword $DApassword -tenantdomain $tenantdomain -appId $appId
$sandboxName = 'My-Wave2-Sandbox'
Get-BCCloudEnvironments -url $url -authHeaderDA $authHeaderDA -envName $sandboxName | ConvertFrom-Json | ConvertTo-Json
function Get-BCCloudEnvironments {
param (
[Parameter(Mandatory=$true)]
[string] $url,
[Parameter(Mandatory=$true)]
$authHeaderDA,
$envName
)
$req = $url + '/v1.2/admin/applications/BusinessCentral/environments/' + $envName
$result = Invoke-WebRequest `
-Uri $req `
-Headers @{Authorization=$authHeaderDA} `
-Method Get
$result.Content
}
with the output
If you notice, there is a Status parameter that we will use.
Check the status of the Sandbox Environment
Now we are ready to get the status of the environment
Clear-Host
$url ='https://api.businesscentral.dynamics.com'
$authHeaderDA = GetAuthHeader -DAemail $DAemail -DApassword $DApassword -tenantdomain $tenantdomain -appId $appId
$sandboxName = 'My-Wave2-Sandbox'
Get-BCCloudSandBoxStatus -url $url -authHeaderDA $authHeaderDA -sandboxName $sandboxName
function Get-BCCloudSandBoxStatus {
param (
[Parameter(Mandatory=$true)]
[string] $url,
[Parameter(Mandatory=$true)]
$authHeaderDA,
[Parameter(Mandatory=$true)]
[string] $sandboxName
)
try{
$result = Get-BCCloudEnvironments -url $url -authHeaderDA $authHeaderDA -envName $sandboxName
if ($result) {$currStatus = $result | ConvertFrom-Json |select status}
return ($currStatus.status)
} catch {
$currStatus = $_.Exception.Response.StatusCode
return ($currStatus)
}
}
and the output
Wait until sandbox will be created
If this script should be embedded in the more general script, where you first create a sandbox and then upload extensions, you need to wait until the sandbox will be created., before doing the rest.
We have status now, so let’s check it periodically during the creation process.
$url ='https://api.businesscentral.dynamics.com'
$authHeaderDA = GetAuthHeader -DAemail $DAemail -DApassword $DApassword -tenantdomain $tenantdomain -appId $appId
$sandboxName = 'My-Wave2-Sandbox'
Get-BCCloudSandBoxStatus -url $url -authHeaderDA $authHeaderDA -sandboxName $sandboxName
function Wait-BCCloudSandBoxReady {
param (
[Parameter(Mandatory=$true)]
[string] $url,
[Parameter(Mandatory=$true)]
$authHeaderDA,
[Parameter(Mandatory=$true)]
[string] $sandboxName
)
$status = Get-BCCloudSandBoxStatus -url $url -authHeaderDA $authHeaderDA -sandboxName $sandboxName
if ($status -eq "NotFound")
{
Write-Host -ForegroundColor Red "Sandbox is not found"
return
}
if ($status -eq "Removing")
{
Write-Host -ForegroundColor Yellow "Sandbox is removing"
return
}
if ($status -eq "Unauthorized")
{
return
}
while (($status -ne "Active") -and ($status -ne "NotFound"))
{
$wait = 30
$status = Get-BCCloudSandBoxStatus -url $url -authHeaderDA $authHeaderDA -sandboxName $sandboxName
if (($status -ne "Active") -and ($status -ne "NotFound"))
{
Write-Host -ForegroundColor Yellow "Sandbox is cooking. Current status is" $status ". Next try in $wait sec"
Start-Sleep -Seconds $wait
}
}
Write-Host -ForegroundColor Green "Sandbox is " $status
}
Join all together
The final script could be found here.
Enjoy!
Update
My MVP friend Stefano Demiliani Technical Blog added a valuable comment: “Be careful that sandboxes created with a preview version can be updated or deleted (yes, deleted!) without notice by Microsoft.”