Tag Archives: Azure Virtual Desktop

Azure Virtual Desktop with Terraform – Pooled, Personal, RemoteApp + Monitoring, Dashboards and Scaling – All-in-one

16 Mar

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.

Repo – askaresh/avd_terraform: Azure Virtual Desktop and Terraform. Everything you need in a single repo!

What we are building

This repo deploys a complete AVD environment and supports four deployment types:

  • pooled_desktop (shared desktops)
  • personal_desktop (dedicated 1:1 desktops)
  • pooled_remoteapp (published apps, shared hosts)
  • personal_remoteapp (published apps, dedicated hosts)

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).

Deployment typeHost pool typeApp group typeNotes
pooled_desktopPooledDesktopScaling supported
personal_desktopPersonalDesktopStart VM on connect behavior differs
pooled_remoteappPooledRemoteAppScaling supported
personal_remoteappPersonalRemoteAppDedicated apps per user

Pre-requisites

  • Terraform + required providers (azurerm/azuread/random/azapi)
  • An Azure subscription with AVD enabled
  • Object IDs for users/groups/service principals that should be granted access (security_principal_object_ids)

Session host default image SKU in the repo is Windows 11 multi-session + M365 (win11-24h2-avd-m365).

Deployment steps (what I run)

Clone the repo

git clone https://github.com/askaresh/avd_terraform
cd avd_terraform

Pick a deployment file (.tfvars)

The repo includes pre-configured examples for different deployment types (dev/prod, pooled/personal, desktop/RemoteApp), plus enhanced options.

Examples:

  • dev-pooled-desktop.tfvars
  • dev-personal-desktop.tfvars
  • dev-pooled-remoteapp.tfvars
  • prod-personal-remoteapp.tfvars
  • dev-pooled-desktop-enhanced-scaling.tfvars

Update the must-have variables

At minimum, set:

  • security_principal_object_ids (who gets access)
  • admin_password (local admin password for session hosts)

RemoteApp tip: for RemoteApp deployment types, you’ll also define published_applications (apps to publish).

<!-- INSERT SCREENSHOT: tfvars edits (redact secrets) -->

Authentication (repo way: .env + set-auth.ps1)

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:
.envset-auth.ps1ARM_* 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.

Thanks,
Aresh Sarkari

Build an AVD “Golden Image” with Terraform + Azure VM Image Builder (and publish to Azure Compute Gallery)

9 Feb

If you’ve been running Azure Virtual Desktop for a while, you already know the pain: keeping session hosts consistent is easy until you start chasing app versions, Windows Updates, Teams changes, and the “one missing dependency” that breaks someone’s day.

Microsoft calls this a golden image approach: bake your base OS + apps + config once, then roll it out to session hosts consistently. (Microsoft Learn)

In this post, I’m sharing a Terraform-based approach that builds a custom AVD image using Azure VM Image Builder (AIB) and publishes it to Azure Compute Gallery (ACG) so you can consume it in your AVD host pool automation. (Microsoft Learn)

Repo: avd-terraform-customimage (GitHub)

What we are building

High level flow:

  1. Terraform provisions the Image Builder “plumbing” (identity, storage, template, etc.)
  2. Azure VM Image Builder spins up a temporary build VM
  3. The build VM runs updates + optimizations + app installs (your choice)
  4. The final image gets published into Azure Compute Gallery
  5. You use that gallery image version when creating/re-imaging AVD session hosts (GitHub)

This repo uses a Windows 11 multi-session AVD + Microsoft 365 marketplace image as the starting point (so M365 and Teams are already there), then layers your customizations on top. (GitHub)

Why I like this approach

A few things in this repo are intentionally “enterprise friendly”:

  • No public script URLs / no long-lived SAS tokens: scripts are stored in a private blob container and downloaded using the Image Builder user-assigned managed identity. (GitHub)
  • SHA256 integrity checks: if someone tampers with your script artifacts, the build fails. (GitHub)
  • Deterministic image versioning: default version format is YYYY.MM.DD, with an option to override. (GitHub)
  • Optional optimization steps like Virtual Desktop Optimization Tool (VDOT) and FSLogix config (handy in AVD land). (GitHub)

Architecture

Pre-requisites

  • Azure subscription + permissions to create IAM assignments, storage, and image resources.
  • Terraform installed (repo expects Terraform + AzureRM provider versions aligned with the README). (GitHub)
  • Azure VM Image Builder basics: it supports starting from Marketplace/custom images and publishing to Azure Compute Gallery. (Microsoft Learn)

