Skip to content

Commit 5955769

Browse files
Update Synchronize-ADForestDCs.ps1
Signed-off-by: LUIZ HAMILTON ROBERTO DA SILVA <luizhamilton.lhr@gmail.com>
1 parent 86922fc commit 5955769

File tree

1 file changed

+128
-123
lines changed

1 file changed

+128
-123
lines changed

SysAdmin-Tools/ActiveDirectory-Management/Synchronize-ADForestDCs.ps1

Lines changed: 128 additions & 123 deletions
Original file line numberDiff line numberDiff line change
@@ -3,193 +3,198 @@
33
PowerShell Script for Synchronizing Domain Controllers Across an AD Forest.
44
55
.DESCRIPTION
6-
This script automates the synchronization of all Domain Controllers (DCs) across an Active Directory
7-
(AD) forest, ensuring that all changes are properly replicated and up-to-date.
6+
Automates the synchronization of all Domain Controllers (DCs) across an Active Directory (AD) forest.
7+
Ensures replication is triggered and up-to-date.
88
99
.AUTHOR
1010
Luiz Hamilton Silva - @brazilianscriptguy
1111
1212
.VERSION
13-
Last Updated: October 22, 2024
13+
UX Enhanced Edition – July 24, 2025
1414
#>
1515

16-
# Hide the PowerShell console window
16+
#region ── Hide Console Window ──
1717
Add-Type @"
1818
using System;
1919
using System.Runtime.InteropServices;
2020
public class Window {
21-
[DllImport("kernel32.dll", SetLastError = true)]
22-
static extern IntPtr GetConsoleWindow();
23-
[DllImport("user32.dll", SetLastError = true)]
24-
[return: MarshalAs(UnmanagedType.Bool)]
25-
static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
26-
public static void Hide() {
27-
var handle = GetConsoleWindow();
28-
ShowWindow(handle, 0); // 0 = SW_HIDE
29-
}
30-
public static void Show() {
31-
var handle = GetConsoleWindow();
32-
ShowWindow(handle, 5); // 5 = SW_SHOW
33-
}
21+
[DllImport("kernel32.dll")] public static extern IntPtr GetConsoleWindow();
22+
[DllImport("user32.dll")] public static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
3423
}
3524
"@
36-
[Window]::Hide()
25+
[Window]::ShowWindow([Window]::GetConsoleWindow(), 0)
26+
#endregion
3727

38-
# Import necessary modules
28+
#region ── Load Required Types ──
3929
Add-Type -AssemblyName System.Windows.Forms
4030
Add-Type -AssemblyName System.Drawing
31+
#endregion
4132

42-
# Determine the script name and set up the logging path
33+
#region ── Logging Setup ──
4334
$scriptName = [System.IO.Path]::GetFileNameWithoutExtension($MyInvocation.MyCommand.Name)
4435
$logDir = 'C:\Logs-TEMP'
45-
$logFileName = "${scriptName}.log"
46-
$logPath = Join-Path $logDir $logFileName
36+
$logFile = Join-Path $logDir "${scriptName}.log"
4737

48-
# Ensure the log directory exists
4938
if (-not (Test-Path $logDir)) {
50-
$null = New-Item -Path $logDir -ItemType Directory -ErrorAction SilentlyContinue
51-
if (-not (Test-Path $logDir)) {
52-
Write-Error "Failed to create log directory at $logDir. Logging will not be possible."
53-
return
54-
}
39+
try { New-Item -Path $logDir -ItemType Directory -Force | Out-Null } catch {}
5540
}
5641

57-
# Enhanced logging function with error handling
5842
function Log-Message {
5943
param (
60-
[Parameter(Mandatory=$true)]
61-
[string]$Message,
62-
[Parameter(Mandatory=$false)]
63-
[string]$MessageType = "INFO"
44+
[Parameter(Mandatory)] [string]$Message,
45+
[ValidateSet('INFO','ERROR','WARN')] [string]$Type = 'INFO'
6446
)
6547
$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
66-
$logEntry = "[$timestamp] [$MessageType] $Message"
48+
$entry = "[$timestamp] [$Type] $Message"
49+
6750
try {
68-
Add-Content -Path $logPath -Value "$logEntry`r`n" -ErrorAction Stop
69-
$global:logBox.Items.Add($logEntry)
70-
$global:logBox.TopIndex = $global:logBox.Items.Count - 1
51+
Add-Content -Path $logFile -Value $entry
52+
$global:logBox.SelectionStart = $global:logBox.TextLength
53+
$global:logBox.SelectionColor = switch ($Type) {
54+
'ERROR' { 'Red' }
55+
'WARN' { 'DarkOrange' }
56+
'INFO' { 'Black' }
57+
}
58+
$global:logBox.AppendText("$entry`r`n")
59+
$global:logBox.ScrollToCaret()
7160
} catch {
72-
Write-Error "Failed to write to log: $_"
61+
Write-Error "Log error: $_"
7362
}
7463
}
64+
#endregion
7565

