Table of Contents
I have decided to create this as a multi part post to hopefully make it easier to understand.
- Part 1 – A high level overview
- Part 2 – Core Component creation
- Part 3 – The Session Host creation
- Part 4 – Completing the setup
- Part 6 – Securing Access
- Part 5 – Logging on to AVD
Introduction
So Following on from my last post I thought I’d share how I actually went about deploying my AVD environment as a PAW. This post is a high level overview of what I have done. The next set of posts will detail each section in a bit more detail and why I did it that way.
I also thought I’d take this as an opportunity to try and automate it as best I can. This lead me down the path of learning some Bicep. This is all new to me! But here are two Bicep scripts that I have created to create the initial AVD infrastructure for creating a vPAW or cPAW depending on what you want to call it.
A small disclaimer here, I am by no means a Bicep expert so there may be an easier way to do this, or even a different way. You might even be able to do it all in the one script, but at the moment this is where I am with my setup.
The scripts I have will deploy AVD Session Hosts joined to Entra ID and enrolled in Intune. I will upload and share on my GitHub
Part 2 will cover off the
Whats needed
To create the cPAW (Cloud Privileged Access Workstation) I needed the following:
- An Azure Subscription
- A Resource Group
- A Hostpool
- A Workspace
- An Application Group
- A Virtual Network and Subnet
- At least one Session Host (I opted for 2)
- 3 Entra Groups
- _User-cPAW-Users – This is a user assigned group
- _User-cPAW-Admins- This is a user assigned group
- _Device-cPAW – This is a dynamic device group
- A public location to host some PowerShell scripts (I used GitHub).
- Entra Tenant
- Intune
Creating the Initial AVD Environment
So in order to be able to connect to a Session Host you need the initial environment setup this is the Hostpool, the Workspace and the Application Group.
This is where my first Bicep script comes in to play:
param location string = 'uksouth'
param sessionHostPrefix string = 'cPAW'
//Deploy the Hostpool
resource HostPool 'Microsoft.DesktopVirtualization/hostPools@2021-07-12' = {
name: '${sessionHostPrefix}-HostPool'
location: location
properties: {
friendlyName: '${sessionHostPrefix} Host Pool'
description: '${sessionHostPrefix} Virual Privileged Access Workstaiotn Host Pool for privielged users to securely access the Microsoft Admin centers from'
hostPoolType: 'Pooled'
loadBalancerType: 'BreadthFirst'
maxSessionLimit: 5
personalDesktopAssignmentType: 'Automatic'
startVMOnConnect: true
preferredAppGroupType: 'Desktop'
customRdpProperty: 'enablecredsspsupport:i:0;authentication level:i:2;enablerdsaadauth:i:1;'
}
}
//Deploy the vPAW Desktop Application Group
resource AppGroup 'Microsoft.DesktopVirtualization/applicationGroups@2021-07-12' = {
name: '${sessionHostPrefix}-AppGroup'
location: location
properties: {
description: '${sessionHostPrefix} Application Group'
friendlyName: '${sessionHostPrefix} Desktop Application Group'
hostPoolArmPath: HostPool.id
applicationGroupType: 'Desktop'
}
}
//Deploy the vPAW Workspace
resource Workspace 'Microsoft.DesktopVirtualization/workspaces@2021-07-12' = {
name: '${sessionHostPrefix}-Workspace'
location: location
properties: {
description: '${sessionHostPrefix} Workspace for Privileged Users'
friendlyName: '${sessionHostPrefix} Workspace'
applicationGroupReferences: [
AppGroup.id
]
}
}
This script will create the initial infrastructure for AVD. There are multiple ways to deploy this but for me, I decided to do it in the Azure Portal itself.
To deploy in the Azure Portal you need to convert the Bicep script in to an ARM Template, in Visual Studio Code that’s simple enough. You can do that by right clicking on the script and then select Build ARM Template:

Once you have created the ARM Template click on the created file in the Visual Studio Code Output pane to open it:

You’ll then have something that looks like this:

Select all and copy it to the clipboard. Then go to the Azure Portal and then search for Deploy a custom template:

