top of page
  • Writer's pictureNathan

Deploying Azure WVD with Terraform

Lately, I've been playing around with Windows Virtual Desktop (WVD) in my personal sandbox. I wanted to see if I could build a full WVD architecture in Azure using Terraform. Microsoft has a GitHub repository containing Terraform code, but it only focuses on the Session Hosts. Also, there's a few small problems with the repo. For one, it's using old Terraform resource types that have been superseded by newer resource types (for example, azurerm_virtual_machine vs. azurerm_windows_virtual_machine). Second, it's using the old-style Terraform expression syntax. In a nutshell, the repo was pretty much useless for me. So, I began to write some new Terraform code from scratch which would deploy everything involved with the new ARM-based version of Azure WVD. In this article I plan to document some of the challenges I ran into and some of my findings.

WVD Building Blocks

Let's start with the basic building blocks of WVD. If you know WVD architecture these are all pretty self-explanatory, but I have a couple of notes for each one.

azurerm_virtual_desktop_host_pool : A registration token is automatically generated for the Host Pool (the Session Hosts require this token in order to join the Host Pool). You can give this token a specific expiration date, but it must be within a certain range. It has to be 1 hour into the future at a minimum, and can not be longer than 27 days into the future.

azurerm_virtual_desktop_application_group : Not much to note here. All you can do is create an Application Group. If you create a 'Desktop' Application Group, then it will automatically create the default desktop Application for you. If you create a 'RemoteApp' Application Group, then you must add your Applications separately, more on that below.

azurerm_virtual_desktop_workspace : This simply creates a Workspace, nothing to note here.

azurerm_virtual_desktop_workspace_application_group_association : This connects a Application Group to a Workspace, nothing to note here.

As of the writing of this article, there is no method in Terraform to create RemoteApp Applications in an Application Group. As an alternative, you could look into the Az PowerShell Module and the New-AzWvdApplication cmdlet, which lets you create RemoteApp Applications.

WVD Session Hosts

Next, you will need to focus on getting your Session Hosts created and connected to your Host Pool. You can use Terraform to create your virtual machines using the standard azurerm_windows_virtual_machine module. You will also want to create 2 azurerm_virtual_machine_extension resources per VM. One extension will be used to join the VM to your AD DS / Azure AD DS domain. The other extension will run PowerShell Desired State Configuration (DSC) on the VM to install the WVD agents and to register the machine to the Host Pool.

Here are the settings that you will need to configure for the domain-join extension. Please do NOT put plain-text passwords in your Terraform code. Also, you can set "OUPath" to an empty string, which will place the new computer objects into the default Computers container in AD.

name                       = "vm1_extension_domainjoin"
virtual_machine_id         =
publisher                  = "Microsoft.Compute"
type                       = "JsonADDomainExtension"
type_handler_version       = "1.3"
auto_upgrade_minor_version = true

settings = <<SETTINGS
    "Name": "",
    "OUPath": "OU=secondlevel,OU=firstlevel,DC=domain,DC=com",
    "User": "",
    "Restart": "true",
    "Options": "3"

protected_settings = <<PSETTINGS

lifecycle {
    ignore_changes = [ settings, protected_settings ]

Here are the settings you will need to configure for the PowerShell DSC extension. In order to figure out these settings, I ran through a WVD deployment using the Azure Portal, but before hitting the final 'go' I saved the deployment as a template. I then reversed engineered the template to see what its doing. As you can see it is calling out to the internet to an official Microsoft blob storage account to grab a file. All of the magic is contained within this zip file, including the WVD Agents and the PowerShell scripts to make sure everything gets installed and configured correctly on the Session Host.

name                       = "vm1_extension_dsc"
virtual_machine_id         =
publisher                  = "Microsoft.Powershell"
type                       = "DSC"
type_handler_version       = "2.73"
auto_upgrade_minor_version = true

settings = <<SETTINGS
    "modulesUrl": "",
    "configurationFunction": "Configuration.ps1\\AddSessionHost",
    "properties": {
      "hostPoolName": "HostPoolNameGoesHere",
      "registrationInfoToken": "${azurerm_virtual_desktop_host_pool.pool1.registration_info[0].token}"

lifecycle {
  ignore_changes = [ settings ]

depends_on = [ azurerm_virtual_machine_extension.vm1_extension_domainjoin ]

Putting it all together and final steps

By now you should be in pretty good shape. You have Host Pools created, Session Hosts created and registered with a Host Pool, Workspaces created, and Application Groups created and registered to Workspaces. You might also have some RemoteApp Applications that you created via PowerShell. So, what's left? There's only one step left and that's to assign users to Application Groups.

Assigning users to Application Groups can be done with Terraform by way of the azurerm_role_assignment module. Just assign an Azure AD user account / group to the "Desktop Virtualization User" role and scope it to the Application Group in question. Here's an example:

scope                =
role_definition_name = "Desktop Virtualization User"
principal_id         = "userIDorGroupIDgoesHere"

We're almost done, we have one scenario where you need to do one final step. If you are running a Host Pool of type "Personal" and you have it configured for "Direct" assignment, then you must also assign users to their dedicated Session Host. There's no way to do this in Terraform as of now. We'll have to fall back to PowerShell again, but this time it's the Update-AzWvdSessionHost cmdlet. See this Microsoft link for more details.

Did I miss anything?

Also, here's a link to my GitHub repo containing all of this example code.



bottom of page