This article is part one of a serie posts about WVD disk management. 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.
This post is a part of the series Windows Virtual Desktop Image Management Automated.
- Create WVD image version based on existing config with PowerShell – Part 1
- Save WVD image with Sysprep as Image Gallery version – Part 2
- Create WVD Sessionhosts based on Shared Image Gallery version – Part 3
- WVD housekeeping, removing all unused sessionhosts, disks and images – Part 4
- Monitor Image Versions with Azure Monitor – Part 5
Table of contents
- WVD in a nutshell
- Requirements
- Used Azure components
- Finding hostpool
- Sessionshosts
- Disk configuration
- Create VM
- Add your public IP to the NSG
- Generate credentials
- 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.
Requirements
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-us/azure/virtual-desktop/powershell-module
We also need the az.network and az.compute PowerShell Modules
Snapshots
After you finished your job you need to sysprep (generalize) the disk before you can attach it to a sessionhost. You can run the sysprep command up to 8 times, after that you have to recreate a new Windows Image. To avoid that we creating a non sysprep version first (also call Before Snapshot) and create a disk from that one.
More info about Sysprep: https://docs.microsoft.com/en-us/windows-hardware/manufacture/desktop/sysprep–system-preparation–overview
Beforce executing the script make sure you have a before sysprep snapshot from a disk. You will need that snapshot for creating a new disk.
Used Azure components
- Windows Virtual Desktop Hostpool
- Virtual Machines (sessions hosts in a hostpool)
- Disks & Snapshosts
- Network
- Shared Image Gallery
Finding hostpool
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 $publicIp. I will talk about this later in this topic. I’m also importing the needed modules.
param(
[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
$hostpool = Get-AzWvdHostPool | ? { $_.Name -eq $hostpoolname }
# Snapshot values
$snapshot = get-azsnapshot -SnapshotName $snapshotname
$resourceGroup = $snapshot.ResourceGroupName
$location = $snapshot.Location
Sessionshosts
Now we have our hostpool and snapshot loaded, first lets determine which sessionshosts are in the hostpool. Before running the Get-AzWvdSessionHost command you need to know the resouregroupname where the hostpool is in. Let’s check the $hostpool variable from where we extract the resourcegroup.
$hostpoolResourceGroup = ($hostpool).id.split("/")[4]
$sessionHosts = Get-AzWvdSessionHost -ResourceGroupName $hostpoolResourceGroup -HostPoolName $hostpool.name
After loading the sessionshosts into a variable, let’s go futher gathering the VM configuration like hardware and networking. We selecting the first VM only and extracting all the info we need.
$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
$NSG = Get-AzNetworkSecurityGroup | ? { $_.subnets.id -eq $virtualNetworkSubnet }
$virtualMachineResourceGroup = $currentVmInfo.ResourceGroupName
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.
$diskConfig = New-AzDiskConfig -SkuName "Premium_LRS" -Location $location -CreateOption Copy -SourceResourceId $snapshot.Id
$diskname = ('disk_' + $snapshot.name)
$disk = Get-azdisk -diskname $diskname
if ($disk) {
Write-Output "Disk $diskname exists in resourcegroup $resourceGroupname"
}
else {
New-AzDisk -Disk $diskConfig -ResourceGroupName $resourceGroupName -DiskName $diskName
#Test if disk is created
$disk = Get-azdisk -diskname $diskname
if ($disk) {
Write-Output "Disk $diskname created succesful in resourcegroup $resourceGroupname"
}
}
Create VM
Ok, we’ve stored every detail we need into variables. Let’s start creating a new VM with a new disk based on a snapshot.
$VirtualMachineName = ('vm' + $snapshot.name)
$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
$publicIp = New-AzPublicIpAddress -Name ($VirtualMachineName.ToLower() + '_ip') -ResourceGroupName $virtualMachineRg -Location $snapshot.Location -AllocationMethod Dynamic -Force
# Create NIC in the first subnet of the virtual network
$nic = New-AzNetworkInterface -Name ($VirtualMachineName.ToLower() + '_nic') -ResourceGroupName $resourceGroupName -Location $snapshot.Location -SubnetId $virtualNetworkSubnet -PublicIpAddressId $publicIp.Id -Force
$VirtualMachine = Add-AzVMNetworkInterface -VM $VirtualMachine -Id $nic.Id
#Create the virtual machine with Managed Disk
$newVm = New-AzVM -VM $VirtualMachine -ResourceGroupName $resourceGroupName -Location $snapshot.Location
Add your public IP to the NSG
In the previous steps we loaded the Azure NSG into a variable. A NSG is the Network Security Group and is the firewall where the VM is behind. In the NSG you can add/remove network rules. Because you want to connect to the recently created VM with RDP (port 3389) you need to add an extra firewall rule which allows you to connect. Now the $publicIp variable is needed.
For adding a firewall rule I wrote a function first.
function add-firewallRule($NSG, $localPublicIp, $port) {
# Pick random number for setting priority. It will exclude current priorities.
$InputRange = 100..200
$Exclude = ($NSG | Get-AzNetworkSecurityRuleConfig | select Priority).priority
$RandomRange = $InputRange | Where-Object { $Exclude -notcontains $_ }
$priority = Get-Random -InputObject $RandomRange
$nsgParameters = @{
Name = "Allow-$port-Inbound-$localPublicIp"
Description = "Allow port $port from local ip address $localPublicIp"
Access = 'Allow'
Protocol = "Tcp"
Direction = "Inbound"
Priority = $priority
SourceAddressPrefix = $localPublicIp
SourcePortRange = "*"
DestinationAddressPrefix = "*"
DestinationPortRange = $port
}
$NSG | Add-AzNetworkSecurityRuleConfig @NSGParameters | Set-AzNetworkSecurityGroup
}
#Adding the role
add-firewallRule -NSG $NSG -localPublicIp $localPublicIp -port 3389

When the VM is created we create an username and password in the VM by installing the VMAccessAgent extention.
https://docs.microsoft.com/en-us/troubleshoot/azure/virtual-machines/support-agent-extensions
Generate credentials
First we need to create a random username and password. I also created a function for that as well.
function create-randomString($type) {
function Get-RandomCharacters($length, $characters) {
$random = 1..$length | ForEach-Object { Get-Random -Maximum $characters.length }
$private:ofs = ""
return [String]$characters[$random]
}
if ($type -eq 'username') {
$username = Get-RandomCharacters -length 8 -characters 'abcdefghiklmnoprstuvwxyz'
return $username
}
if ($type -eq 'password') {
$password = Get-RandomCharacters -length 5 -characters 'abcdefghiklmnoprstuvwxyz'
$password += Get-RandomCharacters -length 1 -characters 'ABCDEFGHKLMNOPRSTUVWXYZ'
$password += Get-RandomCharacters -length 1 -characters '1234567890'
$password += Get-RandomCharacters -length 1 -characters '!$%&/()=?}][{@#*+'
return $password
}
}
$userName = create-randomString -type 'username'
$password = create-randomString -type 'password'
# Convert to SecureString
[securestring]$secStringPassword = ConvertTo-SecureString $password -AsPlainText -Force
[pscredential]$creds = New-Object System.Management.Automation.PSCredential ($userName, $secStringPassword)
# For reset username/password
Set-AzVMAccessExtension -ResourceGroupName $resourceGroupName -Location $snapshot.Location -VMName $VirtualMachineName -Credential $creds -typeHandlerVersion "2.0" -Name VMAccessAgent
Results
At last we combining everything we know together to write the content to our screen.
$publicIp = (Get-AzPublicIpAddress | where { $_.name -match $VirtualMachineName }).IpAddress
$details = "VM $virtualmachinename created succesful"
$bodyValues = [Ordered]@{
details = $details
hostPool = $hostpoolName
virtualMachineName = $VirtualMachineName
resourceGroupName = $resourceGroupName
virtualMachinePublicIp = $publicIp
username = $userName
password = $password
virtualMachineDisk = $diskname
engineersIp = $localPublicIp
}
Write-Host $bodyValues

At the end, you will need the build in some checks like if a disk and VM is really created or does a hostpool has some sessionhosts in the first place. In production of course we have lots of checks but for now i think this is a great step from where to start.
We also created a mailflow behind the script so the executor gets notified when a VM is read. We also using Azure Functions to run scripts like this after requesting the weburl.
The whole script from this article can be found at my Github Windows Virtual Desktop repository.
https://github.com/srozemuller/Windows-Virtual-Destkop
In the next episode I will describe how to finish the disk with an automated sysprep and moving the disk as an version into the Azure Shared Image Gallery.
Pingback:WVD Weekly Blog post 27th September - 4th October 2020 - WVD Community