Azure Virtual Desktop: Cut Entra Hybrid Join Times from Hours to Minutes

G’day! If you’ve ever deployed Azure Virtual Desktop with Entra Hybrid Join, you’ll know the frustration. You deploy a perfectly good session host, everything provisions beautifully, domain join works, applications are installed, and then you sit there. Waiting. And waiting. And waiting some more for the bloody thing to show up as ‘Entra Hybrid Joined’ in Entra ID.

Quick disclaimer: Yes, I know. Entra ID Join is the present and the future; it’s cleaner, it’s what Microsoft wants us all to use. And in an ideal world, we’d all be there. But here’s reality: plenty of orgs are running Entra ID joined client devices whilst still needing AVD with Hybrid Join because they’ve got legacy apps that simply won’t play nice with pure Entra ID. It’s not ideal, but it’s the world we’re living in right now. So if you’re in that boat, this one’s for you.

Right, back to the problem. Sometimes, Hybrid Join takes 30 minutes. Sometimes 2 hours. Sometimes you come back the next day, and it’s finally decided to join. This isn’t just annoying, it’s a proper blocker when you’re trying to deploy hosts during a maintenance window and need them available immediately.

So I built something to fix it. With the right automation in place, I’m consistently seeing Entra Hybrid Join complete in just a few minutes instead of hours. That’s from kicking off the Bicep deployment to the device showing as fully joined in Entra ID. For example, I use Bicep to deploy session hosts, including the VM creation, nic, storage, domain join, session host registration and a few custom script extensions \ run commands, complete end-to-end to be fully ready, including being Entra Hybrid Joined, it averages approx 12 minutes! These scripts can also be easily adapted for other systems that use Hybrid join, e.g. Autopilot.

Let me show you how it works.

Why Is Hybrid Join So Slow Anyway?

The built-in Entra Hybrid Join process has multiple moving parts, and each one adds delays:

1. Group Policy Application
After the VM is domain joined, it needs Group Policy to apply the registry keys that tell it to attempt Hybrid Join (assuming your using targeted deployments otherwise it’ll go to the SCP location in AD). This can take 5 to 15 minutes, depending on your environment.

2. User Certificate Upload
Once the registry keys are in place, the device uploads its userCertificate attribute to the computer object in AD.

3. Entra Connect Sync (The Big One)
Here’s where it really hurts. Entra Connect only syncs objects to Entra ID after the userCertificate attribute is present on the computer object. By default, Entra Connect runs on a 30-minute sync cycle. If your device misses the sync window, you’re waiting another half hour.

4. The Built-in Scheduled Task
Windows has a scheduled task called “Automatic-Device-Join” that triggers dsregcmd /join. This task only runs:

  • On specific event log triggers (which may or may not fire reliably)
  • Every hour after an event triggers it, and only for a duration of a day
  • Or when any user logs on

If the timing doesn’t align, say the device syncs to Entra ID 5 minutes after the task last ran, you’re waiting another 55 minutes, or potentially until the next day. When all these delays stack up, 2 to 4 hours (or more) isn’t uncommon. In production, this is unacceptable.

The Solution: Three Scripts Working Together

Instead of passively waiting for things to happen, I built an active orchestration solution with three components:

  1. Service Account Setup Script – Creates a service account with the right permissions
  2. Entra Connect Server Scheduled Task – Actively monitors for new devices and triggers immediate sync
  3. Session Host Orchestration Script – Deployed via Bicep, manages the join process on each host

Let’s walk through each one.

Component 1: Service Account Setup

Before we automate anything, we need a service account with the right permissions to run the Entra Connect sync task.

What It Does

Creates a dedicated AD user account specifically for this automation, it’ll prompt you to which OU you want the service account created in AD, sets the password to never expire, optionally adds the service account to the ‘ADSyncOperators’ group on the Entra Connect Sync server, and stores the password in plain text in C:\scripts\AVD.

Why We Need It

