🚀 Executive Summary

TL;DR: Terraform’s speed, AVD’s multi-session nature, and Intune’s user-triggered enrollment create a race condition preventing automatic Intune enrollment for AVD multi-session hosts. Solutions involve using post-deployment scripts, integrated Terraform Custom Script Extensions, or pre-staging enrollment in golden images to force device registration and trigger the enrollment task.

🎯 Key Takeaways

  • Terraform’s rapid provisioning of AVD multi-session hosts often creates a race condition with Intune’s user-triggered or GPO-dependent enrollment, leaving VMs unmanaged.
  • The core fix involves manually triggering device registration (`dsregcmd /join`) and the specific Intune enrollment scheduled task (`Schedule created by enrollment client for automatically enrolling in MDM from AAD`) on the session host.
  • Integrating a `CustomScriptExtension` in Terraform, with a `depends_on` clause on the `JsonADDomainExtension`, is the recommended automated solution for reliable Intune enrollment post-domain join.
  • For large-scale, mature environments, building a ‘golden image’ with pre-staged Intune enrollment using tools like Packer offers the most robust and decoupled approach.
  • Multi-session hosts lack a primary user, which complicates Intune’s device-centric model and necessitates explicit enrollment triggers rather than relying on standard user logon processes.

AVD multi‑session + Intune enrollment via Terraform — how are you doing it?

Struggling to automate AVD multi-session Intune enrollment with Terraform? This guide breaks down the root cause and provides three real-world, battle-tested solutions to solve the hybrid join and enrollment race condition for good.

AVD Multi-Session, Intune, and Terraform: A Survivor’s Guide

I remember it was a Thursday afternoon. The finance team’s brand new AVD host pool, `avd-finance-prod-pool`, was supposed to go live at 3 PM. My Terraform plan ran beautifully, everything looked green, the VMs were up and joined to the domain. But in the Intune portal? Crickets. Not a single one of the new session hosts showed up. The pipeline was flashing red, the project manager was pacing, and I was staring at a problem that isn’t in any official manual. That’s when I learned a hard lesson about the three-way brawl between Terraform’s declarative speed, AVD’s multi-session nature, and Intune’s enrollment quirks.

So, Why Does This Keep Breaking?

At its core, this is a classic race condition and context problem. Here’s the deal:

  • Terraform is fast. Too fast. It provisions the VM, joins it to the domain via the JSONADDomainExtension, and then considers its job done. It doesn’t stick around to wait for user logons or Group Policy syncs.
  • Intune enrollment is often user-triggered. The most common method, “Azure AD automatic enrollment,” relies on a user with the right license logging in to kick off the process. But on a fresh multi-session host, no user has logged in yet.
  • Multi-session hosts have no primary user. Unlike a standard Windows 10/11 machine assigned to one person, AVD session hosts are cattle, not pets. This confuses Intune’s device-centric model, which often ties a device to a primary user during enrollment.

The result is that Terraform builds a perfectly good VM that sits there, domain-joined, but completely invisible to Intune because the enrollment trigger never fires. We need to force the issue.

The Fixes: From Duct Tape to a New Engine

I’ve seen this problem burn teams a few times now. Here are the three main ways we at TechResolve tackle it, depending on the situation.

Solution 1: The “Get It Done Now” Fix (Post-Deployment Script)

This is your emergency-fix, the “we need this working an hour ago” approach. You’ve already run your Terraform, the VMs are up, but they aren’t in Intune. You can use a Custom Script Extension via the Azure Portal/CLI to push a script to the existing VMs.

The script’s job is to manually kickstart the enrollment process that GPO normally would. It forces the device registration and then triggers the specific scheduled task for Intune enrollment.

Here’s a PowerShell snippet you can run through the extension:

# PowerShell Script: force-intune-enroll.ps1

# Ensure we're running as System for permissions
Write-Host "Starting Intune Enrollment script..."

# Step 1: Force device registration check-in. This is crucial for Hybrid Join.
# The /join command is idempotent; it will just confirm the join if already completed.
Write-Host "Running dsregcmd /join..."
Start-Process -FilePath "C:\Windows\System32\dsregcmd.exe" -ArgumentList "/join" -Wait -NoNewWindow

# Give it a moment to settle
Start-Sleep -Seconds 30

# Step 2: Manually trigger the Intune enrollment scheduled task.
# This is the task created by the "Enable automatic MDM enrollment" GPO.
Write-Host "Triggering the Intune enrollment scheduled task..."
$schedule = Get-ScheduledTask -TaskPath "\Microsoft\Windows\EnterpriseMgmt\" -TaskName "Schedule created by enrollment client for automatically enrolling in MDM from AAD"
if ($schedule) {
    Start-ScheduledTask -InputObject $schedule
    Write-Host "Task triggered successfully."
} else {
    Write-Error "Could not find the Intune enrollment scheduled task. Is the GPO applied correctly?"
}

Write-Host "Script finished."

Darian’s Take: This is a lifesaver in a pinch, but it’s not automation. It’s a manual reaction. If you find yourself doing this more than once, you need to move to Solution 2.

Solution 2: The “Do It Right” Terraform Fix (Integrated Extension)

