Windows Virtual Desktop with ARM and Azure DevOps


In this post we will cover how to setup Windows Virtual Desktop ARM template with Azure DevOps. We walkthrough 2 different ARM templates. I’m using the ARM WVD templates from Microsoft: that came earlier in 2020.

  • CreateAndProvisionHostPool
  • AddVirtualMachinesToHostPool

Figure 1: Visualized DevOps Workflow ARM pipelines


In Figure 1, we can see that the Azure DevOps build pipeline is integrated with Azure Key Vault for secrets management. We are using Service Connection with Azure, so that the ARM templates and PowerShell scripts can run in the context of the service principal. No Password are exposed in clear text to the pipeline!
As part of the automation pipeline, one of my goals is to use Windows 10 image built using a Shared Image Gallery (SIG) built with Azure Image builder (AIB). Pointing to SIG is just part of the parameters file. I recommend using this guide to configure AIB and SIG:
If you don’t have SIG ready, you can optionally just use the Windows 10 SKU from Gallery if you just need a quick POC. Just change the parameters

I’ve added the “arm-wvd-CreateAndProvisionHostPool.json” and arm-wvd- to my GitHub repo, this will be used by the pipeline. ****
NB! You will need update the parameters in the “CreateAndProvisionHostPool.parameters.json” yourself. The parameters must be equal to your own environment! I will go deeper into some of the parameters used by the arm template. There are also some PowerShell scripts that the pipelines will use.
I’m by no means any expert in ARM or PowerShell, but I will cover the basics of getting these pipelines working.

The Templates and code used is available from my GitHub repo. Clone the repo, so that you can use the files from






  • Azure Subscription
  • VNET and Subnet
  • Azure Key Vault
  • Azure DevOps Project
  • Active Directory Domain or Azure Domain Services to join
  • Shared Image Gallery (Windows 10 Image)
  • GitHub or any other source control that you prefer.

Create And ProvisionHostPool

Azure DevOps Project

Let’ start by getting our Azure DevOps Project ready for the CreateAndProvisionHostPool.

Go to and create your first project
Choose Project Settings to the bottom left.


Service Connections


Add service connection for your GitHub account and Azure Subscription.


Go back to the Pipeline tab on the left Side and choose New release pipeline.


Create an Empty Job


Name your Release pipeline, example “wvd.arm.create.hostpool“, and hit save.



From the artifacts, add your GitHub account, or the source control of your choice. Click save When You’re Done.



Variable Groups

Go to the Pipelines Tab – Library and choose the Variable Groups below.


Create a New Variable Group


Authorize the Key vault to your subscription and key vault name.


When Authorized to the key Vault, remember to add your secret name Variable. This used by the ARM template parameters.



The ARM parameters values in arm-wvd-CreateAndProvisionHostPool.paramters.json match your environment. Below I’ve just listed all the parameters that simply got from the Azure Portal, so that I could see what parameters are generated the parameter.


I Downloaded the Template and used the parameter file generated to see how the syntax for each parameter needed to be.


I will cover the parameters that is not obvious. So that I could more seamlessly see what parameters needs to be added. You can also “get-AzGalleryImageDefinition”. If we Look more into the Azure Share Image Gallery (SIG) parameters, All the values, was then added to the values in “vmTemplate” parameter.
The “customImageId” SIG can be found in the azure portal as well.


With the Parameter “vmCustomImageSourceId“, we also need to add SIG resoruce ID to this value

PowerShell Task ExpirationTime

When we have all the parameter’s updated, we ready to add the PowerShell task. The Task is used to generate the parameter value for “tokenExpirationTime“. The PowerShell task will store the variable to the next DevOps ARM Task. The Script will run “get-date” with correct format. The write-host will bring the variable to the next task.


Search for PowerShell, and add the task.


Choose File Path – Create the Display Name and choose Script Path to the “createtokenExpirationTime.ps1” script.

It’s located in the


Go to Tasks – add tasks – search for ARM, add the ARM template deployment as a task.


Resource group and location must be equal to your environment.


In the screenshot below you can see more of the ARM settings.
Template – Change the location to our GitHub artifact for “arm-wvd-CreateAndProvisionHostPool.json” template location.
Template parameters – Change the location to GitHub artifact for “arm-wvd-CreateAndProvisionHostPool.parameters.json” Parameters.
The templates are located in

