azureterraformazure-virtual-machinevirtual-desktopwindows-virtual-desktop

Terraform Azure VM extension does not join VM to Azure Active Directory for Azure Virtual Desktop


Using https://techcommunity.microsoft.com/t5/azure-virtual-desktop/arm-avd-with-terraform/m-p/2639806 as inspiration, I want to deploy AVD but with AAD joined VMs using terraform. I have defined a VM and extension in Terraform alongside a Azure Virtual Desktop deployment(host pool, app group, workspace) as per the article, and I have adapted the extensions based on the ARM template generated by Azure when VMs are added to the Host Pool and joined with AAD 'manually'/through the portal.

The terraform applies and the extensions run through successfully but the VMs do not join the domain.

When I look at the logs on the deployed VM one of the main errors is 'DsrCmdAzureHelper::GetTenantId: Unable to get Tenant Id, status code 400'. I get 400 status code when getting metadata from Targeting host name:169.254.169.254, url path: /metadata/identity/info?api-version=2018-02-01 and the 'identity not found' errors follow.

What is the issue here? Below is the terraform for the extensions, let me know if more is required.

locals {
  registration_token = azurerm_virtual_desktop_host_pool.hostpool.registration_info[0].token
}


resource "azurerm_virtual_machine_extension" "domain_join" {
  count                      = var.rdsh_count
  name                       = "${var.prefix}-${count.index + 1}-domainJoin"
  virtual_machine_id         = azurerm_windows_virtual_machine.avd_vm.*.id[count.index]
  publisher                  = "Microsoft.Azure.ActiveDirectory"
  type                       = "AADLoginForWindows"
  type_handler_version       = "1.0"
  auto_upgrade_minor_version = true

  depends_on = [
    azurerm_virtual_machine_extension.vmext_dsc
  ]
}

resource "azurerm_virtual_machine_extension" "vmext_dsc" {
  count                      = var.rdsh_count
  name                       = "${var.prefix}${count.index + 1}-avd_dsc"
  # virtual_machine_id         = azurerm_windows_virtual_machine.avd_vm.*.id[count.index]
  virtual_machine_id         = azurerm_windows_virtual_machine.avd_vm.*.id[count.index]
  publisher                  = "Microsoft.Powershell"
  type                       = "DSC"
  type_handler_version       = "2.73"
  auto_upgrade_minor_version = true

  settings = <<-SETTINGS
    {
      "modulesUrl": "https://wvdportalstorageblob.blob.core.windows.net/galleryartifacts/Configuration_9-30-2021.zip",
      "configurationFunction": "Configuration.ps1\\AddSessionHost",
      "properties": {
        "HostPoolName":"${azurerm_virtual_desktop_host_pool.hostpool.name}",
        "aadJoin": true
      }
    }
SETTINGS

  protected_settings = <<PROTECTED_SETTINGS
  {
    "properties": {
      "registrationInfoToken": "${local.registration_token}"
    }
  }
PROTECTED_SETTINGS

  depends_on = [
    azurerm_virtual_desktop_host_pool.hostpool
  ]
}


