---
title: Update VMs using pipelines and d365fo.tools
description: "Now that Microsoft will also [update additional Dynamics 365 Finance and Operations Sandbox environments](https://docs.microsoft.com/en-us/dynamics365/fin-ops-core/dev-itpro/lifecycle-services/configure-service-updates?WT.mc_id=BA-MVP-5003976#additional-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…"
url: "https://ariste.info/dynamics365almguide/update-vms-using-pipelines-and-d365fo-tools/"
updated: "2024-08-25"
category: Legacy Guide / Automation
---

# Update VMs using pipelines and d365fo.tools

Now that Microsoft will also [update additional Dynamics 365 Finance and Operations Sandbox environments](https://docs.microsoft.com/en-us/dynamics365/fin-ops-core/dev-itpro/lifecycle-services/configure-service-updates?WT.mc_id=BA-MVP-5003976#additional-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](http://ariste.info/en/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](http://ariste.info/en/2021/02/azure-devtest-labs-build-dynamics-365-fno/) [many times](http://ariste.info/en/2020/04/lcs-db-api-automating-prod-dev-db-copy/), I'll be using [Mötz Jensen](https://twitter.com/splaxi)'s [d365fo.tools](https://github.com/d365collaborative/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:

![Update pipeline](http://ariste.info/wp-content/uploads/2021/11/image-2.png)
Dev update pipeline

    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.

![Remember this isn't the kind of automation we're interested in](http://ariste.info/wp-content/uploads/2021/11/auto2-1024x1024.png)
Remember, this isn't the kind of automation we're interested in

#### 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](https://docs.microsoft.com/en-us/azure/key-vault/general/overview?WT.mc_id=BA-MVP-5003976) or a [variable group](https://docs.microsoft.com/en-us/azure/devops/pipelines/library/variable-groups?view=azure-devops&tabs=yaml&WT.mc_id=BA-MVP-5003976) with secret values in your pipelines' library:

![Update VMs using pipelines and d365fo.tools](http://ariste.info/wp-content/uploads/2021/11/image-3.png)
AZDO pipelines library variable group

##### 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.

#### Subscribe!

Receive an email when a new post is published