The scheduled task on the Entra Connect server needs to run under an account with permissions to query AD and trigger sync cycles. Using a dedicated service account follows security best practices and makes it easy to track what the automation is doing.

The Script

Script looks like so when ran, note the optional sections:

And the account is created:

Component 2: Entra Connect Server Task

This is where the magic happens. Instead of waiting for the default 30-minute sync cycle, we actively monitor for new AVD computer objects and trigger an immediate sync when they’re created.

What It Does

The script runs every 5 minutes as a scheduled task and searches AD for computer objects matching your AVD naming pattern (like vm-sh-*) created in the last 15 minutes in the OU you specify (there is an option where you can state only scan the OU you input or include the subtrees too). It can optionally scan all domain controllers or specific ones (It’s recommended to use specific ones if in a large enterprise environment where AD replication may be an issue, use the DC’s that are hosted in Azure, if possible). When it finds new devices, it triggers an Entra Connect delta sync and logs everything for troubleshooting. The script can also add the ‘Log on as a batch job’ permissions if required. The solution creates the

Why This Works

Instead of waiting up to 30 minutes for the next scheduled sync, new devices are picked up within 5 minutes of being created in AD (Sync will only occur if the userCertificate attribute is defined; this is why it looks back 15 minutes to ensure this). This alone cuts down the sync time massively.

The Script

This is what it looks like when the script is ran, change the selections to suit you and your environment:

Log on as batch job permission added:

Scheduled task created:

Script created in c:\scripts\AVD:

Logs

All activity is logged to: C:\Scripts\AVD\Logs\AVDSync_[timestamp].log

Check these logs to see when new devices are detected, which DC they were found on, when sync is triggered, and any errors.

Component 3: Session Host Orchestration

This script runs on each AVD session host and actively manages the Hybrid Join process from the host side.

What It Does

The orchestration script creates registry keys telling Windows to attempt Entra Hybrid Join, then creates a scheduled task that runs every 2 minutes. On each run, it checks if the device is Hybrid Joined using dsregcmd /status. If not joined, it triggers the built-in “Automatic-Device-Join” task. Once Hybrid Join is complete (both DomainJoined: YES and AzureAdJoined: YES), it deletes itself. It’ll stop trying after 30 attempts (about 60 minutes) to prevent infinite loops.

Why This Works

Instead of waiting for the built-in scheduled task to run on its unpredictable schedule, we’re actively attempting join every 2 minutes. Combined with the Entra Connect server monitoring, this creates a tight feedback loop that completes Hybrid Join in minutes rather than hours.

The Script

Registry Keys

The script sets these registry values:

HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\CDJ\AAD

  • TenantId – Your Entra tenant GUID (passed through a bicep param)
  • TenantName – Your domain name (passed through a bicep param)

HKLM:\SOFTWARE\Policies\Microsoft\Windows\WorkplaceJoin

  • autoWorkplaceJoin – Set to 1
Logs

All activity is logged to: C:\Windows\Temp\HybridJoinRetry.log

These are real logs as an example of what to expect (as you can see it takes 2 minutes to complete Hybrid join once Entra has sync’d the device):

[2026-01-21 14:30:08] ========================================
[2026-01-21 14:30:08] Entra Hybrid Join Retry Task Started
[2026-01-21 14:30:08] ========================================
[2026-01-21 14:30:08] Checking Hybrid Join status…
[2026-01-21 14:30:17] AzureAdJoined: False
[2026-01-21 14:30:17] DomainJoined: True
[2026-01-21 14:30:17] Hybrid Join status: INCOMPLETE – Device is not yet Hybrid Joined
[2026-01-21 14:30:17] Attempting to trigger Hybrid Join…
[2026-01-21 14:30:18] Successfully triggered ‘Automatic-Device-Join’ scheduled task
[2026-01-21 14:30:18] Hybrid Join trigger attempted – task will retry in 2 minutes
[2026-01-21 14:30:18] ========================================
[2026-01-21 14:32:12] ========================================
[2026-01-21 14:32:12] Entra Hybrid Join Retry Task Started
[2026-01-21 14:32:12] ========================================
[2026-01-21 14:32:12] Checking Hybrid Join status…
[2026-01-21 14:32:13] AzureAdJoined: True
[2026-01-21 14:32:13] DomainJoined: True
[2026-01-21 14:32:13] Hybrid Join status: SUCCESS – Device is Hybrid Joined
[2026-01-21 14:32:13] Hybrid Join complete – removing retry scheduled task
[2026-01-21 14:32:15] Retry task removed successfully
[2026-01-21 14:32:15] Task execution completed – Hybrid Join verified

