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. This will save a lot of extra parameters like VM size, network settings, and type. After the VM is started you will get the information on how to connect to the VM by RDP (3389) with specific credentials specially created for this VM.

Updated 14-03-2021

This post is a part of the series AVD 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 Session hosts based on Shared Image Gallery version – Part 3
  4. AVD housekeeping, removing all unused session hosts , disks, and images – Part 4
  5. Monitor Image Versions with Azure Monitor – Part 5
Table Of Contents

AVD in a nutshell

Azure Virtual Desktop is a desktop and app virtualization service that runs on the cloud. For a full description click here
In the basic AVD exists in three big parts: clients, AVD, and the Azure VM’s & Services. As the picture below says only AVD is Microsoft managed. For that part you can use ARM templates and you are out of control at that part.

MS-avd-arch The real fun starts at the dynamic environment like clients and VM’s. In this article we will focus how to deal with disk- and image management automatically.
Since AVD has no image provisioning like Citrix, disk management goes a bit different. Before knowing how to automate things it is good to know which elements we need and how the tasks need to be done when an image needs to be updated.


PowerShell Modules

Microsoft has enrolled a new PowerShell module for Windows Virtual Desktop. For executing commands in this article you need this module. How to setup this module please check https://docs.microsoft.com/en-u s /azure/virtual-desktop/powershell-module

We also need the az.network and az.compute PowerShell Modules

Used Azure components

  • Windows Virtual Desktop Hostpool
  • Virtual Machines (sessions hosts in a hostpool)
  • Disks & Snapshots
  • Network
  • Shared Image Gallery

Finding hostpool and session hosts

In this case, I assume you already have an AVD environment with a hostpool, and session hosts. I will talk about AVD environment deployment later.

First, we need to know in which hostpool you want to create a new disk. So the script will ask you for that. After all, actually, these are the only variables you really need :). Since the hostpool is the main AVD part we know every other component.
I’ve added an extra variable $localPublicIp. I will talk about this later in this topic. I’m also importing the needed modules.

    [parameter(mandatory = $true)][string]$hostpoolName,
    [parameter(mandatory = $true)][string]$snapshotName,
    [parameter(mandatory = $true)][string]$localPublicIp

import-module az.desktopvirtualization
import-module az.network
import-module az.compute

# Get AVD hostpool information
$hostpool = Get-AzWvdHostPool | Where-Object { $_.Name -eq $hostpoolname }
$hostpoolResourceGroup = ($hostpool).id.split("/")[4]
# Get current AVD Configurgation
$sessionHosts = Get-AzWvdSessionHost -ResourceGroupName $hostpoolResourceGroup -HostPoolName $hostpool.name
$sessionHostName = ($sessionHosts.Name.Split("/")[-1]).Split(".")[0]
$currentVmInfo = Get-AzVM -name $sessionHostName
$virtualMachineSize = $currentVmInfo.hardwareprofile.vmsize
$virtualNetworkSubnet = (Get-AzNetworkInterface -ResourceId $currentVmInfo.NetworkProfile.NetworkInterfaces.id).IpConfigurations.subnet.id

Now we have our hostpool and session hosts loaded, the next step is the needed snapshot. This should be a snaphost which has been not syspreped before. This is because of its limits.

You can run the Sysprep command up to 8 times on a single Windows image. After running Sysprep 8 times, you must recreate your Windows image. In previous versions of Windows, you could use the SkipRearm answer file setting to reset the Windows Product Activation clock when running Sysprep. If you are using a volume licensing key or a retail product key, you don’t have to use SkipRearm because Windows is automatically activated.

More information about sysprep a Windows installation check the Microsoft docs.

