During Workplace Ninja US, I did an in-person session on this topic, and now I am releasing for all. I’ve built Azure Virtual Desktop environments in a few different ways over the years — quick POCs, “just one host pool” builds, and full-blown enterprise deployments. The problem is the same every time: it starts simple and then you end up stitching together monitoring, scaling plans, RBAC, dashboards, and cost alerts… usually across multiple Terraform folders.
So I pulled everything into one modular Terraform repo that can deploy four AVD patterns (pooled/personal + desktop/RemoteApp), with optional enterprise-grade monitoring, dashboards, cost management, and scaling.
It also follows Microsoft Cloud Adoption Framework (CAF) naming patterns for the main AVD + network resources (host pool, app group, workspace, vnet/subnet/nsg, etc.).
Why this repo is “enterprise ready”
A few highlights that make this more than a basic host pool deployment:
Scaling plans for pooled deployments (desktop + RemoteApp) and environment-specific schedules
Monitoring & observability using Log Analytics + diagnostics
Custom dashboards for operational visibility
Cost management with budgets + alerts
CAF-friendly naming so your portal stays clean and consistent
Supported deployment types (quick view)
The configuration automatically adjusts host pool/app group settings depending on deployment_type (pooled vs personal, desktop vs RemoteApp).
The deployment guide uses a Service Principal stored in a local .env file (ignored by git), and a set-auth.ps1 script that loads the values into ARM_* environment variables for Terraform.
High level flow: .env → set-auth.ps1 → ARM_* env vars → Terraform
Create your .env from .env.example, then run:
.\set-auth.ps1
Scaling plans note: your Service Principal needs the Desktop Virtualization Power On Off Contributor role at subscription scope for scaling to work properly.
Terraform init / plan / apply
terraform init terraform plan -var-file=dev-pooled-desktop.tfvars terraform apply -var-file=dev-pooled-desktop.tfvars
Monitoring, dashboards, cost alerts (optional but worth it)
If you use one of the monitoring/scaling-enabled tfvars options, the repo can deploy:
Log Analytics + diagnostics
Dashboards for ops visibility
Budgets/alerts for cost tracking
A quick note on dependency ordering (why it matters)
The repo is intentional about resource ordering — especially for scaling plans — to avoid portal oddities and ensure the host pool association is reliable. The dependency flow is documented and includes a separate scaling plan host pool association resource.
Resoure Group (RG)
Application Groups (AG)
Wrap up
If you want a repeatable way to deploy AVD that supports pooled + personal and desktop + RemoteApp, while also giving you the option to turn on monitoring, dashboards, budgets/alerts, and scaling, this repo is designed for exactly that.
Azure Virtual Machines (VMs) are a powerful tool for running applications, hosting desktops, and performing various tasks in the cloud. However, the cost of running VMs can add up quickly, especially if they are left running when not in use. Microsoft has introduced a new feature called VM Hibernation, which allows you to save costs by hibernating your VMs when they are not needed. In this blog post, we will explore the benefits of VM Hibernation and how you can use it to optimize your Azure costs.
What is VM Hibernation
VM Hibernation is a cost-saving feature that allows you to deallocate a VM while preserving its in-memory state. When a VM is hibernated, you don’t pay for the compute costs associated with the VM. Instead, you only pay for the storage and networking resources associated with the VM. This means you can save a significant amount of money on your Azure bill by hibernating VMs when they are not in use. Obviously this wont work on its own you will have to intergrate this further with Scaling Plans & Azure Automation.
Use Cases for VM Hibernation
Category
Description
Virtual Desktops
If you use Azure Virtual Desktop to provide virtual desktops (Personal Only) to your employees, you can use VM Hibernation to save costs during non-business hours. By hibernating the VMs after business hours and resuming them the next morning, you can avoid paying for compute resources when they are not needed.
Dev/Test Environments
If you have development or testing environments that are not used 24/7, you can use VM Hibernation to save costs. By hibernating the VMs when they are not in use, you can avoid paying for compute resources that are not needed.
Prewarmed VMs
If you have applications that have long initialization times due to memory components, you can use VM Hibernation to save costs. By bringing up the apps and hibernating the VMs, you can quickly start the “prewarmed” VMs when needed, with the applications up and running in the desired state.
How to Enable Hibernation
Enabling hibernation is straightforward and can be done using various tools like Azure Portal, PowerShell, CLI, ARM, SDKs, and APIs. etc. I will be demonstrating within the Azure Portal:
Create a Host Pool – Select Personal
Under the Host Pool creation ensure you have Personal selected as this doesn’t work for Pooled
Under Host Pools –> Virtual Machine –> Select Hibernate
To take full advantage of VM Hibernation, you can configure scaling plans for your VMs. Scaling plans allow you to automatically hibernate VMs based on a schedule or based on user activity.
Start VM on Connect – Ensure that is “Yes“
Configure Hibernation Settings
Define hibernation after a user session disconnects or logs off for a configurable period.
Assign the Scaling Plan
Apply the scaling plan to one or more personal host pools.
Reference Links
Following are the list of helpful links:
Description
Links
Hibernation support now available for Azure Virtual Desktop
VM Hibernation is a powerful cost-saving feature that can help you optimize your Azure costs. By hibernating VMs when they are not in use, you can save a significant amount of money on your Azure bill. Whether you are using VMs for virtual desktops, dev/test environments, or prewarmed VMs, VM Hibernation can help you save costs without sacrificing performance or availability. Do give it a try in your enivornment and see how much you can save?
In today’s digital age, managing cloud resources efficiently is paramount, not just for operational efficacy but also for cost management. Enter Azure Virtual Desktop (AVD) Scaling Plans – Microsoft’s answer to dynamic and intelligent scaling of your virtual desktop infrastructure. No longer do organizations need to overprovision resources or let them sit idle; with AVD Scaling Plans, you get a responsive environment tailored to your usage patterns. In this blog post, we’ll create the scaling plans using Terraform.
In the previous blog post, we delved into the distinctions between the Personal Desktop (1:1 mapping), Pooled Desktop (1:Many mapping) and Remote App configurations, providing a comprehensive guide on their creation via Terraform. The series continues as we further explore how to create the AVD Scaling Plan for Pooled Host Pool.
Permissions within the Azure Subscription for using Terraform
Terraform – Authenticating via Service Principal & Client Secret
Before running any Terraform code, we will execute the following PowerShell (Run as administrator)and store the credentials as environment variables. If we do this via the environment variable, we don’t have to keep the below information within the providers.tf file. In a future blog post, there are better ways to store the below details, and I hope to showcase them:
Azure Subcription ID – Azure Portal Subcription copy the ID
Client ID – From the above step you will have the details
Client Secret – From the above step you will have the details
Tenant ID – While creating the Enterprise Apps in ADD you will have the details
Terraform Folder Structure
The following is the folder structure for the terrraform code:
Azure Virtual Desktop Scaling Plan – Create a directory in which the below Terraform code will be published (providers.tf, main.tf, variables.tf and output.tf)
Create a file named main.tf and insert the following code. Let me explain what all we are attempting to accomplish here:
Leverage a existing Resource Group
Leverage a existing Host Pool
Create a custom role AVD AutoScale and assign to the Resource Group
This is a prerequisite for ensuring the scaling plan can increase and decrease the resources in your resource group.
Assign the role – AVD AutoScale to the service principal (AVD)
Create a a scaling plan with a production grade schedule
Associate the scaling plan with the host pool
# Generate a random UUID for role assignment
resource "random_uuid" "example" {}
# Fetch details of the existing Azure Resource Group
data "azurerm_resource_group" "example" {
name = var.resource_group_name
}
# Fetch details of the existing Azure Virtual Desktop Host Pool
data "azurerm_virtual_desktop_host_pool" "existing" {
name = var.existing_host_pool_name
resource_group_name = var.resource_group_name
}
# Define the Azure Role Definition for AVD AutoScale
resource "azurerm_role_definition" "example" {
name = "AVD-AutoScale"
scope = data.azurerm_resource_group.example.id
description = "AVD AutoScale Role"
# Define the permissions for this role
permissions {
actions = [
# List of required permissions.
"Microsoft.Insights/eventtypes/values/read",
"Microsoft.Compute/virtualMachines/deallocate/action",
"Microsoft.Compute/virtualMachines/restart/action",
"Microsoft.Compute/virtualMachines/powerOff/action",
"Microsoft.Compute/virtualMachines/start/action",
"Microsoft.Compute/virtualMachines/read",
"Microsoft.DesktopVirtualization/hostpools/read",
"Microsoft.DesktopVirtualization/hostpools/write",
"Microsoft.DesktopVirtualization/hostpools/sessionhosts/read",
"Microsoft.DesktopVirtualization/hostpools/sessionhosts/write",
"Microsoft.DesktopVirtualization/hostpools/sessionhosts/usersessions/delete",
"Microsoft.DesktopVirtualization/hostpools/sessionhosts/usersessions/read",
"Microsoft.DesktopVirtualization/hostpools/sessionhosts/usersessions/sendMessage/action",
"Microsoft.DesktopVirtualization/hostpools/sessionhosts/usersessions/read"
]
not_actions = []
}
assignable_scopes = [
data.azurerm_resource_group.example.id,
]
}
# Fetch the Azure AD Service Principal for Windows Virtual Desktop
data "azuread_service_principal" "example" {
display_name = "Azure Virtual Desktop"
}
# Assign the role to the service principal
resource "azurerm_role_assignment" "example" {
name = random_uuid.example.result
scope = data.azurerm_resource_group.example.id
role_definition_id = azurerm_role_definition.example.role_definition_resource_id
principal_id = data.azuread_service_principal.example.id
skip_service_principal_aad_check = true
}
# Define the Azure Virtual Desktop Scaling Plan
resource "azurerm_virtual_desktop_scaling_plan" "example" {
name = var.scaling_plan_name
location = var.location
resource_group_name = var.resource_group_name
friendly_name = var.friendly_name
description = var.scaling_plan_description
time_zone = var.timezone
tags = var.tags
dynamic "schedule" {
for_each = var.schedules
content {
name = schedule.value.name
days_of_week = schedule.value.days_of_week
ramp_up_start_time = schedule.value.ramp_up_start_time
ramp_up_load_balancing_algorithm = schedule.value.ramp_up_load_balancing_algorithm
ramp_up_minimum_hosts_percent = schedule.value.ramp_up_minimum_hosts_percent
ramp_up_capacity_threshold_percent= schedule.value.ramp_up_capacity_threshold_pct
peak_start_time = schedule.value.peak_start_time
peak_load_balancing_algorithm = schedule.value.peak_load_balancing_algorithm
ramp_down_start_time = schedule.value.ramp_down_start_time
ramp_down_load_balancing_algorithm= schedule.value.ramp_down_load_balancing_algorithm
ramp_down_minimum_hosts_percent = schedule.value.ramp_down_minimum_hosts_percent
ramp_down_force_logoff_users = schedule.value.ramp_down_force_logoff_users
ramp_down_wait_time_minutes = schedule.value.ramp_down_wait_time_minutes
ramp_down_notification_message = schedule.value.ramp_down_notification_message
ramp_down_capacity_threshold_percent = schedule.value.ramp_down_capacity_threshold_pct
ramp_down_stop_hosts_when = schedule.value.ramp_down_stop_hosts_when
off_peak_start_time = schedule.value.off_peak_start_time
off_peak_load_balancing_algorithm = schedule.value.off_peak_load_balancing_algorithm
}
}
# Associate the scaling plan with the host pool
host_pool {
hostpool_id = data.azurerm_virtual_desktop_host_pool.existing.id
scaling_plan_enabled = true
}
}
Configure AVD – ScalingPlans – variables.tf
Create a file named variables.tf and insert the following code. The place where we define existing or new variables:
# Define the resource group of the Azure Virtual Desktop Scaling Plan
variable "resource_group_name" {
description = "The name of the resource group."
type = string
default = "AE-DEV-AVD-01-PO-D-RG"
}
# Define the attributes of the Azure Virtual Desktop Scaling Plan
variable "scaling_plan_name" {
description = "The name of the Scaling plan to be created."
type = string
default = "AVD-RA-HP-01-SP-01"
}
# Define the description of the scaling plan
variable "scaling_plan_description" {
description = "The description of the Scaling plan to be created."
type = string
default = "AVD Host Pool Scaling plan"
}
# Define the timezone of the Azure Virtual Desktop Scaling Plan
variable "timezone" {
description = "Scaling plan autoscaling triggers and Start/Stop actions will execute in the time zone selected."
type = string
default = "AUS Eastern Standard Time"
}
# Define the freindlyname of the Azure Virtual Desktop Scaling Plan
variable "friendly_name" {
description = "The friendly name of the Scaling plan to be created."
type = string
default = "AVD-RA-HP-SP-01"
}
# Define the host pool type(Pooled or Dedicated) of the Azure Virtual Desktop Scaling Plan
variable "host_pool_type" {
description = "The host pool type of the Scaling plan to be created."
type = string
default = "Pooled"
}
# Define the details of the scaling plan schedule
variable "schedules" {
description = "The schedules of the Scaling plan to be created."
type = list(object({
name = string
days_of_week = list(string)
ramp_up_start_time = string
ramp_up_load_balancing_algorithm = string
ramp_up_minimum_hosts_percent = number
ramp_up_capacity_threshold_pct = number
peak_start_time = string
peak_load_balancing_algorithm = string
ramp_down_start_time = string
ramp_down_load_balancing_algorithm = string
ramp_down_minimum_hosts_percent = number
ramp_down_capacity_threshold_pct = number
ramp_down_wait_time_minutes = number
ramp_down_stop_hosts_when = string
ramp_down_notification_message = string
off_peak_start_time = string
off_peak_load_balancing_algorithm = string
ramp_down_force_logoff_users = bool
}))
default = [
{
name = "weekdays_schedule"
days_of_week = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"]
ramp_up_start_time = "08:00"
ramp_up_load_balancing_algorithm = "BreadthFirst"
ramp_up_minimum_hosts_percent = 20
ramp_up_capacity_threshold_pct = 60
peak_start_time = "09:00"
peak_load_balancing_algorithm = "DepthFirst"
ramp_down_start_time = "18:00"
ramp_down_load_balancing_algorithm = "DepthFirst"
ramp_down_minimum_hosts_percent = 10
ramp_down_capacity_threshold_pct = 90
ramp_down_wait_time_minutes = 30
ramp_down_stop_hosts_when = "ZeroActiveSessions"
ramp_down_notification_message = "You will be logged off in 30 min. Make sure to save your work."
off_peak_start_time = "20:00"
off_peak_load_balancing_algorithm = "DepthFirst"
ramp_down_force_logoff_users = false
}
]
}
# Define the location of the Azure Virtual Desktop Scaling Plan
variable "location" {
description = "The location where the resources will be deployed."
type = string
default = "australiaeast"
}
# Define the tags of the Azure Virtual Desktop Scaling Plan
variable "tags" {
description = "The tags to be assigned to the Scaling plan."
type = map(string)
default = {
"Billing" = "IT"
"Department" = "IT"
"Location" = "AUS-East"
}
}
# Define the name of the Azure Virtual Desktop Host Pool
variable "existing_host_pool_name" {
description = "The name of the existing Azure Virtual Desktop Host Pool."
type = string
default = "AE-DEV-AVD-01-PO-D-HP"
}
Configure AVD – ScalingPlans – output.tf
Create a file named output.tf and insert the following code. This will showcase in the console what is getting deployed in form of a output.
# Output the ID of the Azure Virtual Desktop Scaling Plan
output "scaling_plan_id" {
description = "The ID of the Virtual Desktop Scaling Plan."
value = azurerm_virtual_desktop_scaling_plan.example.id
}
Intialize Terraform – AVD – ScalingPlans
Run terraform init to initialize the Terraform deployment. This command downloads the Azure provider required to manage your Azure resources. (Its pulling the AzureRM and AzureAD)
terraform init -upgrade
Create Terraform Execution Plan – AVD – ScalingPlans
Run terraform plan to create an execution plan.
terraform plan -out scaleplan.tfplan
Apply Terraform Execution Plan – AVD – ScalingPlans
Run terraform apply to apply the execution plan to your cloud infrastructure.
terraform apply "scaleplan.tfplan"
Validate the Output in Azure Portal
Go to the Azure portal, Select Azure Virtual Desktop and Select Scaling Plans and validate all the details such as Host Pool Assignment and Schedule:
Clean-up the above resources (Optional)
If you want to delete all the above resources then you can use the following commands to destroy. Run terraform plan and specify the destroy flag.
terraform plan -destroy -out scaleplan.destory.tfplan
terraform apply "scaleplan.destory.tfplan"
Quick Start Links
The intention here is to get you quickly started with Terraform on Azure Virtual Desktop Solution:
Description
Links
Create an autoscale scaling plan for Azure Virtual Desktop
I hope you will find this helpful information for getting started with Terraform to deploy the Azure Virtual Desktop – Scaling Plans. Please let me know if I have missed any steps or details, and I will be happy to update the post.
In the July 2023 release for Azure Virtual Desktop, the Watermarking and Session capture protection features became generally available. Numerous blog posts already showcase how to enable the feature using Group Policy. In today’s post, I will showcase how to enable Watermarking and Session Capture protection using Microsoft Intune for Session Host Virtual machines that are Azure AD joined.
Prerequisites
You’ll need the following things ready before you can rollout watermarking/session capture protection:
Azure Virtual Desktop: You must have Azure Virtual Desktop deployed (Pooled or Personal Desktops) and set up in your Azure environment.
Microsoft Intune: You should have an active subscription to Microsoft Intune, which is a cloud-based service that enables device management and security. The role within Intune Portal for creating and assigning the configuration profiles is – Policy and Profile manager built-in role-based access control (RBAC) role.
Azure Active Directory: Your Azure Virtual Desktop environment should be integrated with Azure Active Directory (AD) (The Host pools RDP properties – targetisaadjoined:i:1). The AAD Security groups must be in place, which has the members as the session’s host in AVD.
Azure AD Joined Devices: The session host virtual machines (VMs) you want to enable Watermarking and Session Capture protection for should be Azure AD joined. This means they must be connected to Azure AD and registered as members of your organization’s directory.
Windows 11 operating system for the client along with the Azure Virtual Desktop Client or Remote Desktop Client versions 1.2.x and higher
Configuration Profiles – Intune
To enable the Watermarking and Session Capture protection features in Azure Virtual Desktop using Microsoft Intune Configuration profiles and Azure AD joined devices, you can follow these steps:
In the settings picker, browse to Administrative templates > Windows Components > Remote Desktop Services > Remote Desktop Session Host > Azure Virtual Desktop. You should see settings in the Azure Virtual Desktop subcategory available for you to configure, such as “Enable watermarking” and “Enable screen capture protection”
Select the “Enable screen capture protection” settings, too and leave the values as defaults. (Feel free to tweak it based on your requirements)
Assigning the configuration to the AAD group, which has all the session host devices
Reboot the session host after applying or wait until the next maintenance cycle
Client Validation
Connect to a remote session with a supported client (Azure Virtual Desktop Client or Remote Desktop Client versions 1.2.x), where you should see QR codes appear.
The QR code only works for Windows 11 Multi-session\Windows 11 Enterprise (pooled or personal desktops). The RemoteApps will not show the QR code as its not supported.
Screenshot protection – In the session, it will be completely blank if you try to take a screenshot. Below is an example. I was trying to take a screenshot of the text file, and the screenshot was completely blank.
Mobile Phone Photo
When you try to take a screenshot from the mobile phone, this is how it will look, and it will pop the Connection ID. You have this connection ID you can match in Azure Insights.
Azure Virtual Desktop Insights
To find out the session information from the QR code by using Azure Virtual Desktop Insights:
Open a web browser and go to https://aka.ms/avdi to open Azure Virtual Desktop Insights. Sign-in using your Azure credentials when prompted.
Select the relevant subscription, resource group, host pool and time range, then select the Connection Diagnostics tab.
In the section Success rate of (re)establishing a connection (% of connections), there’s a list of all connections showing First attempt, Connection Id, User, and Attempts. You can look for the connection ID from the QR code in this list, or export to Excel.
I hope you will find this helpful information for getting started with Watermarking and Screenshot protection for the Azure Virtual Desktop – Session Host. Please let me know if I have missed any steps or details, and I will be happy to update the post.
We are going to create the following three types of configurations using Terraform:
Azure Virtual Desktop – Personal Desktop (1×1) – Part 1
Azure Virtual Desktop – Pooled Desktop (Multi-Session Full Desktop Experience) – Part 2
Azure Virtual Desktop – Remote App (Multi-Session Application aka Published Apps) – Part 3
Note – We are creating the Pooled RemoteApp in this post and in the subsequent post the other types were. In this post In this post I will not show case the creation of service principal and secret please refer for the Part 1 for that activity.
Pre-requisites
Following are the pre-requisites before you begin
An Azure subscription
The Terraform CLI
The Azure CLI
Permissions within the Azure Subscription for using Terraform
Terraform – Authenticating via Service Principal & Client Secret
Before running any Terraform code the following powershell (Make sure run as administrator) we will execute and store the credentials as enviornment variables. If we do this via the environment variable we dont have to store the below information within the providers.tf file. In the future blog post there are better way to store the below details and I hope to showcase them:
Azure Subcription ID – Azure Portal Subcription copy the ID
Client ID – From the above step you will have the details
Client Secret – From the above step you will have the details
Tenant ID – While creating the Enterprise Apps in ADD you will have the details
Terraform Folder Structure
The following is the folder structure for the terrraform code:
Azure Virtual Desktop Pooled RemoteApp – Create a directory in which the below Terraform code will be published (providers.tf, main.tf, variables.tf and output.tf)
Create a file named main.tf and insert the following code. Let me explain what all we are attempting to accomplish here:
Create a Resource Group
Create a Workspace
Create a Host Pool
Create a Remote Application Group (RAG)
Associate Workspace and RAG
Assign Azure AD Group to the Desktop Application Group (RAG)
Assign Azure AD Group to the Resource Group for RBAC for the Session Host (Virtual Machine User Login)
# Resource group name is output when execution plan is applied.
resource "azurerm_resource_group" "rg" {
name = var.rg_name
location = var.resource_group_location
tags = var.tags
}
# Create AVD workspace
resource "azurerm_virtual_desktop_workspace" "workspace" {
name = var.workspace
resource_group_name = azurerm_resource_group.rg.name
location = azurerm_resource_group.rg.location
friendly_name = "${var.prefix} Workspace"
description = "${var.prefix} Workspace"
tags = var.tags
}
# Create AVD host pool
resource "azurerm_virtual_desktop_host_pool" "hostpool" {
resource_group_name = azurerm_resource_group.rg.name
location = azurerm_resource_group.rg.location
name = var.hostpool
friendly_name = var.hostpool
validate_environment = true #[true false]
start_vm_on_connect = true
custom_rdp_properties = "targetisaadjoined:i:1;drivestoredirect:s:*;audiomode:i:0;videoplaybackmode:i:1;redirectclipboard:i:1;redirectprinters:i:1;devicestoredirect:s:*;redirectcomports:i:1;redirectsmartcards:i:1;usbdevicestoredirect:s:*;enablecredsspsupport:i:1;redirectwebauthn:i:1;use multimon:i:1;enablerdsaadauth:i:1;"
description = "${var.prefix} HostPool"
type = "Pooled" #[Pooled or Personal]
preferred_app_group_type = "RailApplications" #[Desktop or RailApplications]
maximum_sessions_allowed = 5 #[Tweak based on your vm tshirt size]
load_balancer_type = "DepthFirst" #[BreadthFirst or DepthFirst]
tags = var.tags
scheduled_agent_updates {
enabled = true
timezone = "AUS Eastern Standard Time" # Update this value with your desired timezone
schedule {
day_of_week = "Saturday"
hour_of_day = 1 #[1 here means 1:00 am]
}
}
}
resource "azurerm_virtual_desktop_host_pool_registration_info" "registrationinfo" {
hostpool_id = azurerm_virtual_desktop_host_pool.hostpool.id
expiration_date = var.rfc3339
}
# Create AVD RAG
resource "azurerm_virtual_desktop_application_group" "rag" {
resource_group_name = azurerm_resource_group.rg.name
host_pool_id = azurerm_virtual_desktop_host_pool.hostpool.id
location = azurerm_resource_group.rg.location
type = "RemoteApp"
name = var.app_group_name
friendly_name = "RemoteApp AppGroup"
description = "${var.prefix} AVD RemoteApp application group"
depends_on = [azurerm_virtual_desktop_host_pool.hostpool, azurerm_virtual_desktop_workspace.workspace]
tags = var.tags
}
# Associate Workspace and DAG
resource "azurerm_virtual_desktop_workspace_application_group_association" "ws-dag" {
application_group_id = azurerm_virtual_desktop_application_group.rag.id
workspace_id = azurerm_virtual_desktop_workspace.workspace.id
}
# Assign AAD Group to the Remote Application Group (RAG)
resource "azurerm_role_assignment" "AVDGroupRemoteAppAssignment" {
scope = azurerm_virtual_desktop_application_group.rag.id
role_definition_name = "Desktop Virtualization User"
principal_id = data.azuread_group.AVDGroup.object_id
}
# Assign AAD Group to the Resource Group for RBAC for the Session Host
resource "azurerm_role_assignment" "RBACAssignment" {
scope = azurerm_resource_group.rg.id
role_definition_name = "Virtual Machine User Login"
principal_id = data.azuread_group.AVDGroup.object_id
}
Note – The individual applications are not published yet. They can be published once you have the session host created. After which, using Terraform, the individual applications can be published too. The exe path of apps needs to be mapped within the operating system. I plan to create a separate blog post on session host creation via Terraform.
Configure AVD – Pooled RemoteApp – variables.tf
Create a file named variables.tf and insert the following code:
variable "resource_group_location" {
default = "australiaeast"
description = "Location of the resource group - Australia East"
}
variable "rg_name" {
type = string
default = "AE-DEV-AVD-01-PO-A-RG"
description = "Name of the Resource group in which to deploy service objects"
}
variable "workspace" {
type = string
description = "Name of the Azure Virtual Desktop workspace"
default = "AE-DEV-AVD-01-WS"
}
variable "hostpool" {
type = string
description = "Name of the Azure Virtual Desktop host pool"
default = "AE-DEV-AVD-01-PO-A-HP"
}
variable "app_group_name" {
description = "Name of the Azure Virtual Desktop application group"
type = string
default = "AE-DEV-AVD-01-RAG"
}
variable "rfc3339" {
type = string
default = "2023-05-20T12:43:13Z" #Update this value with a future date
description = "Registration token expiration"
}
variable "prefix" {
type = string
default = "AE-DEV-AVD-01-HP-"
description = "Prefix of the name of the AVD HostPools"
}
variable "tags" {
type = map(string)
default = {
Environment = "Dev"
Department = "IT"
Location = "AustraliaEast"
ServiceClass = "DEV"
Workload = "Host Pool 01"
}
}
data "azuread_client_config" "AzureAD" {}
data "azuread_group" "AVDGroup" {
display_name = "Win365-Users"
}
Configure AVD – Pooled RemoteApp – output.tf
Create a file named output.tf and insert the following code. This will showcase in the console what is getting deployed in form of a output.
output "azure_virtual_desktop_compute_resource_group" {
description = "Name of the Resource group in which to deploy session host"
value = azurerm_resource_group.rg.name
}
output "azure_virtual_desktop_host_pool" {
description = "Name of the Azure Virtual Desktop host pool"
value = azurerm_virtual_desktop_host_pool.hostpool.name
}
output "azurerm_virtual_desktop_application_group" {
description = "Name of the Azure Virtual Desktop DAG"
value = azurerm_virtual_desktop_application_group.rag.name
}
output "azurerm_virtual_desktop_workspace" {
description = "Name of the Azure Virtual Desktop workspace"
value = azurerm_virtual_desktop_workspace.workspace.name
}
output "location" {
description = "The Azure region"
value = azurerm_resource_group.rg.location
}
data "azuread_group" "aad_group" {
display_name = "Win365-Users"
}
output "AVD_user_groupname" {
description = "Azure Active Directory Group for AVD users"
value = data.azuread_group.aad_group.display_name
}
Intialize Terraform – AVD – Pooled RemoteApp
Run terraform init to initialize the Terraform deployment. This command downloads the Azure provider required to manage your Azure resources. (Its pulling the AzureRM and AzureAD)
terraform init -upgrade
Create Terraform Execution Plan – AVD – Pooled RemoteApp
Run terraform plan to create an execution plan.
terraform plan -out mainavdremoteapp.tfplan
Apply Terraform Execution Plan – AVD – Pooled RemoteApp
Run terraform apply to apply the execution plan to your cloud infrastructure.
terraform apply mainavdremoteapp.tfplan
Validate the Output in Azure Portal
Go to the Azure portal, Select Azure Virtual Desktop and Select Host pools, Application Group and Workspace created using Terraform.
Clean-up the above resources (Optional)
If you want to delete all the above resources then you can use the following commands to destroy. Run terraform plan and specify the destroy flag.
terraform plan -destroy -out mainavdremoteapp.destroy.tfplan
Run terraform apply to apply the execution plan.
terraform apply mainavdremoteapp.destroy.tfplan
Quick Start Links
The intention here is to get you quickly started with Terraform on Azure Virtual Desktop Solution:
Description
Links
Setting up your computer to get started with Terrafor using Powershell
I hope you will find this helpful information for getting started with Terraform to deploy the Azure Virtual Desktop – Pooled Remote App. Please let me know if I have missed any steps or details, and I will be happy to update the post.
We are going to create the following three types of configurations using Terraform:
Azure Virtual Desktop – Personal Desktop (1×1) – Part 1
Azure Virtual Desktop – Pooled Desktop (Multi-Session Full Desktop Experience) – Part 2
Azure Virtual Desktop – Remote App (Multi-Session Application aka Published Apps) – Part 3
Note – We are creating the Pooled Desktop in this post and in the subsequent post the other types will be created. In this post In this post I will not show case the creation of service principal and secret please refer for the Part 1 for that activity.
Pre-requisites
Following are the pre-requisites before you begin
An Azure subscription
The Terraform CLI
The Azure CLI
Permissions within the Azure Subscription for using Terraform
Terraform – Authenticating via Service Principal & Client Secret
Before running any Terraform code the following powershell (Make sure run as administrator) we will execute and store the credentials as enviornment variables. If we do this via the environment variable we dont have to store the below information within the providers.tf file. In the future blog post there are better way to store the below details and I hope to showcase them:
Azure Subcription ID – Azure Portal Subcription copy the ID
Client ID – From the above step you will have the details
Client Secret – From the above step you will have the details
Tenant ID – While creating the Enterprise Apps in ADD you will have the details
Terraform Folder Structure
The following is the folder structure for the terrraform code:
Azure Virtual Desktop Pooled Desktop – Create a directory in which the below Terraform code will be published (providers.tf, main.tf, variables.tf and output.tf)
Create a file named main.tf and insert the following code. Let me explain what all we are attempting to accomplish here:
Create a Resource Group
Create a Workspace
Create a Host Pool (Pooled Desktops and Depth first load balancing)
Create a Desktop Application Group (DAG)
Associate Workspace and DAG
Assign Azure AD Group to the Desktop Application Group (DAG)
Assign Azure AD Group to the Resource Group for RBAC for the Session Host (Virtual Machine User Login)
# Resource group name is output when execution plan is applied.
resource "azurerm_resource_group" "rg" {
name = var.rg_name
location = var.resource_group_location
tags = var.tags
}
# Create AVD workspace
resource "azurerm_virtual_desktop_workspace" "workspace" {
name = var.workspace
resource_group_name = azurerm_resource_group.rg.name
location = azurerm_resource_group.rg.location
friendly_name = "${var.prefix} Workspace"
description = "${var.prefix} Workspace"
tags = var.tags
}
# Create AVD host pool
resource "azurerm_virtual_desktop_host_pool" "hostpool" {
resource_group_name = azurerm_resource_group.rg.name
location = azurerm_resource_group.rg.location
name = var.hostpool
friendly_name = var.hostpool
validate_environment = true #[true false]
start_vm_on_connect = true
custom_rdp_properties = "targetisaadjoined:i:1;drivestoredirect:s:*;audiomode:i:0;videoplaybackmode:i:1;redirectclipboard:i:1;redirectprinters:i:1;devicestoredirect:s:*;redirectcomports:i:1;redirectsmartcards:i:1;usbdevicestoredirect:s:*;enablecredsspsupport:i:1;redirectwebauthn:i:1;use multimon:i:1;enablerdsaadauth:i:1;"
description = "${var.prefix} HostPool"
type = "Pooled" #[Pooled or Personal]
maximum_sessions_allowed = 5
load_balancer_type = "DepthFirst" #[BreadthFirst DepthFirst]
tags = var.tags
scheduled_agent_updates {
enabled = true
timezone = "AUS Eastern Standard Time" # Update this value with your desired timezone
schedule {
day_of_week = "Saturday"
hour_of_day = 1 #[1 here means 1:00 am]
}
}
}
resource "azurerm_virtual_desktop_host_pool_registration_info" "registrationinfo" {
hostpool_id = azurerm_virtual_desktop_host_pool.hostpool.id
expiration_date = var.rfc3339
}
# Create AVD DAG
resource "azurerm_virtual_desktop_application_group" "dag" {
resource_group_name = azurerm_resource_group.rg.name
host_pool_id = azurerm_virtual_desktop_host_pool.hostpool.id
location = azurerm_resource_group.rg.location
type = "Desktop"
name = var.app_group_name
friendly_name = "Desktop AppGroup"
description = "${var.prefix} AVD application group"
depends_on = [azurerm_virtual_desktop_host_pool.hostpool, azurerm_virtual_desktop_workspace.workspace]
tags = var.tags
}
# Associate Workspace and DAG
resource "azurerm_virtual_desktop_workspace_application_group_association" "ws-dag" {
application_group_id = azurerm_virtual_desktop_application_group.dag.id
workspace_id = azurerm_virtual_desktop_workspace.workspace.id
}
# Assign AAD Group to the Desktop Application Group (DAG)
resource "azurerm_role_assignment" "AVDGroupDesktopAssignment" {
scope = azurerm_virtual_desktop_application_group.dag.id
role_definition_name = "Desktop Virtualization User"
principal_id = data.azuread_group.AVDGroup.object_id
}
# Assign AAD Group to the Resource Group for RBAC for the Session Host
resource "azurerm_role_assignment" "RBACAssignment" {
scope = azurerm_resource_group.rg.id
role_definition_name = "Virtual Machine User Login"
principal_id = data.azuread_group.AVDGroup.object_id
}
Configure AVD – Pooled Desktop Pool – variables.tf
Create a file named variables.tf and insert the following code:
variable "resource_group_location" {
default = "australiaeast"
description = "Location of the resource group - Australia East"
}
variable "rg_name" {
type = string
default = "AE-DEV-AVD-01-PO-D-RG"
description = "Name of the Resource group in which to deploy service objects"
}
variable "workspace" {
type = string
description = "Name of the Azure Virtual Desktop workspace"
default = "AE-DEV-AVD-01-WS"
}
variable "hostpool" {
type = string
description = "Name of the Azure Virtual Desktop host pool"
default = "AE-DEV-AVD-01-PO-D-HP"
}
variable "app_group_name" {
description = "Name of the Azure Virtual Desktop application group"
type = string
default = "AE-DEV-AVD-01-DAG"
}
variable "rfc3339" {
type = string
default = "2023-05-20T12:43:13Z" #Update this value with a future date
description = "Registration token expiration"
}
variable "prefix" {
type = string
default = "AE-DEV-AVD-01-HP-"
description = "Prefix of the name of the AVD machine(s)"
}
variable "tags" {
type = map(string)
default = {
Environment = "Dev"
Department = "IT"
Location = "AustraliaEast"
ServiceClass = "DEV"
Workload = "Host Pool 01"
}
}
data "azuread_client_config" "AzureAD" {}
data "azuread_group" "AVDGroup" {
display_name = "Win365-Users"
}
Configure AVD – Pooled Desktop Pool – output.tf
Create a file named output.tf and insert the following code. This will showcase in the console what is getting deployed in form of a output.
output "azure_virtual_desktop_compute_resource_group" {
description = "Name of the Resource group in which to deploy session host"
value = azurerm_resource_group.rg.name
}
output "azure_virtual_desktop_host_pool" {
description = "Name of the Azure Virtual Desktop host pool"
value = azurerm_virtual_desktop_host_pool.hostpool.name
}
output "azurerm_virtual_desktop_application_group" {
description = "Name of the Azure Virtual Desktop DAG"
value = azurerm_virtual_desktop_application_group.dag.name
}
output "azurerm_virtual_desktop_workspace" {
description = "Name of the Azure Virtual Desktop workspace"
value = azurerm_virtual_desktop_workspace.workspace.name
}
output "location" {
description = "The Azure region"
value = azurerm_resource_group.rg.location
}
data "azuread_group" "aad_group" {
display_name = "Win365-Users"
}
output "AVD_user_groupname" {
description = "Azure Active Directory Group for AVD users"
value = data.azuread_group.aad_group.display_name
}
Intialize Terraform – AVD – Pooled Desktop Pool
Run terraform init to initialize the Terraform deployment. This command downloads the Azure provider required to manage your Azure resources. (Its pulling the AzureRM and AzureAD)
terraform init -upgrade
Create Terraform Execution Plan – AVD – Pooled Desktop Pool
Run terraform plan to create an execution plan.
terraform plan -out mainavdpooled.tfplan
Apply Terraform Execution Plan – AVD – Pooled Desktop Pool
Run terraform apply to apply the execution plan to your cloud infrastructure.
terraform apply mainavdpooled.tfplan
Validate the Output in Azure Portal
Go to the Azure portal, Select Azure Virtual Desktop and Select Host pools, Application Group and Workspace created using Terraform.
Clean-up the above resources (Optional)
If you want to delete all the above resources then you can use the following commands to destroy. Run terraform plan and specify the destroy flag.
terraform plan -destroy -out mainavdpooled.destroy.tfplan
Run terraform apply to apply the execution plan.
terraform apply mainavdpooled.destroy.tfplan
Quick Start Links
The intention here is to get you quickly started with Terraform on Azure Virtual Desktop Solution:
Description
Links
Setting up your computer to get started with Terrafor using Powershell
I hope you will find this helpful information for getting started with Terraform to deploy the Azure Virtual Desktop – Pooled Desktop. Please let me know if I have missed any steps or details, and I will be happy to update the post.
In the past, I have written blog posts on creating the Azure Virtual Desktop (AVD) solution using PowerShell. In this blog post series, I will demonstrate how to create the AVD Host Pool, Application Group and Workspace using Terraform. Terraform is an open-source infrastructure as code (IaC) software tool that enables you to safely and predictably create, change, and improve infrastructure. Terraform can be used to manage infrastructure on various cloud providers, including Azure.
We are going to create the following three types of configurations using Terraform:
Azure Virtual Desktop – Personal Desktop (1×1)
Azure Virtual Desktop – Pooled Desktop (Multi-Session Full Desktop Experience)
Note – We are creating the Personal Desktop in this post, and the other desktop/app types will be created in the subsequent post. In this post, I will showcase the creation of service principal and secret. In the next part, we shall move straight onto the Terraform code. Referring to part 1 in the series will be essential if you are doing the basics.
Permissions within the Azure Subscription for using Terraform
Terraform Service Principal and Secret (Azure AD – App Registrations)
Let’s pre-create the application ID and client secret we will use to connect and leverage the Terraform code in VScode.
Connect to Azure Portal and go to Azure Active Directory
Click on App Registrations and select – New Registration
Give the App a Name – Terraform
You will get two important information created for later use within Terraform
Application ID
Tenant ID
Now let’s grant this App Terraform Permission. Click on Add a permission and select MS Graph and search for AppRoleAssignment.ReadWrite.All and select read/write permissions and Add Permissions
Select Grant admin consent for domain
We are using client secret so now lets enable that. Click on Certificates & Secrets – Client Secrets and select New client secret\
Give it a name (Terra-secret) and expiry date (12 months)
Copy the Secret Value
Terraform – Authenticating via Service Principal & Client Secret
In the above step, we created the Service Principal and Client secret. We will use it before running any Terraform code in PowerShell (Ensure to run as administrator). We will execute and store the credentials as environment variables. If we do this via the environment variable, we don’t have to store the below information within the providers.tf file. In a future blog post, there are better ways to keep the below details, and I hope to showcase them:
Azure Subscription ID – Azure Portal Subscription copy the ID.
Client ID – From the above step, you will have the details
Client Secret – From the above step, you will have the details
Tenant ID – While creating the Enterprise Apps in Azure AD, you will have the details
Terraform Folder Structure
The following is the folder structure for the Terraform code:
Azure Virtual Desktop Personal Pool – Create a directory in which the below Terraform code will be published (providers.tf, main.tf, variables.tf and output.tf)
Create a file named main.tf and insert the following code. Let me explain what we are attempting to accomplish here: (Note I have # commented the lines with additional info)
Create a Resource Group
Create a Workspace
Create a Host Pool
Create a Desktop Application Group (DAG)
Associate Workspace and DAG
Assign Azure AD Group to the Desktop Application Group (DAG)
Assign Azure AD Group to the Resource Group for RBAC for the Session Host (Virtual Machine User Login)
# Resource group name is output when execution plan is applied.
resource "azurerm_resource_group" "rg" {
name = var.rg_name
location = var.resource_group_location
tags = var.tags
}
# Create AVD workspace
resource "azurerm_virtual_desktop_workspace" "workspace" {
name = var.workspace
resource_group_name = azurerm_resource_group.rg.name
location = azurerm_resource_group.rg.location
friendly_name = "${var.prefix} Workspace"
description = "${var.prefix} Workspace"
tags = var.tags
}
# Create AVD host pool
resource "azurerm_virtual_desktop_host_pool" "hostpool" {
resource_group_name = azurerm_resource_group.rg.name
location = azurerm_resource_group.rg.location
name = var.hostpool
friendly_name = var.hostpool
validate_environment = true #[true false]
start_vm_on_connect = true
custom_rdp_properties = "targetisaadjoined:i:1;drivestoredirect:s:*;audiomode:i:0;videoplaybackmode:i:1;redirectclipboard:i:1;redirectprinters:i:1;devicestoredirect:s:*;redirectcomports:i:1;redirectsmartcards:i:1;usbdevicestoredirect:s:*;enablecredsspsupport:i:1;redirectwebauthn:i:1;use multimon:i:1;enablerdsaadauth:i:1;"
description = "${var.prefix} HostPool"
type = "Personal" #[Pooled or Personal]
personal_desktop_assignment_type = "Automatic"
load_balancer_type = "Persistent"
tags = var.tags
scheduled_agent_updates {
enabled = true
timezone = "AUS Eastern Standard Time" # Update this value with your desired timezone
schedule {
day_of_week = "Saturday"
hour_of_day = 1 #[1 here means 1:00 am]
}
}
}
resource "azurerm_virtual_desktop_host_pool_registration_info" "registrationinfo" {
hostpool_id = azurerm_virtual_desktop_host_pool.hostpool.id
expiration_date = var.rfc3339
}
# Create AVD DAG
resource "azurerm_virtual_desktop_application_group" "dag" {
resource_group_name = azurerm_resource_group.rg.name
host_pool_id = azurerm_virtual_desktop_host_pool.hostpool.id
location = azurerm_resource_group.rg.location
type = "Desktop"
name = var.app_group_name
friendly_name = "Desktop AppGroup"
description = "${var.prefix} AVD application group"
depends_on = [azurerm_virtual_desktop_host_pool.hostpool, azurerm_virtual_desktop_workspace.workspace]
tags = var.tags
}
# Associate Workspace and DAG
resource "azurerm_virtual_desktop_workspace_application_group_association" "ws-dag" {
application_group_id = azurerm_virtual_desktop_application_group.dag.id
workspace_id = azurerm_virtual_desktop_workspace.workspace.id
}
# Assign AAD Group to the Desktop Application Group (DAG)
resource "azurerm_role_assignment" "AVDGroupDesktopAssignment" {
scope = azurerm_virtual_desktop_application_group.dag.id
role_definition_name = "Desktop Virtualization User"
principal_id = data.azuread_group.AVDGroup.object_id
}
# Assign AAD Group to the Resource Group for RBAC for the Session Host
resource "azurerm_role_assignment" "RBACAssignment" {
scope = azurerm_resource_group.rg.id
role_definition_name = "Virtual Machine User Login"
principal_id = data.azuread_group.AVDGroup.object_id
}
Configure AVD – Personal Desktop Pool – variables.tf
Create a file named variables.tf and insert the following code. I have followed a naming convention that includes the following:
AE – Australia East
Environment – PROD or DEV
Instance – 01
RG – Resource Group
WS – Workspace
DAG – Desktop Application Group
variable "resource_group_location" {
default = "australiaeast"
description = "Location of the resource group - Australia East"
}
variable "rg_name" {
type = string
default = "AE-DEV-AVD-01-RG"
description = "Name of the Resource group in which to deploy service objects"
}
variable "workspace" {
type = string
description = "Name of the Azure Virtual Desktop workspace"
default = "AE-DEV-AVD-01-WS"
}
variable "hostpool" {
type = string
description = "Name of the Azure Virtual Desktop host pool"
default = "AE-DEV-AVD-01-PE-D-HP"
}
variable "app_group_name" {
description = "Name of the Azure Virtual Desktop application group"
type = string
default = "AE-DEV-AVD-01-DAG"
}
variable "rfc3339" {
type = string
default = "2023-05-20T12:43:13Z" #Update this value with a future date
description = "Registration token expiration"
}
variable "prefix" {
type = string
default = "AE-DEV-AVD-01-HP-"
description = "Prefix of the name of the AVD machine(s)"
}
variable "tags" {
type = map(string)
default = {
Environment = "Dev"
Department = "IT"
Location = "AustraliaEast"
ServiceClass = "DEV"
Workload = "Host Pool 01"
}
}
data "azuread_client_config" "AzureAD" {}
data "azuread_group" "AVDGroup" {
display_name = "Win365-Users"
}
Configure AVD – Personal Desktop Pool – output.tf
Create a file named output.tf and insert the following code. This will showcase in the console what is getting deployed as output.
output "azure_virtual_desktop_compute_resource_group" {
description = "Name of the Resource group in which to deploy session host"
value = azurerm_resource_group.rg.name
}
output "azure_virtual_desktop_host_pool" {
description = "Name of the Azure Virtual Desktop host pool"
value = azurerm_virtual_desktop_host_pool.hostpool.name
}
output "azurerm_virtual_desktop_application_group" {
description = "Name of the Azure Virtual Desktop DAG"
value = azurerm_virtual_desktop_application_group.dag.name
}
output "azurerm_virtual_desktop_workspace" {
description = "Name of the Azure Virtual Desktop workspace"
value = azurerm_virtual_desktop_workspace.workspace.name
}
output "location" {
description = "The Azure region"
value = azurerm_resource_group.rg.location
}
data "azuread_group" "aad_group" {
display_name = "Win365-Users"
}
output "AVD_user_groupname" {
description = "Azure Active Directory Group for AVD users"
value = data.azuread_group.aad_group.display_name
}
Intialize Terraform – AVD – Personal Desktop Pool
Run the following command to initialize the Terraform deployment. This command downloads the Azure provider required to manage your Azure resources.
terraform init -upgrade
Create Terraform Execution Plan – AVD – Personal Desktop Pool
Run the following command to create an execution plan.
terraform plan -out mainavdpersonal.tfplan
Apply Terraform Execution Plan – AVD – Personal Desktop Pool
Run the following command to apply the execution plan to your cloud infrastructure.
terraform apply mainavdpersonal.tfplan
Validate the Output in Azure Portal
Go to the Azure portal, Select Azure Virtual Desktop and Select Host pools, Application Group and Workspace created using Terraform.
Clean-up the above resources (Optional)
If you want to delete all the above resources then you can use the following commands to destroy. Run terraform plan and specify the destroy flag.
terraform plan -destroy -out mainavdpersonal.destroy.tfplan
Run terraform apply to apply the execution plan.(Destroy)
terraform apply mainavdpersonal.destroy.tfplan
Quick Start Links
The intention here is to get you quickly started with Terraform on Azure Virtual Desktop Solution:
Description
Links
Setting up your computer to get started with Terrafor using Powershell
I hope you will find this helpful information for getting started with Terraform to deploy the Azure Virtual Desktop – Personal Desktop Pool. Please let me know if I have missed any steps or details, and I will be happy to update the post.
Search Highlight is a feature in Windows 11 (Enterprise\Multi-session) that highlights search results in the Start menu and taskbar search box. While this feature can be helpful for some users, others may find it distracting or unnecessary. Fortunately, it is possible to disable the Search Highlight feature in Windows 11 using Microsoft Intune. Plenty of information is available on disabling the Windows 11 Search Highlight using Group policy, Registry and UI. However, we will leverage Custom OMA-URI settings from Microsoft Intune in this blog post.
Search – CSP Details
The Search – Policy configuration service provider enables the enterprise to configure policies on Windows 11. Following are the details on the one we are using for disabling the search highlights:
How to disable Search Highlights in Microsoft Endpoint Manager
To disable the Search Highlight feature in Windows 11 (Enterprise/Multi-session) using Microsoft Intune, follow these steps:
For Profile type, select Templates > Custom and select Create.
Enter a Name – DisableSearchHighlight and description and choose Next
Under the OMA-URI Settings, clicks on Add
Enter the Name, Description, and OMA-URI fetched in the references from the MS CSP link below. The value is an integer based on the documentation, and as we disable the setting, the value is 0.
Remember the MS documentation called out this setting only applies to Devices. In the case of Assignments, we will target Windows 365 Device Group and Azure Virtual Desktop Session Host Pools.
Click on Review and Save
Validate the Policy is applying
After 10-15 mins of waiting, go into the newly configured configuration profiles policy, and you will start seeing it getting applied to the targeted devices (MEM Portal > Devices > Configuration Profiles > DisableSearchHighlights)
Cloud PC – Within Windows 11
Login to the Windows 365 Cloud PC, and now when you click on Search, the advertisements and search highlights are gone.
I hope you will find this helpful information towards disabling the annoying Search Highlights on Windows 365, AVD environment and physical endpoints using Microsoft Endpoint Manager. Please let me know if I have missed any steps or details, and I will be happy to update the post.
Microsoft Intune Compliance Policy can be used to manage the security and compliance of Azure Virtual Desktop (AVD) Session Host virtual machines. The policy can enforce specific configuration settings such as password complexity, security updates, and device encryption to ensure that the virtual machines meet the organization’s security and compliance requirements.
To set up an Intune Compliance Policy for an AVD Session Host virtual machine, the virtual machine must be enrolled with Intune. Then, the policy can be created in the Intune portal and assigned to the virtual machine. The policy settings will be enforced on the virtual machine and monitored for compliance.
Note: The Intune Compliance Policy is just one of the ways to manage the security and compliance of AVD Session Host virtual machines. Other tools such as Azure Security Center and Azure Policy can also be used.
Why create the azure virtual desktop session host compliance policy?
There are several reasons why organizations create Azure Virtual Desktop (AVD) Session Host Compliance Policies:
Security: Compliance policies help ensure that the AVD Session Host virtual machines are configured with the necessary security measures to protect sensitive data and prevent unauthorized access. This includes enforcing encryption, password policies, and software updates.
Compliance: Compliance policies help organizations meet regulatory requirements, such as HIPAA, PCI, and SOC, by ensuring that the AVD Session Host virtual machines are configured in accordance with these regulations.
Consistency: Compliance policies help ensure that all AVD Session Host virtual machines are configured consistently and meet the same standards. This makes it easier for administrators to manage the environment and ensures that all users have a consistent and secure experience.
Monitoring: Compliance policies provide ongoing monitoring of the AVD Session Host virtual machines, so administrators can quickly identify and address any deviations from the desired configuration.
By creating an AVD Session Host Compliance Policy, organizations can ensure that their virtual machines are secure, compliant, consistent, and properly monitored, which can help reduce the risk of security breaches and regulatory violations.
What compliance policies are supported with Azure Virtual Desktop?
The following compliance policies are supported on Windows 10 or Windows 11 Enterprise multi-session VMs:
Minimum OS version
Maximum OS version
Valid operating system builds
Simple passwords
Password type
Minimum password length
Password Complexity
Password expiration (days)
Number of previous passwords to prevent reuse
Microsoft Defender Antimalware
Microsoft Defender Antimalware security intelligence up-to-date
Firewall
Antivirus
Antispyware
Real-time protection
Microsoft Defender Antimalware minimum version
Defender ATP Risk score
Note in my sceanrio I am not using all of the above only a few based on the configuration of my environment. You will need a Azure AD device group containing all the session host for AVD to apply this policy.
What am I configuring?
I am only configuring two things. However, I urge if you to leverage Microsoft Defender and make sure you use the Antivirus and Antimalware settings (Another blog post later day for Defender integrations):
Minimum OS version – 10.0.22621.963
Firewall – Require
The above is not an extensive list, but I am trying to give you an idea here.
Click on Create Policy and Select Platform Windows 10 and later
Give the policy a name and description
Configure the above two parameters
An assignment is the most critical aspect, here, you want an Azure AD Dynamic Device Group that will make sure all the AVD Session hosts are covered.
My current AAD Dynamic Group query is as follows, I am working towards getting a more refine query to make it understand Multi-session(I have raised a query internally within MS)
Device Compliance (AVD Session Host VMs)
After waiting for 15 mins you will start noticing all your AVD Session host VM’s will now begin to show as compliant.
I hope you will find this helpful information for creating a compliance policy for your AVD Session host VMs. Please let me know if I have missed any steps or details, and I will be happy to update the post.
Assign the Azure Active Directory Group to the (AG)
I will break down the code block into smaller chunks first to explain the critical bits, and in the end, I will post the entire code block that can be run all at once. In this way, explaining block by block becomes easier than pasting one single block.
RemoteApp
RemoteApp – This is a way to provide end-users with the business applications alone without giving them an entire desktop. They can access their applications anywhere on any device.
Pre-requisites
Following are the pre-requisites before you begin
PowerShell 5.1 and above
Azure Subscription
Permissions within the Azure Subscription for the creation of AVD – Host Pools
To start working with Azure PowerShell, sign in with your Azure credentials.
Connect-AzAccount
Variable Region
Delcare all the variable within this section. Lets take a look at what we are declaring within the script:
Existing Resource Group within the Azure Subscription (AZ104-RG)
A location where you are deploying this Host Pool (Australia East)
Name of the Host Pool (RA-HP01)
Host Pool Type (Pooled) as it will be shared with multiple end-users
Load balancing method for the Host Pool (DepthFirst)
Maximum users per session host VM (10)
The type of Application Group (RailApplications). As we are only giving out end-users Apps
Application Group Name ($HPName-RAG)
Workspace grouping name ($HPName-WRK01)
Azure AD group that will be assigned to the application group (XXXX4b896-XXXX-XXXX-XXXX-33768d8XXXXX)
# Get existing context
$currentAzContext = Get-AzContext
# Your subscription. This command gets your current subscription
$subscriptionID = $currentAzContext.Subscription.Id
# Existing Resource Group to deploy the Host Pool
$rgName = "AZ104-RG"
# Geo Location to deploy the Host Pool
$location = "australiaeast"
# Host Pool name
$HPName = "RA-HP01"
# Host Pool Type Pooled|Personal
$HPType = "Pooled"
# Host Pool Load Balancing BreadthFirst|DepthFirst|Persistent
$HPLBType = "DepthFirst"
# Max number or users per session host
$Maxusers = "10"
# Preffered App group type Desktop|RailApplications
$AppGrpType = "RailApplications"
# ApplicationGroup Name
$AppGrpName = "$HPName-RAG"
# Workspace Name
$Wrkspace = "$HPName-WRK01"
# AAD Group used to assign the Application Group
# Copy the Object ID GUID from AAD Groups Blade
$AADGroupObjId = "XXXX4b896-XXXX-XXXX-XXXX-33768d8XXXXX"
Execution block
Execution code block within this section. Lets take a look at what we are we executing within the script:
Create the host pool with all the mentioned variables, tags and whether the validation enivornment yes/no.
Create the application group and tie it to the host pool
Finally, we create the workspace and tie it to the application group and hostpool
Last step, we assign the AAD group object ID to the Application Group for all entitlement purposes.
# Create the Host Pool with RemoteApp Configurations
try
{
write-host "Create the Host Pool with Pooled RemoteApp Configurations"
$DeployHPWRA = New-AzWvdHostPool -ResourceGroupName $rgName `
-SubscriptionId $subscriptionID `
-Name $HPName `
-Location $location `
-ValidationEnvironment:$true `
-HostPoolType $HPType `
-LoadBalancerType $HPLBType `
-MaxSessionLimit $Maxusers `
-PreferredAppGroupType $AppGrpType `
-Tag:@{"Billing" = "IT"; "Department" = "IT"; "Location" = "AUS-East" } `
-ErrorAction STOP
}
catch
{
Write-Host $_.Exception.Message -ForegroundColor Yellow
}
# Create the Application Group for the Remote App Host Pool
try
{
write-host "Create the Application Group for the Remote App Host Pool"
$CreateAppGroupRA = New-AzWvdApplicationGroup -ResourceGroupName $rgName `
-Name $AppGrpName `
-Location $location `
-HostPoolArmPath $DeployHPWRA.Id `
-ApplicationGroupType 'RemoteApp' `
-ErrorAction STOP
}
catch
{
Write-Host $_.Exception.Message -ForegroundColor Yellow
}
# Create the Workspace for the RemoteApp Host Pool
try
{
write-host "Create the Workspace for the RemoteApp Host Pool"
$CreateWorkspaceRA = New-AzWvdWorkspace -ResourceGroupName $rgName `
-Name $Wrkspace `
-Location $location `
-ApplicationGroupReference $CreateAppGroupRA.Id `
-ErrorAction STOP
}
catch
{
Write-Host $_.Exception.Message -ForegroundColor Yellow
}
# Assign the AAD group (Object ID) to the Application Group
try
{
write-host "Assigning the AAD Group to the Application Group"
$AssignAADGrpAG = New-AzRoleAssignment -ObjectId $AADGroupObjId `
-RoleDefinitionName "Desktop Virtualization User" `
-ResourceName $CreateAppGroupRA.Name `
-ResourceGroupName $rgName `
-ResourceType 'Microsoft.DesktopVirtualization/applicationGroups' `
-ErrorAction STOP
}
catch
{
Write-Host $_.Exception.Message -ForegroundColor Yellow
}
# Connect to the Azure Subcription
Connect-AzAccount
# Get existing context
$currentAzContext = Get-AzContext
# Your subscription. This command gets your current subscription
$subscriptionID = $currentAzContext.Subscription.Id
# Existing Resource Group to deploy the Host Pool
$rgName = "AZ104-RG"
# Geo Location to deploy the Host Pool
$location = "australiaeast"
# Host Pool name
$HPName = "RA-HP01"
# Host Pool Type Pooled|Personal
$HPType = "Pooled"
# Host Pool Load Balancing BreadthFirst|DepthFirst|Persistent
$HPLBType = "DepthFirst"
# Max number or users per session host
$Maxusers = "10"
# Preffered App group type Desktop|RailApplications
$AppGrpType = "RailApplications"
# ApplicationGroup Name
$AppGrpName = "$HPName-RAG"
# Workspace Name
$Wrkspace = "$HPName-WRK01"
# AAD Group used to assign the Application Group
# Copy the Object ID GUID from AAD Groups Blade
$AADGroupObjId = "dcc4b896-2f2d-49d9-9854-33768d8b65ba"
# Create the Host Pool with RemoteApp Configurations
try
{
write-host "Create the Host Pool with Pooled RemoteApp Configurations"
$DeployHPWRA = New-AzWvdHostPool -ResourceGroupName $rgName `
-SubscriptionId $subscriptionID `
-Name $HPName `
-Location $location `
-ValidationEnvironment:$true `
-HostPoolType $HPType `
-LoadBalancerType $HPLBType `
-MaxSessionLimit $Maxusers `
-PreferredAppGroupType $AppGrpType `
-Tag:@{"Billing" = "IT"; "Department" = "IT"; "Location" = "AUS-East" } `
-ErrorAction STOP
}
catch
{
Write-Host $_.Exception.Message -ForegroundColor Yellow
}
# Create the Application Group for the Remote App Host Pool
try
{
write-host "Create the Application Group for the Remote App Host Pool"
$CreateAppGroupRA = New-AzWvdApplicationGroup -ResourceGroupName $rgName `
-Name $AppGrpName `
-Location $location `
-HostPoolArmPath $DeployHPWRA.Id `
-ApplicationGroupType 'RemoteApp' `
-ErrorAction STOP
}
catch
{
Write-Host $_.Exception.Message -ForegroundColor Yellow
}
# Create the Workspace for the RemoteApp Host Pool
try
{
write-host "Create the Workspace for the RemoteApp Host Pool"
$CreateWorkspaceRA = New-AzWvdWorkspace -ResourceGroupName $rgName `
-Name $Wrkspace `
-Location $location `
-ApplicationGroupReference $CreateAppGroupRA.Id `
-ErrorAction STOP
}
catch
{
Write-Host $_.Exception.Message -ForegroundColor Yellow
}
# Assign the AAD group (Object ID) to the Application Group
try
{
write-host "Assigning the AAD Group to the Application Group"
$AssignAADGrpAG = New-AzRoleAssignment -ObjectId $AADGroupObjId `
-RoleDefinitionName "Desktop Virtualization User" `
-ResourceName $CreateAppGroupRA.Name `
-ResourceGroupName $rgName `
-ResourceType 'Microsoft.DesktopVirtualization/applicationGroups' `
-ErrorAction STOP
}
catch
{
Write-Host $_.Exception.Message -ForegroundColor Yellow
}
Next Steps on the Host Pool
Now that the host pool, application group and workspaces are ready following are the next steps involved:
Generate a registration token
Add the session host virtual machine to the host pool
Create Applications within the Application Group. You can create multiple Applications in single AG or 1 AG per Application.
I hope you will find this helpful information for deploying a host pools, application group and workspaces within Azure Virtual Desktop. If you want to see a Powershell version of the applications & session host activities, leave me a comment below or on my socials. Please let me know if I have missed any steps or details, and I will be happy to update the post.
Recent Comments