🚀 Executive Summary
TL;DR: Azure Virtual Desktop multi-session hosts often fail Intune enrollment when deployed via Terraform due to a mismatch between Terraform’s declarative infrastructure provisioning and Intune’s event-driven, user-centric enrollment process. The article provides three solutions: a quick custom script extension, a robust golden image strategy using Packer, and an enterprise orchestrator approach for complex environments.
🎯 Key Takeaways
- The core problem stems from Terraform’s declarative nature building infrastructure without user context, while Intune enrollment (especially Hybrid Azure AD Join) is an event-driven, user-centric process often triggered by GPO on user logon.
- Implementing a “Golden Image” strategy using Packer is the most robust and scalable method, baking the scheduled task for `deviceenroller.exe` directly into the image before deployment.
- For immediate fixes or smaller environments, a custom script extension can force enrollment post-VM build by creating a scheduled task to run `deviceenroller.exe /c /AutoEnrollMDM` with SYSTEM privileges.
Struggling to get Azure Virtual Desktop multi-session hosts to enroll in Intune via Terraform? This guide breaks down why it fails and provides three real-world solutions, from a quick script fix to a robust golden image strategy.
Taming the Beast: A Real-World Guide to AVD Multi-Session Intune Enrollment with Terraform
I still remember the feeling. It was 9 PM on a Thursday, the night before the big finance department go-live. The Terraform plan was clean, the `terraform apply` was a sea of green, and the AVD host pool `avd-hp-finance-prod` was up and running. But my Intune portal looked like a Christmas tree gone wrong—all red. Not a single session host had enrolled. The project manager was pinging me on Teams with “any update??” every five minutes because none of the test users could access apps protected by Conditional Access. Terraform said the infrastructure was perfect, but reality said we were dead in the water. That night, I learned a hard lesson about the gap between infrastructure deployment and device configuration.
So, Why Is This So Hard?
Let’s get one thing straight: this isn’t a bug. It’s a classic case of two different worlds colliding. Terraform is a declarative infrastructure tool. You tell it what you want—a VM, a network, a host pool. It builds that exact state and then walks away. Intune enrollment, on the other hand, is an event-driven, user-centric process. Specifically for Hybrid Azure AD Join, it’s designed to be triggered by a Group Policy that kicks in when a user logs on.
Here’s the core problem:
- Terraform builds the machine. The machine exists, but it has no user context yet.
- The Intune enrollment GPO is configured to trigger “using device credentials.”
- On a multi-session host, there is no single “primary user” to own the enrollment process in the same way as a personal laptop. The machine itself needs to enroll.
- The trigger for this machine-level enrollment can be missed or fail during the automated build process because no user is there to kick it off, and the initial machine startup window might close before the Azure AD Connect sync completes.
You’re left with a perfectly built but “unmanaged” session host that your security team will (rightfully) have a fit over. So, how do we bridge this gap? I’ve seen three main patterns in the wild.
Solution 1: The “Get It Done Yesterday” Script Extension
This is the quick and dirty fix. It’s what I ended up using that Thursday night to save the go-live. We use a custom script extension in Terraform to force the enrollment after the VM is built. It’s essentially a big hammer that tells the machine, “Hey, you! Enroll in Intune. Now.”
The PowerShell Script
First, you need a simple PowerShell script. This script creates a scheduled task that runs the `deviceenroller.exe` command. Why a scheduled task? Because it ensures the command runs with the necessary SYSTEM privileges and can re-run if it fails the first time.
# enroll-avd-intune.ps1
$Command = "C:\Windows\System32\deviceenroller.exe /c /AutoEnrollMDM"
$TaskName = "IntuneEnrollment"
$TaskDescription = "Force AVD Intune Enrollment"
$Trigger = New-ScheduledTaskTrigger -AtStartup
$User = "NT AUTHORITY\SYSTEM"
$Action = New-ScheduledTaskAction -Execute $Command
$Settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -StartWhenAvailable -ExecutionTimeLimit "PT0S"
# Check if the task already exists and remove it to ensure a clean slate
if (Get-ScheduledTask -TaskName $TaskName -ErrorAction SilentlyContinue) {
Unregister-ScheduledTask -TaskName $TaskName -Confirm:$false
}
Register-ScheduledTask -TaskName $TaskName -Description $TaskDescription -Action $Action -Trigger $Trigger -User $User -Settings $Settings -Force
Start-ScheduledTask -TaskName $TaskName
The Terraform Resource
Then, in your Terraform code where you define the `azurerm_windows_virtual_machine`, you add this extension resource. It will run your script after the VM is provisioned.
resource "azurerm_virtual_machine_extension" "intune_enrollment" {
name = "force-intune-enrollment"
virtual_machine_id = azurerm_windows_virtual_machine.avd_session_host.id
publisher = "Microsoft.Compute"
type = "CustomScriptExtension"
type_handler_version = "1.10"
settings = <<SETTINGS
{
"fileUris": ["https://yourstorageaccount.blob.core.windows.net/scripts/enroll-avd-intune.ps1"],
"commandToExecute": "powershell -ExecutionPolicy Unrestricted -File enroll-avd-intune.ps1"
}
SETTINGS
}
Warning: This method can feel a bit “hacky.” It relies on timing and can sometimes fail if there are delays in AAD sync. It’s great for emergencies or smaller environments, but it’s not my first choice for a long-term, scalable solution.
Solution 2: The “Do It Right” Golden Image
If you’re managing AVD at any scale, you should be using custom images. This is the most robust and reliable method. Instead of fixing the machine after it’s built, we bake the solution right into the “golden image” using a tool like Packer.
The philosophy is simple: create a perfect, pre-configured VM image that already has everything it needs. When Terraform deploys a VM from this image, it’s 99% of the way there. The enrollment happens naturally and reliably on first boot.
The Packer Process
- Start with a base image: Begin with a standard Windows 10/11 multi-session image from the Azure Marketplace.
- Install apps and agents: Use Packer’s provisioners to install your line-of-business apps, monitoring agents, and any required runtimes.
- Force Enrollment Prep: During the Packer build, run the same PowerShell script from Solution 1 to create the scheduled task. The key difference is that this task will exist in the image *before* the VM is ever created.
- Sysprep and Generalize: This is the most critical step. Packer will run Sysprep to generalize the image, making it a reusable template. When a new VM is created from this image, the Out-of-Box Experience (OOBE) runs, and our scheduled task will kick off the Intune enrollment perfectly.
Your Packer provisioner block might look something like this:
# Part of a packer.pkr.hcl file
source "azure-arm" "windows_multisession" {
# ... your source configuration ...
}
build {
sources = ["source.azure-arm.windows_multisession"]
provisioner "powershell" {
inline = [
"& { # Your script content here to create the scheduled task }",
"& C:/Windows/System32/Sysprep/sysprep.exe /generalize /oobe /quiet /quit"
]
}
# ... other build steps ...
}
Pro Tip: Use an Azure Compute Gallery (formerly Shared Image Gallery) to store and version your golden images. This gives you replication, versioning, and RBAC, making your AVD deployments far more manageable.
Solution 3: The “Enterprise Orchestrator” Approach
Sometimes, you’re in a highly complex environment where infrastructure and configuration are managed by different teams or tools. In this case, you can decouple the processes completely. This is what I call the “nuclear option” because it adds another moving part, but it’s incredibly powerful and explicit.
The workflow looks like this:
- Terraform runs: It provisions the AVD session hosts and, crucially, outputs the list of new VM resource IDs.
- An orchestrator triggers: This could be an Azure DevOps Pipeline, an Azure Automation Runbook, or even a Logic App that triggers on the completion of the Terraform run.
- The orchestrator connects and configures: The pipeline/runbook takes the list of VM IDs as input. It then uses the `Az.ConnectedMachine` PowerShell module or Azure CLI to remotely execute the enrollment script on each new host.
This approach gives you a clear separation of concerns and fantastic logging and retry capabilities. You know exactly when the infrastructure build ended and when the configuration step began.
| Pros of Orchestration | Cons of Orchestration |
|
|
Wrapping Up
There’s no one-size-fits-all answer. If you’re in a burning building like I was, the Script Extension will get you out. If you’re building a proper, scalable AVD environment, invest the time in a Golden Image pipeline. And if you’re in a large enterprise with complex workflows, the Orchestrator Approach provides the control and auditability you need.
The key is to understand why it fails—the gap between machine provisioning and user-triggered events. Once you see that, the solutions become clear. Now go get that host pool managed.
🤖 Frequently Asked Questions
❓ Why do Azure Virtual Desktop multi-session hosts fail to enroll in Intune when deployed with Terraform?
This failure occurs because Terraform provisions the VM without user context, while Intune enrollment, particularly for Hybrid Azure AD Join, is an event-driven process typically triggered by a Group Policy on user logon. Multi-session hosts lack a single primary user to initiate this, and the machine-level enrollment trigger can be missed during automated builds.
❓ How do the different Intune enrollment solutions for AVD multi-session hosts compare?
The “Script Extension” is a quick, hacky fix for emergencies. The “Golden Image” approach using Packer is the most robust and scalable, baking enrollment prep into the image. The “Enterprise Orchestrator” provides explicit control and auditability for complex environments by decoupling infrastructure deployment from configuration.
❓ What is a common implementation pitfall for AVD multi-session Intune enrollment via Terraform, and how can it be addressed?
A common pitfall is relying solely on default GPO triggers for enrollment, which often fail due to the lack of a primary user or timing issues during automated provisioning. This can be addressed by explicitly forcing enrollment via a scheduled task (using `deviceenroller.exe`) either through a custom script extension post-deployment or, more robustly, by pre-configuring this task within a golden image.
Leave a Reply