Deployment steps (what I run)

1. Clone the repo

git clone https://github.com/askaresh/avd-terraform-customimage
cd avd-terraform-customimage

2. Configure variables

Start from the example and edit what you need (region, naming, feature toggles, etc.). (GitHub)

3. Authenticate

Use your preferred method (interactive az login locally, or service principal in CI/CD). The repo also calls out using .env locally and keeping secrets out of Git. (GitHub). The PowerShell script set-auth.ps1 calls these environment variables.

4. Terraform init / plan / apply

terraform init -upgrade
terraform plan -out image.tfplan
terraform apply image.tfplan

Important note: Terraform provisions the Image Builder template, but doesn’t wait for the build to finish. (GitHub)

5. Trigger + monitor the image build

You can monitor runs in the portal, or with Azure CLI:

  • az image builder show-runs shows run outputs for the template. (Microsoft Learn)

Once the build completes, you’ll see a new image version in Azure Compute Gallery. ACG is designed to manage/share images and versions cleanly across environments. (Microsoft Learn)

Customizing applications (the fun part)

This repo supports multiple install strategies with fallback (so you’re not stuck when winget is blocked in some environments):

  • winget (default)
  • direct download (fallback)
  • offline packages from storage
  • psadt for complex enterprise apps (GitHub)

Gotchas / things to keep in mind

  • Build time: expect ~45–75 minutes depending on Windows Updates and app installs. (GitHub)
  • Template updates: Image Builder doesn’t really do “in-place template updates” the way you’d hope—plan on the “replace template” pattern when you change major parts. (Microsoft Learn)
  • Networking: if you go private networking / locked-down egress, make sure your update/app endpoints are reachable (or use offline packages). (GitHub)
  • AVD consumption: this pipeline publishes the image version—your AVD session host deployment should point to that exact gallery version. (GitHub)

Where this fits in your AVD build

If you already deploy host pools/workspaces/scaling plans via Terraform, think of this repo as the image factory that feeds your host pool automation. (Build image → publish to gallery → deploy session hosts from that version.) (GitHub)

Wrap up

That’s it — repeatable AVD image builds, versioned in Azure Compute Gallery, with a setup that avoids public script endpoints and keeps things predictable. (GitHub)

I hope you find this helpful information for building and maintaining an AVD golden image using Terraform + Azure VM Image Builder. If I have missed any steps or details, I will be happy to update the post.

Thanks,
Aresh Sarkari

Optimize Your Azure Costs with VM Hibernation – Cost saving for Azure Virtual Desktop

3 Jun

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

CategoryDescription
Virtual DesktopsIf 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 EnvironmentsIf 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 VMsIf 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

Hibernate is only available for personal host pools. For more information, see Hibernation in virtual machines.

Configuring Scaling Plans

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.

Following are the list of helpful links:

DescriptionLinks
Hibernation support now available for Azure Virtual DesktopHibernation support now available for Azure Virtual Desktop | Azure Virtual Desktop Blog (microsoft.com)
Cost Optimization for General Purpose VMs using Hibernation now Generally AvailableCost Optimization for General Purpose VMs using Hibernation now Generally Available – Microsoft Community Hub

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?

Thanks,
Aresh Sarkari

App Volumes for Azure Virtual Desktop and Windows 365 Cloud PC

4 Dec

Recently the Apps on demand was released, essentially this an Azure market place offering from VMware aka Broadcom. You can deploy App Volumes manager virtual machine that runs Windows Server 2022 with App Volumes Manager pre-installed. Additionally, deploy Azure file share and database configuration.

What is an AppStack

An AppStack in VMware App Volumes is a virtual container that contains a set of applications packaged together. It’s used in virtual desktop environments such as (AVD and Windows 365) to dynamically deliver these applications to users. AppStacks are read-only, can be attached to user sessions transparently, and appear as if the applications are natively installed. They offer efficient application management, allowing for easy updates and maintenance without impacting the underlying system. This approach simplifies application lifecycle management and enhances user experience in virtualized desktop scenarios.

Prerequisites

