How to Deploy WVD Session Hosts using Terraform

Following on from this article covering how to deploy Windows Virtual Desktop resources, this article will show how you can also deploy WVD session hosts using Terraform. The previous article detailed how to use Terraform to deploy a AVD workspace, app group and host pool. This article will show how you can use Terraform to add session hosts to the empty host pool.

To create a session host we need to define the following resources in a Terraform plan:

  • azurerm_network_interface – Network Interface resource for the session host
  • azurerm_virtual_machine– Session host virtual machine
  • azurerm_virtual_machine_extension – Virtual Machine Domain Join Extension
  • azurerm_virtual_machine_extension – Virtual Machine Powershell DSC Extension

Really we are just deploying a Azure virtual machine, then registering it with WVD (or Azure Virtual Desktop as it’s now called). Let’s go through though each of the resources defined in the Terraform plan to explain how each part works. If you have experience already of deploying Azure virtual machines using Terraform, then a lot of this will look very familiar.

To start with, I have defined the following variables. I have put some example values in here for demonstration purposes, but you should use a terraform.tfvars file to populate your variables, especially for anything sensitive such as passwords. Note there are some resources referenced which are not part of the Terraform plan. We are assuming here that a number of things already exist such as the vNet and Subnet to deploy the VM into, a domain to which the VMs can be joined, and a WVD / AVD host pool to which the new session host(s) can be registered.

Azure Virtual Desktop Terraform Plan Variables

variable "resource_group" {
  description = "The name of the resource group in which to create the Azure resources"
  default = "myResourceGroup"
}

variable "location" {
  description = "The location/region where the session hosts are created"
  default = "East US"
}

variable "vm_size" {
  description = "Specifies the size of the virtual machine."
  default = "Standard_B2s"
}

variable "image_publisher" {
  description = "Image Publisher"
  default = "MicrosoftWindowsServer"
}

variable "image_offer" {
  description = "Image Offer"
  default = "WindowsServer"
}

variable "image_sku" {
  description = "Image SKU"
  default = "2016-Datacenter"
}

variable "image_version" {
  description = "Image Version"
  default = "latest"
}

variable "admin_username" {
  description = "Local Admin Username"
  default = "azureadmin"
}

variable "admin_password" {
  description = "Admin Password"
  default = "P@55W0rD!!"
}

variable "subnet_id" {
  description = "Azure Subnet ID"
  default = "/subscriptions/xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxx/resourceGroups/wvd-rg/providers/Microsoft.Network/virtualNetworks/wvd-vnet/subnets/wvd"
}

variable "vm_name" {
  description = "Virtual Machine Name"
  default = "avd-vm-host-"
}

variable "vm_count" {
  description = "Number of Session Host VMs to create" 
  default = "5"
}

variable "domain" {
  description = "Domain to join" 
  default = "avd.local"
}

variable "domainuser" {
  description = "Domain Join User Name" 
  default = "admin@avd.local"
}

variable "oupath" {
  description = "OU Path"
  default = "OU=AVD,DC=avd,DC=local"
}

variable "domainpassword" {
  description = "Domain User Password" 
  default = "password"
}

variable "regtoken" {
  description = "Host Pool Registration Token" 
  default = "registration token generated by hostpool goes here"
}

variable "hostpoolname" {
  description = "Host Pool Name to Register Session Hosts" 
  default = "avd-host-pool"
  }

variable "artifactslocation" {
  description = "Location of WVD Artifacts" 
  default = "https://wvdportalstorageblob.blob.core.windows.net/galleryartifacts/Configuration.zip"
}

With the variables set, we can move on to defining the required resources to deploy a Azure Virtual Desktop session host.

Creating the AVD Session Host’s Network Interface

As with creating any Azure VM using Terraform, we first need to define a network interface resource:

resource "azurerm_network_interface" "nic" {
  name                = "${var.vm_name}${count.index + 1}"
  location            = "${var.location}"
  resource_group_name = "${var.res_group}"
  count               = "${var.vm_count}"

  ip_configuration {
    name                                    = "webipconfig${count.index}"
    subnet_id                               = "${var.subnet_id}"
    private_ip_address_allocation           = "Dynamic"
    }
}

I am using variables to set the name for the NIC, Location, Resource Group and SubnetID. Note that I am also using a count index. This allows us to create multiple session hosts, based on the value assigned to the count variable.

Create the Session Host Virtual Machines

Like with the network interfaces, the following will look familiar if you have used Terraform to deploy Azure virtual machines:

resource "azurerm_virtual_machine" "vm" {
  name                  = "${var.vm_name}${count.index + 1}"
  location              = "${var.location}"
  resource_group_name   = "${var.res_group}"
  vm_size               = "${var.vm_size}"
  network_interface_ids = ["${element(azurerm_network_interface.nic.*.id, count.index)}"]
  count                 = "${var.vm_count}"
  delete_os_disk_on_termination = true

  storage_image_reference {
    publisher = "${var.image_publisher}"
    offer     = "${var.image_offer}"
    sku       = "${var.image_sku}"
    version   = "${var.image_version}"
  }

  storage_os_disk {
    name          = "${var.vm_name}${count.index + 1}"
    create_option = "FromImage"
  }

  os_profile {
    computer_name  = "${var.vm_name}${count.index + 1}"
    admin_username = "${var.admin_username}"
    admin_password = "${var.admin_password}"
  }

  os_profile_windows_config {
    provision_vm_agent = true
  }
}