This is the proper, idempotent, Infrastructure-as-Code solution. Instead of running a script after the fact, we integrate it directly into our Terraform configuration for the AVD session hosts. We’ll use the azurerm_virtual_machine_extension resource to run the same kind of logic from Solution 1, but we’ll make it part of the deployment.

The key here is using depends_on to ensure this script only runs after the VM has successfully joined the domain.

Here’s what your Terraform might look like:

# In your main.tf or avd-session-host.tf

resource "azurerm_windows_virtual_machine" "session_host" {
  # ... all your vm config like name, size, network_interface_ids, etc. ...
  name                = "avd-prod-sh-01"
  resource_group_name = azurerm_resource_group.rg.name
  location            = azurerm_resource_group.rg.location
  # ...
}

# This is the extension that joins the VM to your Active Directory domain
resource "azurerm_virtual_machine_extension" "domain_join" {
  name                 = "${azurerm_windows_virtual_machine.session_host.name}-domainjoin"
  virtual_machine_id   = azurerm_windows_virtual_machine.session_host.id
  publisher            = "Microsoft.Compute"
  type                 = "JsonADDomainExtension"
  type_handler_version = "1.3"

  # ... settings for domain join ...
}

# THIS IS THE MAGIC: The Intune Enrollment Extension
resource "azurerm_virtual_machine_extension" "intune_enroll" {
  name                 = "${azurerm_windows_virtual_machine.session_host.name}-intune-enroll"
  virtual_machine_id   = azurerm_windows_virtual_machine.session_host.id
  publisher            = "Microsoft.Compute"
  type                 = "CustomScriptExtension"
  type_handler_version = "1.10"

  settings = <<SETTINGS
    {
        "script": "${base64encode(file("./scripts/force-intune-enroll.ps1"))}"
    }
  SETTINGS

  # CRITICAL: This ensures we don't try to enroll before the machine is domain-joined
  depends_on = [
    azurerm_virtual_machine_extension.domain_join
  ]
}

Pro Tip: Your PowerShell script for this should have some retry logic or longer sleeps. Azure networking can sometimes take a moment to propagate, and the dsregcmd command might fail on the first try. A simple loop checking the output can make this far more resilient.

Solution 3: The “Golden Image” Fix (The Packer Approach)

For large-scale, mature environments, the most robust solution is to fix the problem at the source: the image itself. Instead of using a standard marketplace image and configuring it on the fly, you build a “golden image” using a tool like Packer.

During the Packer image build process, you can systematically install all your required software, apply security baselines, and—most importantly—pre-stage the Intune enrollment. You can use the same script logic from before, but bake it into the image creation sequence. The benefit is that when Terraform provisions a VM from this image, it comes online already aware of what it needs to do.

This approach decouples the application/configuration logic from the infrastructure provisioning logic, which is a much cleaner architecture.

Solution Comparison
Approach Complexity Reliability Best For
1. Post-Deployment Script Low Low (Manual) Emergency fixes, one-off deployments.
2. Terraform Extension Medium High Most standard, automated deployments. The sweet spot.
3. Golden Image (Packer) High Very High Large, mature environments needing standardization and speed.

Final Thoughts

The AVD and Intune enrollment dance is a classic example of where declarative IaC meets the messy reality of stateful system configuration. There’s no single “right” answer, only the best answer for your team’s maturity and scale. For most of us, the integrated Terraform extension (Solution 2) is the perfect balance. It keeps your logic in code, it’s repeatable, and it solves the root race condition.

Hopefully, this saves you from a panic-filled Thursday afternoon like the one I had. Now go get that pipeline green.

Darian Vance - Lead Cloud Architect

Darian Vance

Lead Cloud Architect & DevOps Strategist

With over 12 years in system architecture and automation, Darian specializes in simplifying complex cloud infrastructures. An advocate for open-source solutions, he founded TechResolve to provide engineers with actionable, battle-tested troubleshooting guides and robust software alternatives.


🤖 Frequently Asked Questions

❓ Why do AVD multi-session hosts provisioned by Terraform often fail to enroll in Intune automatically?

This occurs due to a race condition: Terraform provisions VMs and joins them to the domain too quickly, while Intune enrollment is typically user-triggered or relies on GPO syncs that haven’t occurred, and multi-session hosts lack a primary user to initiate the process.

❓ How does the integrated Terraform extension solution compare to using a golden image for AVD Intune enrollment?

The integrated Terraform extension (Solution 2) is a balanced, automated approach for most deployments, running a script post-domain join. The golden image (Solution 3, via Packer) is more robust for large, mature environments, pre-staging enrollment during image creation, which decouples configuration from infrastructure provisioning.

❓ What is a critical dependency to manage when automating Intune enrollment for AVD hosts with Terraform extensions?

It is critical to ensure the Intune enrollment `CustomScriptExtension` runs only after the VM has successfully joined the domain. This is managed in Terraform using the `depends_on` argument, linking the enrollment extension to the `JsonADDomainExtension`.

Leave a Reply

Discover more from TechResolve - SaaS Troubleshooting & Software Alternatives

Subscribe now to keep reading and get access to the full archive.

Continue reading