You’ll need the following things ready before you can rollout App Volumes:

  • Azure Subscription: Have an active Microsoft Azure subscription. Azure Documentation
  • Azure Permissions: Ensure permissions to create virtual machines for App Volumes Manager, storage accounts, and file shares on Azure.
  • Access to On-Prem Active Directory: Required for identity and access control.
  • Azure Virtual Desktop Access: For remote connectivity.
  • Azure AD Connect: Install Azure AD Connect on the Active Directory Domain Controller.
  • Active Directory Configurations:
  • App Volumes License File: Download and install the App Volumes license file. App Volumes in Azure follows a Bring Your Own License model. VMware App Volumes Product Download Page
  • Download App Volumes Agent:
  • System Requirements Awareness: Familiarize yourself with the system requirements for using App Volumes. VMware App Volumes 4 Installation Guide
  • SQL Server Database for Production: For a production environment, ensure the availability of an existing SQL server database, as the default SQL Server Express Database is not recommended for production use.

My party came crashing on the requirement of Active Directory (AD) Domain Controller. In my entire Azure, Azure Virtual Desktop, Windows 365 Cloud PC and Microsoft Intune I have made deliberate choice to keep it with mordern management\authentication and follow the Microsoft best practice guidance. Though I am not able to complete the entire configurations due to AD I decided to share whatever configurations I manage to perform to deploy the AV Manager appliance within my Azure subscription for this blog post.

Starting in the Azure Portal

  • Access the Marketplace: Begin by navigating to the Microsoft Azure portal and clicking on ‘Marketplace’.
  • Find VMware App Volumes: Search for the ‘VMware App Volumes’ offer and click on it.
  • Select App Volumes Version: In the VMware App Volumes Azure Application page, choose the desired version from the Plan drop-down box.
  • Click ‘Create’

