Use scheduled task replication via source DC session

This commit is contained in:
2026-01-29 12:52:05 +13:00
parent 39982249db
commit d9a7c56788

View File

@@ -266,6 +266,7 @@ function Invoke-Replication {
[string]$Command,
[bool]$UseRemote,
[pscredential]$Credential,
[string]$SourceDc,
[scriptblock]$Log
)
@@ -276,47 +277,61 @@ function Invoke-Replication {
return
}
$sessions = @{}
if ([string]::IsNullOrWhiteSpace($SourceDc)) {
& $Log "Replication skipped: source DC is empty."
return
}
$session = $null
try {
if ($UseRemote) {
try {
if ($Credential) {
$session = New-PSSession -ComputerName $SourceDc -Credential $Credential -ErrorAction Stop
} else {
$session = New-PSSession -ComputerName $SourceDc -ErrorAction Stop
}
& $Log "Replication session opened: $SourceDc"
} catch {
& $Log ("Replication session error on {0}: {1}" -f $SourceDc, $_.Exception.Message)
return
}
}
foreach ($server in $targets) {
$usesToken = $Command -match "\{server\}"
$cmd = if ($usesToken) { $Command.Replace("{server}", $server) } else { $Command }
$usesToken = $Command -match "\{server\}|\{dest\}"
$cmd = $Command.Replace("{server}", $server).Replace("{dest}", $server)
$cmd = $cmd.Trim()
if ($cmd -match "^(?i)\s*/repadmin\b") {
$cmd = $cmd -replace "^(?i)\s*/repadmin\b", "repadmin"
}
if ($UseRemote) {
if (-not $sessions.ContainsKey($server)) {
try {
if ($Credential) {
$sessions[$server] = New-PSSession -ComputerName $server -Credential $Credential -ErrorAction Stop
} else {
$sessions[$server] = New-PSSession -ComputerName $server -ErrorAction Stop
}
& $Log "Replication session opened: $server"
} catch {
& $Log ("Replication session error on {0}: {1}" -f $server, $_.Exception.Message)
continue
}
}
$remoteCmd = $cmd.Replace("{server}", "").Trim()
$remoteCmd = [regex]::Replace($remoteCmd, "\s{2,}", " ")
if ($remoteCmd -match "(?i)\brepadmin\b" -and $remoteCmd -match "(?i)\bsyncall\b") {
$remoteCmd = [regex]::Replace($remoteCmd, "(?i)\brepadmin\s+/syncall\s+\S+", "repadmin /syncall")
}
$remoteCmd = [regex]::Replace($remoteCmd, "(?i)\brepadmin\s+/syncall\b", "repadmin /syncall")
$remoteCmd = $remoteCmd.Replace("/AdeP", "/AedP")
$remoteCmd = [regex]::Replace($remoteCmd, "\s{2,}", " ").Trim()
if ([string]::IsNullOrWhiteSpace($remoteCmd)) {
$taskName = ("OneShot_AD_DNS_Repl_{0}" -f ($server -replace "[^A-Za-z0-9_-]", "_"))
$outFile = ("C:\Windows\Temp\repadmin-{0}.txt" -f $server)
$repadminCmd = $cmd
$repadminCmd = [regex]::Replace($repadminCmd, "\s{2,}", " ").Trim()
if ([string]::IsNullOrWhiteSpace($repadminCmd)) {
& $Log "Replication skipped: empty command for $server."
continue
}
& $Log "Replication (remote): $server -> $remoteCmd"
& $Log "Replication (scheduled task): $SourceDc -> $server"
try {
Invoke-Command -Session $sessions[$server] -ScriptBlock { param($c) & $env:ComSpec /c $c } -ArgumentList $remoteCmd |
ForEach-Object { & $Log $_ }
Invoke-Command -Session $session -ScriptBlock {
param($DestDC, $TaskName, $OutFile, $RepadminCmd)
$cmdLine = "cmd.exe /c $RepadminCmd > `"$OutFile`" 2>&1"
schtasks /Create /F /TN $TaskName /RU SYSTEM /SC ONCE /ST 00:00 /TR $cmdLine | Out-Null
schtasks /Run /TN $TaskName | Out-Null
Start-Sleep 6
$output = if (Test-Path $OutFile) { Get-Content $OutFile } else { "No output file found" }
schtasks /Delete /F /TN $TaskName | Out-Null
Remove-Item $OutFile -Force -ErrorAction SilentlyContinue
$output
} -ArgumentList $server, $taskName, $outFile, $repadminCmd | ForEach-Object { & $Log $_ }
} catch {
& $Log ("Replication error on {0}: {1}" -f $server, $_.Exception.Message)
}
@@ -333,7 +348,7 @@ function Invoke-Replication {
& $env:ComSpec /c $cmd | ForEach-Object { & $Log $_ }
}
} finally {
foreach ($session in $sessions.Values) {
if ($session) {
try { Remove-PSSession -Session $session } catch {}
}
}
@@ -626,7 +641,7 @@ $panel.Controls.Add($ipRefreshBtn)
Style-ButtonSecondary $ipRefreshBtn
$y += $rowHeight + $gap
Add-Label "Primary DNS server" $xLabel $y $labelWidth $rowHeight
Add-Label "Primary DNS server (source DC)" $xLabel $y $labelWidth $rowHeight
$dnsServerBox = New-Object System.Windows.Forms.ComboBox
$dnsServerBox.Location = [System.Drawing.Point]::new($xInput, $y)
$dnsServerBox.Size = [System.Drawing.Size]::new(($inputWidth - ($buttonWidth + $buttonGap)), $rowHeight)
@@ -671,9 +686,9 @@ $panel.Controls.Add($primaryFromSelectedBtn)
Style-ButtonSecondary $primaryFromSelectedBtn
$y += 70 + $gap
Add-Label "Replication command ({server} optional)" $xLabel $y $labelWidth $rowHeight
Add-Label "Replication command ({dest} optional)" $xLabel $y $labelWidth $rowHeight
$replicationCmdBox = Add-TextBox $xInput $y $inputWidth $rowHeight $false
$replicationCmdBox.Text = "repadmin /syncall /AedP"
$replicationCmdBox.Text = "repadmin /syncall {dest} /AdeP"
$y += $rowHeight + $gap
Add-Label "Replication wait (seconds)" $xLabel $y $labelWidth $rowHeight
@@ -681,7 +696,7 @@ $replicationDelayBox = Add-TextBox $xInput $y $inputWidth $rowHeight $false
$replicationDelayBox.Text = "30"
$y += $rowHeight + $gap
$replicationRemoteBox = Add-CheckBox "Run repadmin remotely (PowerShell)" $xInput $y $inputWidth $rowHeight
$replicationRemoteBox = Add-CheckBox "Run replication via scheduled task on source DC" $xInput $y $inputWidth $rowHeight
$replicationRemoteBox.Checked = $true
$y += $rowHeight + $gap
@@ -1197,7 +1212,13 @@ $runBtn.Add_Click({
& $logAction "Replication credentials saved for this user."
}
}
Invoke-Replication -Servers $replicationTargets -Command $replicationCmdBox.Text -UseRemote $replicationRemoteBox.Checked -Credential $replicationCredential -Log $logAction
Invoke-Replication `
-Servers $replicationTargets `
-Command $replicationCmdBox.Text `
-UseRemote $replicationRemoteBox.Checked `
-Credential $replicationCredential `
-SourceDc $dnsServer `
-Log $logAction
if ($replicationDelaySeconds -gt 0) {
& $logAction "Waiting $replicationDelaySeconds seconds for replication."
Start-Sleep -Seconds $replicationDelaySeconds