Skip to content

Part 2: Deploying Azure Virtual Desktop (AVD) Desktops

This article provides a detailed, step-by-step guide to deploying AVD VMs from a custom image in the Azure Compute Gallery, joining them to a domain, and ensuring a successful deployment. For the image creation process, see Part 1: Building and Publishing an AVD Image.


Overview

The process involves: - Reviewing and updating parameter files for deployment - Deploying AVD VMs using Bicep and Azure CLI - Joining VMs to the domain using a privileged service account - Post-deployment validation and best practices


Step 1: Review and Update Parameter File

Before deploying, ensure your parameter file (e.g., avdp.json) is up to date with the correct image reference, VM size, network settings, and domain join information.

Example Parameter File (avdp.json)

{
  "resource_group": { "value": "rg-gdep-peus-avd-pools" },
  "nic_subnet_resourcegroup": { "value": "rg-gdep-peus-vnets" },
  "nic_vnet_name": { "value": "GDEP_VNET_PROD" },
  "nic_subnet_name": { "value": "Prod_AVD_FS_Subnet" },
  "vm_name": { "value": "AZGDEPENG" },
  "adminusername": { "value": "avdadmin" },
  "hostpoolname": { "value": "GDEP_Engineering" },
  "adminPassword": {
    "reference": {
      "keyVault": {
        "id": "/subscriptions/<sub-id>/resourceGroups/<rg>/providers/Microsoft.KeyVault/vaults/<vault-name>"
      },
      "secretName": "new-vm-password"
    }
  },
  "adadminusername": { "value": "service-account@yourdomain.com" },
  "adadminPassword": {
    "reference": {
      "keyVault": {
        "id": "/subscriptions/<sub-id>/resourceGroups/<rg>/providers/Microsoft.KeyVault/vaults/<vault-name>"
      },
      "secretName": "ad-join-password"
    }
  },
  "hostpoolregistrationkey": {
    "reference": {
      "keyVault": {
        "id": "/subscriptions/<sub-id>/resourceGroups/<rg>/providers/Microsoft.KeyVault/vaults/<vault-name>"
      },
      "secretName": "avd-host-pool-reg-key"
    }
  },
  "vmCount": { "value": 1 }
}

Parameter Explanations: - resource_group, nic_subnet_resourcegroup, nic_vnet_name, nic_subnet_name: Networking and resource group settings. - vm_name: Prefix for VM names. - adminusername/adminPassword: Local admin credentials for the VM (use Key Vault for secrets). - adadminusername/adadminPassword: Service account with delegated rights to join computers to the domain. Do not use personal admin accounts; use a dedicated service account. - hostpoolname, hostpoolregistrationkey: AVD host pool registration details. - vmCount: Number of VMs to deploy.


Step 2: Full Bicep Code for AVD Deployment

Below is the full Bicep code for deploying AVD VMs, joining them to the domain, and registering them with the AVD host pool.

param location string = resourceGroup().location
param resource_group string
param nic_subnet_resourcegroup string
param nic_vnet_name string
param nic_subnet_name string
param vm_name string
param adminusername string
param adadminusername string
param hostpoolregistrationkey string
param hostpoolname string  
param vmCount int

@secure()
param adminPassword string
@secure()
param adadminPassword string

var applicationtag = 'Virtual Desktop'
var environmetag = 'Production'
var domain = 'yourdomain.com'
var ou2add2 = 'OU=Computers,OU=Standard,OU=Virtual Desktops,OU=Azure,OU=GD,DC=yourdomain,DC=com'

@description('Create NICs for each VM')
module nicgdepavd '../../../networking/nic/nic.bicep' = [for i in range(0, vmCount):{
  name: '${deployment().name}-nic${i}'
  scope: resourceGroup(resource_group)
  params: {
    name: 'nicgdepp${location}${vm_name}${i}'
    location: location
    tags: { Application: applicationtag, Environment: environmetag }
    properties: {
      ipConfigurations: [
        {
          name: 'ipconfig'
          properties: {
            privateIPAllocationMethod: 'Dynamic'
            subnet: {
              id: resourceId(
                '${nic_subnet_resourcegroup}',
                'Microsoft.Network/virtualNetworks/subnets',
                '${nic_vnet_name}',
                '${nic_subnet_name}'
              )
            }
          }
        }
      ]
    }
  }
}]