Deploy the Virtual Machine Appliance

  • Details: Select your Subscription and Resource group. You can also create a new resource group if needed.
  • Instance Details: Choose the Region and enter the name for your virtual machine. Set up the credentials for the local administrator. (#TIP – Ensure you deploy the Appliance where the session host pools are located)
  • Public IP Address: A new public IP address is created by default, but you can opt for an existing one or none at all. (#TIP – This allows accessing the App Volumes Manager over the internet )
  • DNS Prefix: If using a public IP address, consider creating a DNS prefix for easier access. (#TIP – Pick a friendly name I decided to leave it as defaults)
  • By default, a new virtual network and subnet are created. However, in my situation, I want to leverage the existing VNET and Subnet.
  • Database Selection: Choose between a Local SQL Server Express Database (not recommended for production) or a Remote SQL Server database. Enter the necessary database connection details. (#TIP – Explore for production use evaluate using the Azure SQL DB as its a managed SQL instance and you will not have the overhead of managing and maintaining a SQL Server)
  • File Share Configuration: Decide if you want Azure Marketplace to automatically provision storage with file shares or use existing storage. Configure accordingly. (#TIP – Same if you have Azure Dell or NetApp Files storage you can leverage that or using Azure storage account)
  • Tags: Add any desired tags. (#TIP – Ensure you add the tags as it will help you later during Billing and Automation)
  • Review and Launch Deployment: Review your setting Click ‘Create’ to start the deployment process.
  • Navigate to App Volumes Manager Admin UI: This is where you’ll configure Active Directory and other settings.
  • Configure Storage: If you opted out of automatic storage provisioning, manually configure the storage in the App Volumes Manager admin UI.
  • When you navigate to the Storage the newly created SMB shares are listed for templates and AppStacks stroage.

I will come back to this blog post once I learn more about the “No AD” mode as this will enable me to further integrate App Volumes into my Azure Virtual Desktop and Windows 365 Cloud PCs.

Resources deployed within the Azure Resource Group

Following resources are deployed within your Azure Resource Group for App Volumes

Whislist

I would like to see the following enhacements at a later day:

  • VMware aka Broadcom should evlaute a complete SaaS offering taking it to the next level instead of deploying an appliance and required to do alot of configuration. Just give me “as a Service” and the only responsiblity is to create/update and mange the applications with Entra ID Groups.
  • Microsoft Entra ID integration is a must

I hope you will find this helpful information for getting started with App Volumes for Windows 365 Cloud PC and Azure Virtual Desktop. Please let me know if I have missed any steps or details, and I will be happy to update the post.

Thanks,
Aresh Sarkari

Azure Virtual Desktop – Terraform – Create a Scaling Plan for Pooled Host Pools (Part 4)

27 Oct

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.

Table of Content

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, 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:

# PowerShell
$env:ARM_CLIENT_ID = "9e453b62-0000-0000-0000-00000006e1ac"
$env:ARM_CLIENT_SECRET = "Z318Q~00000000000000000000000000000000_"
$env:ARM_TENANT_ID = "a02e602c-0000-000-0000-0e0000008bba61"
$env:ARM_SUBSCRIPTION_ID = "7b051460-00000-00000-00000-000000ecb1"
  • 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)

+---Config-AVD-ScalingPlans
|   |   main.tf
|   |   output.tf
|   |   providers.tf
|   |   variables.tf

Configure AVD – ScalingPlans – Providers.tf

Create a file named providers.tf and insert the following code:

terraform {
  required_providers {
    azurerm = {
      source  = "hashicorp/azurerm"
      version = "3.76.0"
    }
    azuread = {
      source = "hashicorp/azuread"
    }
  }
}

provider "azurerm" {
  features {}
}

Configure AVD – ScalingPlans – main.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"

The intention here is to get you quickly started with Terraform on Azure Virtual Desktop Solution:

DescriptionLinks
Create an autoscale scaling plan for Azure Virtual DesktopCreate an autoscale scaling plan for Azure Virtual Desktop | Microsoft Learn
Setting up your computer to get started with Terrafor using PowershellInstall Terraform on Windows with Azure PowerShell
AVD Configure Azure Virtual Desktophttps://learn.microsoft.com/en-us/azure/developer/terraform/configure-azure-virtual-desktop
Terraform Learninghttps://youtube.com/playlist?list=PLLc2nQDXYMHowSZ4Lkq2jnZ0gsJL3ArAw

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.

Thanks,
Aresh Sarkari

Watermarking & Session Capture Protection in Azure Virtual Desktop using Microsoft Intune and Azure AD Joined devices

31 Aug

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.
  • Admin Access: You need administrative access to the Azure portal (https://portal.azure.com) and Microsoft Intune (https://intune.microsoft.com).
  • 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:

  1. 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.
  2. Select the relevant subscription, resource group, host pool and time range, then select the Connection Diagnostics tab.
  3. In the section Success rate of (re)establishing a connection (% of connections), there’s a list of all connections showing First attemptConnection IdUser, 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.

Thanks,
Aresh Sarkari

Azure Virtual Desktop – Terraform – Create a Host Pool, Desktop Application Group and Workspace for Pooled Remote App aka Published Applications (Part 3)

15 May

In the previous blog post we look at creating the Personal Desktop (1×1 mapping) and Pooled Desktop (1 x Many) using Terraform Azure Virtual Desktop – Terraform – Create a Host Pool, Desktop Application Group and Workspace for Personal Desktop (Part 1) | AskAresh and Azure Virtual Desktop – Terraform – Create a Host Pool, Desktop Application Group and Workspace for Pooled Desktop (Part 2). In this blog post series I am going to demonstrate how to create the AVD Host Pool, Application Group and Workspace using Terraform for Pooled Remote App aka Published Applications (1xMany)

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:

# PowerShell
$env:ARM_CLIENT_ID = "9e453b62-0000-0000-0000-00000006e1ac"
$env:ARM_CLIENT_SECRET = "Z318Q~00000000000000000000000000000000_"
$env:ARM_TENANT_ID = "a02e602c-0000-000-0000-0e0000008bba61"
$env:ARM_SUBSCRIPTION_ID = "7b051460-00000-00000-00000-000000ecb1"
  • 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)

+---Config-AVD-Pooled-RemoteApp
|   |   main.tf
|   |   output.tf
|   |   providers.tf
|   |   variables.tf

Configure AVD – Pooled RemoteApp – Providers.tf

Create a file named providers.tf and insert the following code:

terraform {
  required_providers {
    azurerm = {
      source  = "hashicorp/azurerm"
      version = "3.49.0"
    }
    azuread = {
      source = "hashicorp/azuread"
    }
  }
}

provider "azurerm" {
  features {}
}

Configure AVD – Pooled RemoteApp – main.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

The intention here is to get you quickly started with Terraform on Azure Virtual Desktop Solution:

DescriptionLinks
Setting up your computer to get started with Terrafor using PowershellInstall Terraform on Windows with Azure PowerShell
AVD Configure Azure Virtual Desktophttps://learn.microsoft.com/en-us/azure/developer/terraform/configure-azure-virtual-desktop
Terraform Learninghttps://youtube.com/playlist?list=PLLc2nQDXYMHowSZ4Lkq2jnZ0gsJL3ArAw

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.

Thanks,
Aresh Sarkari

Azure Virtual Desktop – Terraform – Create a Host Pool, Desktop Application Group and Workspace for Pooled Desktop (Part 2)

15 May

In the previous blog post we look at creating the Personal Desktop (1×1 mapping) using Terraform. Azure Virtual Desktop – Terraform – Create a Host Pool, Desktop Application Group and Workspace for Personal Desktop (Part 1) | AskAresh. In this blog post series I am going to demonstrate how to create the AVD Host Pool, Application Group and Workspace using Terraform for Pooled Desktop (1xMany)

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:

# PowerShell
$env:ARM_CLIENT_ID = "9e453b62-0000-0000-0000-00000006e1ac"
$env:ARM_CLIENT_SECRET = "Z318Q~00000000000000000000000000000000_"
$env:ARM_TENANT_ID = "a02e602c-0000-000-0000-0e0000008bba61"
$env:ARM_SUBSCRIPTION_ID = "7b051460-00000-00000-00000-000000ecb1"
  • 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)

+---Config-AVD-Pooled-Desktop
|   |   main.tf
|   |   output.tf
|   |   providers.tf
|   |   variables.tf

Configure AVD – Pooled Desktop Pool – Providers.tf

Create a file named providers.tf and insert the following code:

terraform {
  required_providers {
    azurerm = {
      source  = "hashicorp/azurerm"
      version = "3.49.0"
    }
    azuread = {
      source = "hashicorp/azuread"
    }
  }
}

provider "azurerm" {
  features {}
}

Configure AVD – Pooled Desktop Pool – main.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

The intention here is to get you quickly started with Terraform on Azure Virtual Desktop Solution:

DescriptionLinks
Setting up your computer to get started with Terrafor using PowershellInstall Terraform on Windows with Azure PowerShell
AVD Configure Azure Virtual Desktophttps://learn.microsoft.com/en-us/azure/developer/terraform/configure-azure-virtual-desktop
Terraform Learninghttps://youtube.com/playlist?list=PLLc2nQDXYMHowSZ4Lkq2jnZ0gsJL3ArAw

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.

Thanks,
Aresh Sarkari

Azure Virtual Desktop – Terraform – Create a Host Pool, Desktop Application Group and Workspace for Personal Desktop (Part 1)

8 May

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)
  • Azure Virtual Desktop – Remote App (Multi-Session Application aka Published Apps)

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.

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

# PowerShell
$env:ARM_CLIENT_ID = "9e453b62-0000-0000-0000-00000006e1ac"
$env:ARM_CLIENT_SECRET = "Z318Q~00000000000000000000000000000000_"
$env:ARM_TENANT_ID = "a02e602c-0000-000-0000-0e0000008bba61"
$env:ARM_SUBSCRIPTION_ID = "7b051460-00000-00000-00000-000000ecb1"
  • 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)

+---Config-AVD-Personal-Desktop
|   |   main.tf
|   |   output.tf
|   |   providers.tf
|   |   variables.tf

Note – I am not demonstrating how to install Terraform as it’s dead simple, and the information is available everywhere.

Configure AVD – Personal Desktop Pool – Providers.tf

Create a file named providers.tf and insert the following code. (We are using the AzureRM and AzureAD providers)

terraform {
  required_providers {
    azurerm = {
      source  = "hashicorp/azurerm"
      version = "3.49.0"
    }
    azuread = {
      source = "hashicorp/azuread"
    }
  }
}

provider "azurerm" {
  features {}
}

Configure AVD – Personal Desktop Pool – main.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

The intention here is to get you quickly started with Terraform on Azure Virtual Desktop Solution:

DescriptionLinks
Setting up your computer to get started with Terrafor using PowershellInstall Terraform on Windows with Azure PowerShell
AVD Configure Azure Virtual Desktophttps://learn.microsoft.com/en-us/azure/developer/terraform/configure-azure-virtual-desktop
Terraform Learninghttps://youtube.com/playlist?list=PLLc2nQDXYMHowSZ4Lkq2jnZ0gsJL3ArAw

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.

Thanks,
Aresh Sarkari

Disable Search Highlights on Windows 365 Cloud PC and Azure Virtual Desktop using Microsoft Intune

24 Feb

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:

  • Login to the MEM Portal – https://endpoint.microsoft.com/
  • Select Devices > Configuration Profiles > Create Profile.
  • For Platform, select Windows 10 and later.
  • 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.

Useful LinksCredits
Search – CSP Policy – https://learn.microsoft.com/en-us/windows/client-management/mdm/policy-csp-searchMicrosoft
5 Unique Ways to Disable Search Highlights on Windows 11Prajwal Desai
Disable Enable Search Highlights in Windows 11Jitesh Kumar

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.

Thanks,
Aresh Sarkari