diff --git a/certy.ps1 b/certy.ps1 index 844ac9d..c0ebbaf 100644 --- a/certy.ps1 +++ b/certy.ps1 @@ -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