Deploying via Bicep

The real power comes from integrating this directly into your AVD session host deployment using Bicep.

How It Works

In your Bicep template, you include a Run Command extension that downloads the orchestration script from Azure Storage (with a SAS token), executes it with your tenant parameters, and starts the automated join process immediately after the VM is provisioned.

Bicep Parameter Configuration

In your .bicepparam file:

// Entra Hybrid Join Configuration
param executeHybridJoinRunCommand = true
param tenantId = 'your-tenant-guid-here'
param tenantName = 'yourdomain.com'
param hybridJoinScriptUri = 'https://yourstorageaccount.blob.core.windows.net/scripts/Deploy-EntraHybridJoinOrchestration.ps1?sp=r&st=...'
BICEP

Bicep Integration Snippet

Here’s how the Run Command looks in Bicep:

//params
@description('Execute Entra Hybrid Join run command on session hosts')
param executeHybridJoinRunCommand bool

@description('Entra Tenant ID (GUID) for Hybrid Join registry configuration')
param tenantId string

@description('Entra Tenant Name (e.g., contoso.onmicrosoft.com) for Hybrid Join registry configuration')
param tenantName string

@description('Script URI for Hybrid Join orchestration - include SAS token if required')
param hybridJoinScriptUri string

// Run Command for Entra Hybrid Join synchronisation using external script
resource hybridJoinRunCommand 'Microsoft.Compute/virtualMachines/runCommands@2024-03-01' = [for i in range(0, numberOfNewInstances): if (executeHybridJoinRunCommand && isHybridJoin && !empty(hybridJoinScriptUri)) {
  name: '${vmPrefix}-${i + startingHostNumber}/EntraHybridJoinSync'
  location: location
  properties: {
    source: {
      scriptUri: hybridJoinScriptUri  // External script from storage account
    }
    parameters: [
      {
        name: 'tenantId'
        value: tenantId  // Passed from Bicep parameter
      }
      {
        name: 'tenantName'
        value: tenantName  // Passed from Bicep parameter
      }
    ]
    asyncExecution: false
    timeoutInSeconds: 300  // 5 minutes timeout
    treatFailureAsDeploymentFailure: true  // Fail deployment if Hybrid Join setup fails
  }
  dependsOn: [
    vm[i]
    joindomain[i]
    dscextension[i]
  ]
}]
BICEP

How Long Does It Actually Take?

With everything in place, here’s what the timeline typically looks like:

T+0Bicep deployment starts
T+3VM created, domain join in progress
T+5Domain join complete, Run Command executes
T+5Registry keys created, retry task deployed on host
T+6First retry task runs, triggers join attempt
T+7Computer object created in AD with userCertificate
T+8Entra Connect task detects new device (next 5 min cycle)
T+8Delta sync triggered immediately
T+10Device synced to Entra ID
T+12Next host retry task runs, join completes
T+12Task removes itself – Hybrid Join COMPLETE

For example, here’s a deployment that I did for 25 hosts, it started at 14:48:

And all was completed, including FULL hybrid join by 14:59:

Average time: About 12 minutes end to end

That’s from starting the deployment to having a fully Hybrid Joined session host. Compare this to the traditional approach, where you might wait 30+ minutes for Entra Connect sync, then another hour for the scheduled task, then potentially until the next day if timing doesn’t align.

The time savings are massive when you’re deploying 10, 20, or 50 hosts at once. Instead of waiting hours for Hybrid Join before migrating users, you’ve got hosts fully ready in under 15 minutes.

