Azure Virtual Desktop with pure Azure AD and Intune built with Terraform

First post in a long time... Too much management and not enough geeking, so on with the joys of building Azure Virtual Desktop in a completely Azure AD setup.

First thing to be said is that the Microsoft documentation on all of this sucks. Badly.This post comes from pulling together bits from there, about a dozen different blog posts and bug reports in github. Yes, that painful for what is actually very simple code.

I won't bother with a fully working piece of Terraform as if you're reading this you probably already know Terraform and AzureRM enough to not need an example of creating a VNet.

On with the show...

The Workspace

resource "azurerm_virtual_desktop_workspace" "workspace" {
  name                = var.workspace
  resource_group_name = azurerm_resource_group.resourcegroup.name
  location            = azurerm_resource_group.resourcegroup.location
  friendly_name       = "${var.prefix} Workspace"
  description         = "${var.prefix} Workspace"
  tags                = var.default_tags
}

Nothing interesting there so we'll move on...

The Host pool

# Create AVD host pool
resource "azurerm_virtual_desktop_host_pool" "hostpool" {
  resource_group_name      = azurerm_resource_group.resourcegroup.name
  location                 = azurerm_resource_group.resourcegroup.location
  name                     = "desktop-hostpool"
  friendly_name            = "desktop-hostpool"
  validate_environment     = true
  custom_rdp_properties    = "audiocapturemode:i:1;audiomode:i:0;targetisaadjoined:i:1;enablecredsspsupport:i:0;authenticationlevel:i:2;"
  description              = "${var.prefix} Terraform HostPool"
  type                     = "Pooled"
  maximum_sessions_allowed = 10
  load_balancer_type       = "DepthFirst" #[BreadthFirst DepthFirst]
  tags                     = var.default_tags
}

custom_rdp_properties is what matters here. The audio parts are up to you, but the remainder are important to allow you to log on with Azure AD credentials with your client machine credentials without entering them again when using the RemoteDesktop app. More on RemoteDesktop later as hours were wasted due to it.

The Host pool registration

resource "azurerm_virtual_desktop_host_pool_registration_info" "hostpoolregistration" {
  hostpool_id     = azurerm_virtual_desktop_host_pool.hostpool.id
  expiration_date = "2022-06-10T23:40:52Z"
}

expiration_date needs to be up to 30 days. I'm sure there's something sexier to replace a fixed date but I'd had enough of Googling putting the rest of it together.

The Application Group

# Create AVD DAG
resource "azurerm_virtual_desktop_application_group" "dag" {
  resource_group_name = azurerm_resource_group.resourcegroup.name
  host_pool_id        = azurerm_virtual_desktop_host_pool.hostpool.id
  location            = azurerm_resource_group.resourcegroup.location
  type                = "Desktop"
  name                = "${var.prefix}-dag"
  friendly_name       = "Desktop AppGroup"
  description         = "AVD application group"
  depends_on          = [azurerm_virtual_desktop_host_pool.hostpool, azurerm_virtual_desktop_workspace.workspace]
  tags                = var.default_tags
}

Nothing exciting here, but you'll need to add the users into it via the portal or your CLI of choice.

The Association

# Associate Workspace and DAG
resource "azurerm_virtual_desktop_workspace_application_group_association" "ws-dag" {
  application_group_id = azurerm_virtual_desktop_application_group.dag.id
  workspace_id         = azurerm_virtual_desktop_workspace.workspace.id
}

Link up the group with the pool and we're ready for...

The VM

resource "azurerm_windows_virtual_machine" "this" {
  name                = var.vm_name
  resource_group_name = var.resource_group_name
  location            = var.location
  size                = var.vm_size
  zone                = var.zones
  admin_username      = "admin"
  admin_password      = var.vm_local_admin_password
  timezone            = "UTC"
  license_type        = "Windows_Client"
  network_interface_ids = [
    azurerm_network_interface.this.id,
  ]

  os_disk {
    name                    = "${var.vm_name}-os"
    caching                 = "ReadWrite"
    storage_account_type    = "Premium_LRS"
  }

  source_image_reference {
    publisher = "microsoftwindowsdesktop"
    offer     = "office-365"
    sku       = "win11-21h2-avd-m365"
    version   = "latest"
  }
  tags = var.tags
}

