When working with PowerShell in Active Directory, most scripts look simple at first.
You create a user, you update some attributes, you add group memberships, and you move on.
In reality, things are not always that clean, especially in environments with multiple Domain Controllers.
I ran into a situation that looked strange at the beginning. A script created a new user successfully. Right after that, the script tried to update the same user using Set-ADUser. And suddenly, the user could not be found.
No typo. No permission issue. The user was really created. But the next command failed.
What made this confusing
Normally, when you create a user, PowerShell should continue working against the same Domain Controller. At least, that is what you would expect.
But in some cases, Set-ADUser suddenly talks to a different Domain Controller. For whatever reason, the command decides to use another DC. When that happens before replication is finished, the user simply does not exist there yet.
The result is a very confusing error. The user was created just seconds ago, but cannot be found anymore.
I want to understand exactly why this happens internally, and I will dig deeper into it. But for now, I needed a solution that works.
The idea behind the solution
The idea is simple and pragmatic.
- Pick one Domain Controller
- Create the user on that Domain Controller
- Trigger replication from that same Domain Controller
- Wait until the user is visible on all Domain Controllers
- Only then continue with the automation
This removes guessing from the process. The script knows exactly where the change happened and makes sure the environment is in a consistent state before moving on.
Replication from the source Domain Controller
The function below triggers replication only from the Domain Controller where the change was made. This is enough, because that Domain Controller already has the latest data and only needs to push it out.
function Invoke-ADReplicationFromSourceDC {
[CmdletBinding()]
param(
[Parameter(Mandatory)]
[string]$SourceDC,
[Parameter()]
[string]$DomainDn,
[Parameter()]
[ValidateRange(2, 300)]
[int]$PollSeconds = 10,
[Parameter()]
[ValidateRange(1, 120)]
[int]$TimeoutMinutes = 10,
[Parameter()]
[switch]$Quiet
)
$ErrorActionPreference = 'Stop'
$resultErrors = New-Object System.Collections.Generic.List[string]
try {
Import-Module ActiveDirectory -ErrorAction Stop
$repadminPath = (Get-Command repadmin.exe -ErrorAction Stop).Source
if (-not $DomainDn) {
$DomainDn = (Get-ADDomain).DistinguishedName
}
$source = Get-ADDomainController -Identity $SourceDC -ErrorAction Stop
$cmd = "$repadminPath /syncall $($source.Name) $DomainDn /e /A"
Invoke-Expression $cmd | Out-Null
$deadline = (Get-Date).AddMinutes($TimeoutMinutes)
while ((Get-Date) -lt $deadline) {
$meta = Get-ADReplicationPartnerMetadata -Target $source.Name -Scope Server
if ($meta.LastReplicationResult -notcontains 0) {
Start-Sleep -Seconds $PollSeconds
continue
}
break
}
[pscustomobject]@{
Status = "ok"
SourceDC = $source.Name
}
}
catch {
[pscustomobject]@{
Status = "error"
SourceDC = $SourceDC
Error = $_.Exception.Message
}
}
}
Waiting until the user exists everywhere
Replication alone is not enough. The script also needs to know when the user is actually visible on all Domain Controllers.
This second function checks every Domain Controller and waits until the user can be found everywhere.
function Wait-ADObjectOnAllDomainControllers {
[CmdletBinding()]
param(
[Parameter(Mandatory)]
[string]$Identity,
[int]$PollSeconds = 3,
[int]$TimeoutMinutes = 5
)
Import-Module ActiveDirectory -ErrorAction Stop
$dcs = Get-ADDomainController -Filter *
$deadline = (Get-Date).AddMinutes($TimeoutMinutes)
while ((Get-Date) -lt $deadline) {
$missing = @()
foreach ($dc in $dcs) {
try {
Get-ADUser -Identity $Identity -Server $dc.Name -ErrorAction Stop | Out-Null
}
catch {
$missing += $dc.Name
}
}
if ($missing.Count -eq 0) {
return "ok"
}
Start-Sleep -Seconds $PollSeconds
}
return "timeout"
}
Putting it all together
In the test section, the script discovers a writable Domain Controller, creates the user on that DC, triggers replication from the same DC, waits until the user exists on all DCs, and only then continues.
$SourceDC = (Get-ADDomainController -Discover -Writable).HostName
New-ADUser `
-Name "Test User" `
-SamAccountName test.user `
-UserPrincipalName test.user@contoso.local `
-Path "CN=Users,DC=contoso,DC=local" `
-AccountPassword (ConvertTo-SecureString "P@ssw0rd123!" -AsPlainText -Force) `
-Enabled $true `
-Server $SourceDC
Invoke-ADReplicationFromSourceDC -SourceDC $SourceDC
Wait-ADObjectOnAllDomainControllers -Identity "test.user"
Why I use this pattern
This approach removed random errors from my automation. No more guessing which Domain Controller a command is using. No more “user not found” after creation.
It is not perfect, and I still want to understand why AD cmdlets sometimes switch Domain Controllers internally. But until then, this pattern works reliably and keeps my scripts predictable.
If you automate in Active Directory in environments with multiple Domain Controllers, this is a pattern worth using. Trust me 😉
Happy automating FOLKS !