Troubleshooting

Devices Not Being Detected on Entra Connect Server

Check:

  • Is the scheduled task running? Check Task Scheduler
  • Are logs being written? Check C:\Scripts\AVD\Logs\
  • Does the $devicePrefix match your actual device names?
  • Is the service account password correct?

Review the log files. You should see entries every 5 minutes. If devices aren’t being found, verify the naming pattern and time threshold.

Top tip – In large enterprises, where AD replication is something to be considered, if you’re finding that your Entra Connect Server isn’t finding the Computer objects in a timely manner, I’d recommend opening up the synchronisation service and selecting the Only use preferred domain controllers within the properties of the AD connector, like so:

I’d recommend setting the same DC’s in Entra Sync Connect to the same DC’s that the scheduled task is set to look at (assuming you’re using specific DC’s in the script), ensure all are in the same location as the AVD session hosts site for quicker replication.

Join Attempts Failing on Session Hosts

Check:

  • Are registry keys created? Run Get-ItemProperty HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\CDJ\AAD
  • Is the device domain joined? Run dsregcmd /status and check DomainJoined: YES
  • Check C:\Windows\Temp\HybridJoinRetry.log for errors

The most common issue is that the device hasn’t been synced to Entra ID yet. Check the Entra Connect server logs to confirm sync is running. If sync is working but join still fails, verify the tenant ID and name in the registry are correct.

Task Not Removing Itself After Join

Check:

  • Run dsregcmd /status manually. Are both DomainJoined and AzureAdJoined showing YES?
  • Check the retry log for errors in the status check logic

If the device is genuinely joined but the task isn’t removing itself, you can manually delete it:

Unregister-ScheduledTask -TaskName "EntraHybridJoinRetry" -Confirm:$false
PowerShell

Sync Task Running But Not Triggering Sync

Check:

  • Does the service account have permissions to run Start-ADSyncSyncCycle?
  • Are there errors in the Entra Connect server logs?

Test manually by running the scheduled task interactively and watching the output. Common issues include service account permissions or Entra Connect being in maintenance mode.


Where to Find the Logs

Quick reference for all log locations:

ComponentLog Location
Entra Connect Server TaskC:\Scripts\AVD\Logs\AVDSync_[timestamp].log
Session Host Retry TaskC:\Windows\Temp\HybridJoinRetry.log
Run Command ExtensionAzure Portal → Sub \ Resource Group → Deployments

Wrapping Up

Entra Hybrid Join doesn’t have to be a bottleneck in your AVD deployments. By taking control with active orchestration instead of relying on passive scheduled tasks and 30 minute sync cycles, you can achieve consistent join times of just a few minutes instead of hours.

The solution needs a bit of upfront setup. You’ll need to create the service account, deploy the Entra Connect task, and integrate the Run Command into your Bicep templates. But once it’s in place, it works reliably deployment after deployment.

If you’re managing AVD environments with Entra Hybrid Join, I’d recommend giving this a crack. The time savings alone justify the effort, and your operations team will thank you for eliminating the “wait and see” approach.

The scripts are all posted above in code blocks and links to my GitHub repo. Feel free to adapt them for your environment. If you’ve got questions or have implemented something similar, let me know in the comments!

Happy AVD’ing!

Alex

2 thoughts on “Azure Virtual Desktop: Cut Entra Hybrid Join Times from Hours to Minutes”

  1. Hey Alex
    nice job, we where facing the same issue. What we needed also to do is:
    Our Sessionhosts are joined on the 2 DC’s we are running in the cloud as we have a big environment. We neede also to make sure that the sync from the Computer Object in AD is getting synced asap. We have a AD Site especially for Azure configured in the Domain. Do you have also a DC in the cloud?

    1. Hey, thanks Marc.

      Typically yes, we have an AD site configured for each region, e.g. Azure-UKS, Azure-UKW, etc, etc. With two domain controllers in each.

Leave a Reply...

Scroll to Top