Daily Active Directory Monitoring Made Easy with PowerShell
If you’re a Domain Admin responsible for maintaining the health of your Active Directory environment, running dcdiag and repadmin commands daily can be time-consuming. To simplify this routine task, I’ve created a PowerShell script that automates these checks and generates a clear, HTML-based report. This makes daily AD monitoring faster, easier, and far more efficient — a real time-saver for any IT admin
Pre-requisites
- GMSA Account to run the Scheduled Tasks
- Domain Admin rights for the GMSA Account
- Valid Email address and SMTP Address
Usage
Copy the Powershell code
Edit the email settings in the highlighted part
Run the Powershell
Single Domain
########################################################################################################
# Author: Karanth
# Modified : 19/6/2025
# Usage : To gather Domain Controllers and their Status
########################################################################################################
###########################Define Variables#####################################################
<#
.SYNOPSIS
Get-ADHealth.ps1
#>
# Defining the Variables
$allTestedDomainControllers = [System.Collections.Generic.List[Object]]::new()
$allDomainControllers = [System.Collections.Generic.List[Object]]::new()
$now = Get-Date
$date = $now.ToShortDateString()
$reportTime = $now
$reportFileNameTime = $now.ToString("yyyyMMdd_HHmmss")
$reportemailsubject = "Active Directory Health Check for $allDomains"
$smtpsettings = @{
To = 'XXYY@xx.com '
From = 'XXYY@xx.com'
Subject = "$reportemailsubject - $date"
SmtpServer = "XXYY@xx.com"
Port = "25"
}
# Funtion to gather Domain Info
Function Get-AllDomains() {
Write-Verbose "Running function Get-AllDomains"
$allDomains = (Get-ADForest).Domains
return $allDomains
}
# Funtion to gather Domain Controllers name
Function Get-AllDomainControllers ($ComputerName) {
Write-Verbose "Running function Get-AllDomainControllers"
$allDomainControllers = Get-ADDomainController -Filter * -Server $ComputerName | Sort-Object HostName
return $allDomainControllers
}
# Funtion to gather Domain Controllers Uptime
Function Get-DomainControllerUptimeDays {
param($ComputerName)
Write-Verbose "Running function Get-DomainControllerUptimeDays"
if (Test-Connection $ComputerName -Count 1 -Quiet) {
try {
$W32OS = Get-WmiObject Win32_OperatingSystem -ComputerName $ComputerName -ErrorAction Stop
if ($W32OS) {
$uptimeDays = ($W32OS | Select-Object -ExpandProperty LastBootUpTime)
$lastBoot = $W32OS.ConvertToDateTime($uptimeDays)
$uptime = (New-TimeSpan -Start $lastBoot -End (Get-Date)).Days
} else {
$uptime = "Data Error"
}
} catch {
$uptime = 'WMI Failure'
}
}
else {
$uptime = 'Fail'
}
return $uptime
}
# Funtion to gather NETLOGON and NTDS Services
Function Get-DomainControllerServices($ComputerName) {
Write-Verbose "Running function DomainControllerServices"
$thisDomainControllerServicesTestResult = [PSCustomObject]@{
NTDSService = $null
NETLOGONService = $null
}
if ((Test-Connection $ComputerName -Count 1 -quiet) -eq $True) {
if ((Get-Service -ComputerName $ComputerName -Name NTDS -ErrorAction SilentlyContinue).Status -eq 'Running') {
$thisDomainControllerServicesTestResult.NTDSService = 'Success'
}
else {
$thisDomainControllerServicesTestResult.NTDSService = 'Fail'
}
if ((Get-Service -ComputerName $ComputerName -Name netlogon -ErrorAction SilentlyContinue).Status -eq 'Running') {
$thisDomainControllerServicesTestResult.NETLOGONService = 'Success'
}
else {
$thisDomainControllerServicesTestResult.NETLOGONService = 'Fail'
}
}
else {
$thisDomainControllerServicesTestResult.NTDSService = 'Fail'
$thisDomainControllerServicesTestResult.NETLOGONService = 'Fail'
}
return $thisDomainControllerServicesTestResult
}
# Funtion to gather DCDIAG results
Function Get-DomainControllerDCDiagTestResults($ComputerName) {
Write-Verbose "Running function Get-DomainControllerDCDiagTestResults"
$DCDiagTestResults = [PSCustomObject]@{
ServerName = $ComputerName
Connectivity = $null
Advertising = $null
FrsEvent = $null
DFSREvent = $null
SysVolCheck = $null
KccEvent = $null
KnowsOfRoleHolders = $null
NetLogons = $null
ObjectsReplicated = $null
Replications = $null
RidManager = $null
Services = $null
}
if ((Test-Connection $ComputerName -Count 1 -quiet) -eq $True) {
$params = @(
"/s:$ComputerName",
"/test:Connectivity",
"/test:Advertising",
"/test:FrsEvent",
"/test:DFSREvent",
"/test:SysVolCheck",
"/test:KccEvent",
"/test:KnowsOfRoleHolders",
"/test:NetLogons",
"/test:ObjectsReplicated",
"/test:Replications",
"/test:RidManager",
"/test:Services"
)
$DCDiagTest = (Dcdiag.exe @params) -split ('[\r\n]')
$TestName = $null
$TestStatus = $null
$DCDiagTest | ForEach-Object {
switch -Regex ($_) {
"Starting test:" {
$TestName = ($_ -replace ".*Starting test:").Trim()
}
"passed test|failed test" {
$TestStatus = if ($_ -match "passed test") { "Passed" } else { "Failed" }
}
}
if ($TestName -and $TestStatus) {
$DCDiagTestResults.$TestName = $TestStatus
$TestName = $null
$TestStatus = $null
}
}
}
else {
foreach ($property in $DCDiagTestResults.PSObject.Properties.Name) {
if ($property -ne "ServerName") {
$DCDiagTestResults.$property = "Failed"
}
}
}
return $DCDiagTestResults
}
# Funtion to gather Free space in C drive
Function Get-DomainControllerOSDriveFreeSpaceGB {
Param($ComputerName)
Write-Verbose "Running function Get-DomainControllerOSDriveFreeSpaceGB"
if (Test-Connection $ComputerName -Count 1 -Quiet) {
try {
$os = Get-WmiObject -Class Win32_OperatingSystem -ComputerName $ComputerName -ErrorAction Stop
$osDriveLetter = $os.SystemDrive
$osDrive = Get-WmiObject -Class Win32_LogicalDisk -ComputerName $ComputerName `
-Filter "DeviceID='$osDriveLetter'" -ErrorAction Stop
$freeSpaceGB = [math]::Round($osDrive.FreeSpace / 1GB, 2)
}
catch {
$freeSpaceGB = 'WMI Failure'
}
}
else {
$freeSpaceGB = 'Fail'
}
return $freeSpaceGB
}
# Funtion to gather Domain Controllers CPU Usage
Function Get-DomainControllerCPUUsage {
Param($ComputerName)
Write-Verbose "Running function Get-DomainControllerCPUUsage"
if (Test-Connection $ComputerName -Count 1 -Quiet) {
try {
$avgProc = Get-WmiObject -Class win32_processor -ComputerName $ComputerName -ErrorAction Stop |
Measure-Object -Property LoadPercentage -Average |
Select-Object -ExpandProperty Average
$cpuLoad = [math]::Round($avgProc, 2)
}
catch {
$cpuLoad = 'WMI Failure'
}
}
else {
$cpuLoad = 'Fail'
}
return $cpuLoad
}
# Funtion to gather Memory Usage
Function Get-DomainControllerMemoryUsage {
Param($ComputerName)
Write-Verbose "Running function Get-DomainControllerMemoryUsage"
if (Test-Connection $ComputerName -Count 1 -Quiet) {
try {
$os = Get-WmiObject -Class Win32_OperatingSystem -ComputerName $ComputerName -ErrorAction Stop
$total = $os.TotalVisibleMemorySize
$free = $os.FreePhysicalMemory
$used = $total - $free
$memPercentUsed = [math]::Round(($used / $total) * 100, 2)
}
catch {
$memPercentUsed = 'WMI Failure'
}
}
else {
$memPercentUsed = 'Fail'
}
return $memPercentUsed
}
# Funtion to put all the gathered information into HTML
Function New-ServerHealthHTMLTableCell() {
param( $lineitem )
$htmltablecell = $null
switch ($($reportline."$lineitem")) {
"Success" { $htmltablecell = "<td class=""pass"">$($reportline."$lineitem")</td>" }
"Passed" { $htmltablecell = "<td class=""pass"">$($reportline."$lineitem")</td>" }
"Pass" { $htmltablecell = "<td class=""pass"">$($reportline."$lineitem")</td>" }
"Warn" { $htmltablecell = "<td class=""warn"">$($reportline."$lineitem")</td>" }
"Fail" { $htmltablecell = "<td class=""fail"">$($reportline."$lineitem")</td>" }
"Failed" { $htmltablecell = "<td class=""fail"">$($reportline."$lineitem")</td>" }
"Could not test server uptime." { $htmltablecell = "<td class=""fail"">$($reportline."$lineitem")</td>" }
default { $htmltablecell = "<td>$($reportline."$lineitem")</td>" }
}
return $htmltablecell
}
if (!($DomainName)) {
Write-Host "No domain specified, using all domains in forest" -ForegroundColor Yellow
$allDomains = Get-AllDomains
$reportFileName = 'forest_health_report_' + (Get-ADForest).name + '_' + $reportFileNameTime + '.html'
}
else {
Write-Host "Domain name specified on cmdline" -ForegroundColor Cyan
$allDomains = $DomainName
$reportFileName = 'dc_health_report_' + $DomainName + '_' + $reportFileNameTime + '.html'
}
foreach ($domain in $allDomains) {
Write-Host "Testing domain" $domain -ForegroundColor Green
$allDomainControllers = Get-AllDomainControllers $domain
$allDomainControllers = @($allDomainControllers)
$totalDCs = $allDomainControllers.Count
$currentDCNumber = 0
foreach ($domainController in $allDomainControllers) {
$currentDCNumber++
$stopWatch = [system.diagnostics.stopwatch]::StartNew()
Write-Host "Testing domain controller ($currentDCNumber of $totalDCs) $($domainController.HostName)" -ForegroundColor Cyan
$DCDiagTestResults = Get-DomainControllerDCDiagTestResults $domainController.HostName
$svcResults = Get-DomainControllerServices $domainController.HostName
$thisDomainController = [PSCustomObject]@{
Server = ($domainController.HostName).ToLower()
Site = $domainController.Site
"IPv4 Address" = $domainController.IPv4Address
"OS Free Space (GB)" = Get-DomainControllerOSDriveFreeSpaceGB $domainController.HostName
"CPU Usage (%)" = Get-DomainControllerCPUUsage $domainController.HostName
"Memory Usage (%)" = Get-DomainControllerMemoryUsage $domainController.HostName
"Uptime (days)" = Get-DomainControllerUptimeDays $domainController.HostName
"NTDS Service" = $svcResults.NTDSService
"NetLogon Service" = $svcResults.NETLOGONService
"DCDIAG: Connectivity" = $DCDiagTestResults.Connectivity
"DCDIAG: Advertising" = $DCDiagTestResults.Advertising
"DCDIAG: FrsEvent" = $DCDiagTestResults.FrsEvent
"DCDIAG: DFSREvent" = $DCDiagTestResults.DFSREvent
"DCDIAG: SysVolCheck" = $DCDiagTestResults.SysVolCheck
"DCDIAG: KccEvent" = $DCDiagTestResults.KccEvent
"DCDIAG: FSMO " = $DCDiagTestResults.KnowsOfRoleHolders
"DCDIAG: NetLogons" = $DCDiagTestResults.NetLogons
"DCDIAG: ObjectsReplicated" = $DCDiagTestResults.ObjectsReplicated
"DCDIAG: Replications" = $DCDiagTestResults.Replications
"DCDIAG: RidManager" = $DCDiagTestResults.RidManager
"DCDIAG: Services" = $DCDiagTestResults.Services
}
$allTestedDomainControllers.Add($thisDomainController)
}
}
# Modified HTML and table creation: Outlook-friendly
$htmlhead = @"
<html>
<body style='font-family:Segoe UI,Tahoma,Geneva,Verdana,sans-serif;font-size:10pt;'>
<h1 style='font-size:20px;text-align:left;'>Domain Controller Health Check Report</h1>
<h3 style='font-size:14px;text-align:left;'>Generated: $reportTime</h3>
"@
$htmltableheader = @"
<h3 style='font-size:14px;color:#333;margin-bottom:3px;'>Domain Controller Health Summary</h3>
<h3 style='font-size:12px;margin-top:0;font-weight:normal;'>Forest: $((Get-ADForest).Name)</h3>
<table border='1' cellpadding='0' cellspacing='0' style='width:100%;border-collapse:collapse;font-size:10pt;'>
<tr style='background-color:#f2f2f2;'>
<th style='border:1px solid #ccc; padding:6px; color:#000; text-align:center;'>Server</th>
<th style='border:1px solid #ccc; padding:6px; color:#000; text-align:center;'>Site</th>
<th style='border:1px solid #ccc; padding:6px; color:#000; text-align:center;'>IPv4 Address</th>
<th style='border:1px solid #ccc; padding:6px; color:#000; text-align:center;'>OS Free Space (GB)</th>
<th style='border:1px solid #ccc; padding:6px; color:#000; text-align:center;'>CPU Usage (%)</th>
<th style='border:1px solid #ccc; padding:6px; color:#000; text-align:center;'>Memory Usage (%)</th>
<th style='border:1px solid #ccc; padding:6px; color:#000; text-align:center;'>Uptime (days)</th>
<th style='border:1px solid #ccc; padding:6px; color:#000; text-align:center;'>NTDS Service</th>
<th style='border:1px solid #ccc; padding:6px; color:#000; text-align:center;'>NetLogon Service</th>
<th style='border:1px solid #ccc; padding:6px; color:#000; text-align:center;'>DCDIAG: Connectivity</th>
<th style='border:1px solid #ccc; padding:6px; color:#000; text-align:center;'>DCDIAG: Advertising</th>
<th style='border:1px solid #ccc; padding:6px; color:#000; text-align:center;'>DCDIAG: FrsEvent</th>
<th style='border:1px solid #ccc; padding:6px; color:#000; text-align:center;'>DCDIAG: DFSREvent</th>
<th style='border:1px solid #ccc; padding:6px; color:#000; text-align:center;'>DCDIAG: SysVolCheck</th>
<th style='border:1px solid #ccc; padding:6px; color:#000; text-align:center;'>DCDIAG: KccEvent</th>
<th style='border:1px solid #ccc; padding:6px; color:#000; text-align:center;'>DCDIAG: FSMO</th>
<th style='border:1px solid #ccc; padding:6px; color:#000; text-align:center;'>DCDIAG: NetLogons</th>
<th style='border:1px solid #ccc; padding:6px; color:#000; text-align:center;'>DCDIAG: ObjectsReplicated</th>
<th style='border:1px solid #ccc; padding:6px; color:#000; text-align:center;'>DCDIAG: Replications</th>
<th style='border:1px solid #ccc; padding:6px; color:#000; text-align:center;'>DCDIAG: RidManager</th>
<th style='border:1px solid #ccc; padding:6px; color:#000; text-align:center;'>DCDIAG: Services</th>
</tr>
"@
Function New-ServerHealthHTMLTableCell() {
param( $lineitem )
$cellValue = $reportline."$lineitem"
switch ($cellValue) {
"Success" { return "<td style='border:1px solid #ccc;padding:6px; background-color:#6BBF59; color:#000;text-align:center;'>$cellValue</td>" }
"Passed" { return "<td style='border:1px solid #ccc;padding:6px; background-color:#6BBF59; color:#000;text-align:center;'>$cellValue</td>" }
"Pass" { return "<td style='border:1px solid #ccc;padding:6px; background-color:#6BBF59; color:#000;text-align:center;'>$cellValue</td>" }
"Warn" { return "<td style='border:1px solid #ccc;padding:6px; background-color:#FFD966; color:#000;text-align:center;'>$cellValue</td>" }
"Fail" { return "<td style='border:1px solid #ccc;padding:6px; background-color:#D9534F; color:#fff;text-align:center;'>$cellValue</td>" }
"Failed" { return "<td style='border:1px solid #ccc;padding:6px; background-color:#D9534F; color:#fff;text-align:center;'>$cellValue</td>" }
"Could not test server uptime." { return "<td style='border:1px solid #ccc;padding:6px; background-color:#D9534F; color:#fff;'>$cellValue</td>" }
default { return "<td style='border:1px solid #ccc;padding:6px;'>$cellValue</td>" }
}
}
$serverhealthhtmltable = ""
$serverhealthhtmltable += $htmltableheader
foreach ($reportline in $allTestedDomainControllers) {
$htmltablerow = "<tr>"
$htmltablerow += "<td style='border:1px solid #ccc;padding:6px;text-align:center;'>$($reportline.Server)</td>"
$htmltablerow += "<td style='border:1px solid #ccc;padding:6px;text-align:center;'>$($reportline.Site)</td>"
$htmltablerow += "<td style='border:1px solid #ccc;padding:6px;text-align:center;'>$($reportline.'IPv4 Address')</td>"
# OS Free Space
$osFree = $reportline.'OS Free Space (GB)'
if ($osFree -is [double] -or $osFree -is [int]) {
if ($osFree -lt 20) {
$htmltablerow += "<td style='border:1px solid #ccc;padding:6px; background-color:#D9534F; color:#fff;text-align:center;'>$osFree</td>" # Red (<20GB)
} else {
$htmltablerow += "<td style='border:1px solid #ccc;padding:6px; background-color:#6BBF59; color:#000;text-align:center;'>$osFree</td>" # Green (>=20GB)
}
} else {
$htmltablerow += "<td style='border:1px solid #ccc;padding:6px;text-align:center;'>$osFree</td>"
}
# color coding CPU Usage
$cpu = $reportline.'CPU Usage (%)'
if ($cpu -is [double] -or $cpu -is [int]) {
if ($cpu -le 75) {
$htmltablerow += "<td style='border:1px solid #ccc;padding:6px; background-color:#6BBF59; color:#000;text-align:center;'>$cpu</td>"
} elseif ($cpu -le 90) {
$htmltablerow += "<td style='border:1px solid #ccc;padding:6px; background-color:#FFD966; color:#000;text-align:center;'>$cpu</td>"
} else {
$htmltablerow += "<td style='border:1px solid #ccc;padding:6px; background-color:#D9534F; color:#fff;text-align:center;'>$cpu</td>"
}
} else {
$htmltablerow += "<td style='border:1px solid #ccc;padding:6px;text-align:center;'>$cpu</td>"
}
#color coding Memory Usage
$mem = $reportline.'Memory Usage (%)'
if ($mem -is [double] -or $mem -is [int]) {
if ($mem -le 75) {
$htmltablerow += "<td style='border:1px solid #ccc;padding:6px; background-color:#6BBF59; color:#000;text-align:center;'>$mem</td>"
} elseif ($mem -le 90) {
$htmltablerow += "<td style='border:1px solid #ccc;padding:6px; background-color:#FFD966; color:#000;text-align:center;'>$mem</td>"
} else {
$htmltablerow += "<td style='border:1px solid #ccc;padding:6px; background-color:#D9534F; color:#fff;text-align:center;'>$mem</td>"
}
} else {
$htmltablerow += "<td style='border:1px solid #ccc;padding:6px;text-align:center;'>$mem</td>"
}
# color coding Uptime
$uptimeDays = $reportline.'Uptime (days)'
if ($uptimeDays -eq "CIM Failure" -or $uptimeDays -eq "Fail") {
$htmltablerow += "<td style='border:1px solid #ccc;padding:6px; background-color:#D9534F; color:#fff;'>$uptimeDays</td>"
} elseif ($uptimeDays -is [int] -or $uptimeDays -is [double]) {
if ($uptimeDays -le 31) {
$htmltablerow += "<td style='border:1px solid #ccc;padding:6px; background-color:#6BBF59; color:#000;text-align:center;'>$uptimeDays</td>"
} elseif ($uptimeDays -le 45) {
$htmltablerow += "<td style='border:1px solid #ccc;padding:6px; background-color:#FFD966; color:#000;text-align:center;'>$uptimeDays</td>"
} else {
$htmltablerow += "<td style='border:1px solid #ccc;padding:6px; background-color:#D9534F; color:#fff;text-align:center;'>$uptimeDays</td>"
}
} else {
$htmltablerow += "<td style='border:1px solid #ccc;padding:6px;text-align:center;'>$uptimeDays</td>"
}
# Filling the Remaining items in HTML
$htmltablerow += (New-ServerHealthHTMLTableCell "NTDS Service")
$htmltablerow += (New-ServerHealthHTMLTableCell "NetLogon Service")
$htmltablerow += (New-ServerHealthHTMLTableCell "DCDIAG: Connectivity")
$htmltablerow += (New-ServerHealthHTMLTableCell "DCDIAG: Advertising")
$htmltablerow += (New-ServerHealthHTMLTableCell "DCDIAG: FrsEvent")
$htmltablerow += (New-ServerHealthHTMLTableCell "DCDIAG: DFSREvent")
$htmltablerow += (New-ServerHealthHTMLTableCell "DCDIAG: SysVolCheck")
$htmltablerow += (New-ServerHealthHTMLTableCell "DCDIAG: KccEvent")
$htmltablerow += (New-ServerHealthHTMLTableCell "DCDIAG: FSMO ")
$htmltablerow += (New-ServerHealthHTMLTableCell "DCDIAG: NetLogons")
$htmltablerow += (New-ServerHealthHTMLTableCell "DCDIAG: ObjectsReplicated")
$htmltablerow += (New-ServerHealthHTMLTableCell "DCDIAG: Replications")
$htmltablerow += (New-ServerHealthHTMLTableCell "DCDIAG: RidManager")
$htmltablerow += (New-ServerHealthHTMLTableCell "DCDIAG: Services")
$htmltablerow += "</tr>"
$serverhealthhtmltable += $htmltablerow
}
$serverhealthhtmltable += "</table>"
# Example explanation table
$explanationTable = @"
<h3 style='font-family: Arial, sans-serif; color: #0056b3;'>Column Reference</h3>
<table border='1' cellpadding='0' cellspacing='0' style='border-collapse:collapse; width:45%; font-family:Arial, sans-serif; font-size:12px;'>
<thead>
<tr style='background-color:#f2f2f2;'>
<th style='border:1px solid #ccc; padding:6px; text-align:left;'>Field</th>
<th style='border:1px solid #ccc; padding:6px; text-align:left;'>Description</th>
</tr>
</thead>
<tbody>
<tr><td style='border:1px solid #ccc; padding:6px;'>Site</td><td style='border:1px solid #ccc; padding:6px;'>The AD site where the domain controller resides.</td></tr>
<tr><td style='border:1px solid #ccc; padding:6px;'>IPv4 Address</td><td style='border:1px solid #ccc; padding:6px;'>The IP address of the domain controller.</td></tr>
<tr><td style='border:1px solid #ccc; padding:6px;'>OS Free Space (GB)</td><td style='border:1px solid #ccc; padding:6px;'>Available disk space on the system drive (in GB).</td></tr>
<tr><td style='border:1px solid #ccc; padding:6px;'>CPU Usage (%)</td><td style='border:1px solid #ccc; padding:6px;'>Current CPU utilization of the server.</td></tr>
<tr><td style='border:1px solid #ccc; padding:6px;'>Memory Usage (%)</td><td style='border:1px solid #ccc; padding:6px;'>Current RAM utilization.</td></tr>
<tr><td style='border:1px solid #ccc; padding:6px;'>Uptime (days)</td><td style='border:1px solid #ccc; padding:6px;'>How many days the DC has been running since the last reboot.</td></tr>
<tr><td style='border:1px solid #ccc; padding:6px;'>NTDS Service</td><td style='border:1px solid #ccc; padding:6px;'>Whether the AD DS (NTDS) service is running.</td></tr>
<tr><td style='border:1px solid #ccc; padding:6px;'>NetLogon Service</td><td style='border:1px solid #ccc; padding:6px;'>Whether the NetLogon service is running.</td></tr>
<tr><td style='border:1px solid #ccc; padding:6px;'>Connectivity</td><td style='border:1px solid #ccc; padding:6px;'>Checks basic connectivity between DCs.</td></tr>
<tr><td style='border:1px solid #ccc; padding:6px;'>Advertising</td><td style='border:1px solid #ccc; padding:6px;'>Ensures the DC is advertising itself correctly.</td></tr>
<tr><td style='border:1px solid #ccc; padding:6px;'>FrsEvent</td><td style='border:1px solid #ccc; padding:6px;'>Checks legacy File Replication Service errors.</td></tr>
<tr><td style='border:1px solid #ccc; padding:6px;'>DFSREvent</td><td style='border:1px solid #ccc; padding:6px;'>Checks DFS Replication health for SYSVOL.</td></tr>
<tr><td style='border:1px solid #ccc; padding:6px;'>SysVolCheck</td><td style='border:1px solid #ccc; padding:6px;'>Verifies SYSVOL availability.</td></tr>
<tr><td style='border:1px solid #ccc; padding:6px;'>KccEvent</td><td style='border:1px solid #ccc; padding:6px;'>Checks KCC (replication topology).</td></tr>
<tr><td style='border:1px solid #ccc; padding:6px;'>FSMO</td><td style='border:1px solid #ccc; padding:6px;'>Confirms the DC knows FSMO role holders.</td></tr>
<tr><td style='border:1px solid #ccc; padding:6px;'>NetLogons</td><td style='border:1px solid #ccc; padding:6px;'>Validates secure channel (Netlogon).</td></tr>
<tr><td style='border:1px solid #ccc; padding:6px;'>ObjectsReplicated</td><td style='border:1px solid #ccc; padding:6px;'>Confirms that AD objects replicate properly.</td></tr>
<tr><td style='border:1px solid #ccc; padding:6px;'>Replications</td><td style='border:1px solid #ccc; padding:6px;'>Checks replication health between DCs.</td></tr>
<tr><td style='border:1px solid #ccc; padding:6px;'>RidManager</td><td style='border:1px solid #ccc; padding:6px;'>Verifies RID Master functionality.</td></tr>
<tr><td style='border:1px solid #ccc; padding:6px;'>Services</td><td style='border:1px solid #ccc; padding:6px;'>Checks critical services (DNS, Kerberos, etc.).</td></tr>
</tbody>
</table>
"@
$serverhealthhtmltable += $explanationTable
$htmltail = "<p style='font-size:11px; color:#555;'>* DNS test is performed using <code>Resolve-DnsName</code>. This cmdlet is only available from Windows Server 2012 onwards.</p>
</body>
</html>"
$htmlreport = $htmlhead + $serverhealthhtmltable
$htmlreport | Out-File $reportFileName -Encoding UTF8
Send-MailMessage @smtpsettings -Body $htmlreport -BodyAsHtml -Attachments $reportFileName -Encoding ([System.Text.Encoding]::UTF8) -ErrorAction Stop
Write-Host "Email sent successfully." -ForegroundColor Green
Multi Domain Forest
<#
.SYNOPSIS
Get-ADHealth.ps1
Gathers the health of Active Directory domain controllers for every domain in the forest,
Splits the results into HTML tables (per domain), and sends a **single email** containing all tables.
Author: Karanth
#>
# Defining the Variables
$now = Get-Date
$date = $now.ToShortDateString()
$reportTime = $now
$reportFileNameTime = $now.ToString("yyyyMMdd_HHmmss")
$allDomains = (Get-ADForest).Domains
$reportemailsubject = "Active Directory Health Check for $($allDomains -join ', ')"
#define email settings
$smtpsettings = @{
To = 'emailaddress@email.com' # Recipient Email address
From = 'AD.Monitoring@email.com' #Senders Email address
Subject = "$reportemailsubject - $date"
SmtpServer = "smtp.email.com" # use your SMTP Server
Port = "25"
}
# Function to gather Domain Controllers for a domain
Function Get-AllDomainControllers ($ComputerName) {
Get-ADDomainController -Filter * -Server $ComputerName | Sort-Object HostName
}
# Function to gather DC uptime
Function Get-DomainControllerUptimeDays {
param($ComputerName)
if (Test-Connection $ComputerName -Count 1 -Quiet) {
try {
$os = Get-WmiObject Win32_OperatingSystem -ComputerName $ComputerName -ErrorAction Stop
if ($os) {
$lastBoot = $os.ConvertToDateTime($os.LastBootUpTime)
return (New-TimeSpan -Start $lastBoot -End (Get-Date)).Days
} else {
return "Data Error"
}
} catch {
return 'WMI Failure'
}
} else {
return 'Fail'
}
}
# Function to gather DCDIAG test results
Function Get-DomainControllerDCDiagTestResults($ComputerName) {
$DCDiagTestResults = [PSCustomObject]@{
ServerName = $ComputerName
Connectivity = $null
DFSREvent = $null
KccEvent = $null
KnowsOfRoleHolders = $null
NetLogons = $null
ObjectsReplicated = $null
}
if ((Test-Connection $ComputerName -Count 1 -quiet) -eq $True) {
$params = @(
"/s:$ComputerName",
"/test:Connectivity",
"/test:DFSREvent",
"/test:KccEvent",
"/test:KnowsOfRoleHolders",
"/test:NetLogons",
"/test:ObjectsReplicated"
)
$DCDiagTest = (Dcdiag.exe @params) -split ('[\r\n]')
$TestName = $null
$TestStatus = $null
$DCDiagTest | ForEach-Object {
switch -Regex ($_) {
"Starting test:" {
$TestName = ($_ -replace ".*Starting test:").Trim()
}
"passed test|failed test" {
$TestStatus = if ($_ -match "passed test") { "Passed" } else { "Failed" }
}
}
if ($TestName -and $TestStatus) {
$DCDiagTestResults.$TestName = $TestStatus
$TestName = $null
$TestStatus = $null
}
}
}
else {
foreach ($property in $DCDiagTestResults.PSObject.Properties.Name) {
if ($property -ne "ServerName") {
$DCDiagTestResults.$property = "Failed"
}
}
}
return $DCDiagTestResults
}
# Function to get free space of C: drive
Function Get-DomainControllerOSDriveFreeSpaceGB {
Param($ComputerName)
if (Test-Connection $ComputerName -Count 1 -Quiet) {
try {
$os = Get-WmiObject -Class Win32_OperatingSystem -ComputerName $ComputerName -ErrorAction Stop
$osDriveLetter = $os.SystemDrive
$osDrive = Get-WmiObject -Class Win32_LogicalDisk -ComputerName $ComputerName `
-Filter "DeviceID='$osDriveLetter'" -ErrorAction Stop
[math]::Round($osDrive.FreeSpace / 1GB, 2)
} catch {
'WMI Failure'
}
} else {
'Fail'
}
}
# Function to get CPU usage
Function Get-DomainControllerCPUUsage {
Param($ComputerName)
if (Test-Connection $ComputerName -Count 1 -Quiet) {
try {
$avgProc = Get-WmiObject -Class win32_processor -ComputerName $ComputerName -ErrorAction Stop |
Measure-Object -Property LoadPercentage -Average |
Select-Object -ExpandProperty Average
[math]::Round($avgProc, 2)
} catch {
'WMI Failure'
}
}
else {
'Fail'
}
}
# Function to get Memory usage
Function Get-DomainControllerMemoryUsage {
Param($ComputerName)
if (Test-Connection $ComputerName -Count 1 -Quiet) {
try {
$os = Get-WmiObject -Class Win32_OperatingSystem -ComputerName $ComputerName -ErrorAction Stop
$total = $os.TotalVisibleMemorySize
$free = $os.FreePhysicalMemory
$used = $total - $free
[math]::Round(($used / $total) * 100, 2)
}
catch {
'WMI Failure'
}
}
else {
'Fail'
}
}
# Function to build HTML table cell (with color)
Function New-ServerHealthHTMLTableCell {
param( $lineitem, $Width = "80px" )
$cellValue = $reportline."$lineitem"
switch ($cellValue) {
"Success" { return "<td style='height:25px;width:$Width; border:1px solid #000;padding:6px; background-color:#6BBF59; color:#000;text-align:center;'>$cellValue</td>" }
"Passed" { return "<td style='height:25px;width:$Width; border:1px solid #000;padding:6px; background-color:#6BBF59; color:#000;text-align:center;'>$cellValue</td>" }
"Pass" { return "<td style='height:25px;width:$Width; border:1px solid #000;padding:6px; background-color:#6BBF59; color:#000;text-align:center;'>$cellValue</td>" }
"Warn" { return "<td style='height:25px;width:$Width; border:1px solid #000;padding:6px; background-color:#FFD966; color:#000;text-align:center;'>$cellValue</td>" }
"Fail" { return "<td style='height:25px;width:$Width; border:1px solid #000;padding:6px; background-color:#D9534F; color:#fff;text-align:center;'>$cellValue</td>" }
"Failed" { return "<td style='height:25px;width:$Width; border:1px solid #000;padding:6px; background-color:#D9534F; color:#fff;text-align:center;'>$cellValue</td>" }
default { return "<td style='height:25px;width:$Width; border:1px solid #000;padding:6px;text-align:center;'>$cellValue</td>" }
}
}
# HTML HEADERS/FOOTERS
$htmlhead = @"
<html>
<body style='font-family:Segoe UI,Tahoma,Geneva,Verdana,sans-serif;font-size:10pt;'>
<h1 style='font-size:20px;text-align:left;'>Domain Controller Health Check Report</h1>
<h3 style='font-size:14px;text-align:left;'>Generated: $reportTime</h3>
"@
$htmltableheader = @"
<table border='1' cellpadding='0' cellspacing='0' style='width:1300px;border-collapse:collapse;border-spacing:0;margin:0;font-size:10pt;table-layout:fixed;'>
<tr style='background-color:#f2f2f2; margin:0; border-spacing:0;'>
<th style='height:25px;width:120px;'>Server</th>
<th style='height:25px;width:110px;'>Site</th>
<th style='height:25px;width:70px;'>Connectivity</th>
<th style='height:25px;width:70px;'>DFSREvent</th>
<th style='height:25px;width:70px;'>FSMO</th>
<th style='height:25px;width:70px;'>NetLogons</th>
<th style='height:25px;width:70px;'>Replication</th>
<th style='height:25px;width:70px;'>OS Free Space(GB)</th>
<th style='height:25px;width:70px;'>CPU Usage (%)</th>
<th style='height:25px;width:70px;'>Memory Usage (%)</th>
<th style='height:25px;width:70px;'>Uptime (days)</th>
</tr>
"@
$explanationTable = @"
<h3 style='font-family: Arial, sans-serif; color: #0056b3;'>Column Reference</h3>
<table border='1' cellpadding='0' cellspacing='0' style='border-collapse:collapse; width:50%; font-family:Arial, sans-serif; font-size:12px;'>
<thead>
<tr style='background-color:#f2f2f2;'>
<th style='height:25px;'>Field</th>
<th style='height:25px;'>Description</th>
</tr>
</thead>
<tbody>
<tr><td>Connectivity</td><td>Checks basic connectivity between DCs.</td></tr>
<tr><td>DFSREvent</td><td>Checks DFS Replication health for SYSVOL.</td></tr>
<tr><td>FSMO</td><td>Confirms the DC knows FSMO role holders.</td></tr>
<tr><td>NetLogons</td><td>Validates secure channel (Netlogon).</td></tr>
<tr><td>Replication</td><td>Confirms that AD objects replicate properly.</td></tr>
<tr><td>OS Free Space (GB)</td><td>Available disk space on the system drive (in GB).</td></tr>
<tr><td>CPU Usage (%)</td><td>Current CPU utilization of the server.</td></tr>
<tr><td>Memory Usage (%)</td><td>Current RAM utilization.</td></tr>
<tr><td>Uptime (days)</td><td>How many days the DC has been running since the last reboot.</td></tr>
</tbody>
</table>
"@
$htmltail = "<p style='font-size:11px; color:#555;'>* DNS test is performed using <code>Resolve-DnsName</code>. This cmdlet is only available from Windows Server 2012 onwards.</p>
</body>
</html>"
# ---- Main Logic ----
# Collect domain reports as HTML snippets
$allDomainTables = @()
foreach ($domain in $allDomains) {
Write-Host "Testing domain $domain" -ForegroundColor Green
$perDomainTestedDomainControllers = [System.Collections.Generic.List[Object]]::new()
$allDomainControllers = Get-AllDomainControllers $domain
$allDomainControllers = @($allDomainControllers)
$totalDCs = $allDomainControllers.Count
$currentDCNumber = 0
foreach ($domainController in $allDomainControllers) {
$currentDCNumber++
Write-Host "Testing domain controller ($currentDCNumber of $totalDCs) $($domainController.HostName)" -ForegroundColor Cyan
$DCDiagTestResults = Get-DomainControllerDCDiagTestResults $domainController.HostName
$thisDomainController = [PSCustomObject]@{
Server = ($domainController.HostName.Split('.')[0]).ToUpper()
Site = $domainController.Site
"DCDIAG: Connectivity" = $DCDiagTestResults.Connectivity
"DCDIAG: DFSREvent" = $DCDiagTestResults.DFSREvent
"DCDIAG: KccEvent" = $DCDiagTestResults.KccEvent
"DCDIAG: FSMO " = $DCDiagTestResults.KnowsOfRoleHolders
"DCDIAG: NetLogons" = $DCDiagTestResults.NetLogons
"Replication" = $DCDiagTestResults.ObjectsReplicated
"OS Free Space (GB)" = Get-DomainControllerOSDriveFreeSpaceGB $domainController.HostName
"CPU Usage (%)" = Get-DomainControllerCPUUsage $domainController.HostName
"Memory Usage (%)" = Get-DomainControllerMemoryUsage $domainController.HostName
"Uptime (days)" = Get-DomainControllerUptimeDays $domainController.HostName
}
$perDomainTestedDomainControllers.Add($thisDomainController)
}
# Build HTML just for this domain's DCs
$serverhealthhtmltable = "<h2 style='color:#174ea6;'>Domain: $domain</h2>" + $htmltableheader
foreach ($reportline in $perDomainTestedDomainControllers) {
$htmltablerow = "<tr>"
$htmltablerow += "<td style='text-align:center;'><b>$($reportline.Server)</b></td>"
$htmltablerow += "<td style='text-align:center;'><b>$($reportline.Site)</b></td>"
$htmltablerow += (New-ServerHealthHTMLTableCell "DCDIAG: Connectivity" -Width "70px")
$htmltablerow += (New-ServerHealthHTMLTableCell "DCDIAG: DFSREvent" -Width "70px")
$htmltablerow += (New-ServerHealthHTMLTableCell "DCDIAG: FSMO " -Width "70px")
$htmltablerow += (New-ServerHealthHTMLTableCell "DCDIAG: NetLogons" -Width "70px")
$htmltablerow += (New-ServerHealthHTMLTableCell "Replication" -Width "70px")
# Now add OS/CPU/Memory/Uptime columns (moved to the end)
$osFree = $reportline.'OS Free Space (GB)'
if ($osFree -is [double] -or $osFree -is [int]) {
if ($osFree -lt 20) {
$htmltablerow += "<td style='background-color:#D9534F; color:#fff; text-align:center; margin:0; border-spacing:0;'>$osFree</td>"
} else {
$htmltablerow += "<td style='background-color:#6BBF59; color:#000; text-align:center; margin:0; border-spacing:0;'>$osFree</td>"
}
} else {
$htmltablerow += "<td>$osFree</td>"
}
$cpu = $reportline.'CPU Usage (%)'
if ($cpu -is [double] -or $cpu -is [int]) {
if ($cpu -le 75) {
$htmltablerow += "<td style='background-color:#6BBF59; color:#000; text-align:center; margin:0; border-spacing:0;'>$cpu</td>"
} elseif ($cpu -le 90) {
$htmltablerow += "<td style='background-color:#FFD966; color:#000; text-align:center; margin:0; border-spacing:0;'>$cpu</td>"
} else {
$htmltablerow += "<td style='background-color:#D9534F; color:#fff; text-align:center; margin:0; border-spacing:0;'>$cpu</td>"
}
} else {
$htmltablerow += "<td>$cpu</td>"
}
$mem = $reportline.'Memory Usage (%)'
if ($mem -is [double] -or $mem -is [int]) {
if ($mem -le 75) {
$htmltablerow += "<td style='background-color:#6BBF59; color:#000; text-align:center; margin:0; border-spacing:0;'>$mem</td>"
} elseif ($mem -le 90) {
$htmltablerow += "<td style='background-color:#FFD966; color:#000; text-align:center; margin:0; border-spacing:0;'>$mem</td>"
} else {
$htmltablerow += "<td style='background-color:#D9534F; color:#fff; text-align:center; margin:0; border-spacing:0;'>$mem</td>"
}
} else {
$htmltablerow += "<td>$mem</td>"
}
$uptimeDays = $reportline.'Uptime (days)'
if ($uptimeDays -eq "CIM Failure" -or $uptimeDays -eq "Fail") {
$htmltablerow += "<td style='background-color:#D9534F; color:#fff; text-align:center; margin:0; border-spacing:0;'>$uptimeDays</td>"
} elseif ($uptimeDays -is [int] -or $uptimeDays -is [double]) {
if ($uptimeDays -le 30) {
$htmltablerow += "<td style='background-color:#6BBF59; color:#000; text-align:center; margin:0; border-spacing:0;'>$uptimeDays</td>"
} elseif ($uptimeDays -le 45) {
$htmltablerow += "<td style='background-color:#FFD966; color:#000; text-align:center; margin:0; border-spacing:0;'>$uptimeDays</td>"
} else {
$htmltablerow += "<td style='background-color:#D9534F; color:#fff; text-align:center; margin:0; border-spacing:0;'>$uptimeDays</td>"
}
} else {
$htmltablerow += "<td>$uptimeDays</td>"
}
$htmltablerow += "</tr>"
$serverhealthhtmltable += $htmltablerow
}
$serverhealthhtmltable += "</table>"
# $serverhealthhtmltable += $explanationTable
# Store this domain's report in the list
$allDomainTables += $serverhealthhtmltable
}
# Assemble one big HTML body with all tables for all domains
$htmlbody = $htmlhead + ($allDomainTables -join "<br/><br/>") + $explanationTable
# Send the email (one email, all tables)
Send-MailMessage @smtpsettings -Body $htmlbody -BodyAsHtml -Encoding ([System.Text.Encoding]::UTF8) -ErrorAction Stop
Write-Host "Combined Email sent successfully with per-domain health tables." -ForegroundColor Green