Then select Build your own template in the editor:

Paste your ARM Template in to the template editor over writing any existing code in there and then click Save:

Select the subscription you are going to use for the deployment and then either use an existing Resource Group or create a new one. In my case I created a new Resource Group and called is cPAW.

Then set the prefix for the HostPool, Workspace and App Group names. This will also then need to be used in the next script at it will use the same prefix for the session hosts. Now click Review + create:

You will then be presented with a screen detailing the Terms etc… much like you do when you create a resource in in the Azure Portal normally by clicking Create you are agreeing to the terms:

It should only take moments to deploy:

Once deployed you will have your Resource Group, your Hostpool, Workspace and AppGroup:

Thats the base infrastructure set for the AVD Environment.
The next thing you need to do is to get the Registration Key of the Hostpool so you can use that in the next step to register the Session Hosts you create
Obtaining the registration key
Once you have the basic AVD infrastructure setup go to the Hostpool and click on Registration key:

Click on Generate new key. Then set the required duration you would like the key to be valid for, I just set it to be the next day, it filled the time in automatically. Then click OK.

It will then generate the registration key:

Copy this as you will need this later.Deploying the Session Hosts
Creating the session hosts
So, the next step is to deploy the vNet and the session hosts. This is done in the same way as above by a custom ARM Template. So the Bicep script looks like this:
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'
}
}
]
}
}
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
}
}
}
]
}
}]
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_B4ms'
}
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
}
]
}
}
}]
resource entraIdJoin 'Microsoft.Compute/virtualMachines/extensions@2021-11-01' = [for i in range(0, numberOfHosts): {
parent: VM[i]
name: 'AADLoginForWindows'
location: location
properties: {
publisher: 'Microsoft.Azure.ActiveDirectory'
type: 'AADLoginForWindows'
typeHandlerVersion: '2.2'
autoUpgradeMinorVersion: true
enableAutomaticUpgrade: false
settings: {
mdmId: '0000000a-0000-0000-c000-000000000000'
}
}
}]
resource SessionPrep 'Microsoft.Compute/virtualMachines/extensions@2021-03-01' = [for i in range(0, numberOfHosts): {
parent: VM[i]
name: 'CustomScriptExtension'
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: [
entraIdJoin
]
}]
// 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}-DSC'
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
]
}]
Convert it to an ARM Template like you did previously in Visual Studio Code and then deploy as a custom script, however you’ll notice a few more options in the Custom Deployment. Firstly select the Resource Group you created in the previous step. I mean you can deploy to another resource group but why would you?
Then, you will need to set the following:
- Session Host Prefix – this will need to match what you set previously
- Number of Hosts – this will be the number of VM’s that you deploy
- Admin Password – This will be the local admin password to access the VM’s locally
- Admin Username – This can be whatever you decide upon. again this will be used to log on to the device locally if needed.
- Hostpool registration key – this is what was created above.
the Admin Password for the Session Hosts. You can also set the Admin username for the hosts by updating the Admin username value. The other thing that you will need is the Host Pool registration key. This will be used to register the Session hosts in the Host Pool.

Once all that is filled in click on Review + create. Then click Create on the review + create summary, this will take some time to deploy as it is doing the following:
- Creating the vNet
- Creating 2 Nics (assuming only 2 session hosts were chosen)
- Creating 2 Session hosts
- Then on each of the session hosts it is doing the following:
- Joining to Entra
- Enrolling in to Intune
- Performing some customisation of the session hosts and remove some of the Windows store apps from windows
- Join the sessions hosts to the hostpool

Once deployed if you go to the HostPool you will then see that there are 2 virtual machines available:

You will then also see them in Devices in Microsoft Entra:

And also you will see them in Intune:

Once this was all setup I added the groups I’d created to the correct permissions and then was able to log on to the Session Hosts, I will detail that in the next series of posts.
Conclusion
This for me is the best way I can deploy AVD, and replicate my set up easily enough. IT means I can standarise my config and it leaves very little room for any miss configuration. Whilst it maybe not the best way to deploy this via Bicep, for me this is certainly a step in the right direction.