Azure Virtual Desktop Image Management Automated - Part 3 Create AVD sessionhosts on image version with ARM

  • last updated: Fri, 10 Feb 2023 14:17:14

This article is serie of posts about AVD Image Management Automated. In this part we are going to add new sessionhosts to an existing AVD hostpool based on a ARM template.

This post is a part of the series Azure Virtual Desktop Image Management Automated.

  1. Create AVD image version based on existing config with PowerShell – Part 1
  2. Save AVD image with Sysprep as Image Gallery version – Part 2
  3. Create AVD Sessionhosts based on Shared Image Gallery version – Part 3
  4. AVD housekeeping, removing all unused sessionhosts, disks and images – Part 4
  5. Monitor Image Versions with Azure Monitor – Part 5
  6. Enroll MSIX packages automated – Part 6 – (coming soon)


The situation so far: we have created a new image, did a Sysprep, set the image as a version into the Shared Image Gallery.
In this part we deploy new AVD sessionhosts and add it to an existing hostpool.


There are some few requirements which are important.

Before continuing make sure have at least module version 2.5.1 of AZ.Resources in PowerShell. There is a bug fixed about passing a securestring into a templateParameterObject. (See github issue securitystring broken)

I assume you know how to update PowerShell modules.

Let’s start

Information we need first:

  • hostpoolname,
  • administrator credentials (needed to join the sessionhosts into the domain),
  • the number of instances you like to create (optional)
  • drainmode to on at the old sessionshosts.
    [parameter(mandatory = $true)][string]$hostpoolName,
    [parameter(mandatory = $true)][string]$administratorAccountUsername,
    [parameter(mandatory = $true)][securestring]$administratorAccountPassword,
    [parameter(mandatory = $false)][int]$sessionHostsNumber,
    [parameter(mandatory = $true)][boolean]$setDrainModeToOn

import-module az.desktopvirtualization
import-module az.compute
import-module az.resources #At least version 2.5.1

Hostpool registration key

You need a registration key before you are able to add sessionhosts to a hostpool. Otherwise you will be notified.

To make code nice and clean I wrote a function for creating a AVD Hostpool registration key. After running the function you will receive the whole registration info.

