About Me

My photo
I am an MCSE in Data Management and Analytics, specializing in MS SQL Server, and an MCP in Azure. With over 19+ years of experience in the IT industry, I bring expertise in data management, Azure Cloud, Data Center Migration, Infrastructure Architecture planning, as well as Virtualization and automation. I have a deep passion for driving innovation through infrastructure automation, particularly using Terraform for efficient provisioning. If you're looking for guidance on automating your infrastructure or have questions about Azure, SQL Server, or cloud migration, feel free to reach out. I often write to capture my own experiences and insights for future reference, but I hope that sharing these experiences through my blog will help others on their journey as well. Thank you for reading!

AVD in a minute



 <#

.SYNOPSIS

    Complete Azure Virtual Desktop (AVD) Setup Script


.DESCRIPTION

    This script sets up Azure Virtual Desktop for an existing Windows VM:

    - Creates Host Pool (Personal type)

    - Creates Desktop Application Group

    - Creates Workspace

    - Azure AD joins the VM

    - Installs AVD agents

    - Registers session host

    - Assigns user to session host


.PARAMETER ResourceGroupName

    The resource group containing the VM


.PARAMETER VMName

    The name of the existing Windows VM


.PARAMETER Location

    Azure region (default: eastus)


.PARAMETER HostPoolName

    Name for the AVD Host Pool


.PARAMETER AppGroupName

    Name for the Application Group


.PARAMETER WorkspaceName

    Name for the AVD Workspace


.PARAMETER AssignedUser

    User Principal Name to assign to the session host


.EXAMPLE

    .\Setup-AVD-Complete.ps1 -ResourceGroupName "RG-WIN11-BASTION" -VMName "VM-Win11" -AssignedUser "user@domain.com"


.NOTES

    Author: Generated by Claude Code

    Date: 2026-01-03

    Requires: Azure CLI, Owner/Contributor role on subscription

#>


param(

    [Parameter(Mandatory=$true)]

    [string]$ResourceGroupName,


    [Parameter(Mandatory=$true)]

    [string]$VMName,


    [Parameter(Mandatory=$false)]

    [string]$Location = "eastus",


    [Parameter(Mandatory=$false)]

    [string]$HostPoolName = "HP-AVD-Personal",


    [Parameter(Mandatory=$false)]

    [string]$AppGroupName = "AG-AVD-Desktop",


    [Parameter(Mandatory=$false)]

    [string]$WorkspaceName = "WS-AVD",


    [Parameter(Mandatory=$false)]

    [string]$AssignedUser

)


$ErrorActionPreference = "Stop"


Write-Host "============================================" -ForegroundColor Cyan

Write-Host "  Azure Virtual Desktop Setup Script" -ForegroundColor Cyan

Write-Host "============================================" -ForegroundColor Cyan

Write-Host ""


# Get subscription ID

Write-Host "[1/12] Getting subscription information..." -ForegroundColor Yellow

$subscriptionInfo = az account show --query "{id:id, name:name}" -o json | ConvertFrom-Json

$SubscriptionId = $subscriptionInfo.id

Write-Host "  Subscription: $($subscriptionInfo.name)" -ForegroundColor Green

Write-Host "  ID: $SubscriptionId" -ForegroundColor Green


# Verify VM exists

Write-Host ""

Write-Host "[2/12] Verifying VM exists..." -ForegroundColor Yellow

$vmInfo = az vm show --resource-group $ResourceGroupName --name $VMName --query "{name:name, location:location, vmId:vmId}" -o json 2>$null | ConvertFrom-Json

if (-not $vmInfo) {

    Write-Host "  ERROR: VM '$VMName' not found in resource group '$ResourceGroupName'" -ForegroundColor Red

    exit 1

}

Write-Host "  VM Found: $($vmInfo.name)" -ForegroundColor Green

Write-Host "  Location: $($vmInfo.location)" -ForegroundColor Green


# Use VM's location if not specified

