Table of Contents
So this is the 3rd part of a multi part series on my journey with Bicep and ARM Templates and deploying AVD as a PAW..
- Part 1 – A high level overview
- Part 2 – The core components
- Part 3 – Deploying the session hosts
- Part 4 – Completing the setup
- Part 6 – Securing Access
- Part 7 – Logging on to AVD
Introduction
Welcome to the 3rd installment of my multipart series on using a virtual/cloud PAW. By now you have hopefully deployed the core components for AVD, the Host pool, the Application group and the Workspace. Now you have that you need to create the session hosts.
So, what is a session host?
According to Copilot:
An AVD Session Host (Azure Virtual Desktop Session Host) is a virtual machine (VM) in the Azure Virtual Desktop environment that hosts user sessions. It allows multiple users to connect and run their applications and desktops remotely.
The session host is part of a host pool, which is a collection of VMs that provide resources to users. These hosts manage user connections, ensuring that each user has a responsive and secure experience
My addition to this is that other than using Windows 11 Multi Session, there is not much difference in the VM’s until it comes to the VM Extensions, one of which is what joins it to the Host pool.
More info can be found here:
Add session hosts to a host pool – Azure Virtual Desktop | Microsoft Learn
Microsoft Entra joined session hosts in Azure Virtual Desktop | Microsoft Learn
The sessions hosts I am deploying are specifically joined to Entra only and not a local domain. They will also be on a completely separate vNet away from any additional resources.
Requirements for the session hosts
So whats needed for the session hosts? Firstly, you need the core components, which we’ve covered off in the previous post. Next we need the following
- vNet – a network for the cPAW’s to connect to
- Nic – this is required before the VM is deployed so that it can then be attached to the VM
- The VM itself
- The VM Extensions
These are all taken care of by the Create-SessionHost Bicep file.
The script
For the latest and most up to date script I recommend that you check my GitHub and get it from there as the one here might have changed slightly.
param location string = 'uksouth'
param sessionHostPrefix string = 'cPAW'
param numberOfHosts int = 2
@secure()
param adminPassword string
param adminUsername string = 'cPAW-Admin'
param hostPoolRegistrationInfoToken string = 'Enter HostPool Registration Key here'
var modulesURL = 'https://wvdportalstorageblob.blob.${environment().suffixes.storage}/galleryartifacts/Configuration_1.0.02797.442.zip'
// Reference the HostPool
resource HostPool 'Microsoft.DesktopVirtualization/hostpools@2021-07-12' existing = {
name: '${sessionHostPrefix}-HostPool'
}
// Deploy the network infrastructure
resource vNet 'Microsoft.Network/virtualNetworks@2021-05-01' = {
name: '${sessionHostPrefix}-vNet'
location: location
properties: {
addressSpace: {
addressPrefixes: [
'192.168.250.0/24'
]
}
subnets: [
{
name: '${sessionHostPrefix}-Subnet'
properties: {
addressPrefix: '192.168.250.0/24'
}
}
]
}
}
//Create the NIc's for the VM's
resource nic 'Microsoft.Network/networkInterfaces@2020-11-01' = [for i in range(0, numberOfHosts): {
name: '${sessionHostPrefix}-${i}-nic'
location: location
properties: {
ipConfigurations: [
{
name: 'name'
properties: {
privateIPAllocationMethod: 'Dynamic'
subnet: {
id: vNet.properties.subnets[0].id
}
}
}
]
}
}]
//Create the VM's
resource VM 'Microsoft.Compute/virtualMachines@2020-12-01' = [for i in range(0, numberOfHosts): {
name: '${sessionHostPrefix}-${i}'
location: location
identity: {
type: 'SystemAssigned'
}
properties: {
hardwareProfile: {
vmSize: 'Standard_d2as_v5'
}
osProfile: {
computerName: '${sessionHostPrefix}-${i}'
adminUsername: adminUsername
adminPassword: adminPassword
}
storageProfile: {
imageReference: {
publisher: 'MicrosoftWindowsDesktop'
offer: 'Windows-11'
sku: 'win11-24h2-avd'
version: 'latest'
}
}
networkProfile: {
networkInterfaces: [
{
id: nic[i].id
}
]
}
}
}]
//Join the VM's to Entra and Entroll in intune
resource entraIdJoin 'Microsoft.Compute/virtualMachines/extensions@2021-11-01' = [for i in range(0, numberOfHosts): {
parent: VM[i]
name: '${sessionHostPrefix}-${i}-EntraJoinEntrollIntune'
location: location
properties: {
publisher: 'Microsoft.Azure.ActiveDirectory'
type: 'AADLoginForWindows'
typeHandlerVersion: '2.2'
autoUpgradeMinorVersion: true
enableAutomaticUpgrade: false
settings: {
mdmId: '0000000a-0000-0000-c000-000000000000'
}
}
}]
//Install the Guest Attestation Extension
resource guestAttestationExtension 'Microsoft.Compute/virtualMachines/extensions@2024-07-01' = [for i in range(0, numberOfHosts): {
parent: VM[i]
name: '${sessionHostPrefix}-${i}-guestAttestationExtension'
location: location
properties: {
publisher: 'Microsoft.Azure.Security.WindowsAttestation'
type: 'GuestAttestation'
typeHandlerVersion: '1.0'
autoUpgradeMinorVersion: true
}
dependsOn:[
entraIdJoin
]
}]
//run some preperation on the VM's to remove any windows apps and also enable cloud kerberos
resource SessionPrep 'Microsoft.Compute/virtualMachines/extensions@2021-03-01' = [for i in range(0, numberOfHosts): {
parent: VM[i]
name: '${sessionHostPrefix}-${i}-CSessionPrep'
location: location
properties: {
publisher: 'Microsoft.Compute'
type: 'CustomScriptExtension'
typeHandlerVersion: '1.10'
autoUpgradeMinorVersion: true
settings: {
fileUris: [
'https://raw.githubusercontent.com/andrew-kemp/CloudPAW/refs/heads/main/SessionHostPrep.ps1'
]
commandToExecute: 'powershell -ExecutionPolicy Unrestricted -File SessionHostPrep.ps1'
}
}
dependsOn: [
guestAttestationExtension
]
}]
// Join the SessionHosts to the HostPool
resource dcs 'Microsoft.Compute/virtualMachines/extensions@2024-03-01' = [for i in range(0, numberOfHosts): {
parent: VM[i]
name: '${sessionHostPrefix}-${i}-JointoHostPool'
location: location
properties: {
publisher: 'Microsoft.Powershell'
type: 'DSC'
typeHandlerVersion: '2.76'
settings: {
modulesUrl: modulesURL
configurationFunction: 'Configuration.ps1\\AddSessionHost'
properties: {
hostPoolName: HostPool.name
aadJoin: true
}
}
protectedSettings: {
properties: {
registrationInfoToken: hostPoolRegistrationInfoToken
}
}
}
dependsOn: [
SessionPrep
]
}]
This can be broken down into 9 sections:
- The Parameters and Variables
- Referencing the Host pool to join
- Create the vNet
- Create the Nic or Nic’s depending on the number of hosts you’re going to deploy
- Create the session host/s it/them selves
- Join the session hosts to Entra and enroll in to Intune
- Install the Guest Attestation Extension
- Performs a bit of the session host prep (runs a script on the VM)
- Joins the session host to the host pool
Again, like before, to run the script you need to convert the Bicep file to JSON, hopefully you’ll be familiar with that process now.
Deploying the custom tempate.
So again, like before you need to go to the Azure Portal and deploy a custom template, so follow the process you did previously but past in the JSON of this Bicep file instead. And you will see the following:

Verify that you have the Parameters and variables that you have set are listed, and you will see that there are 7 resources to deploy.
Click on Save.

Select the subscription and resource group you created previously. if you set that to something else you might want to then set the session host prefix to be what you set it to, otherwise my script later on will not work.
Feel free to change the number of session hosts if you wish. Set a complex local admin password. Again, feel free to change the local Admin username to a username of your choosing.
Next we then need to get the Host pool registration key, so that we can register the session hosts in the Host pool.
To do this you will need to open another tab in your browser and go to the recently created Host Pool and click on the Registration key. As this is a new host pool there will not be a key available so you will need to create it.

Click on Generate new key:

you can specify the duration of the validity of the key, I just set it to one day and then click on OK:
you will then see the new key:

Copy this and paste in to the deployment template

Click on Review + create and then Create on the overview:

This process may take some time so go and make a cuppa.
After about 25 Minutes my deployment had completed with green ticks for all the resources.

Then, when I go to the Host pool in Azure I can see that I now have 2 machines that I can connect to:

However, I cannot connect to them just now as I need to add the permissions etc, that will come in the next post.
When I then go to device in Entra I can see these 2 Devices and the Same for Intune:


VM Extensions installed
As I mentioned above I have 4 Extensions installed on the VM’s:
- Entra join and enroll in Intune
- Custom Extension – Runs a PowerShell Scripts for the session host prep
- Guest Attestation Extension
- Join to Host pool

Extensions 1,2 and 4 are pretty self explanatory, as these are usually deployed with all Session hosts, however the 3rd one is something I have added in myself. There is probably a better name for it than session prep but thats what I called it.
Session host prep extension
This extension is running a custom PowerShell Script that can be found in my GitHub:
CloudPAW/SessionHostPrep.ps1 at main · andrew-kemp/CloudPAW
//run some preperation on the VM's to remove any windows apps and also enable cloud kerberos
resource SessionPrep 'Microsoft.Compute/virtualMachines/extensions@2021-03-01' = [for i in range(0, numberOfHosts): {
parent: VM[i]
name: '${sessionHostPrefix}-${i}-SessionPrep'
location: location
properties: {
publisher: 'Microsoft.Compute'
type: 'CustomScriptExtension'
typeHandlerVersion: '1.10'
autoUpgradeMinorVersion: true
settings: {
fileUris: [
'https://raw.githubusercontent.com/andrew-kemp/CloudPAW/refs/heads/main/SessionHostPrep.ps1'
]
commandToExecute: 'powershell -ExecutionPolicy Unrestricted -File SessionHostPrep.ps1'
}
}
dependsOn: [
guestAttestationExtension
]
}]
So what does this do? Well its simple really, it does 2 things.
- It removes all unwanted Windows Apps off the session hosts before they are logged on to.
- it enables CloudKerberos. This is used for when FSLogix is located on Azure storage. IF you are having a lot of users log on to the cPAW’s you’ll not want to have all the profiles stored locally as you will soon run out of disk space.
This will run the script directly from the Raw GitHub URL so there is no need to copy the script if needed.
Conclusion
I now have the Host pool, Application group, Workspace, vNet and Session hosts that are both Entra joined and Enrolled in Intune. This is the basics in place for using AVD in general not just as a PAW.
I was hoping to get the whole thing automated, with retrieving the registration key for the Host pool and using that. That way I can look at one Bicep script for it all, That might be the next project I work on.
But for now, this will meet my needs. I still only need to have a very lite touch in my deployment and it will do me for now.