Michael Dumdei – Texarkana College IT Department

While working on account provisioning scripts for an Azure hybrid environment, I needed to run DirSync from a script. The goal was to:

  • Test if a DirSync operation was already in progress and wait for it to end if there were one
  • Initiate a new DirSync cycle
  • Wait for the cycle to complete and then proceed with the rest of the script

Looking online for methods to monitor DirSync activity turned up some solutions – most relying on monitoring the Event logs on the DirSync server. My initial solution used that method, but it seemed a little awkward and my code sometimes missed it when attempting to initiate a cycle and tried to launch while a DirSync process was already ongoing. I had started on some new code that would do a more thorough analysis of the log events when I ran across this link:

https://docs.microsoft.com/en-us/azure/active-directory/connect/active-directory-aadconnectsync-feature-scheduler

The link describes Get-ADSyncScheduler command that is in the Microsoft ADSync module. Get-ADSyncScheduler returns a property called ‘NextSyncCycleStartTimeInUTC’ that is ideal for accomplishing what I was trying to do. The property contains the time the next DirSync cycle is scheduled to run and what makes it so useful is it is not updated until any current operation completes. The logic becomes simple. If the value of ‘NextSyncCycleStartTimeInUTC’ is less than the current time, a DirSync operation is in progress. If it is greater than the current time, DirSync is not running and will launch at the time indicated. Another option for testing this I found via web searching was testing the ‘RunState’ property of the Get-ADSyncConnectorRunStatus for “Busy”. The problem with that was “Busy” falls out during the step transitions between doing the Deltas and the Exports. Using the ‘NextSyncCycleStartTimeInUTC’ property is rock solid and easy.

The code that follows meets the criteria of my goals for reliably triggering a DirSync cycle. The ‘ads’ name mangling when importing the PSSession is so I could use Get-Date on the DirSync server vs. Get-Date on the system running the script. While they should be close, a few seconds difference could cause problems if relying on the time returned by Get-SecsUntilNextDirSync to be accurate.

$dirSyncSrv = "dirsync.domain.com"
$sess = $null
$creds = $null

#
# Import session from the DirSync server.  Pull commands needed for ADSync. Pull Get-Date so we can get the
#  time from the target server in case client time is off from DS server time. The "ads" command prefix keeps
#  server Get-Date from colliding with client Get-Date. AllowClobber is in case we import more than once for 
#  whatever reason - with the "ads" prefix, no local commands will be clobbered.
# 
Function Connect-DirSyncServer {
    $sess = New-PSSession -ComputerName $dirSyncSrv -Credential $creds
    Import-PSSession -Session $sess -AllowClobber -Prefix ads `
     -CommandName @("Get-ADSyncScheduler", "Start-ADSyncSyncCycle", "Get-Date")  | Out-Null
}

# 
# Gets the next scheduled DirSync cycle time and converts it from UTC to local time. Gets the current local
#  time. Subtract current time from next scheduled. Will be negative if next scheduled is in the past which
#  means we are in the middle of a running cycle. If result is positve, the next cycle is in the future and
#  cycle is currently idle.
#
Function Get-SecsUntilNextDirSync() { 
    $nextSync = [System.TimeZoneInfo]::ConvertTimeFromUtc( `
      $(Get-adsADSyncScheduler).NextSyncCycleStartTimeInUTC, `
      [System.TimeZoneInfo]::FindSystemTimeZoneById($(Get-WmiObject win32_timezone).StandardName))
    $timeOnSrv = Get-adsDate
    return $($nextSync - $timeOnSrv).TotalSeconds
}

#
# First test to see if sync cycle already in progress and wait for it to complete if one is. Next, if a cycle will
#  be starting within the next 10 seconds, let nature takes it course otherwise force a cycle. Wait for the cycle
#  to start. Wait for the cycle to end.
# 
Function Run-DirSync {
    $nextRun = $(Get-SecsUntilNextDirSync)
    Write-Host -Fore Cyan "Starting DirSync...($nextRun)"
    if ($nextRun -lt 0) { # neg result means sync cycle in proccess
        Write-Host -Fore Yellow -NoNewLine "  Found existing DirSync running - waiting for it to end"
        While ($nextRun -lt 0) {
            Write-Host -NoNewLine "."
            Start-Sleep 5
            $nextRun = $(Get-SecsUntilNextDirSync)
        }
        Write-Host
    }
     # start unless it will start within 10 secs on its own
    if ($nextRun -ge 10) {  
        Start-adsADSyncSyncCycle -PolicyType Delta | Out-Null
    }
    Write-Host -NoNewline "  Waiting for remote process to start"
    While ($(Get-SecsUntilNextDirSync) -ge 0) {
        Write-Host -NoNewLine "."
        Start-Sleep 2
    }
    Write-Host
    Write-Host -NoNewline "  Waiting for DirSync to complete"
    While ($(Get-SecsUntilNextDirSync) -lt 0) {
        Write-Host -NoNewLine "."
        Start-Sleep 5
    }
    Write-Host
}

$creds = Get-Credential
Connect-DirSyncServer
Run-DirSync
Remove-PSSession $sess | Out-Null