Solution

  • I tested it in my environment and the issue was same like below :

    enter image description here

    As per the discussion in this Microsoft Q & A Thread , It is mentioned that a key AADJPrivate should be present under path HKLM\Software\microsoft\RDInfraAgent , if it is not present then the VM will add the extensions properly but fail to domain join with Azure AD.

    So, as a solution , I changed few things in the code like :

    • The host had custom_rdp_properties as "audiocapturemode:i:1;audiomode:i:0;" instead ,added "audiocapturemode:i:1;audiomode:i:0;targetisaadjoined:i:1;".
    • Provided auto_upgrade_minor_version = true in the AADLoginForWindows extension.
    • Added another Custom script extension to add the key AADJPRIVATE for the VM's.

    After Modifications ,I tried with something like below :

    provider "azurerm" {
      features {}
      version = "2.90.0"
    }
    
    provider "azuread" {}
    
    data "azuread_group" "aad_group" {
      display_name = "xxxx"
      security_enabled = true
    }
    data "azurerm_role_definition" "vm_user_login" {
      name = "Virtual Machine User Login"
    }
    resource "azurerm_role_assignment" "vm_user_role" {
      scope              = azurerm_resource_group.rg-avd.id
      role_definition_id = data.azurerm_role_definition.vm_user_login.id
      principal_id       = data.azuread_group.aad_group.id
    }
    
    data "azurerm_role_definition" "desktop_user" { 
      name = "Desktop Virtualization User"
    }
    
    resource "azurerm_role_assignment" "desktop_role" {
      scope              = azurerm_virtual_desktop_application_group.desktopapp.id
      role_definition_id = data.azurerm_role_definition.desktop_user.id
      principal_id       = data.azuread_group.aad_group.id
    }
    
    resource "azurerm_resource_group" "rg-avd" {
      name     = "avd-test"
      location = "West Europe"
    }
    
    resource "azurerm_virtual_network" "vnet" {
      name                = "avd-vnet"
      location            = azurerm_resource_group.rg-avd.location
      resource_group_name = azurerm_resource_group.rg-avd.name
      address_space       = ["10.0.0.0/16"]
    }
    
    resource "azurerm_subnet" "defaultSubnet" {
      name           = "avd-subnet"
      resource_group_name = azurerm_resource_group.rg-avd.name
      virtual_network_name = azurerm_virtual_network.vnet.name
      address_prefixes = ["10.0.0.0/24"]
    }
    
    resource "azurerm_network_security_group" "nsg" {
      name                = "avd-nsg"
      location            = azurerm_resource_group.rg-avd.location
      resource_group_name = azurerm_resource_group.rg-avd.name
      security_rule {
        name                       = "allow-rdp"
        priority                   = 100
        direction                  = "Inbound"
        access                     = "Allow"
        protocol                   = "Tcp"
        source_port_range          = "*"
        destination_port_range     = 3389
        source_address_prefix      = "*"
        destination_address_prefix = "*"
      }
    }
    
    resource "azurerm_subnet_network_security_group_association" "nsg_association" {
      subnet_id                 = azurerm_subnet.defaultSubnet.id
      network_security_group_id = azurerm_network_security_group.nsg.id
    }
    
    resource "time_rotating" "avd_token" {
      rotation_days = 30
    }
    
    resource "azurerm_virtual_desktop_host_pool" "avd-hp" {
      location            = azurerm_resource_group.rg-avd.location
      resource_group_name = azurerm_resource_group.rg-avd.name
    
      name                     = "testhostpool"
      friendly_name            = "ansupool"
      validate_environment     = true
      start_vm_on_connect      = true
      custom_rdp_properties    = "audiocapturemode:i:1;audiomode:i:0;targetisaadjoined:i:1;"
      description              = "ansu host-poool demo"
      type                     = "Pooled"
      maximum_sessions_allowed = 10
      load_balancer_type       = "DepthFirst"
    
      registration_info {
        expiration_date = time_rotating.avd_token.rotation_rfc3339
      }
    }
    
    resource "azurerm_virtual_desktop_application_group" "desktopapp" {
      name                = "ANS-Desktop"
      location            = azurerm_resource_group.rg-avd.location
      resource_group_name = azurerm_resource_group.rg-avd.name
      type          = "Desktop"
      host_pool_id  = azurerm_virtual_desktop_host_pool.avd-hp.id
      friendly_name = "ANS-application"
      description   = "ansuman applications"
    }
    
    resource "azurerm_virtual_desktop_workspace" "workspace" {
      name                = "ANS-WORKSPACE"
      location            = azurerm_resource_group.rg-avd.location
      resource_group_name = azurerm_resource_group.rg-avd.name
      friendly_name = "ANS-AVD_WRSPC"
      description   = "Work Purporse"
    }
    
    resource "azurerm_virtual_desktop_workspace_application_group_association" "workspaceremoteapp" {
      workspace_id         = azurerm_virtual_desktop_workspace.workspace.id
      application_group_id = azurerm_virtual_desktop_application_group.desktopapp.id
    }
    
    
    resource "azurerm_network_interface" "sessionhost_nic" {
        count=2
      name                = "nic-ansu-${count.index}"
      location            = azurerm_resource_group.rg-avd.location
      resource_group_name = azurerm_resource_group.rg-avd.name
    
      ip_configuration {
        name                          = "internal"
        subnet_id                     = azurerm_subnet.defaultSubnet.id
        private_ip_address_allocation = "Dynamic"
      }
    }
    
    resource "azurerm_windows_virtual_machine" "avd_sessionhost" {
      depends_on = [
          azurerm_network_interface.sessionhost_nic
      ]
      count=2
      name                = "ansuvm-${count.index}"
      resource_group_name = azurerm_resource_group.rg-avd.name
      location            = azurerm_resource_group.rg-avd.location
      size                = "Standard_B2MS"
      admin_username      = "adminuser"
      admin_password      = "Password@1234"
      provision_vm_agent = true
      
      network_interface_ids = [azurerm_network_interface.sessionhost_nic.*.id[count.index]]
    
      identity {
        type  = "SystemAssigned"
      }
      
      os_disk {
        caching              = "ReadWrite"
        storage_account_type = "Premium_LRS"
      }
    
     source_image_reference {
        publisher = "MicrosoftWindowsDesktop"
        offer     = "Windows-10"
        sku       = "20h2-evd"
        version   = "latest"
      }
    }
    
    locals {
      registration_token = azurerm_virtual_desktop_host_pool.avd-hp.registration_info[0].token
      shutdown_command     = "shutdown -r -t 10"
      exit_code_hack       = "exit 0"
      commandtorun         = "New-Item -Path HKLM:/SOFTWARE/Microsoft/RDInfraAgent/AADJPrivate"
      powershell_command   = "${local.commandtorun}; ${local.shutdown_command}; ${local.exit_code_hack}"
    }
    
    resource "azurerm_virtual_machine_extension" "AVDModule" {
      depends_on = [
          azurerm_windows_virtual_machine.avd_sessionhost
      ]
      count = 2
      name                 = "Microsoft.PowerShell.DSC"
      virtual_machine_id   = azurerm_windows_virtual_machine.avd_sessionhost.*.id[count.index]
      publisher            = "Microsoft.Powershell"
      type                 = "DSC"
      type_handler_version = "2.73"
      settings = <<-SETTINGS
        {
            "modulesUrl": "https://wvdportalstorageblob.blob.core.windows.net/galleryartifacts/Configuration_11-22-2021.zip",
            "ConfigurationFunction": "Configuration.ps1\\AddSessionHost",
            "Properties" : {
              "hostPoolName" : "${azurerm_virtual_desktop_host_pool.avd-hp.name}",
              "aadJoin": true
            }
        }
    SETTINGS
    
      protected_settings = <<PROTECTED_SETTINGS
      {
        "properties": {
          "registrationInfoToken": "${local.registration_token}"
        }
      }
    PROTECTED_SETTINGS
    
    }
    resource "azurerm_virtual_machine_extension" "AADLoginForWindows" {
      depends_on = [
          azurerm_windows_virtual_machine.avd_sessionhost,
            azurerm_virtual_machine_extension.AVDModule
      ]
      count = 2
      name                 = "AADLoginForWindows"
      virtual_machine_id   = azurerm_windows_virtual_machine.avd_sessionhost.*.id[count.index]
      publisher            = "Microsoft.Azure.ActiveDirectory"
      type                 = "AADLoginForWindows"
      type_handler_version = "1.0"
      auto_upgrade_minor_version = true
    }
    resource "azurerm_virtual_machine_extension" "addaadjprivate" {
        depends_on = [
          azurerm_virtual_machine_extension.AADLoginForWindows
        ]
        count = 2
      name                 = "AADJPRIVATE"
      virtual_machine_id =    azurerm_windows_virtual_machine.avd_sessionhost.*.id[count.index]
      publisher            = "Microsoft.Compute"
      type                 = "CustomScriptExtension"
      type_handler_version = "1.9"
    
      settings = <<SETTINGS
        {
            "commandToExecute": "powershell.exe -Command \"${local.powershell_command}\""
        }
    SETTINGS
    }
    

    Output:

    enter image description here

    enter image description here

    enter image description here

    Note: As per this GitHub Issue , you have to use azurerm provider version 2.90.0.