if (-not $Location) {

    $Location = $vmInfo.location

}


# Create Host Pool

Write-Host ""

Write-Host "[3/12] Creating Host Pool: $HostPoolName..." -ForegroundColor Yellow

az desktopvirtualization hostpool create `

    --resource-group $ResourceGroupName `

    --name $HostPoolName `

    --location $Location `

    --host-pool-type "Personal" `

    --personal-desktop-assignment-type "Automatic" `

    --load-balancer-type "Persistent" `

    --preferred-app-group-type "Desktop" `

    --registration-info expiration-time="$((Get-Date).AddDays(1).ToString('yyyy-MM-ddTHH:mm:ss.fffffffZ'))" registration-token-operation="Update" `

    --output none


Write-Host "  Host Pool created successfully" -ForegroundColor Green


# Create Application Group

Write-Host ""

Write-Host "[4/12] Creating Application Group: $AppGroupName..." -ForegroundColor Yellow

$hostPoolArmPath = "/subscriptions/$SubscriptionId/resourceGroups/$ResourceGroupName/providers/Microsoft.DesktopVirtualization/hostPools/$HostPoolName"


az desktopvirtualization applicationgroup create `

    --resource-group $ResourceGroupName `

    --name $AppGroupName `

    --location $Location `

    --application-group-type "Desktop" `

    --host-pool-arm-path $hostPoolArmPath `

    --output none


Write-Host "  Application Group created successfully" -ForegroundColor Green


# Create Workspace

Write-Host ""

Write-Host "[5/12] Creating Workspace: $WorkspaceName..." -ForegroundColor Yellow

az desktopvirtualization workspace create `

    --resource-group $ResourceGroupName `

    --name $WorkspaceName `

    --location $Location `

    --output none


Write-Host "  Workspace created successfully" -ForegroundColor Green


# Associate Application Group with Workspace

Write-Host ""

Write-Host "[6/12] Associating Application Group with Workspace..." -ForegroundColor Yellow

$appGroupArmPath = "/subscriptions/$SubscriptionId/resourceGroups/$ResourceGroupName/providers/Microsoft.DesktopVirtualization/applicationGroups/$AppGroupName"


az desktopvirtualization workspace update `

    --resource-group $ResourceGroupName `

    --name $WorkspaceName `

    --application-group-references $appGroupArmPath `

    --output none


Write-Host "  Association completed successfully" -ForegroundColor Green


# Generate Registration Token

Write-Host ""

Write-Host "[7/12] Generating registration token..." -ForegroundColor Yellow

$expirationTime = (Get-Date).AddHours(24).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ss.fffffffZ')


$tokenResult = az rest --method POST `

    --uri "https://management.azure.com/subscriptions/$SubscriptionId/resourceGroups/$ResourceGroupName/providers/Microsoft.DesktopVirtualization/hostPools/$HostPoolName/retrieveRegistrationToken?api-version=2023-09-05" `

    --body "{}" `

    -o json | ConvertFrom-Json


$RegistrationToken = $tokenResult.token

Write-Host "  Registration token generated (expires in 24 hours)" -ForegroundColor Green


# Enable System Assigned Managed Identity

Write-Host ""

Write-Host "[8/12] Enabling managed identity on VM..." -ForegroundColor Yellow

az vm identity assign `

    --resource-group $ResourceGroupName `

    --name $VMName `

    --output none


Write-Host "  Managed identity enabled" -ForegroundColor Green


# Install AADLoginForWindows extension (Azure AD Join)

Write-Host ""

Write-Host "[9/12] Installing Azure AD Login extension (Azure AD Join)..." -ForegroundColor Yellow

az vm extension set `

    --resource-group $ResourceGroupName `

    --vm-name $VMName `

    --name "AADLoginForWindows" `

    --publisher "Microsoft.Azure.ActiveDirectory" `

    --output none


Write-Host "  Azure AD Login extension installed" -ForegroundColor Green


