Menu Close

WVD Image Management Automated – Part 1 – Create WVD image version based on existing config with PowerShell

Last updated 1 year ago by Sander Rozemuller

This article is part one of a serie posts about WVD 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 VMsize, network settings and type. After the VM is started you will get the information 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 WVD Image Management Automated.

  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 Session hosts based on Shared Image Gallery version – Part 3
  4. WVD housekeeping, removing all unused sessionhosts, disks and images – Part 4
  5. Monitor Image Versions with Azure Monitor – Part 5

Table of contents

  1. WVD in a nutshell
  2. Requirements
  3. Used Azure components
  4. Finding hostpool and session hosts
  5. Disk configuration
  6. Networking
  7. Create VM
  8. Generate credentials
  9. How to use
  10. Results

WVD in a nutshell

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

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 WVD 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

We also need the and az.compute PowerShell Modules

Used Azure components

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

Finding hostpool and session hosts

In this case I assume you already have a WVD environment with a hostpool, and sessionhosts. I will talk about WVD environment deployment later.

First we need to now 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 WVD 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.compute

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

Now we have our hostpool and sessionhosts loaded, the next step is the needed snapshot. This should be a snaphost which has been not syspreped before. This 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 thing to Azure. Because of the number of temporary components I will create a new resource group first. This will help you cleaning 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 setup a disk configuration. After then you can create a new disk if it not exists already.

$VirtualMachineName = ('vm_' + $
# 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 WVD 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 how to use this script.

$hostpoolname = 'wvd-hostpool'
$snapshotname = 'wvd-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 { $ -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

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 WVD Image Management Automated.

1 Comment

  1. Pingback:WVD Weekly Blog post 27th September - 4th October 2020 - WVD Community

Leave a Reply

Your email address will not be published.