In the Override template parameters, we will set
administratorAccountPassword” variable and the “tokenExperationTime” is the variable from the PowerShell task we created earlier.

-administratorAccountPassword “$(wvd-ad-join)” -tokenExpirationTime “$(ExpirationTime)”


Pipeline Variables - override “more” parameters

if you don’t like working with the “CreateAndProvisionHostPool.paramters.json“, you can just override even more parameters for example “ouPath” and “administratorAccountUsername” that you down want to be coded in the parameter json. I will recommend choosing Variables – Pipeline Variables and variables to match your needs.


you must remember to override it the arm template as well!


Create a new release and Deploy

If you’re done with updating all the parameters, we should be ready for a release. Create release and deploy.


In the logs you can see that the Agent has finalized the job. Go to the troubleshooting guide at the end if you get any problems.


Pipeline Add Virtual Machines To HostPool

When the ARM template deployment is finished, we can go to the next Pipeline – AddVirtualMachinesToHostPool. This’s probably the one, we’ll use more often for your Maintenace. To get started we must create a new release pipeline, example “wvd.arm.AddVMToHostPool“. Add you Artifact and Stage and click save.


PowerShell Task RdsRegistration Info token

In this Task we will configure a PowerShell task with Azure Powershell.
We will use the
AddVirtualMachinesToHostPool/RdsRegistrationInfotoken.ps1 scirpt. I’ve borrowed the script from, and added some small modifications. The main goal for the scirpt is to obtain the **RdsRegistrationInfotoken **and send the variable to the next task for the ARM parameter hostpoolToken.
This will ensure that we have a valid token, or generate a new if token has expired. A Token is required from the Host Pool to allow, new VM’s to a Host Pool. The write-host will bring the variable to the next task.

To get stated I will add the “Azure PowerShell”


Before running the pipeline and the script, you will need to update the variables to your own environment. Optionally, you could just use an Inline script a Copy Past scirpt, if you prefer that.

$azureSubscriptionID = “your-wvd-sub-id”
$resourceGroupName = “rg-wvd-Pooled-desktop”
$existingWVDHostPoolName = “HostPool-Test”

Next go to the Authenticate to your Subscription to allow the task to use Service Connection and Service Principal, that will authenticate to PowerShell during the task.


Add your Template and Template parameters, and Override template parameters

-administratorAccountPassword “$(wvd-ad-join)” -hostpoolToken “$(RdsRegistrationInfotoken)”

ARM Template Deployment Task - AddVmsToHoostPool


The last part of the pipeline is to link the variable group, so that pipeline can use the Key Vault credentials authorized to the DevOps Project earlier.


Create a new release and Deploy


You could also automate the drain Mode for your sessions host.
Like example in the pipeline. I prefer to schedule, this at a later stage since, I like to know that everything is working as it should with the new image.


During this project I’ve failed multiple times because of parameter or value that was in the wrong context. Your DevOps pipeline will give you some details about the error, but it’s not always easy to see what failed for ARM, so I prefer looking into the RAW Error from the Resource Group and Deployments tab. I really recommend investing some time in this ARM video series from if you need some advice regarding test and validation in arm templates. It’s for sure helped me a lot. In the picture below the task failed because I was using the wrong prefix. Looking into the json file I see that the variable was using rdshPrefix to the vmNamePrefix.


Another example the passsword that did not work. A closer look to the varaible group for the pipeline, and I could see that the correct variable group was not linked.


Update 02.06.2021 (thnx to one of my readers Ben! We found that I had a bug with spaces in the Gist Github file!) this file has been updated so that the variable with ExpirationTime will be correct ). If you copied the code prior to 02.06.2021 the variable would cause a space to be generated which could cause some issues with the parameter input.
I’ve also updated the task.setvariable that was wrong in github and gist github.

“Spaces” in DevOps task Varaible with createtokenExpirationTime.ps1


It was a nice learning curve to configure WVD ARM templates with DevOps. What I like about Azure DevOps with the ARM templates, is that it gives additional security, and flexibility and scalability when you need to do deploy across multiple environments. You can export your templates, so it’s more repeatable in your CI/CD environments. With the override parameters is quick and easy to change parameters at your needs.