Short answer: run .\Invoke-ADHealthReport.ps1 as a Domain Admin. For every domain controller it runs dcdiag /q, checks the time source with w32tm and reads the SYSVOL DFSR state, then adds repadmin /replsummary and the FSMO role holders, and saves one colour-coded HTML report. Add -SmtpServer, -From and -To to e-mail it, or just schedule it and open the file each morning.
We ran this script on 6 October 2026 on a Windows Server 2025 Standard domain controller (build 26100.33438, September 2026 update, Windows PowerShell 5.1) and a Windows 11 Pro member PC in our lab domain contoso.com. The example output below is from that run; the lab domain is small and new, so your numbers will be bigger. It was also syntax-checked with PowerShell 7.4.6 and PSScriptAnalyzer 1.25.
Table of Contents
What it does
The daily DC check most admins do by hand is the same handful of commands. This script runs them for all DCs, turns the results into a Pass / Warning / Fail table, and keeps the raw output underneath for when you need detail. It is read-only: it runs diagnostic commands and queries, nothing else.
| Check | How | Pass / Warning / Fail |
|---|---|---|
| dcdiag | dcdiag /s:<DC> /q per DC | Quiet mode prints only errors, so any output is a Fail; the text goes into the report |
| Time source | w32tm /query /computer:<DC> /source | “Local CMOS Clock” or “Free-running System Clock” is a Warning, and a Fail on the forest-root PDC emulator, which should sync from an external source |
| SYSVOL replication | CIM class DfsrReplicatedFolderInfo (namespace root\MicrosoftDfs), folder “SYSVOL Share” | Pass only in state 4 (Normal); 5 (In Error) is a Fail; 0 to 3 and query failures are Warnings |
| Replication | repadmin /replsummary once | Fail if any DC shows failures in the fails/total column or the command fails |
| FSMO roles | Get-ADForest and Get-ADDomain | Listed per DC and in a separate table |
Each DC row also shows site, operating system, global catalog and read-only status from Get-ADDomainController. If you want to know what each dcdiag test means and how to fix the usual failures, read dcdiag and repadmin health check: 7 critical tests explained.
Requirements
- Windows PowerShell 5.1 or PowerShell 7 on Windows, with the ActiveDirectory module.
dcdiag.exeandrepadmin.exe: present on every DC, and on management machines with the RSAT AD DS tools.w32tm.exeis built into Windows. The script stops with a clear message if one is missing (use-SkipDcdiagto run without dcdiag).- A Domain Admin or equivalent account: several dcdiag tests and the DFSR WMI namespace need administrative rights on the DCs.
- WinRM (WS-Management) access to the DCs for the CIM query; it is on by default on Windows Server.
- For e-mail: an SMTP relay that accepts mail from the machine running the script.
Download and first run
- Copy the script from the box on this page (or use the download button) and save it as
C:\Scripts\Invoke-ADHealthReport.ps1. - If you downloaded the file, remove the “downloaded from the internet” mark so the execution policy lets it run:
Unblock-File C:\Scripts\Invoke-ADHealthReport.ps1. - Run it on a domain-joined machine that has the ActiveDirectory module: a domain controller, a management server, or a Windows 11 PC with RSAT (see Install RSAT on Windows 11).
- Read the built-in help once, then run it with no options to see the result on screen before you export anything.
cd C:\Scripts
Get-Help .\Invoke-ADHealthReport.ps1 -Full
.\Invoke-ADHealthReport.ps1
.\Invoke-ADHealthReport.ps1 -OutputPath C:\Reports\ad-health.html
Options
| Parameter | What it does | Default |
|---|---|---|
-Server | Domain to check | Current domain |
-DomainController | Check only these DCs (host names) | All DCs |
-OutputPath | HTML file to write | .\ADHealth-<domain>-<yyyyMMdd-HHmm>.html |
-SkipDcdiag | Skip dcdiag, the slowest check | Off |
-SmtpServer, -From, -To | E-mail the report (all three needed) | No e-mail |
-Port, -UseSsl, -Credential | SMTP port, TLS and authentication | 25, off, none |
-PassThru | Return the per-DC summary objects | Off |
Usage examples
# All DCs, report in the current folder
.\Invoke-ADHealthReport.ps1
# Quick run without dcdiag, fixed file name
.\Invoke-ADHealthReport.ps1 -SkipDcdiag -OutputPath C:\Reports\ad-health.html
# Two DCs only
.\Invoke-ADHealthReport.ps1 -DomainController DC01.contoso.com, DC02.contoso.com
# E-mail through an internal relay
.\Invoke-ADHealthReport.ps1 -SmtpServer relay.contoso.com -From adreport@contoso.com -To admins@contoso.com
# Use the summary in your own monitoring
.\Invoke-ADHealthReport.ps1 -PassThru | Where-Object Overall -ne 'Pass'
About the e-mail option
E-mail uses Send-MailMessage. Microsoft marks that cmdlet as obsolete because it does not guarantee a secure connection to the SMTP server, and there is no direct replacement built into PowerShell. Use it only with an internal relay you trust, add -UseSsl if the relay supports it, or skip e-mail entirely: save the file to a share and open it, or let your monitoring system pick up the -PassThru objects. The subject line starts with PASS, WARNING or FAIL so mail rules can sort it.
What is in the report
The example output further down is from our lab run. The HTML file contains:
- A header with domain, forest, time and number of DCs checked.
- The domain controller table, one row per DC with these columns: DomainController, Site, OperatingSystem, GlobalCatalog, ReadOnly, FSMORoles, Dcdiag, TimeSource, TimeStatus, SysvolDfsr, SysvolStatus, Overall. Status cells are coloured green, amber or red.
- The FSMO role holders table (schema master, domain naming master, PDC emulator, RID master, infrastructure master).
- The replication status and the full
repadmin /replsummaryoutput. - For each DC that failed dcdiag, the error text dcdiag printed.
-PassThru returns the same per-DC columns as objects.
Example output
Run with -OutputPath C:\srvs-lab\out\ad-health.html -PassThru on the single lab DC (3 seconds):
Report saved to C:\srvs-lab\out\ad-health.html
DomainController Site OperatingSystem GlobalCatalog ReadOnly FSMORoles
---------------- ---- --------------- ------------- -------- ---------
WinSrv.contoso.com Default-First-Site-Name Windows Server 2025 Standard True False Schema master, Domain naming master, PDC emulator, RID master, Infrastructure master
With one DC there is no replication partner, so the replication section of the HTML report is empty. On a real domain it lists each partner and its last result.
Schedule it
Run it every morning before the helpdesk opens. The task account needs Domain Admin-level rights for dcdiag, so a gMSA in Domain Admins is the usual choice; restrict which servers may use that gMSA (see our gMSA guide) and run the task only on a hardened management server or a DC.
$action = New-ScheduledTaskAction -Execute 'powershell.exe' `
-Argument '-NoProfile -ExecutionPolicy Bypass -File C:\Scripts\Invoke-ADHealthReport.ps1 -OutputPath C:\Reports\ad-health.html -SmtpServer relay.contoso.com -From adreport@contoso.com -To admins@contoso.com'
$trigger = New-ScheduledTaskTrigger -Daily -At 6:30am
$principal = New-ScheduledTaskPrincipal -UserId 'CONTOSO\gmsa-adreport$' -LogonType Password
Register-ScheduledTask -TaskName 'AD daily health report' -Action $action -Trigger $trigger -Principal $principal
With a fixed -OutputPath the file is overwritten daily; leave -OutputPath out to get a new time-stamped file each run (and clean the folder now and then).
How it works
- Gets the domain, forest and DC list with
Get-ADDomain,Get-ADForestandGet-ADDomainController -Filter *, and builds the FSMO map. - For each DC runs the three per-DC checks. Native commands are called with stderr captured as text, so a failing command produces a status instead of stopping the script.
- Reads SYSVOL state with
Get-CimInstance -ComputerName <DC> -Namespace root/MicrosoftDfs -ClassName DfsrReplicatedFolderInfoand keeps the “SYSVOL Share” folder. No result means SYSVOL is not replicated by DFSR (still on FRS) or the namespace is not there. - Runs
repadmin /replsummaryonce and checks every “fails / total” pair. - Builds the HTML with encoded text (no external CSS or scripts), saves it, and optionally sends it.
Limitations
- dcdiag is run with its default tests. Use
dcdiag /cby hand when you need the comprehensive set. - The repadmin check reads the text output; the error column is shown but not parsed, so read the raw block when it says Fail.
- Replication is summarised forest-wide from the machine you run it on; very large forests may take a while.
- DNS, certificate expiry and backup age are not checked.
- Not yet run against a live domain (see the note at the top).
Official documentation: dcdiag (Microsoft Learn) · repadmin /replsummary · Windows Time service tools (w32tm) · Send-MailMessage (obsolete note)
Related: dcdiag repadmin Health Check: 7 Critical Tests Explained · AD Replication Error 1722 and 8453: Fixes · PDC Emulator NTP Time Sync: Reliable Domain Time · Transfer FSMO Roles with PowerShell and ntdsutil: Safe Steps · Restore Domain Controller Backups: System State and DSRM Steps
See also: Export AD Users to CSV: PowerShell Script with Last Logon · Locked Out AD Users Report: PowerShell Script with Lockout Source · AD Privileged Group Report: Domain Admins and adminCount Audit · AD Nested Group Membership: PowerShell Tree with Loop Detection · Inactive AD Accounts Report: PowerShell Script with Safe Disable
The script
# AD Health Check Report: dcdiag and repadmin PowerShell Script (v1.0.0) - from srvScripts.com
# Source, docs and updates: https://srvscripts.com/scripts/ad-health-check-report/
# Copyright (c) 2026 srvScripts.com. MIT licence: if you copy, share or adapt this script, keep this notice and credit srvScripts.com.
<#
.SYNOPSIS
One HTML health report for every domain controller: dcdiag, replication summary, FSMO holders, time source
and SYSVOL (DFSR) state. Optionally e-mails it.
.DESCRIPTION
Read-only. For each DC in the domain (Get-ADDomainController -Filter *) the script runs:
dcdiag /s:<DC> /q quiet mode: prints only errors, so empty output = all default tests passed
w32tm /query /computer:<DC> /source
DfsrReplicatedFolderInfo (CIM, namespace root\MicrosoftDfs) state of the "SYSVOL Share" folder:
0 Uninitialized, 1 Initialized, 2 Initial Sync, 3 Auto Recovery,
4 Normal, 5 In Error
Once per run it also captures:
repadmin /replsummary largest replication delta and failures per DC
FSMO role holders from Get-ADForest and Get-ADDomain
The HTML report starts with a summary table (one row per DC, Pass / Warning / Fail per check) and keeps
the raw dcdiag and repadmin output underneath for anyone who needs the detail.
E-mail: with -SmtpServer, -To and -From the report is sent with Send-MailMessage. Microsoft marks
Send-MailMessage as obsolete because it does not guarantee a secure connection to the SMTP server; use it
only with an internal relay you trust, or leave the e-mail options out and collect the saved file.
.PARAMETER Server
Domain to check (default: current domain).
.PARAMETER DomainController
Check only these DCs (host names) instead of all DCs in the domain.
.PARAMETER OutputPath
Where to save the HTML report (default: .\ADHealth-<domain>-<yyyyMMdd-HHmm>.html).
.PARAMETER SkipDcdiag
Do not run dcdiag (it is the slowest check, run once per DC).
.PARAMETER SmtpServer
SMTP relay to send the report through. Requires -To and -From.
.PARAMETER To
Recipient address(es).
.PARAMETER From
Sender address.
.PARAMETER Port
SMTP port (default 25).
.PARAMETER UseSsl
Use TLS for the SMTP connection.
.PARAMETER Credential
SMTP credentials, if the relay needs authentication.
.PARAMETER PassThru
Output the per-DC summary objects to the pipeline.
.EXAMPLE
.\Invoke-ADHealthReport.ps1
.EXAMPLE
.\Invoke-ADHealthReport.ps1 -OutputPath C:\Reports\ad-health.html -SkipDcdiag
.EXAMPLE
.\Invoke-ADHealthReport.ps1 -SmtpServer relay.contoso.com -From adreport@contoso.com -To admins@contoso.com
.NOTES
Name: Invoke-ADHealthReport.ps1
Version: 1.0.0
Source: https://srvscripts.com/scripts/ad-health-check-report/
License: MIT
Requires: Windows PowerShell 5.1 or PowerShell 7 on Windows, ActiveDirectory module and the AD DS command-line
tools (dcdiag.exe, repadmin.exe: RSAT AD DS tools or a DC), w32tm.exe (built in), a Domain Admin
or equivalent account (dcdiag and the DFSR WMI namespace need admin rights on the DCs), and
WinRM/DCOM access to the DCs for the CIM query.
#>
[CmdletBinding()]
param(
[ValidateNotNullOrEmpty()]
[string]$Server,
[ValidateNotNullOrEmpty()]
[string[]]$DomainController,
[ValidateNotNullOrEmpty()]
[string]$OutputPath,
[switch]$SkipDcdiag,
[ValidateNotNullOrEmpty()]
[string]$SmtpServer,
[ValidateNotNullOrEmpty()]
[string[]]$To,
[ValidateNotNullOrEmpty()]
[string]$From,
[ValidateRange(1, 65535)]
[int]$Port = 25,
[switch]$UseSsl,
[System.Management.Automation.PSCredential]
[System.Management.Automation.Credential()]
$Credential = [System.Management.Automation.PSCredential]::Empty,
[switch]$PassThru
)
Set-StrictMode -Version 2
$ErrorActionPreference = 'Stop'
if (-not (Get-Module -ListAvailable -Name ActiveDirectory)) {
throw "The ActiveDirectory module is not installed. Install RSAT: Active Directory Domain Services tools, then run again."
}
Import-Module ActiveDirectory -Verbose:$false
if ($SmtpServer -and (-not $To -or -not $From)) { throw "-SmtpServer needs -To and -From." }
foreach ($exe in 'repadmin.exe', 'w32tm.exe') {
if (-not (Get-Command $exe -ErrorAction SilentlyContinue)) { throw "$exe not found. Run on a DC or install RSAT: Active Directory Domain Services tools." }
}
if (-not $SkipDcdiag -and -not (Get-Command 'dcdiag.exe' -ErrorAction SilentlyContinue)) {
throw "dcdiag.exe not found. Run on a DC, install RSAT: Active Directory Domain Services tools, or use -SkipDcdiag."
}
function ConvertTo-HtmlText { param([string]$Text) [System.Net.WebUtility]::HtmlEncode($Text) }
function Invoke-Native {
param([string]$FilePath, [string[]]$ArgumentList)
# Windows PowerShell 5.1 turns redirected stderr into error records; do not let them stop the script.
$ErrorActionPreference = 'Continue'
$text = (& $FilePath @ArgumentList 2>&1 | ForEach-Object { [string]$_ }) -join "`n"
[pscustomobject]@{ ExitCode = $LASTEXITCODE; Output = $text.Trim() }
}
# ---- Domain, forest, DCs, FSMO --------------------------------------------------------------------------
$domArgs = @{}
if ($Server) { $domArgs.Server = $Server }
$domain = Get-ADDomain @domArgs
$forest = Get-ADForest -Server $domain.DNSRoot
$dcs = @(Get-ADDomainController -Filter * -Server $domain.DNSRoot | Sort-Object HostName)
if ($DomainController) {
$dcs = @($dcs | Where-Object { $DomainController -contains $_.HostName -or $DomainController -contains $_.Name })
if (-not $dcs.Count) { throw "None of the -DomainController names is a DC of $($domain.DNSRoot)." }
}
if (-not $dcs.Count) { throw "No domain controllers returned for $($domain.DNSRoot)." }
$fsmo = [ordered]@{
'Schema master' = $forest.SchemaMaster
'Domain naming master' = $forest.DomainNamingMaster
'PDC emulator' = $domain.PDCEmulator
'RID master' = $domain.RIDMaster
'Infrastructure master' = $domain.InfrastructureMaster
}
if (-not $OutputPath) { $OutputPath = Join-Path (Get-Location).Path ("ADHealth-{0}-{1:yyyyMMdd-HHmm}.html" -f $domain.DNSRoot, (Get-Date)) }
# ---- Per-DC checks --------------------------------------------------------------------------------------
$summary = [System.Collections.Generic.List[object]]::new()
$details = [System.Collections.Generic.List[string]]::new()
$dfsrStates = @{ 0 = 'Uninitialized'; 1 = 'Initialized'; 2 = 'Initial Sync'; 3 = 'Auto Recovery'; 4 = 'Normal'; 5 = 'In Error' }
$i = 0
foreach ($dc in $dcs) {
$i++
$h = $dc.HostName
Write-Progress -Activity 'AD health check' -Status $h -PercentComplete (100 * ($i - 1) / $dcs.Count)
$roles = @($fsmo.Keys | Where-Object { $fsmo[$_] -eq $h })
# dcdiag
$dcdiagStatus = 'Skipped'; $dcdiagOut = ''
if (-not $SkipDcdiag) {
Write-Verbose "dcdiag /s:$h /q"
$r = Invoke-Native 'dcdiag.exe' @("/s:$h", '/q')
$dcdiagOut = $r.Output
$dcdiagStatus = if (-not $r.Output) { 'Pass' } else { 'Fail' }
}
# time source
$t = Invoke-Native 'w32tm.exe' @('/query', "/computer:$h", '/source')
$timeSource = $t.Output
$timeStatus = 'Pass'
if ($t.ExitCode -ne 0 -or -not $timeSource) { $timeStatus = 'Fail' }
elseif ($timeSource -match 'Local CMOS Clock|Free-running System Clock') {
$timeStatus = if ($h -eq $domain.PDCEmulator -and $domain.DNSRoot -eq $forest.RootDomain) { 'Fail' } else { 'Warning' }
}
# SYSVOL DFSR state
$sysvol = 'Unknown'; $sysvolStatus = 'Warning'
try {
$rf = @(Get-CimInstance -ComputerName $h -Namespace 'root/MicrosoftDfs' -ClassName 'DfsrReplicatedFolderInfo' -OperationTimeoutSec 60 |
Where-Object { $_.ReplicatedFolderName -eq 'SYSVOL Share' })
if ($rf.Count) {
$st = [int]$rf[0].State
$sysvol = if ($dfsrStates.ContainsKey($st)) { $dfsrStates[$st] } else { "State $st" }
$sysvolStatus = if ($st -eq 4) { 'Pass' } elseif ($st -eq 5) { 'Fail' } else { 'Warning' }
} else {
$sysvol = 'No DFSR SYSVOL folder found (FRS-replicated SYSVOL, or DFSR not set up)'
}
} catch {
$sysvol = "Query failed: $($_.Exception.Message)"
}
$overall = if (@($dcdiagStatus, $timeStatus, $sysvolStatus) -contains 'Fail') { 'Fail' }
elseif (@($dcdiagStatus, $timeStatus, $sysvolStatus) -contains 'Warning') { 'Warning' } else { 'Pass' }
$summary.Add([pscustomobject]@{
DomainController = $h
Site = $dc.Site
OperatingSystem = $dc.OperatingSystem
GlobalCatalog = $dc.IsGlobalCatalog
ReadOnly = $dc.IsReadOnly
FSMORoles = ($roles -join ', ')
Dcdiag = $dcdiagStatus
TimeSource = $timeSource
TimeStatus = $timeStatus
SysvolDfsr = $sysvol
SysvolStatus = $sysvolStatus
Overall = $overall
})
if ($dcdiagOut) { $details.Add("<h3>dcdiag errors: $(ConvertTo-HtmlText $h)</h3><pre>$(ConvertTo-HtmlText $dcdiagOut)</pre>") }
}
Write-Progress -Activity 'AD health check' -Completed
# ---- Replication summary --------------------------------------------------------------------------------
Write-Verbose 'repadmin /replsummary'
$rep = Invoke-Native 'repadmin.exe' @('/replsummary')
$repStatus = 'Pass'
# Each DC row has a "fails / total" pair. Any row with fails above zero, or a non-zero exit code, is a failure.
$failCounts = @(($rep.Output -split "`n") | Where-Object { $_ -notmatch 'Start Time' } | ForEach-Object {
$m = [regex]::Match($_, '(?<![\d/])(\d+)\s*/\s*(\d+)\s+(\d{1,3})(?![\d/:])')
if ($m.Success) { [int]$m.Groups[1].Value }
})
if ($rep.ExitCode -ne 0 -or -not $rep.Output) { $repStatus = 'Fail' }
elseif (@($failCounts | Where-Object { $_ -gt 0 }).Count) { $repStatus = 'Fail' }
elseif ($rep.Output -match 'operational errors') { $repStatus = 'Warning' }
# ---- HTML -----------------------------------------------------------------------------------------------
$css = @'
body{font-family:Segoe UI,Arial,sans-serif;font-size:13px;margin:20px;color:#222}
table{border-collapse:collapse;margin-bottom:16px}th,td{border:1px solid #ccc;padding:4px 8px;text-align:left;vertical-align:top}
th{background:#f0f0f0}pre{background:#f7f7f7;border:1px solid #ddd;padding:8px;overflow:auto;font-size:12px}
.Pass{background:#e3f4e3}.Warning{background:#fff4d6}.Fail{background:#fbe0e0}
'@
$cell = { param($v) $c = [string]$v; if ($c -in 'Pass', 'Warning', 'Fail') { "<td class=`"$c`">$c</td>" } else { "<td>$(ConvertTo-HtmlText $c)</td>" } }
$cols = 'DomainController', 'Site', 'OperatingSystem', 'GlobalCatalog', 'ReadOnly', 'FSMORoles', 'Dcdiag', 'TimeSource', 'TimeStatus', 'SysvolDfsr', 'SysvolStatus', 'Overall'
$sb = New-Object System.Text.StringBuilder
[void]$sb.Append("<!DOCTYPE html><html><head><meta charset=`"utf-8`"><title>AD health report $(ConvertTo-HtmlText $domain.DNSRoot)</title><style>$css</style></head><body>")
[void]$sb.Append(("<h1>AD health report: {0}</h1><p>Generated {1:yyyy-MM-dd HH:mm} on {2}. Forest {3}, {4} DC(s) checked.</p>" -f (ConvertTo-HtmlText $domain.DNSRoot), (Get-Date), $env:COMPUTERNAME, (ConvertTo-HtmlText $forest.Name), $summary.Count))
[void]$sb.Append('<h2>Domain controllers</h2><table><tr>' + (($cols | ForEach-Object { "<th>$_</th>" }) -join '') + '</tr>')
foreach ($s in $summary) { [void]$sb.Append('<tr>' + (($cols | ForEach-Object { & $cell $s.$_ }) -join '') + '</tr>') }
[void]$sb.Append('</table><h2>FSMO role holders</h2><table><tr><th>Role</th><th>Holder</th></tr>')
foreach ($k in $fsmo.Keys) { [void]$sb.Append("<tr><td>$k</td><td>$(ConvertTo-HtmlText $fsmo[$k])</td></tr>") }
[void]$sb.Append("</table><h2>Replication summary (repadmin /replsummary)</h2><table><tr><th>Status</th></tr><tr>$(& $cell $repStatus)</tr></table><pre>$(ConvertTo-HtmlText $rep.Output)</pre>")
if ($details.Count) { [void]$sb.Append('<h2>dcdiag details</h2>' + ($details -join '')) }
[void]$sb.Append('<p>dcdiag ran in quiet mode (/q): only failing tests print text. Status rules: time source "Local CMOS Clock" or "Free-running System Clock" is a warning on a DC and a failure on the forest-root PDC emulator; SYSVOL is Pass only in DFSR state 4 (Normal).</p></body></html>')
$sb.ToString() | Out-File -LiteralPath $OutputPath -Encoding utf8
Write-Information ("Report saved to {0}" -f $OutputPath) -InformationAction Continue
# ---- Optional e-mail ------------------------------------------------------------------------------------
if ($SmtpServer) {
$states = @($summary | ForEach-Object { $_.Overall })
$worst = if ($states -contains 'Fail' -or $repStatus -eq 'Fail') { 'FAIL' } elseif ($states -contains 'Warning' -or $repStatus -eq 'Warning') { 'WARNING' } else { 'PASS' }
$mail = @{
SmtpServer = $SmtpServer; Port = $Port; To = $To; From = $From
Subject = "AD health $($domain.DNSRoot): $worst"; Body = $sb.ToString(); BodyAsHtml = $true; Attachments = $OutputPath
}
if ($UseSsl) { $mail.UseSsl = $true }
if ($Credential -ne [System.Management.Automation.PSCredential]::Empty) { $mail.Credential = $Credential }
try {
Send-MailMessage @mail -WarningAction SilentlyContinue
Write-Information "Report e-mailed to $($To -join ', ')" -InformationAction Continue
} catch {
Write-Warning "E-mail failed: $($_.Exception.Message). The report is still saved at $OutputPath."
}
}
if ($PassThru) { return $summary }
$summary | Format-Table DomainController, Dcdiag, TimeStatus, SysvolStatus, Overall, FSMORoles -AutoSize
Write-Information "Replication summary: $repStatus" -InformationAction Continue
002f207dce5c5e970985b8293f36228966f7f90967ed0e4578016f7d8a850250curl -fsSL -o Invoke-ADHealthReport.ps1 https://scr.srvscripts.com/ad-health-check-report/Invoke-ADHealthReport.ps1 && curl -fsSL https://scr.srvscripts.com/ad-health-check-report/Invoke-ADHealthReport.ps1.sha256 | sha256sum -cInvoke-WebRequest -Uri 'https://scr.srvscripts.com/ad-health-check-report/Invoke-ADHealthReport.ps1' -OutFile 'Invoke-ADHealthReport.ps1'; if ((Get-FileHash 'Invoke-ADHealthReport.ps1' -Algorithm SHA256).Hash -eq '002F207DCE5C5E970985B8293F36228966F7F90967ED0E4578016F7D8A850250') { 'OK: the file is intact' } else { 'MISMATCH: do not run this file' }Copy the whole line. In Windows PowerShell, curl and sha256sum are not the Linux tools, so use the PowerShell line there.Also on GitHub: github.com/srvscripts/scripts
Frequently asked questions
How do I check Active Directory health with PowerShell?
Run Invoke-ADHealthReport.ps1. It wraps dcdiag, repadmin /replsummary and w32tm, reads SYSVOL DFSR state and FSMO holders, and writes one HTML report for all DCs.
Why does the report say the time source is a warning?
A DC that reports Local CMOS Clock or Free-running System Clock is not syncing from the domain hierarchy or an external NTP server. Only the forest-root PDC emulator should sync externally; the rest should follow the domain hierarchy.
What does SYSVOL state 4 mean?
State 4 is Normal for the DfsrReplicatedFolderInfo class. 5 means In Error, and 0 to 3 mean the folder is still initialising or recovering.
Is Send-MailMessage safe to use?
Microsoft marks it obsolete because it cannot guarantee a secure SMTP connection. Use it only with a trusted internal relay, or skip e-mail and save the report to a share.
How long does the script take?
Most of the time goes to dcdiag, the slowest check, which runs once per DC. Use -SkipDcdiag for a quick check of time, SYSVOL and replication.
Does it fix anything?
No. It only runs diagnostic commands and queries and writes the report.