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:
- Service Account Setup Script – Creates a service account with the right permissions
- Entra Connect Server Scheduled Task – Actively monitors for new devices and triggers immediate sync
- 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:

⚠️ Important
Run this script once on the Entra Sync server or domain controllers. It’ll prompt you for the service account username and the Entra Connect server name. Make note of the generated password because you’ll need it for the next step. It’ll be stored in C:\Scripts\AVD in plain text. Make sure you copy and delete this file straight away. If you’re having issues opening the password.txt file, ensure you run your text editor as an administrator.
Remember to add the account to the ‘ADSyncOperators’ group on the Entra Connect server if you selected no within the script.
Optionally, you can create your own service account manually or using your existing scripts \ tools of course, so this script isn’t required as such.
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⚠️ Important
The script needs to be in an Azure Storage account with a SAS token. The SAS token gives temporary access to download the script during deployment. You’ll want to generate a SAS token with read permissions that’s valid for a decent timeframe. I usually do 1 to 2 years and set a calendar reminder to regenerate it.
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]
]
}]BICEPHow Long Does It Actually Take?
With everything in place, here’s what the timeline typically looks like:
| T+0 | Bicep deployment starts |
| T+3 | VM created, domain join in progress |
| T+5 | Domain join complete, Run Command executes |
| T+5 | Registry keys created, retry task deployed on host |
| T+6 | First retry task runs, triggers join attempt |
| T+7 | Computer object created in AD with userCertificate |
| T+8 | Entra Connect task detects new device (next 5 min cycle) |
| T+8 | Delta sync triggered immediately |
| T+10 | Device synced to Entra ID |
| T+12 | Next host retry task runs, join completes |
| T+12 | Task 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
$devicePrefixmatch 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 /statusand checkDomainJoined: YES - Check
C:\Windows\Temp\HybridJoinRetry.logfor 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 /statusmanually. Are bothDomainJoinedandAzureAdJoinedshowingYES? - 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:$falsePowerShellSync 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:
| Component | Log Location |
|---|---|
| Entra Connect Server Task | C:\Scripts\AVD\Logs\AVDSync_[timestamp].log |
| Session Host Retry Task | C:\Windows\Temp\HybridJoinRetry.log |
| Run Command Extension | Azure 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





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