# Install AVD Agents and set registration token via Run Command

Write-Host ""

Write-Host "[10/12] Installing AVD agents and configuring registration..." -ForegroundColor Yellow


$avdInstallScript = @"

`$ErrorActionPreference = 'Stop'

`$tempDir = 'C:\AVDTemp'


# Create temp directory

if (!(Test-Path `$tempDir)) {

    New-Item -ItemType Directory -Path `$tempDir -Force | Out-Null

}


# Download AVD Agent

Write-Host 'Downloading AVD Agent...'

`$agentUrl = 'https://query.prod.cms.rt.microsoft.com/cms/api/am/binary/RWrmXv'

`$agentPath = Join-Path `$tempDir 'Microsoft.RDInfra.RDAgent.Installer-x64.msi'

Invoke-WebRequest -Uri `$agentUrl -OutFile `$agentPath -UseBasicParsing


# Download Boot Loader

Write-Host 'Downloading Boot Loader...'

`$bootLoaderUrl = 'https://query.prod.cms.rt.microsoft.com/cms/api/am/binary/RWrxrH'

`$bootLoaderPath = Join-Path `$tempDir 'Microsoft.RDInfra.RDAgentBootLoader.Installer-x64.msi'

Invoke-WebRequest -Uri `$bootLoaderUrl -OutFile `$bootLoaderPath -UseBasicParsing


# Install Boot Loader first

Write-Host 'Installing Boot Loader...'

Start-Process msiexec.exe -ArgumentList "/i `$bootLoaderPath /quiet /norestart" -Wait -NoNewWindow


# Install AVD Agent with registration token

Write-Host 'Installing AVD Agent with registration token...'

Start-Process msiexec.exe -ArgumentList "/i `$agentPath /quiet /norestart REGISTRATIONTOKEN=$RegistrationToken" -Wait -NoNewWindow


Write-Host 'AVD Agent installation completed'

"@


az vm run-command invoke `

    --resource-group $ResourceGroupName `

    --name $VMName `

    --command-id RunPowerShellScript `

    --scripts $avdInstallScript `

    --output none


Write-Host "  AVD agents installed" -ForegroundColor Green


# Set registration token in registry and restart services

Write-Host ""

Write-Host "[11/12] Configuring registry and restarting services..." -ForegroundColor Yellow


$registryScript = @"

`$token = '$RegistrationToken'

`$regPath = 'HKLM:\SOFTWARE\Microsoft\RDInfraAgent'


if (!(Test-Path `$regPath)) {

    New-Item -Path `$regPath -Force | Out-Null

}


Set-ItemProperty -Path `$regPath -Name 'RegistrationToken' -Value `$token -Force

Set-ItemProperty -Path `$regPath -Name 'IsRegistered' -Value 0 -Type DWord -Force


Stop-Service RDAgentBootLoader -Force -ErrorAction SilentlyContinue

Stop-Service RDAgent -Force -ErrorAction SilentlyContinue

Start-Sleep -Seconds 5

Start-Service RDAgentBootLoader

Start-Service RDAgent

Start-Sleep -Seconds 15


`$regValue = Get-ItemProperty -Path `$regPath

Write-Host "IsRegistered: `$(`$regValue.IsRegistered)"

"@


$result = az vm run-command invoke `

    --resource-group $ResourceGroupName `

    --name $VMName `

    --command-id RunPowerShellScript `

    --scripts $registryScript `

    --query "value[0].message" `

    -o tsv


Write-Host "  Registry configured, services restarted" -ForegroundColor Green

Write-Host "  $result" -ForegroundColor Gray


# Assign user to session host (if specified)

Write-Host ""

Write-Host "[12/12] Assigning user to session host..." -ForegroundColor Yellow


if ($AssignedUser) {

    # Wait for session host to register

    Start-Sleep -Seconds 10


    az rest --method PATCH `

        --uri "https://management.azure.com/subscriptions/$SubscriptionId/resourceGroups/$ResourceGroupName/providers/Microsoft.DesktopVirtualization/hostPools/$HostPoolName/sessionHosts/$VMName`?api-version=2023-09-05" `

        --body "{`"properties`":{`"assignedUser`":`"$AssignedUser`"}}" `

        --output none


    Write-Host "  User '$AssignedUser' assigned to session host" -ForegroundColor Green

} else {

    Write-Host "  No user specified, skipping assignment" -ForegroundColor Yellow

}


# Verify session host registration

Write-Host ""

Write-Host "============================================" -ForegroundColor Cyan

Write-Host "  Verifying Setup" -ForegroundColor Cyan

Write-Host "============================================" -ForegroundColor Cyan


Start-Sleep -Seconds 5


$sessionHost = az rest --method GET `

    --uri "https://management.azure.com/subscriptions/$SubscriptionId/resourceGroups/$ResourceGroupName/providers/Microsoft.DesktopVirtualization/hostPools/$HostPoolName/sessionHosts?api-version=2023-09-05" `

    -o json | ConvertFrom-Json


if ($sessionHost.value.Count -gt 0) {

    $host = $sessionHost.value[0]

    Write-Host ""

    Write-Host "Session Host Status:" -ForegroundColor Green

    Write-Host "  Name: $($host.name.Split('/')[1])" -ForegroundColor White

    Write-Host "  Status: $($host.properties.status)" -ForegroundColor White

    Write-Host "  Agent Version: $($host.properties.agentVersion)" -ForegroundColor White

    Write-Host "  Assigned User: $($host.properties.assignedUser)" -ForegroundColor White

    Write-Host "  Allow New Session: $($host.properties.allowNewSession)" -ForegroundColor White

} else {

    Write-Host "  Session host not yet registered. Please wait a few minutes." -ForegroundColor Yellow

}


# Summary

Write-Host ""

Write-Host "============================================" -ForegroundColor Cyan

Write-Host "  Setup Complete!" -ForegroundColor Cyan

Write-Host "============================================" -ForegroundColor Cyan

Write-Host ""

Write-Host "Resources Created:" -ForegroundColor Green

Write-Host "  - Host Pool: $HostPoolName" -ForegroundColor White

Write-Host "  - Application Group: $AppGroupName" -ForegroundColor White

Write-Host "  - Workspace: $WorkspaceName" -ForegroundColor White

Write-Host "  - Session Host: $VMName" -ForegroundColor White

Write-Host ""

Write-Host "IMPORTANT - Manual Step Required:" -ForegroundColor Yellow

Write-Host "  Assign 'Desktop Virtualization User' role to user(s) on the Application Group:" -ForegroundColor Yellow

Write-Host ""

Write-Host "  az role assignment create \" -ForegroundColor Gray

Write-Host "    --assignee `"<user-object-id>`" \" -ForegroundColor Gray

Write-Host "    --role `"Desktop Virtualization User`" \" -ForegroundColor Gray

Write-Host "    --scope `"$appGroupArmPath`"" -ForegroundColor Gray

Write-Host ""

Write-Host "Or via Azure Portal:" -ForegroundColor Yellow

Write-Host "  1. Go to Application Group '$AppGroupName'" -ForegroundColor White

Write-Host "  2. Access control (IAM) -> Add role assignment" -ForegroundColor White

Write-Host "  3. Select 'Desktop Virtualization User' role" -ForegroundColor White

Write-Host "  4. Add user(s) and save" -ForegroundColor White

Write-Host ""

Write-Host "To Connect:" -ForegroundColor Green

Write-Host "  1. Download AVD Client: https://aka.ms/avdclient" -ForegroundColor White

Write-Host "  2. Open client and click 'Subscribe'" -ForegroundColor White

Write-Host "  3. Sign in with your Azure AD account" -ForegroundColor White

Write-Host "  4. Double-click the desktop to connect" -ForegroundColor White

Write-Host ""