Active Directory Health Check
Comprehensive health check for Active Directory domain controllers
Table of Contents
Comprehensive diagnostic script to check Active Directory domain controller health, replication status, and identify common issues.
Overview
This script performs a complete health assessment of your Active Directory environment including domain controller diagnostics, replication status, FSMO role verification, DNS health, and service status checks.
Use Case: Routine health checks, troubleshooting replication issues, validating DC configuration, or diagnosing authentication problems.
Platform: Windows Server 2012 R2+ Requirements: Domain Administrator privileges, Active Directory module Execution Time: 3-5 minutes depending on domain size
The Script
1<#
2.SYNOPSIS
3 Active Directory health check script
4
5.DESCRIPTION
6 Performs comprehensive health checks on Active Directory including:
7 - DCDiag tests (connectivity, replication, DNS, services)
8 - Replication status with repadmin
9 - FSMO role holder verification
10 - Domain controller service status
11 - DNS SRV record validation
12 - Time synchronization check
13 - Event log analysis for critical AD errors
14
15.PARAMETER ExportPath
16 Path to export HTML report. If not specified, results display in console only.
17
18.PARAMETER SkipDNSTests
19 Skip DNS SRV record validation tests
20
21.PARAMETER QuickCheck
22 Perform quick health check (skips detailed event log analysis)
23
24.EXAMPLE
25 .\Test-ADHealth.ps1
26 Runs full health check with console output
27
28.EXAMPLE
29 .\Test-ADHealth.ps1 -ExportPath "C:\Reports\ADHealth.html"
30 Runs health check and exports HTML report
31
32.EXAMPLE
33 .\Test-ADHealth.ps1 -QuickCheck
34 Runs quick health check without detailed event log analysis
35
36.NOTES
37 Author: glyph.sh
38 Requires: Domain Administrator privileges, AD PowerShell module
39 Reference: https://glyph.sh/kb/active-directory-troubleshooting/
40#>
41
42[CmdletBinding()]
43param(
44 [string]$ExportPath,
45 [switch]$SkipDNSTests,
46 [switch]$QuickCheck
47)
48
49#Requires -RunAsAdministrator
50#Requires -Modules ActiveDirectory
51
52# Initialize results collection
53$script:HealthResults = @{
54 Timestamp = Get-Date
55 OverallStatus = "Healthy"
56 Issues = @()
57 Warnings = @()
58}
59
60function Write-HealthStatus {
61 param(
62 [string]$Message,
63 [ValidateSet('Info', 'Success', 'Warning', 'Error')]
64 [string]$Level = 'Info'
65 )
66
67 $color = switch ($Level) {
68 'Success' { 'Green' }
69 'Warning' { 'Yellow' }
70 'Error' { 'Red' }
71 default { 'White' }
72 }
73
74 $prefix = switch ($Level) {
75 'Success' { '[OK]' }
76 'Warning' { '[WARN]' }
77 'Error' { '[ERROR]' }
78 default { '[INFO]' }
79 }
80
81 Write-Host "$prefix $Message" -ForegroundColor $color
82
83 if ($Level -eq 'Error') {
84 $script:HealthResults.Issues += $Message
85 $script:HealthResults.OverallStatus = "Critical"
86 }
87 elseif ($Level -eq 'Warning' -and $script:HealthResults.OverallStatus -eq "Healthy") {
88 $script:HealthResults.Warnings += $Message
89 $script:HealthResults.OverallStatus = "Warning"
90 }
91}
92
93Write-Host "========================================" -ForegroundColor Cyan
94Write-Host " Active Directory Health Check" -ForegroundColor Cyan
95Write-Host "========================================" -ForegroundColor Cyan
96Write-Host ""
97
98# Get domain information
99try {
100 $domain = Get-ADDomain -ErrorAction Stop
101 $forest = Get-ADForest -ErrorAction Stop
102 Write-HealthStatus "Domain: $($domain.DNSRoot)" -Level Info
103 Write-HealthStatus "Forest: $($forest.Name)" -Level Info
104 Write-Host ""
105}
106catch {
107 Write-HealthStatus "Failed to retrieve domain information: $($_.Exception.Message)" -Level Error
108 exit 1
109}
110
111# Get all domain controllers
112try {
113 $domainControllers = Get-ADDomainController -Filter * | Sort-Object Name
114 Write-HealthStatus "Found $($domainControllers.Count) domain controller(s)" -Level Success
115 Write-Host ""
116}
117catch {
118 Write-HealthStatus "Failed to retrieve domain controllers: $($_.Exception.Message)" -Level Error
119 exit 1
120}
121
122# Check 1: Domain Controller Services
123Write-Host "[1/7] Checking Domain Controller Services..." -ForegroundColor Yellow
124Write-Host ""
125
126$criticalServices = @('ADWS', 'KDC', 'NTDS', 'DNS', 'Netlogon', 'W32Time')
127
128foreach ($dc in $domainControllers) {
129 Write-Host " Checking $($dc.Name)..." -ForegroundColor Cyan
130
131 foreach ($serviceName in $criticalServices) {
132 try {
133 $service = Get-Service -Name $serviceName -ComputerName $dc.HostName -ErrorAction Stop
134
135 if ($service.Status -eq 'Running') {
136 Write-HealthStatus " $serviceName is running" -Level Success
137 }
138 else {
139 Write-HealthStatus " $serviceName is $($service.Status) on $($dc.Name)" -Level Error
140 }
141 }
142 catch {
143 Write-HealthStatus " Cannot check $serviceName on $($dc.Name): $($_.Exception.Message)" -Level Warning
144 }
145 }
146 Write-Host ""
147}
148
149# Check 2: FSMO Roles
150Write-Host "[2/7] Verifying FSMO Role Holders..." -ForegroundColor Yellow
151Write-Host ""
152
153try {
154 $pdcEmulator = $domain.PDCEmulator
155 $ridMaster = $domain.RIDMaster
156 $infraMaster = $domain.InfrastructureMaster
157 $schemaMaster = $forest.SchemaMaster
158 $domainNamingMaster = $forest.DomainNamingMaster
159
160 Write-HealthStatus " PDC Emulator: $pdcEmulator" -Level Info
161 Write-HealthStatus " RID Master: $ridMaster" -Level Info
162 Write-HealthStatus " Infrastructure Master: $infraMaster" -Level Info
163 Write-HealthStatus " Schema Master: $schemaMaster" -Level Info
164 Write-HealthStatus " Domain Naming Master: $domainNamingMaster" -Level Info
165
166 # Verify FSMO role holders are online
167 $fsmoHolders = @($pdcEmulator, $ridMaster, $infraMaster, $schemaMaster, $domainNamingMaster) | Select-Object -Unique
168
169 foreach ($fsmo in $fsmoHolders) {
170 $dcName = $fsmo.Split('.')[0]
171 $dcOnline = $domainControllers | Where-Object { $_.Name -eq $dcName }
172
173 if (-not $dcOnline) {
174 Write-HealthStatus " FSMO role holder $fsmo is not responding" -Level Error
175 }
176 }
177}
178catch {
179 Write-HealthStatus " Failed to retrieve FSMO roles: $($_.Exception.Message)" -Level Error
180}
181
182Write-Host ""
183
184# Check 3: Replication Status
185Write-Host "[3/7] Checking Replication Status..." -ForegroundColor Yellow
186Write-Host ""
187
188foreach ($dc in $domainControllers) {
189 Write-Host " Checking replication on $($dc.Name)..." -ForegroundColor Cyan
190
191 try {
192 $replStatus = repadmin /showrepl $dc.HostName 2>&1
193
194 if ($LASTEXITCODE -eq 0) {
195 # Check for replication errors
196 $errors = $replStatus | Select-String -Pattern "last (error|failure)"
197
198 if ($errors) {
199 Write-HealthStatus " Replication errors detected on $($dc.Name)" -Level Error
200 $errors | ForEach-Object { Write-Host " $_" -ForegroundColor Red }
201 }
202 else {
203 Write-HealthStatus " Replication is healthy" -Level Success
204 }
205 }
206 else {
207 Write-HealthStatus " Failed to check replication on $($dc.Name)" -Level Warning
208 }
209 }
210 catch {
211 Write-HealthStatus " Error checking replication: $($_.Exception.Message)" -Level Warning
212 }
213}
214
215Write-Host ""
216
217# Check replication summary
218Write-Host " Replication Summary:" -ForegroundColor Cyan
219try {
220 $replSummary = repadmin /replsummary 2>&1
221
222 if ($LASTEXITCODE -eq 0) {
223 $replSummary | Select-String -Pattern "errors|failures" | ForEach-Object {
224 if ($_ -match "0 (/|errors|failures)") {
225 Write-HealthStatus " $_" -Level Success
226 }
227 else {
228 Write-HealthStatus " $_" -Level Warning
229 }
230 }
231 }
232}
233catch {
234 Write-HealthStatus " Could not retrieve replication summary" -Level Warning
235}
236
237Write-Host ""
238
239# Check 4: DCDiag Tests
240Write-Host "[4/7] Running DCDiag Tests..." -ForegroundColor Yellow
241Write-Host ""
242
243foreach ($dc in $domainControllers) {
244 Write-Host " Running diagnostics on $($dc.Name)..." -ForegroundColor Cyan
245
246 # Run core DCDiag tests
247 $tests = @('Connectivity', 'Replications', 'Services', 'Advertising', 'FsmoCheck')
248
249 foreach ($test in $tests) {
250 try {
251 $result = dcdiag /s:$($dc.HostName) /test:$test 2>&1
252
253 if ($result -match "passed test $test") {
254 Write-HealthStatus " $test test passed" -Level Success
255 }
256 elseif ($result -match "failed test $test") {
257 Write-HealthStatus " $test test failed on $($dc.Name)" -Level Error
258 }
259 else {
260 Write-HealthStatus " $test test inconclusive on $($dc.Name)" -Level Warning
261 }
262 }
263 catch {
264 Write-HealthStatus " Error running $test test: $($_.Exception.Message)" -Level Warning
265 }
266 }
267 Write-Host ""
268}
269
270# Check 5: DNS Health
271if (-not $SkipDNSTests) {
272 Write-Host "[5/7] Checking DNS Configuration..." -ForegroundColor Yellow
273 Write-Host ""
274
275 # Test critical SRV records
276 $srvRecords = @(
277 "_ldap._tcp.dc._msdcs.$($domain.DNSRoot)",
278 "_kerberos._tcp.dc._msdcs.$($domain.DNSRoot)",
279 "_ldap._tcp.$($domain.DNSRoot)",
280 "_kerberos._tcp.$($domain.DNSRoot)"
281 )
282
283 foreach ($record in $srvRecords) {
284 try {
285 $result = Resolve-DnsName -Name $record -Type SRV -ErrorAction Stop
286
287 if ($result) {
288 Write-HealthStatus " SRV record $record resolved successfully" -Level Success
289 }
290 }
291 catch {
292 Write-HealthStatus " Failed to resolve SRV record $record" -Level Error
293 }
294 }
295
296 Write-Host ""
297
298 # Run DCDiag DNS test
299 Write-Host " Running comprehensive DNS tests..." -ForegroundColor Cyan
300 try {
301 $dnsTest = dcdiag /test:DNS /v 2>&1
302
303 if ($dnsTest -match "failed") {
304 Write-HealthStatus " DNS test detected issues" -Level Warning
305 $dnsTest | Select-String -Pattern "failed|error" | ForEach-Object {
306 Write-Host " $_" -ForegroundColor Yellow
307 }
308 }
309 else {
310 Write-HealthStatus " DNS tests passed" -Level Success
311 }
312 }
313 catch {
314 Write-HealthStatus " Could not complete DNS tests" -Level Warning
315 }
316
317 Write-Host ""
318}
319else {
320 Write-Host "[5/7] Skipping DNS tests (SkipDNSTests parameter used)" -ForegroundColor Gray
321 Write-Host ""
322}
323
324# Check 6: Time Synchronization
325Write-Host "[6/7] Checking Time Synchronization..." -ForegroundColor Yellow
326Write-Host ""
327
328foreach ($dc in $domainControllers) {
329 try {
330 $timeConfig = w32tm /query /computer:$($dc.HostName) /configuration 2>&1
331 $timeStatus = w32tm /query /computer:$($dc.HostName) /status 2>&1
332
333 if ($dc.OperationMasterRoles -contains 'PDCEmulator') {
334 Write-HealthStatus " $($dc.Name) is PDC Emulator (time source for domain)" -Level Info
335
336 if ($timeStatus -match "Source:.*Local CMOS") {
337 Write-HealthStatus " WARNING: PDC using local CMOS, should sync to external source" -Level Warning
338 }
339 }
340 else {
341 if ($timeStatus -match "Source:.*$pdcEmulator") {
342 Write-HealthStatus " $($dc.Name) is syncing with PDC Emulator" -Level Success
343 }
344 else {
345 Write-HealthStatus " $($dc.Name) may not be syncing with PDC Emulator" -Level Warning
346 }
347 }
348 }
349 catch {
350 Write-HealthStatus " Could not check time sync on $($dc.Name)" -Level Warning
351 }
352}
353
354Write-Host ""
355
356# Check 7: Event Log Analysis
357if (-not $QuickCheck) {
358 Write-Host "[7/7] Analyzing Event Logs for Critical AD Errors..." -ForegroundColor Yellow
359 Write-Host ""
360
361 $criticalEventIDs = @{
362 2042 = "It has been too long since this machine last replicated"
363 1311 = "The KDC cannot find a suitable certificate to use for smart card logon"
364 1645 = "Active Directory Domain Services did not find any writable domain controllers"
365 2087 = "DNS lookup failure caused replication to fail"
366 2088 = "Replication access was denied"
367 }
368
369 foreach ($dc in $domainControllers) {
370 Write-Host " Checking $($dc.Name) event logs..." -ForegroundColor Cyan
371
372 foreach ($eventID in $criticalEventIDs.Keys) {
373 try {
374 $events = Get-WinEvent -ComputerName $dc.HostName -FilterHashtable @{
375 LogName = 'Directory Service', 'System'
376 ID = $eventID
377 StartTime = (Get-Date).AddDays(-7)
378 } -MaxEvents 5 -ErrorAction SilentlyContinue
379
380 if ($events) {
381 Write-HealthStatus " Event ID $eventID found: $($criticalEventIDs[$eventID])" -Level Warning
382 Write-Host " Count in last 7 days: $($events.Count)" -ForegroundColor Yellow
383 }
384 }
385 catch {
386 # Silently continue if event not found or access denied
387 }
388 }
389 }
390
391 Write-Host ""
392}
393else {
394 Write-Host "[7/7] Skipping event log analysis (QuickCheck parameter used)" -ForegroundColor Gray
395 Write-Host ""
396}
397
398# Summary
399Write-Host "========================================" -ForegroundColor Cyan
400Write-Host " Health Check Summary" -ForegroundColor Cyan
401Write-Host "========================================" -ForegroundColor Cyan
402Write-Host ""
403
404$statusColor = switch ($script:HealthResults.OverallStatus) {
405 'Healthy' { 'Green' }
406 'Warning' { 'Yellow' }
407 'Critical' { 'Red' }
408}
409
410Write-Host "Overall Status: $($script:HealthResults.OverallStatus)" -ForegroundColor $statusColor
411Write-Host "Scan Time: $($script:HealthResults.Timestamp)" -ForegroundColor White
412Write-Host ""
413
414if ($script:HealthResults.Issues.Count -gt 0) {
415 Write-Host "Critical Issues Found: $($script:HealthResults.Issues.Count)" -ForegroundColor Red
416 $script:HealthResults.Issues | ForEach-Object {
417 Write-Host " - $_" -ForegroundColor Red
418 }
419 Write-Host ""
420}
421
422if ($script:HealthResults.Warnings.Count -gt 0) {
423 Write-Host "Warnings: $($script:HealthResults.Warnings.Count)" -ForegroundColor Yellow
424 $script:HealthResults.Warnings | ForEach-Object {
425 Write-Host " - $_" -ForegroundColor Yellow
426 }
427 Write-Host ""
428}
429
430if ($script:HealthResults.OverallStatus -eq "Healthy") {
431 Write-Host "No critical issues detected!" -ForegroundColor Green
432 Write-Host ""
433}
434
435# Export HTML report if requested
436if ($ExportPath) {
437 Write-Host "Exporting report to $ExportPath..." -ForegroundColor Cyan
438
439 $html = @"
440<!DOCTYPE html>
441<html>
442<head>
443 <title>Active Directory Health Check Report</title>
444 <style>
445 body { font-family: Arial, sans-serif; margin: 20px; }
446 h1 { color: #0066cc; }
447 h2 { color: #333; border-bottom: 2px solid #0066cc; padding-bottom: 5px; }
448 .healthy { color: green; font-weight: bold; }
449 .warning { color: orange; font-weight: bold; }
450 .critical { color: red; font-weight: bold; }
451 .info { color: #333; }
452 ul { margin: 10px 0; }
453 li { margin: 5px 0; }
454 .timestamp { color: #666; font-size: 0.9em; }
455 </style>
456</head>
457<body>
458 <h1>Active Directory Health Check Report</h1>
459 <p class="timestamp">Generated: $($script:HealthResults.Timestamp)</p>
460 <p>Domain: $($domain.DNSRoot)</p>
461 <p>Forest: $($forest.Name)</p>
462
463 <h2>Overall Status: <span class="$($script:HealthResults.OverallStatus.ToLower())">$($script:HealthResults.OverallStatus)</span></h2>
464
465 <h2>Domain Controllers</h2>
466 <ul>
467$(foreach ($dc in $domainControllers) { " <li>$($dc.Name) - $($dc.Site)</li>`n" })
468 </ul>
469
470 <h2>FSMO Role Holders</h2>
471 <ul>
472 <li>PDC Emulator: $pdcEmulator</li>
473 <li>RID Master: $ridMaster</li>
474 <li>Infrastructure Master: $infraMaster</li>
475 <li>Schema Master: $schemaMaster</li>
476 <li>Domain Naming Master: $domainNamingMaster</li>
477 </ul>
478
479 $(if ($script:HealthResults.Issues.Count -gt 0) {
480 "<h2>Critical Issues</h2><ul>" +
481 ($script:HealthResults.Issues | ForEach-Object { "<li class='critical'>$_</li>" }) -join "`n" +
482 "</ul>"
483 })
484
485 $(if ($script:HealthResults.Warnings.Count -gt 0) {
486 "<h2>Warnings</h2><ul>" +
487 ($script:HealthResults.Warnings | ForEach-Object { "<li class='warning'>$_</li>" }) -join "`n" +
488 "</ul>"
489 })
490
491 <h2>Recommendations</h2>
492 <ul>
493 <li>Review and resolve any critical issues immediately</li>
494 <li>Schedule regular health checks (weekly recommended)</li>
495 <li>Monitor replication status continuously</li>
496 <li>Ensure proper backup procedures are in place</li>
497 <li>Keep domain controllers patched and up to date</li>
498 </ul>
499</body>
500</html>
501"@
502
503 try {
504 $html | Out-File -FilePath $ExportPath -Encoding UTF8
505 Write-HealthStatus "Report exported successfully" -Level Success
506 }
507 catch {
508 Write-HealthStatus "Failed to export report: $($_.Exception.Message)" -Level Error
509 }
510}
511
512Write-Host ""
513Write-Host "Health check complete!" -ForegroundColor Cyan
514Write-Host ""
515
516# Return results object for further processing
517return $script:HealthResultsUsage
Basic Health Check
Run a complete health check with console output:
1.\Test-ADHealth.ps1Export HTML Report
Generate an HTML report for documentation:
1.\Test-ADHealth.ps1 -ExportPath "C:\Reports\ADHealth_$(Get-Date -Format 'yyyyMMdd').html"Quick Check
Perform a quick health check without event log analysis:
1.\Test-ADHealth.ps1 -QuickCheckSkip DNS Tests
Run health check without DNS validation (useful in split-DNS environments):
1.\Test-ADHealth.ps1 -SkipDNSTestsWhat It Does
1. Domain Controller Services
Verifies that critical AD services are running on all DCs:
- ADWS (Active Directory Web Services)
- KDC (Kerberos Key Distribution Center)
- NTDS (Active Directory Domain Services)
- DNS (Domain Name System)
- Netlogon (Net Logon Service)
- W32Time (Windows Time Service)
2. FSMO Role Verification
Checks and displays all five FSMO role holders:
- PDC Emulator
- RID Master
- Infrastructure Master
- Schema Master
- Domain Naming Master
Validates that FSMO role holders are online and responding.
3. Replication Status
Uses repadmin to check:
- Replication partners for each DC
- Last replication success/failure
- Replication errors and warnings
- Overall replication summary
4. DCDiag Tests
Runs comprehensive diagnostic tests:
- Connectivity: Network connectivity between DCs
- Replications: Active Directory replication health
- Services: Critical service status
- Advertising: DC advertisement via DNS
- FsmoCheck: FSMO role consistency
5. DNS Health Checks
Validates DNS configuration:
- Tests critical SRV records (
_ldap,_kerberos) - Verifies DNS zone configuration
- Runs comprehensive DCDiag DNS tests
- Checks DNS server responsiveness
6. Time Synchronization
Checks time sync configuration:
- Verifies PDC Emulator time source
- Confirms other DCs sync with PDC
- Identifies time sync issues (Kerberos requires sync within 5 minutes)
7. Event Log Analysis
Scans for critical AD events in the past 7 days:
- Event 2042: Replication hasn’t occurred recently
- Event 1311: Smart card certificate issues
- Event 1645: No writable DCs found
- Event 2087: DNS lookup failures
- Event 2088: Replication access denied
Interpreting Results
Overall Status
- Healthy: No critical issues detected
- Warning: Minor issues found that should be addressed
- Critical: Serious issues requiring immediate attention
Common Issues
| Issue | Possible Cause | Resolution |
|---|---|---|
| Service not running | Service crashed or disabled | Restart service, check event logs |
| Replication failure | Network, DNS, or firewall issue | Run repadmin /syncall, check connectivity |
| DNS SRV records missing | Netlogon service issue | Restart Netlogon, run ipconfig /registerdns |
| Time sync error | W32Time misconfigured | Reconfigure time source on PDC |
| DCDiag test failed | Various (check specific test) | Review test output, consult KB |
Scheduled Health Checks
Create Scheduled Task
Run health checks automatically and email reports:
1# Create scheduled task to run weekly
2$action = New-ScheduledTaskAction -Execute "PowerShell.exe" -Argument "-File C:\Scripts\Test-ADHealth.ps1 -ExportPath C:\Reports\ADHealth.html"
3$trigger = New-ScheduledTaskTrigger -Weekly -DaysOfWeek Monday -At 6am
4$principal = New-ScheduledTaskPrincipal -UserId "DOMAIN\ServiceAccount" -LogonType Password -RunLevel Highest
5
6Register-ScheduledTask -TaskName "AD Health Check" -Action $action -Trigger $trigger -Principal $principal -Description "Weekly Active Directory health check"Email Reports
Combine with email to send reports to administrators:
1$report = "C:\Reports\ADHealth_$(Get-Date -Format 'yyyyMMdd').html"
2.\Test-ADHealth.ps1 -ExportPath $report
3
4Send-MailMessage -From "ad-monitoring@domain.com" `
5 -To "it-team@domain.com" `
6 -Subject "Weekly AD Health Report - $(Get-Date -Format 'yyyy-MM-dd')" `
7 -Body "Please see attached AD health report." `
8 -Attachments $report `
9 -SmtpServer "mail.domain.com"Troubleshooting Common Failures
Replication Failures
1# Force replication from all partners
2repadmin /syncall /AdeP
3
4# Check specific replication error
5repadmin /showrepl /errorsonly
6
7# View replication queue
8repadmin /queueDNS Issues
1# Re-register DC DNS records
2ipconfig /registerdns
3
4# Restart Netlogon to recreate SRV records
5net stop netlogon && net start netlogonService Failures
1# Check service dependencies
2Get-Service -Name NTDS -DependentServices
3
4# Review event logs for service errors
5Get-WinEvent -LogName System | Where-Object {$_.Id -eq 7034 -or $_.Id -eq 7031}Best Practices
- Run Weekly: Schedule automated health checks at least weekly
- Monitor Trends: Compare reports over time to identify degradation
- Alert on Critical: Set up alerts for critical status results
- Document Baseline: Establish baseline health metrics
- Test Recovery: Regularly test backup and recovery procedures
- Patch Management: Keep DCs updated with latest security patches
- Capacity Planning: Monitor disk space, especially NTDS database
See Also
Download
1# Download the script
2Invoke-WebRequest -Uri "https://glyph.sh/scripts/Test-ADHealth.ps1" -OutFile "Test-ADHealth.ps1"
3
4# Run basic health check
5.\Test-ADHealth.ps1
6
7# Run with HTML report export
8.\Test-ADHealth.ps1 -ExportPath "C:\Reports\ADHealth.html"