# Snapshot values for creating a disk
try {
    $snapshot = get-azsnapshot -SnapshotName $snapshotname
    $resourceGroupName = $snapshot.ResourceGroupName
catch {
    Throw "No snapshot found, $_"

After loading all the basics it is time to deploy things to Azure. Because of the number of temporary components, I will create a new resource group first. This will help you clean up resources at the end since every component is in the same resource group. Just deleting the resource group will be fine then.

# Creating a new temporary resource group first
$ResourceGroup = New-AzResourceGroup -Name $TempResourceGroup -Location $ResourceGroupLocation

Disk configuration

When creating a new disk you need to set up a disk configuration. After then you can create a new disk if it not exists already.

$VirtualMachineName = ('vm_' + $snapshot.name)
# Creating a disk
$diskConfig = New-AzDiskConfig -SkuName "Premium_LRS" -Location $ResourceGroup.location -CreateOption Copy -SourceResourceId $snapshot.Id
$diskname = ($VirtualMachineName.ToLower()+ '-OS')
$disk = Get-azdisk -diskname $diskname
try {
    $disk = New-AzDisk -Disk $diskConfig -ResourceGroupName $ResourceGroup.resourceGroupName -DiskName $diskName
catch {
    Throw "$diskname allready exits, $_"


The next step is the networking part. This consists of three components. A network interface card, public ip and a network security group. In the first part I will create a public IP. This will be the IP to connect when the virtual machine is finished.
In the next step I will create a network interface card and will connect the public IP to it.

In the third step I will create a Network Security Group (NSG) to protect the virtual machine for attackers. The NSG will be connected to the virtual machine only. Positive side effect is we leave the production NSG which is on the AVD subnet untouched.
At last I will add the RDP port 3389 to the NSG with my private IP only instead of the whole internet. The function Add-FirewallRule can be found in the complete script at my GitHub page .

$PublicIpParameters = @{
    Name              = ($VirtualMachineName.ToLower() + '_ip')
    ResourceGroupName = $ResourceGroup.ResourceGroupName
    Location          = $ResourceGroup.Location
    AllocationMethod  = 'Dynamic'
    Force             = $true
$publicIp = New-AzPublicIpAddress @PublicIpParameters

$NicParameters = @{
    Name              = ($VirtualMachineName.ToLower() + '_nic')
    ResourceGroupName = $ResourceGroup.resourceGroupName
    Location          = $ResourceGroup.Location
    SubnetId          = $virtualNetworkSubnet
    PublicIpAddressId = $publicIp.Id
    Force             = $true
$nic = New-AzNetworkInterface @NicParameters

# Creating a temporary network security group
$NsgParameters = @{
    ResourceGroupName = $ResourceGroup.ResourceGroupName
    Location          = $ResourceGroup.Location
    Name              = ($VirtualMachineName.ToLower() + '_nsg')
$nsg = New-AzNetworkSecurityGroup @NsgParameters

# Adding a security rule to only the network interface card
Add-FirewallRule -NSG $NSG -localPublicIp $localPublicIp -port 3389
$nic.NetworkSecurityGroup = $nsg
$nic | Set-AzNetworkInterface


Create VM

Now the network is in place and save let’s create a new VM with a new disk based on a snapshot.

# Creating virtual machine configuration
$VirtualMachine = New-AzVMConfig -VMName $VirtualMachineName -VMSize $virtualMachineSize
# Use the Managed Disk Resource Id to attach it to the virtual machine. Please change the OS type to linux if OS disk has linux OS
$VirtualMachine = Set-AzVMOSDisk -VM $VirtualMachine -ManagedDiskId $disk.Id -CreateOption Attach -Windows
# Create a public IP for the VM
$VirtualMachine = Add-AzVMNetworkInterface -VM $VirtualMachine -Id $nic.Id
#Create the virtual machine with Managed Disk
$newVm = New-AzVM -VM $VirtualMachine -ResourceGroupName $ResourceGroup.resourceGroupName -Location $ResourceGroup.Location

When the VM is created we create an username and password in the VM by installing the VMAccessAgent extention.

Generate credentials

First we need to create a random username and password. I also created a function for that as well, which can be found in the complete script.

$userName = create-randomString -type 'username'
$password = ConvertTo-SecureString (create-randomString -type 'password') -AsPlainText -Force
$Credential = New-Object System.Management.Automation.PSCredential ($userName, $password);

$CredentialParameters = @{
    ResourceGroupName  = $ResourceGroup.ResourceGroupName
    Location           = $ResourceGroup.Location
    VMName             = $VirtualMachineName
    Credential         = $Credential
    typeHandlerVersion = "2.0"
    Name               = "VMAccessAgent"
Set-AzVMAccessExtension @CredentialParameters

How to use

An example of how to use this script.

$hostpoolname = 'avd-hostpool'
$snapshotname = 'avd-acc-per-2021.03.12-BS'
$TempResourceGroup = 'temp-deploy-updates'
$ResourceGroupLocation = 'westeurope'
$localPublicIp = ''

.\WVD-Create-UpdateVM.ps1 -HostpoolName $hostpoolname -SnapshotName $snapshotname -TempResourceGroup $TempResourceGroup -ResourceGroupLocation $ResourceGroupLocation -localPublicIp $localPublicIp


At last we combining everything we know together to write the content to our screen.

if ($newVm) {
    #Adding the role
    $publicIp = (Get-AzPublicIpAddress | where { $_.name -match $VirtualMachineName }).IpAddress
    $bodyValues = [Ordered]@{
        Status                 = $newVm.StatusCode
        hostPool               = $hostpoolName
        virtualMachineName     = $VirtualMachineName
        resourceGroupName      = $ResourceGroup.ResourceGroupName
        virtualMachinePublicIp = $publicIp
        username               = $userName
        password               = $password | ConvertFrom-SecureString -AsPlainText
        virtualMachineDisk     = $diskname
        engineersIp            = $localpublicIp
Write-Output $bodyValues

image-1159 image-1 The whole script from this article can be found at my Github Windows Virtual Desktop repository .

In the next episode I will describe how to finish the disk with an automated sysprep and moving the disk as a version into the Azure Shared Image Gallery.

Thank you for reading my blog post about AVD Image Management Automated.

Thank you for reading my blog avd image management automated - part 1 create an avd image version based on the existing config with powershell.
I hope you got a bit inspired. Enjoy your day and happy automating 👋

comments powered by Disqus