Skip to the content.

Detailed Deployment Guide

This guide provides comprehensive deployment instructions, architectural context, and advanced configuration options. For quick-start steps, see the Quick Start Guide.

Important Prerequisites

⚠️ This template deploys Entra ID-Joined session hosts. VMs authenticate via cloud-based Entra ID (no on-premises AD required). See Prerequisites Guide: Entra ID Authentication for details.

⚠️ Role assignments are NOT automatic. After deployment, users must be assigned two RBAC roles. See Quick Start: Step 3 for instructions.

Prerequisites Checklist

For detailed setup, see Prerequisites Guide.


Deployment Architecture

Idempotent Design

This template is fully idempotent, meaning:

Technical implementation:

Session Host Extension Architecture

The deployment uses two extensions deployed in sequence on each VM:

1. AADLoginForWindows:

2. DSC (Desired State Configuration):

Benefit: Clean separation of concerns—Entra ID authentication is configured first, then AVD host pool registration follows. Deployment is reliable and works correctly on redeployment.

Explicit Outbound Connectivity (March 2026 Compliance)

Starting March 31, 2026, Azure requires explicit outbound connectivity methods for new virtual networks. This template implements compliance through:

Implementation

Standard Public IPs on NICs

Service Endpoints (cost-free optimization)

Network Defaults

Public IP Lifecycle

Why This Approach:


Step-by-Step Deployment

1. Clone Repository

git clone https://github.com/markheydon/avd-occasional.git
cd avd-occasional

2. Verify Tools

# Check Azure CLI version
az --version

# Check Bicep CLI
az bicep version

# Check PowerShell version
$PSVersionTable.PSVersion

# Authenticate to Azure
az login

# Verify subscription
az account show  # Ensure correct subscription is selected

If you have multiple subscriptions, select the target one:

az account set --subscription <subscription-id-or-name>

3. Customise Parameters

Edit infra/parameters.json to customize deployment:

{
  "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "environment": { "value": "dev" },
    "workloadSize": { "value": "moderate" },
    "location": { "value": "ukwest" },
    "vmCount": { "value": 1 },
    "adminUsername": { "value": "avdadmin" },
    "tags": {
      "value": {
        "project": "avd-occasional",
        "managedBy": "bicep"
      }
    }
  }
}

Common Customisations:

⚠️ Important: Costs are estimates, in GBP, based on pricing information available publically in February 2026 and are subject to change by Microsoft at any time. You should use the official Azure Pricing Calculator to determine your potential costs before deployment.

Parameter Options Default Cost Impact
environment dev, test, prod dev None (affects naming)
workloadSize light, moderate moderate Light: £35/mo, Moderate: £100–120/mo
location Any Azure region ukwest Regional pricing variance
vmCount 15 1 Each VM adds ~£100/mo active
adminUsername Any valid Windows name avdadmin None

4. Prepare Admin Password

Create a secure password. Never hardcode it in scripts or parameters.json. Instead, prompt interactively:

# Option 1: Interactive prompt (recommended)
$adminPassword = Read-Host "Enter admin password for session hosts" -AsSecureString

# Option 2: Generate random secure password (PowerShell 5.1+)
Add-Type -AssemblyName System.Web
$adminPassword = ConvertTo-SecureString ([System.Web.Security.Membership]::GeneratePassword(16, 3)) -AsPlainText -Force

# Option 3: Supply via environment variable (for CI/CD)
$adminPassword = ConvertTo-SecureString $env:AVD_ADMIN_PASSWORD -AsPlainText -Force

Security note: Passwords marked @secure() in Bicep are never logged to Azure activity logs.

5. Validate Template (Dry Run)

Test the deployment before creating resources:

# Get admin password
$adminPassword = Read-Host "Enter admin password for session hosts" -AsSecureString

# Validate template
az deployment group validate `
  --resource-group avd-occasional-rg `
  --template-file infra/main.bicep `
  --parameters @infra/parameters.json `
  --parameters adminPassword=$adminPassword `
  -o json | ConvertFrom-Json

Or use the PowerShell script with -WhatIf:

$adminPassword = Read-Host "Enter admin password for session hosts" -AsSecureString
.\scripts\Deploy-AvdOccasional.ps1 -AdminPassword $adminPassword -WhatIf

Expected output: Lists resources to be created with no errors.

6. Create Resource Group

az group create `
  --name avd-occasional-rg `
  --location ukwest

Alternatively, let the deployment script create it (steps below).

7. Deploy Infrastructure

# Get admin password
$adminPassword = Read-Host "Enter admin password for session hosts" -AsSecureString

# Deploy (20–30 minutes for first deployment)
.\scripts\Deploy-AvdOccasional.ps1 -AdminPassword $adminPassword

Expected duration:

Monitor progress in Azure Portal:

8. Verify Deployment

# List all resources (should show ~15 resources)
az resource list --resource-group avd-occasional-rg -o table

# Check VM status (should show "running")
az vm list --resource-group avd-occasional-rg `
  --query "[].{Name:name, Status:powerState, VMSize:hardwareProfile.vmSize}" -o table

# View deployment outputs
az deployment group show --resource-group avd-occasional-rg `
  --name main `
  --query properties.outputs -o json

Expected resources:

9. Configure User Access (Required)

⚠️ CRITICAL: Users cannot connect without these role assignments. Allow 5–10 minutes for propagation after assignment.

Full instructions: Quick Start: Step 3 or Prerequisites: Role Assignment Requirements.

Quick scripts:

# Role 1: Desktop Virtualization User
$appGroupId = (az resource list --resource-group avd-occasional-rg `
  --resource-type "Microsoft.DesktopVirtualization/applicationGroups" `
  --query '[0].id' -o tsv)

$userId = (az ad signed-in-user show --query id -o tsv)

az role assignment create `
  --role "Desktop Virtualization User" `
  --assignee $userId `
  --scope $appGroupId

# Role 2: Virtual Machine User Login
$vmIds = @(az vm list --resource-group avd-occasional-rg --query '[].id' -o tsv)
foreach ($vmId in $vmIds) {
    az role assignment create `
      --role "Virtual Machine User Login" `
      --assignee $userId `
      --scope $vmId
}

10. Test Connection

Using Windows App (Recommended):

  1. Install Windows App from Microsoft Store
  2. Launch Windows App
  3. Sign in with your Entra ID account
  4. (First time) Subscribe to workspace: avd-dev (or custom name based on environment parameter)
  5. Click Personal Desktop
  6. Verify desktop is displayed

Using Remote Desktop Connection (Advanced):

RDP Shortpath requires additional setup (not recommended for occasional use). See Azure Virtual Desktop RDP Properties.


Customising Deployment

Add More Session Hosts (Scale Out)

# Edit parameters.json
"vmCount": { "value": 3 }  # Changed from 1 to 3

# Redeploy (idempotent; only new VMs created)
$adminPassword = Read-Host "Enter admin password" -AsSecureString
.\scripts\Deploy-AvdOccasional.ps1 -AdminPassword $adminPassword

# Assign roles to new VMs
$userId = (az ad signed-in-user show --query id -o tsv)
$vmIds = @(az vm list --resource-group avd-occasional-rg --query '[].id' -o tsv)
foreach ($vmId in $vmIds) {
    az role assignment create `
      --role "Virtual Machine User Login" `
      --assignee $userId `
      --scope $vmId
}

Cost impact: Each VM adds ~£100–120/month active, ~£0 deallocated.

Switch Between Light and Moderate Workloads

# Edit parameters.json
"workloadSize": { "value": "light" }  # From "moderate" to "light"

# Redeploy
$adminPassword = Read-Host "Enter admin password" -AsSecureString
.\scripts\Deploy-AvdOccasional.ps1 -AdminPassword $adminPassword

Cost impact:

Note: Existing VMs are replaced (not upgraded in-place). Redeploy may take longer as old VMs are removed first.

Change Azure Region

# Edit parameters.json
"location": { "value": "northeurope" }  # From "ukwest" to europe

# Redeploy
$adminPassword = Read-Host "Enter admin password" -AsSecureString
.\scripts\Deploy-AvdOccasional.ps1 -AdminPassword $adminPassword

Supported regions:

See Azure Virtual Desktop Supported Regions.

Use Custom Tags for Billing

# Edit parameters.json
"tags": {
  "value": {
    "project": "avd-occasion",
    "team": "engineering",
    "cost-center": "1234",
    "environment": "dev"
  }
}

# Redeploy
$adminPassword = Read-Host "Enter admin password" -AsSecureString
.\scripts\Deploy-AvdOccasional.ps1 -AdminPassword $adminPassword

Tags are used for cost analysis and resource filtering in Azure Cost Management.


Troubleshooting Deployment

General Debugging

# Run with verbose output
$adminPassword = Read-Host "Enter password" -AsSecureString
.\scripts\Deploy-AvdOccasional.ps1 -AdminPassword $adminPassword -Verbose

# Check latest deployment status
az deployment group show --resource-group avd-occasional-rg --name main -o json | ConvertFrom-Json | .properties

Common Errors

See Troubleshooting Guide: Deployment Issues for:


Lifecycle Management

Deallocate to Save Costs

# Stop VMs and delete Public IPs (~98% cost savings)
.\scripts\Stop-AvdOccasional.ps1

Monthly cost drops from £94–126 to £2–3. Data and configuration preserved.

Resume After Deallocate

# Start VMs and recreate Public IPs (2–3 minute startup)
.\scripts\Start-AvdOccasional.ps1 -WaitForStartup

Completely Delete Infrastructure

# Permanently delete all resources (cannot be undone)
.\scripts\Remove-AvdOccasional.ps1 -Force

Deploy Again After Deletion

# Redeploy fresh infrastructure
$adminPassword = Read-Host "Enter admin password" -AsSecureString
.\scripts\Deploy-AvdOccasional.ps1 -AdminPassword $adminPassword

Cost Analysis

Component Costs (UK Pricing, February 2026)

Component Deallocated Active Notes
Session Host VM (D2s_v3, moderate) £0 £90–120 Largest cost driver
Session Host VM (B2s, light) £0 £30–40 For lighter workloads
OS Disk (Standard SSD) £2–3 £2–3 Always charged
Standard Public IP £0 £2–3 Deleted when stopped
VNet/NSG/Services £0 £0 Free (non-metered)
TOTAL (moderate, 1 VM) £2–3 £94–126 98% savings when deallocated

Scaling Costs

Configuration Monthly Active Monthly Deallocated Use Case
1× Moderate £94–126 £2–3 Single occasional user
2× Moderate £188–252 £4–6 Two occasional users
3× Light £90–120 £6–9 Three light workload users
5× Moderate (max) £470–630 £10–15 Five dedicated users

Cost Optimization Strategies

Strategy 1: Deallocate after use (Recommended)

Strategy 2: Delete and redeploy (for infrequent use)

Strategy 3: Light workload



Last Updated: February 2026