function create-wvdHostpoolToken($hostpoolName,$resourceGroup,$hostpoolSubscription) {
    $now = get-date
    # Create a registration key for adding machines to the AVD Hostpool
    $registered = Get-AzWvdRegistrationInfo -SubscriptionId $hostpoolSubscription -ResourceGroupName $resourceGroup -HostPoolName $hostpoolName
    if (($null -eq $registered.ExpirationTime) -or ($registered.ExpirationTime -le ($now))) {
        $registered = New-AzWvdRegistrationInfo -SubscriptionId $hostpoolSubscription -ResourceGroupName $resourceGroup -HostPoolName $hostpool.Name -ExpirationTime $now.AddHours(4)
    if ($registered.Token) {
    return $registered

AVD Hostpool information

The function needs parameters and then continues.
The script depends on existing session hosts. If there are no hosts, the script will not continue.

# Get the hostpool information
$hostpool = Get-AzWvdHostPool | ? { $_.Name -eq $hostpoolName }
$resourceGroup = ($hostpool).id.split("/")[4].ToUpper()
$hostpoolSubscription = ($hostpool).id.split("/")[2]
# Get current sessionhost information
$sessionHosts = Get-AzWvdSessionHost -ResourceGroupName $resourceGroup -HostPoolName $

# Doing some checks beforce continuing
if ($null -eq $sessionHosts) {
    Write-Host "No sessionhosts found in hostpool $hostpoolname, exiting script"

$hostPoolRegistration = create-wvdHostpoolToken -hostpoolName $hostpoolName -resourceGroup $resourceGroup -hostpoolSubscription $hostpoolSubscription
if ($hostPoolRegistration) {
    $hostPoolToken = (ConvertTo-SecureString -AsPlainText -Force ($hostPoolRegistration).Token)

AVD Sessionhost information

With all the needed information the script will continue to the final check about the number of session hosts that will be created. If the variable $sessionHostNumber is empty the script will count the existing AVD session hosts and will use that number.

if ($null -eq $sessionHostsNumber) {
    $sessionHostsNumber = $sessionHosts.count
    Write-Host "No sessionHostsNumber provided, creating $sessionHostsNumber hosts"

In the next part all other variables will be filled based on a existing sessionhost. This is almost the same part I described in part one.
(in the future I will create a module or function for that)

# Get current sessionhost configuration, used in the next steps
$existingHostName = $sessionHosts[-1].Id.Split("/")[-1]
$prefix = $existingHostName.Split("-")[0]
$currentVmInfo = Get-AzVM -Name $existingHostName.Split(".")[0]
$vmInitialNumber = [int]$existingHostName.Split("-")[-1].Split(".")[0] + 1
$vmNetworkInformation = (Get-AzNetworkInterface -ResourceId $
$virtualNetworkName = $"/")[-3]
$virutalNetworkResoureGroup = $"/")[4]
$virtualNetworkSubnet = $"/")[-1]

# Get the image gallery information for getting latest image
$imageReference = ($currentVmInfo.storageprofile.ImageReference).id
$galleryImageDefintion = get-AzGalleryImageDefinition -ResourceId $imageReference
$galleryName = $imageReference.Split("/")[-3]
$gallery = Get-AzGallery -Name $galleryName
$latestImageVersion = (Get-AzGalleryImageVersion -ResourceGroupName $gallery.ResourceGroupName -GalleryName $gallery.Name -GalleryImageDefinitionName $galleryImageDefintion.Name)[-1]


To make things easier to find later I’m using tags which are deployed to every sessionhost component like networkinterface, virtual machine, disk, etc. By adding extra values into the hashtable you are able to extent the tags.

$tags = @{
    ImageVersion = $latestImageVersion.Name
    HostPool     = $hostpoolName



Splatting parameters

After setting all the variables the template parameter will be created. In most situations you will use a JSON template file in combination with a JSON parameter file. It is unnecessary filling a lot of parameters which are mostly known by the system, with all its consequences.

$templateParameters = @{
    resourceGroupName               = $resourceGroup
    hostpoolName                    = $hostpoolName
    administratorAccountUsername    = $administratorAccountUsername
    administratorAccountPassword    = (ConvertTo-SecureString $administratorAccountPassword -AsPlainText -Force)
    createAvailabilitySet           = $false
    hostpooltoken                   = $hostPoolToken
    vmInitialNumber                 = $vmInitialNumber
    vmResourceGroup                 = ($resourceGroup).ToUpper()
    vmLocation                      = $currentVmInfo.Location
    vmSize                          = $currentVmInfo.HardwareProfile.vmsize
    vmNumberOfInstances             = $sessionHostsNumber
    vmNamePrefix                    = $prefix
    vmImageType                     = "CustomImage"
    vmDiskType                      = $currentVmInfo.StorageProfile.osdisk.ManagedDisk.StorageAccountType
    vmUseManagedDisks               = $true
    existingVnetName                = $virtualNetworkName
    existingSubnetName              = $virtualNetworkSubnet
    virtualNetworkResourceGroupName = $virutalNetworkResoureGroup
    usePublicIP                     = $false
    createNetworkSecurityGroup      = $false
    vmCustomImageSourceId           = $imageReference
    availabilitySetTags             = $tags
    networkInterfaceTags            = $tags
    networkSecurityGroupTags        = $tags
    publicIPAddressTags             = $tags
    virtualMachineTags              = $tags
    imageTags                       = $tags

Because of splatting parameters instead of using a parameter file you will need at least PowerShell module Az.Resources version 2.5.1. (As I mentioned at the beginning).
At the total end we use the new-AzResourceGroupDeployment command for deploying the environment by an ARM template.

Deploy with ARM

$deploy = new-AzresourcegroupDeployment -TemplateUri "" @templateParameters -Name "deploy-version-$($latestImageVersion.Name)"



After deployment was successful the “old” session hosts drainmode should be set to ON. This will ensure no new sessions will be accepted. Actually you force new connections to new session hosts.

if (($deploy.ProvisioningState -eq "Succeeded") -and ($setDrainModeToOn)) {
   foreach ($sessionHost in $sessionHosts) {
        $sessionHostName = $"/")[-1]
        Update-AzWvdSessionHost -HostPoolName $Hostpoolname -ResourceGroupName $ResourceGroup -Name $sessionHostName -AllowNewSession:$false

At my Github you will find the script and template file.

Thank you for reading my blog azure virtual desktop image management automated - part 3 create avd sessionhosts on image version with arm.
I hope you got a bit inspired. Enjoy your day and happy automating 👋

comments powered by Disqus

Related Posts

Azure Virtual Desktop Image Management Automated - Part 2 Save AVD image with Sysprep as Image Gallery version

This is part two of a serie posts about AVD disk management. In this blogpost I will explain how to finish a disk with sysprep and deploy it as a version into the Azure Shared Image Gallery automated.

Read more

AVD Image Management Automated - Part 1 Create an AVD image version based on the existing config with PowerShell

This article is part one of a series of posts about AVD image management automated. In this first part, I will describe how to create and connect a new disk (based on a snapshot) to a new Azure VM based on the existing sessionhost configuration.

Read more

Monitor active M365 ServiceHealth services only with PowerShell

You have some Microsoft 365 (M365) customers which you like to monitor. Every day you are looking at the customer specific M365 portal looking for Microsoft event.

Read more