Windows Virtual Desktop Image Management Automated – Part 3 – Create WVD sessionhosts on image version with ARM

This article is a part of the serie WVD Image Management

  1. Create WVD image version based on existing config with PowerShell – Part 1
  2. Save WVD image with Sysprep as Image Gallery version – Part 2
  3. Create WVD Sessionhosts based on Shared Image Gallery version – Part 3
  4. WVD housekeeping, removing all unused sessionhosts, disks and images – Part 4 (coming soon)

Table of contents

  1. Introduction
  2. Let’s start
  3. Hostpool registration key
  4. Hostpool information
  5. WVD Sessionhost information
  6. Tags
  7. Splatting parameters
  8. Deploy with ARM
  9. Drainmode


The situation so far: we have created a new image, did a sysprep and put the new created image as a version into the Shared Image Gallery.In this part we deploy new WVD 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

In the first place we need to gather some start information. Information we need is the hostpoolname, administrator username and password (needed to join the sessionhosts into the domain), the number of instances you like to create (is optional) and if you like to set 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

Before you are able to add sessionhosts to a hostpool you will need te create a registration key. When adding sessionhosts without a registration you will be notified.

To make code nice and clean I wrote a function for creating a WVD 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 WVD 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

WVD Hostpool information

The function needs some parameters which need to be gathered first. After filling the needed variables there are some checks before continuing.
The script depends on existing sessionhosts. 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"

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

WVD Sessionhost information

Will all the needed information the script will continue to the final check about the number of sessionhosts that will be created. If the variable $sessionHostNumber is empty the script will count the existing WVD sessionhosts 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 this part so we can use it independently)

# Get current sessionhost configuration, used in the next steps
$existingHostName = $sessionHosts[-1].ResourceId.Split("/")[-1]
$prefix = $existingHostName.Split("-")[0]
$currentVmInfo = Get-AzVM -name $existingHostName
$vmInitialNumber = ([int]$existingHostName.Split("-")[-1]) + 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 hostpoolname and image version. By adding extra values into the hashtable you are able to extent the tags.

$tags = @{
    ImageVersion = $latestImageVersion.Name
    HostPool     = $hostpoolName
Virtual Machine Tags
Network Interface Tags

Splatting parameters

After setting all the variables the template parameter will be created. In most situations you wil use a JSON template file in combination with a JSON parameter file. Because of the parameters are variable and mostly known by the system it is a bit unnecessary filling a lot of parameters, with al 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.

Deploy with ARM

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


When a deployment was successful at last the “old” sessionhosts drainmode could be set to ON. This will ensure no new sessions will be accepted. Actually you force new connections to new sessionhosts.

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.

Leave a Reply

Your email address will not be published. Required fields are marked *