Again we are using the count index to ensure each VM gets paired with a network interface. Aside from that, we are just pulling a virtual machine image from the Azure marketplace, deploying it into the specified resource group, and setting it’s size, along with OS parameters for the local administrator account and password. As such, this is a fairly minimal example of a VM deployment, but additional configuration could be added here if that is a requirement. Or, for example, rather than using an Azure Marketplace machine image, a customised image from a shared image gallery could be used.

Note, all of these parameter values used here to configure the virtual machine are taken from the Terraform variables defined earlier.

Join the Session Hosts to a Active Directory Domain

The next resource we need to define is the domain join extension, so that the session host virtual machines will be joined to Active Directory when they have been powered on for the first time. Being a member of a domain is a requirement of running a AVD Session Host. This uses the built in domain join extension:

resource "azurerm_virtual_machine_extension" "domainjoinext" {
  name                 = "join-domain"
  virtual_machine_id   = element(azurerm_virtual_machine.vm.*.id, count.index)
  publisher            = "Microsoft.Compute"
  type                 = "JsonADDomainExtension"
  type_handler_version = "1.0"
  depends_on           = ["azurerm_virtual_machine.vm"]
  count                = "${var.vm_count}"

  settings = <<SETTINGS
    {
        "Name": "${var.domain}",
        "OUPath": "${var.oupath}",
        "User": "${var.domainuser}",
        "Restart": "true",
        "Options": "3"
    }
SETTINGS

  protected_settings = <<PROTECTED_SETTINGS
    {
        "Password": "${var.domainpassword}"
    }
PROTECTED_SETTINGS
}

We’re using variables to pass the resource the parameters it needs, including the domain to join, domain user account, password and OU. These are passed to the extension using the settings, and protected settings for the password.

Azure Virtual Desktop Agent and Configuration

This bit is where the magic happens! So far we have pretty much a regular Azure virtual machine (or machines if the count variable has been set higher than 1), which are joined to Active Directory. Again we are going to use a virtual machine extension:

resource "azurerm_virtual_machine_extension" "registersessionhost" {
  name                 = "registersessionhost"
  virtual_machine_id   = element(azurerm_virtual_machine.vm.*.id, count.index)
  publisher            = "Microsoft.Powershell"
  depends_on           = ["azurerm_virtual_machine_extension.domainjoinext"]
  count                = "${var.vm_count}"
  type = "DSC"
  type_handler_version = "2.73"
  auto_upgrade_minor_version = true
  settings = <<SETTINGS
    {
        "ModulesUrl": "${var.artifactslocation}",
        "ConfigurationFunction" : "Configuration.ps1\\AddSessionHost",
        "Properties": {
            "hostPoolName": "${var.hostpoolname}",
            "registrationInfoToken": "${var.regtoken}"
        }
    }
SETTINGS
}

This one is a Powershell DSC extension, which downloads the scripts necessary from Microsoft to join the session hosts to the specified host pool. The var.artifactslocation variable is populated with the URL to download the necessary scripts. These are found at https://wvdportalstorageblob.blob.core.windows.net/galleryartifacts/Configuration.zip and are the same as what are used when manually adding a session host using the Azure portal. Specifically we are telling it to run the AddSessionHost function in the configuration.ps1 file contained within in the zip. The configuration.zip file also contains the agent required to register the session host with the AVD host pool. I’d encourage you to take a look at the contents of the zip file so you can see how the scripts work.

For the AddSessionHost function, we just need to provide the host pool name and the host pool registration token, which is done using the var.hostpoolname and var.regtoken variables. And that’s all we need. With these 4 resources and the variables defined we are ready to deploy!

Running the Terraform Plan to Deploy AVD Session Hosts

Once you have created the necessary terraform files containing the resources, and the variables / tfvars, we are ready to test the plan, using terraform plan:

$ terraform plan

This will show all the resources that Terraform will create for us if we run the plan for real. Always review the output from this before proceeding, but in summary it should show that we will be created 20 new resources as part of this plan:

Plan: 20 to add, 0 to change, 0 to destroy.

Now we can run the plan for real using terraform apply:

$ terraform apply

You will be prompted to confirm that you want Terraform to go ahead and build the resources. Enter ‘Yes’ when prompted to do so. The plan doesn’t take long to run – once it’s finished you should see:

Apply complete! Resources: 20 added, 0 changed, 0 destroyed.

There should now be a bunch of session hosts attached to the new host pool!

Summary

In this article you have seen how to use Terraform to deploy Azure Virtual Desktop (Windows Virtual Desktop) Session hosts into a new host pool. Remember to check out this previous article on how to deploy the Azure Virtual Desktop host pool.

Related posts

Docker Exec Command With Practical Examples

Debugging with Git Bisect

A Beginners Guide to Azure Repos

This website uses cookies to improve your experience. We'll assume you're ok with this, but you can opt-out if you wish. Read More