The only interesting bit to point out about this is licence_type. Set this to "Windows_Client" or you'll incur additional licensing costs. This is presuming that you have the right type of license for you users (e.g. Microsoft 365 E3) to set it to this value.

Tweak the SKU to suit your needs but make sure its a multi-user Windows 10 or 11.

The VM Extensions

These are what do all the heavy lifting to get the VM into the pool, join Azure and enroll in Intune.

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

resource "azurerm_virtual_machine_extension" "registersessionhost" {
  name                 = "registersessionhost"
  virtual_machine_id   = azurerm_windows_virtual_machine.this.id
  publisher            = "Microsoft.Powershell"
  type = "DSC"
  type_handler_version = "2.73"
  auto_upgrade_minor_version = true
  settings = <<SETTINGS
    {
        "ModulesUrl": "${var.artifactslocation}",
        "ConfigurationFunction" : "Configuration.ps1\\AddSessionHost",
        "Properties": {
            "hostPoolName": "desktop-hostpool",
            "aadJoin": true
        }
    }
SETTINGS

  protected_settings = <<PROTECTED_SETTINGS
  {
    "properties": {
      "registrationInfoToken": "${var.hostpooltoken}"
    }
  }
PROTECTED_SETTINGS

tags = var.tags
}

As you can probably tell, this registers the VM in the pool taking the token generated earlier.

artifactslocation points to a set of MS scripts for deploying AVD. Also, they're pretty much completely undocumented and I only really worked it out through looking at the generated ARM and reading github bug reports. Note the date on the end, so new ones may well come along that you'll never know about. Thanks, Microsoft.

Make sure your VM can access that URL otherwise you'll have to get your own copy of the zip and host it somewhere it can talk to.

Don't miss the aadJoin property.

resource "azurerm_virtual_machine_extension" "domain_join_azuread" {
 
  name                       = "aad-join"
  virtual_machine_id         = azurerm_windows_virtual_machine.this.id
  publisher                  = "Microsoft.Azure.ActiveDirectory"
  type                       = "AADLoginForWindows"
  type_handler_version       = "1.0"
  auto_upgrade_minor_version = true

  settings = <<SETTINGS
    {
      "mdmId": "0000000a-0000-0000-c000-000000000000"  
    }
SETTINGS

  tags = var.tags

  depends_on = [azurerm_virtual_machine_extension.registersessionhost]
}

Fairly straightforward and you'll only need the mdmID setting if you want the VM in Intune

Gotchas

That's basically everything you need to get things up and running, but a couple of warnings.

The Workspace will only work properly with the MSI version of Remote Desktop from here. The version with the same name and same icon from the Windows Store DOES NOT WORK. This wasted loads of my time (and Microsoft's as I raised a ticket when I couldn't log in to the Workspace). As I had both on my machine, it got a little confusing. Again, thanks, Microsoft.

You'll also need to grant the users Virtual Machine User Login with IAM.

Conclusion

It's really not that complicated at all, but MS documentation is heavy on words and slim on actual implementation detail which made it take so much longer.
VM extensions also tend to be very black box and relying on pulling DSC scripts from some arcane storage account doesn't exactly make the process particularly simple.

Ah well, working now. Enjoy!

Comments

  1. thx great post
    any idea why i get
    Code="VMExtensionProvisioningError" Message="VM has reported a failure when processing extension 'aad-join'.

    ReplyDelete

Post a Comment

Popular posts from this blog

Avoid Microsoft Intune if you use G-Suite and Android

DFS "Waiting for Initial Replication"

Setting Wallpaper for a Remote Desktop Session