Marcel
Marcel That's me: Marcel

Automatically add or change Azure Active Directory computer objects on-demand

Automatically add or change Azure Active Directory computer objects on-demand

Hybrid joined AVD session hosts are great for working with conditional access, Intune (MEM), access to Azure files, and other AAD integrated services.

But there are some challenges: One challenge is that it takes a while to see new session hosts after the deployment in AAD - which avoids that AAD services (like ca) can be used directly. One reason for the delay is that AD objects are synced every 30 minutes by default using ADConnect. In the worst case, it takes 30 minutes before the computer object is visible in AAD.

My approach to lowering the delay is to pimp ADConnect with a custom PowerShell tool (SyncOnDemand.ps1): The tool checks every 3 seconds for computer objects in AD. The tool triggers ADConnect to start a differential sync if a computer object is new or updated after the last 30 minutes. To avoid the sync being triggered for the same computer object multiple times, already detected computer objects are ignored for the next 30 minutes.

This should bring down the time for a host before the host is also visible in AAD.

How does it work? Copy the script to the ADConnect server. Modify line 31 ($OuPath = …) to match your OU and domain.

Run the script in an administrative PowerShell. The script checks every 3 seconds for changed computer objects. Change one of the computer objects in the right OU to test it (e.g., change the description). The script detects the change and triggers a delta sync to AAD.

If everything works as expected, you can install the script with:

SyncOnDemand.ps1 -Install

That creates a scheduled task to run the script on the startup of the ADConnect server. The scheduled task is started directly and runs in the background.

Update: To speed up the replication between AD sites, you can use (“Use Notify”)[https://social.technet.microsoft.com/wiki/contents/articles/16929.set-active-directory-to-use-notify-replication.aspx]

Note: This is a workaround. Use at your own risk

Script (must be run as an administrator):

param(
    [Parameter(Mandatory=$false)]
    [Switch]$Install
)
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12


if ($Install) {
    Write-Host "Installing script to $env:windir"
    Copy-Item "$($MyInvocation.InvocationName)" -Destination ("$($env:windir)\SyncOnDemand.ps1")

	$action = New-ScheduledTaskAction -Execute "$env:windir\System32\WindowsPowerShell\v1.0\Powershell.exe" -Argument "-executionPolicy Unrestricted -File `"$($env:windir)\SyncOnDemand.ps1`""
	$trigger = New-ScheduledTaskTrigger	-AtStartup
	$principal = New-ScheduledTaskPrincipal 'NT Authority\SYSTEM'
	$settingsSet = New-ScheduledTaskSettingsSet
    $settingsSet.Enabled=$true
    #$settingsSet.IdleSettings.IdleDuration=0
    $settingsSet.StopIfGoingOnBatteries=$false
    $settingsSet.DisallowStartIfOnBatteries=$false
    $settingsSet.ExecutionTimeLimit="PT0H"
    
	$task = New-ScheduledTask -Action $action -Principal $principal -Trigger $trigger -Settings $settingsSet 
	Register-ScheduledTask -TaskName 'ITPC-ADConnect-OnDemand' -InputObject $task -ErrorAction Ignore | Out-Null
	Start-ScheduledTask -TaskName 'ITPC-ADConnect-OnDemand'
    Write-Host "Starting the task"
    return
}


# Parameter
$OuPath = "LDAP://OU=Servers,OU=Sys,OU=Organisation,DC=ITProCloud,DC=test"
$AcceptChangesForTheLastMinutes=30
$RunEachSeconds = 3

# Initialize
$cycle = Get-Date
$table=@{}


try {
    Get-ADSyncScheduler | Out-Null
} catch {
    Write-Error "Command Get-ADSyncScheduler not exist. Please check your configuration. This script must run on a computer with AD-Connect installed"
    return
}


do {
    $lastStart = Get-Date
    $dt=($lastStart.AddMinutes(-$AcceptChangesForTheLastMinutes)).ToUniversalTime().ToString("yyyyMMddHHmmss.0Z")
    $newObjects=0
    # cleanup ignore-list
    $table.clone().GetEnumerator() | foreach {
        if ($_.Value -le $lastStart.AddMinutes(-$AcceptChangesForTheLastMinutes)) {
                Write-Host "Removing computer from the ignore-list: $($_.Name)"
                $table.Remove($_.Name)
            }
    }

    $search=[adsisearcher]"(&(ObjectCategory=computer)(|(whenchanged>=$dt)(whencreated>=$dt)))"
    $search.SearchScope = "Subtree"
    $search.PageSize=150000
    $search.SearchRoot = $OuPath

    $obj2=$search.FindAll()
    Write-Host "Computer objects found: $($obj2.Count)"
    $obj2 | foreach {
        $le=$table[$_.Path]
        if ($le -eq $null) {
            $table.Add($_.Path,$lastStart)
            Write-Host "Found an updated computer: $($_.Path)"
            $newObjects++
        } else {
            Write-Host "Skipping computer while object is in the ignore-list for a while: $($_.Path)"
        }
    }

    Write-Host "Ignore list: $($table.Count)"
    Write-Host "New objects: $newObjects"
    if ($newObjects -gt 0) {
        Write-Host "Starting delta sync"
        try {
            do {
                Sleep -Seconds 1
            } while ((Get-ADSyncScheduler).SyncCycleInProgress)
            Start-ADSyncSyncCycle -PolicyType Delta | Out-Null
        } catch {
            Write-Error "Delta sync failed"
            $table=@{}
        }
        do {
            Sleep -Seconds 1
        } while ((Get-ADSyncScheduler).SyncCycleInProgress)
        Write-Host "Delta sync done"
    }
    Write-Host "Waiting for the next cycle"
    Write-Host
    Sleep -Seconds $RunEachSeconds
} while ($true)