Now that Microsoft will also update additional Dynamics 365 Finance and Operations Sandbox environments, partners and customers will only need to take care of updating cloud-hosted environments, as we’ve always done.
I’m sure each team manages this differently, maybe leaving it to each developer to update their VM, or there’s someone in the customer or partner side that will do it. That’s in the best cases, maybe nobody is updating the developer machines…
If you want to know more about builds, releases, and the Dev ALM of Dynamics 365 you can read my full guide on MSDyn365 & Azure DevOps ALM.
Today, I’m bringing you a PowerShell script that you can run in a pipeline that will automatically update all your developer virtual machines!
Update script
As I’ve already done many times, I’ll be using Mötz Jensen‘s d365fo.tools to run all the operations.
This is the complete script:
# CHANGE THIS!!
$AssetId = "LCS_ASSET_ID"
$User = "YOUR_USER"
$Pass = "YOUR_USER_PASSWORD"
$ClientId = "AAD AppId"
$ProjectId = "LCS_PROJECT_ID"
#Get LCS auth token
Get-D365LcsApiToken -ClientId $ClientId -Username $User -Password $Pass -LcsApiUri https://lcsapi.lcs.dynamics.com | Set-D365LcsApiConfig -ProjectId $ProjectId
Get-D365LcsApiConfig
# Get list of all LCS project environments
$Environments = Get-D365LcsEnvironmentMetadata -TraverseAllPages
$StartedEnvs = @()
Write-Host "=================== STARTING ENVIRONMENTS ==================="
Foreach ($Env in $Environments)
{
# Start Dev VMs only
if ($Env.EnvironmentType -eq "DevTestDev" -and $Env.CanStart)
{
$EnvStatus = Invoke-D365LcsEnvironmentStart -EnvironmentId $Env.EnvironmentId
if ($EnvStatus.IsSuccess -eq "True") {
Write-Host ("Environment {0} started." -f $Env.EnvironmentName)
$StartedEnvs += $Env.EnvironmentId
}
else {
Write-Host ("Environment {0} couldn't be started. Error message: {1}" -f $Env.EnvironmentName, $EnvStatus.ErrorMessage)
}
}
}
Write-Host "=================== STARTING ENVIRONMENTS DONE ==================="
Write-Host "=================== SLEEPING FOR 180 seconds ==================="
# Wait 3 minutes for the VMs to start
Start-Sleep -Seconds 180
$Retries = 0
Write-Host "=================== STARTING DEPLOYMENT ==================="
Do
{
Foreach ($EnvD in $StartedEnvs)
{
$EnvStatus = Get-D365LcsEnvironmentMetadata -EnvironmentId $EnvD
# If the VM has started, deploy the DP
if ($EnvStatus.DeploymentStatusDisplay -eq "Deployed")
{
$OpResult = Invoke-D365LcsDeployment -AssetId $AssetId -EnvironmentId $EnvD
if ($OpResult.IsSuccess -eq "True") {
Write-Host ("Updating environment {0} has started." -f $EnvD)
$StartedEnvs = $StartedEnvs -notmatch $EnvD
}
else {
Write-Host ("Updating environment {0} has failed. Error Message: {1}." -f $EnvD, $OpResult.ErrorMessage)
Write-Host ("Will retry {0} more time(s)" -f 3 - $Retries)
}
}
}
$Retries++
} While ($StartedEnvs.Count -ne 0 -or $Retries -eq 3)
Write-Host "=================== STARTING DEPLOYMENT DONE ==================="
Write-Host "Done"
Now let’s take a look at the steps.
Authenticating and getting environments
The first step will be authenticating to LCS with the Get-D365LcsApiToken cmdlet and getting a list of all our environments with Get-D365LcsEnvironmentMetadata. This includes the sandbox and prod environments, but don’t worry, these won’t be updated.
In the last line, we’ll be initializing an array to store the IDs of started environments in the next step.
# CHANGE THIS!!
$AssetId = "LCS_ASSET_ID"
$User = "YOUR_USER"
$Pass = "YOUR_USER_PASSWORD"
$ClientId = "AAD AppId"
$ProjectId = "LCS_PROJECT_ID"
#Get LCS auth token
Get-D365LcsApiToken -ClientId $ClientId -Username $User -Password $Pass -LcsApiUri https://lcsapi.lcs.dynamics.com | Set-D365LcsApiConfig -ProjectId $ProjectId
Get-D365LcsApiConfig
# Get list of all LCS project environments
$Environments = Get-D365LcsEnvironmentMetadata -TraverseAllPages
$StartedEnvs = @()
Starting developer VMs
Now that we have a list with our environments, we need to start only the cloud-hosted ones. We will attain this by looping through the list we got in the first part and filtering on the EnvironmentType property where it equals DevTestDev. And using the Invoke-D365LcsEnvironmentStart cmdlet we will start each VM.
Next we will check if the operation succeeds, or it doesn’t. When we’ve done this for all VMs, we’ll call the Start-Sleep cmdlet and give 3 minutes to the VMs to start.
Write-Host "=================== STARTING ENVIRONMENTS ==================="
Foreach ($Env in $Environments)
{
# Start Dev VMs only
if ($Env.EnvironmentType -eq "DevTestDev" -and $Env.CanStart)
{
$EnvStatus = Invoke-D365LcsEnvironmentStart -EnvironmentId $Env.EnvironmentId
if ($EnvStatus.IsSuccess -eq "True") {
Write-Host ("Environment {0} started." -f $Env.EnvironmentName)
$StartedEnvs += $Env.EnvironmentId
}
else {
Write-Host ("Environment {0} couldn't be started. Error message: {1}" -f $Env.EnvironmentName, $EnvStatus.ErrorMessage)
}
}
}
Write-Host "=================== STARTING ENVIRONMENTS DONE ==================="
Write-Host "=================== SLEEPING FOR 180 seconds ==================="
# Wait 3 minutes for the VMs to start
Start-Sleep -Seconds 180
Trigger the updates
In the final part, we will start deploying the update to each running VM. Looping through the array we created in the beginning, we’ll use the Get-D365LcsEnvironmentMetadata command to get the status of the VM, and if it’s running we’ll start the deployment using the Invoke-D365LcsDeployment cmdlet.
If the operation succeeds, we’ll remove that environment from the array and continue, otherwise we’ll try again up until three times (note that everything is inside a Do-While loop).
$Retries = 0 Write-Host "=================== STARTING DEPLOYMENT ===================" Do { Foreach ($EnvD in $StartedEnvs) { $EnvStatus = Get-D365LcsEnvironmentMetadata -EnvironmentId $EnvD # If the VM has started, deploy the DP if ($EnvStatus.DeploymentStatusDisplay -eq "Deployed") { $OpResult = Invoke-D365LcsDeployment -AssetId $AssetId -EnvironmentId $EnvD if ($OpResult.IsSuccess -eq "True") { Write-Host ("Updating environment {0} has started." -f $EnvD) $StartedEnvs = $StartedEnvs -notmatch $EnvD } else { Write-Host ("Updating environment {0} has failed. Error Message: {1}." -f $EnvD, $OpResult.ErrorMessage) Write-Host ("Will retry {0} more time(s)" -f 3 - $Retries) } } } $Retries++ } While ($StartedEnvs.Count -ne 0 -or $Retries -eq 3) Write-Host "=================== STARTING DEPLOYMENT DONE ===================" Write-Host "Done"
And after this we should see all our dev VMs servicing on LCS.
Running it in a pipeline
Once the script is working, running it in a pipeline is totally trivial, and you can do it in a build or a release pipeline, it’s up to you. My pipeline looks like this:
I’m installing d365fo.tools in the first step with the following script:
Install-Module -Name d365fo.tools -AllowClobber -Scope CurrentUser -Force -Confirm:$false
And in the second task, I’ll be running the update script we’ve just seen at the beginning of this post.
Of course, you can do it all in a single task, but I prefer to split it in two because it looks prettier to me.
Some advice
Credentials
Of course, if you run this in a pipeline DO NOT put the service account credentials there, either use an Azure Key Vault or a variable group with secret values in your pipelines’ library:
Not so automated
Of course, the only really automagic part of this is the starting and updating of the VMs. When the servicing is done, you need to stop the VMs. You can also run a pipeline that stops them after X hours, that’s up to you.
Also, if servicing fails you have to resume the operations or fix whatever is wrong and resume them, that’s pretty manual.
And that’s all, I hope this helps, specially if you have lots of CHE VMs because updating all of them manually is a bit slow if you have to do it from LCS.