@description('Create the AVD VM(s)')
module vmgdepavd '../vm.bicep' = [for i in range(0, vmCount): {
  name: '${deployment().name}-${i}'
  scope: resourceGroup(resource_group)
  params: {
    name: '${vm_name}-${i}'
    location: location
    tags: { Application: applicationtag, Environment: environmetag }
    properties: {
      hardwareProfile: { vmSize: 'Standard_NV16as_v4' }
      storageProfile: {
        imageReference: {
          id: resourceId('Microsoft.Compute/galleries/images', 'GDEP_Azure_Compute_Gallery', 'Win11AVD')
        }
        osDisk: {
          createOption: 'FromImage'
          diskSizeGB: 512
          managedDisk: { storageAccountType: 'Premium_LRS' }
        }
      }
      osProfile: {
        computerName: '${vm_name}-${i}'
        adminUsername: adminusername
        adminPassword: adminPassword
        windowsConfiguration: {
          provisionVMAgent: true
          enableAutomaticUpdates: true
        }
      }
      networkProfile: {
        networkInterfaces: [
          {
            id: nicgdepavd[i].outputs.id
            properties: { deleteOption: 'Delete' }
          }
        ]
      }
      diagnosticsProfile: { bootDiagnostics: { enabled: true } }
      licenseType: 'Windows_Client'
    }
  }
}]

@description('Add Custom Script Extension for Kofax')
resource extkofaxcustomscript 'Microsoft.Compute/virtualMachines/extensions@2024-03-01' = [for i in range(0, vmCount): {
  name: '${vm_name}-${i}/CustomScriptExtension'
  location: location
  tags: {
    Environment: environmetag
    Application: applicationtag
  }
  properties: {
    publisher: 'Microsoft.Compute'
    type: 'CustomScriptExtension'
    typeHandlerVersion: '1.9'
    autoUpgradeMinorVersion: true
    settings: {
      commandToExecute: 'powershell.exe -ExecutionPolicy Unrestricted -Command "Invoke-WebRequest -Uri \'https://storeusgdepsoftware.blob.core.windows.net/software4avd/avdkofax.ps1\' -OutFile \'C:\\software\\avdkofax.ps1\'; Set-Location -Path \'C:\\software\'; .\\avdkofax.ps1"'
    }
  }
  dependsOn: [
    vmgdepavd[i]
  ]
}]

@description('Join VM(s) to the domain')
resource extadd2adcustomscript 'Microsoft.Compute/virtualMachines/extensions@2024-03-01' = [for i in range(0, vmCount): {
  name: '${vm_name}-${i}/joindomain'
  location: location
  tags: {
    Environment: environmetag
    Application: applicationtag
  }
  properties: {
    autoUpgradeMinorVersion: true
    publisher: 'Microsoft.Compute'
    type: 'JsonADDomainExtension'
    typeHandlerVersion: '1.3'
    settings: {
      name: domain
      ouPath: ou2add2
      user: adadminusername
      restart: true
      options: '3'
    }
    protectedSettings: {
      password: adadminPassword
    }
  }
  dependsOn: [
    vmgdepavd[i]
    extkofaxcustomscript[i]
  ]
}]

@description('Register VM(s) with AVD Host Pool')
resource vmExtension 'Microsoft.Compute/virtualMachines/extensions@2024-03-01' = [for i in range(0, vmCount): {
  name: '${vm_name}-${i}/Microsoft.PowerShell.DSC'
  location: location
  tags: {
    Environment: environmetag
    Application: applicationtag
  }
  properties: {
    autoUpgradeMinorVersion: true
    publisher: 'Microsoft.Powershell'
    type: 'DSC'
    typeHandlerVersion: '2.73'
    settings: {
      modulesUrl: 'https://wvdportalstorageblob.blob.core.windows.net/galleryartifacts/Configuration_1.0.02714.342.zip'
      configurationFunction: 'Configuration.ps1\\AddSessionHost'
      properties: {
        hostPoolName: hostpoolname
        registrationInfoToken: hostpoolregistrationkey
        aadJoin: false
        UseAgentDownloadEndpoint: true
      }
    }
  }
  dependsOn: [
    vmgdepavd[i]
    extkofaxcustomscript[i]
    extadd2adcustomscript[i]
  ]
}]

Step 3: Deploy the AVD VM(s) Using Azure CLI

Use the following command to deploy the VM(s):

az deployment group create --name gdepiacavd --resource-group rg-gdep-peus-avd-pools --template-file ./compute/vm/avd/avd.bicep --parameters @./compute/vm/avd/avdp.json
  • This command deploys the VM(s) using the custom image and parameters.

Step 4: Domain Join and Permissions

  • The adadminusername must be a service account with delegated permissions to join computers to the specified OU in Active Directory.
  • Do not use personal admin accounts; use a dedicated, least-privilege service account.
  • The deployment will use the provided credentials to join the VM to the domain and place it in the correct OU.

Step 5: Post-Deployment Validation

  • Log in to the new VM(s) as the local admin account.
  • Verify log files (e.g., in C:\Software) to ensure a clean build.
  • Expand the disk to 512 GB if required.
  • (Optional) Install additional software as needed.
  • For validation desktops, use the correct host pool name and ensure only authorized users have access.

Security and Best Practices

  • Always use secure methods (Key Vault references) for passwords and sensitive data.
  • Review all scripts and templates for security and compliance.
  • Mask all sensitive information in documentation.
  • Use service accounts for domain join, not personal accounts.