76-
# Function to force synchronization on all DCs
66+
#region ── Core Functions ──
7767
function Sync-AllDCs {
78-
# Import the Active Directory module
79-
Import-Module ActiveDirectory
80-
81-
Log-Message "Starting Active Directory synchronization process: $(Get-Date)"
82-
83-
# Get a list of all domains in the forest
68+
Log-Message "Sync process started"
8469
try {
70+
Import-Module ActiveDirectory -ErrorAction Stop
8571
$forest = Get-ADForest
86-
$allDomains = $forest.Domains
72+
$domains = $forest.Domains
8773
} catch {
88-
Log-Message "Error retrieving forest domains: $_" -MessageType "ERROR"
89-
[System.Windows.Forms.MessageBox]::Show("Error retrieving forest domains. See log for details.", "Error", [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Error)
74+
Log-Message "Failed to load forest info: $_" -Type 'ERROR'
75+
[System.Windows.Forms.MessageBox]::Show("Could not retrieve domains. See log.", "Error", "OK", "Error")
9076
return
9177
}
9278

93-
# Collect all domain controllers from all domains
9479
$allDCs = @()
95-
foreach ($domain in $allDomains) {
80+
foreach ($domain in $domains) {
9681
try {
97-
$domainDCs = Get-ADDomainController -Filter * -Server $domain
98-
$allDCs += $domainDCs
82+
$allDCs += Get-ADDomainController -Filter * -Server $domain
9983
} catch {
100-
Log-Message "Error retrieving domain controllers from ${domain}: $_" -MessageType "ERROR"
84+
Log-Message "Error retrieving DCs for ${domain}: $_" -Type 'ERROR'
10185
}
10286
}
10387

104-
# Force synchronization on all domain controllers
10588
foreach ($dc in $allDCs) {
106-
$dcName = $dc.HostName
107-
Write-Output "Forcing synchronization on $dcName"
108-
Log-Message "Forcing synchronization on ${dcName}: $(Get-Date)"
89+
$name = $dc.HostName
90+
Log-Message "Syncing $name"
10991
try {
110-
# Perform the synchronization
111-
$syncResult = & repadmin /syncall /e /A /P /d /q $dcName
112-
# Log the result of the synchronization
113-
Log-Message "Synchronization result for ${dcName}: $syncResult"
92+
$output = & repadmin /syncall /e /A /P /d /q $name
93+
Log-Message "Result: $output"
11494
} catch {
115-
# Log any errors that occur
116-
Log-Message "Error synchronizing ${dcName}: $_" -MessageType "ERROR"
95+
Log-Message "Sync error for ${name}: $_" -Type 'ERROR'
11796
}
11897
}
11998

120-
Log-Message "Active Directory synchronization process completed: $(Get-Date)"
121-
[System.Windows.Forms.MessageBox]::Show("Synchronization process completed.", "Info", [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Information)
99+
Log-Message "Sync completed"
100+
[System.Windows.Forms.MessageBox]::Show("Sync completed. See log for details.", "Info", "OK", "Information")
122101
}
123102

124-
# Function to display the log file
125103
function Show-Log {
126-
notepad $logPath
104+
Start-Process notepad.exe $logFile
127105
}
128106

129-
# Function to run Repadmin.exe /replsummary and display output in the list box
130107
function Show-ReplSummary {
131-
Log-Message "Starting Repadmin.exe /replsummary: $(Get-Date)"
108+
Log-Message "Running replsummary"
132109
try {
133-
$replSummaryResult = & repadmin /replsummary
134-
135-
# Split the output into individual lines and add them to the list box
136-
$replSummaryResultLines = $replSummaryResult -split "`r`n"
137-
foreach ($line in $replSummaryResultLines) {
138-
$global:logBox.Items.Add($line)
110+
$summary = & repadmin /replsummary
111+
$summary -split "`r`n" | ForEach-Object {
112+
$global:logBox.AppendText("$_`r`n")
139113
}
140-
141-
$global:logBox.TopIndex = $global:logBox.Items.Count - 1
142-
Log-Message "Repadmin.exe /replsummary completed"
114+
$global:logBox.ScrollToCaret()
115+
Log-Message "replsummary complete"
143116
} catch {
144-
Log-Message "Error executing Repadmin.exe /replsummary: $_" -MessageType "ERROR"
145-
[System.Windows.Forms.MessageBox]::Show("Error executing Repadmin.exe /replsummary. See log for details.", "Error", [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Error)
117+
Log-Message "Error running replsummary: $_" -Type 'ERROR'
118+
[System.Windows.Forms.MessageBox]::Show("Error running replsummary. See log.", "Error", "OK", "Error")
146119
}
147120
}
121+
#endregion
148122

149-
# Create the form
150-
$form = New-Object System.Windows.Forms.Form
151-
$form.Text = "AD Forest Sync Tool"
152-
$form.Size = New-Object System.Drawing.Size(800, 620) # Increased size to fit content
153-
$form.StartPosition = "CenterScreen"
154-
155-
# Create a ListBox to display log messages
156-
$global:logBox = New-Object System.Windows.Forms.ListBox
157-
$logBox.Location = New-Object System.Drawing.Point(10, 10)
158-
$logBox.Size = New-Object System.Drawing.Size(760, 500) # Adjusted size to fit form
159-
$form.Controls.Add($logBox)
160-
161-
# Create a button to start synchronization
162-
$syncButton = New-Object System.Windows.Forms.Button
163-
$syncButton.Location = New-Object System.Drawing.Point(50, 520)
164-
$syncButton.Size = New-Object System.Drawing.Size(150, 50)
165-
$syncButton.Text = "Sync All Forest DCs"
166-
$syncButton.Add_Click({
167-
Sync-AllDCs
168-
})
169-
$form.Controls.Add($syncButton)
170-
171-
# Create a button to view the log
172-
$logButton = New-Object System.Windows.Forms.Button
173-
$logButton.Location = New-Object System.Drawing.Point(250, 520)
174-
$logButton.Size = New-Object System.Drawing.Size(150, 50)
175-
$logButton.Text = "View Output Logs"
176-
$logButton.Add_Click({
177-
Show-Log
123+
#region ── GUI Setup ──
124+
125+
$form = New-Object Windows.Forms.Form -Property @{
126+
Text = "AD Forest Sync Tool"
127+
Size = '800,660'
128+
StartPosition = 'CenterScreen'
129+
FormBorderStyle = 'FixedDialog'
130+
MaximizeBox = $false
131+
}
132+
133+
# Status bar
134+
$statusStrip = New-Object Windows.Forms.StatusStrip
135+
$statusLabel = New-Object Windows.Forms.ToolStripStatusLabel
136+
$statusLabel.Text = "Ready"
137+
$statusStrip.Items.Add($statusLabel)
138+
$form.Controls.Add($statusStrip)
139+
140+
# RichTextBox for logs
141+
$global:logBox = New-Object Windows.Forms.RichTextBox -Property @{
142+
Location = '10,10'
143+
Size = '760,500'
144+
ReadOnly = $true
145+
Font = New-Object Drawing.Font("Consolas", 9)
146+
WordWrap = $false
147+
ScrollBars = "Vertical"
148+
}
149+
$form.Controls.Add($global:logBox)
150+
151+
# Button: Sync
152+
$syncBtn = New-Object Windows.Forms.Button -Property @{
153+
Text = "Sync All Forest DCs"
154+
Location = '50,520'
155+
Size = '150,50'
156+
}
157+
$syncBtn.Add_Click({
158+
$syncBtn.Enabled = $false
159+
$statusLabel.Text = "Syncing domain controllers..."
160+
try {
161+
Sync-AllDCs
162+
$statusLabel.Text = "Sync completed"
163+
} finally {
164+
$syncBtn.Enabled = $true
165+
}
178166
})
179-
$form.Controls.Add($logButton)
180-
181-
# Create a button to show Repadmin.exe /replsummary
182-
$replSummaryButton = New-Object System.Windows.Forms.Button
183-
$replSummaryButton.Location = New-Object System.Drawing.Point(450, 520)
184-
$replSummaryButton.Size = New-Object System.Drawing.Size(250, 50)
185-
$replSummaryButton.Text = "Show Replication Summary"
186-
$replSummaryButton.Add_Click({
187-
Show-ReplSummary
167+
$form.Controls.Add($syncBtn)
168+
169+
# Button: View Logs
170+
$logBtn = New-Object Windows.Forms.Button -Property @{
171+
Text = "View Output Logs"
172+
Location = '250,520'
173+
Size = '150,50'
174+
}
175+
$logBtn.Add_Click({ Show-Log })
176+
$form.Controls.Add($logBtn)
177+
178+
# Button: Show Replication Summary
179+
$replBtn = New-Object Windows.Forms.Button -Property @{
180+
Text = "Show Replication Summary"
181+
Location = '450,520'
182+
Size = '250,50'
183+
}
184+
$replBtn.Add_Click({
185+
$replBtn.Enabled = $false
186+
$statusLabel.Text = "Running replication summary..."
187+
try {
188+
Show-ReplSummary
189+
$statusLabel.Text = "Replication summary complete"
190+
} finally {
191+
$replBtn.Enabled = $true
192+
}
188193
})
189-
$form.Controls.Add($replSummaryButton)
194+
$form.Controls.Add($replBtn)
190195

191-
# Show the form
192-
$form.Add_Shown({$form.Activate()})
193-
[void] $form.ShowDialog()
196+
$form.Add_Shown({ $form.Activate() })
197+
[void]$form.ShowDialog()
198+
#endregion
194199

195-
# End of script
200+
# ── End of Script ──

0 commit comments

Comments
 (0)