This is a updated version of the Active Directory Domain Controller Health report that rather than sends you an email will create a HTML website instead that shows the status of all the Domain Controllers something like this, I did not like the e-mail version as I found you ended up not looking at the details when you observed green, so lets make a simple design that is easy to read when all is Healthy:
View Log option
This view shows you that all the Domain Controller are healthy and online, which you can then confirm with the "View Log File" button as below, which will give you a full log of what was checked:
Log Sample
This output per Domain Controller will look like this, so you can see why the status is "Healthy" as it above:
Log Sample
This output per Domain Controller will look like this, so you can see why the status is "Healthy" as it above:
[2025-02-10 10:41:51] [Info] Starting new health check run
[2025-02-10 10:41:51] [Info] Starting Domain Controller Health Check Script
[2025-02-10 10:41:51] [Info] Script Version: 1.1
[2025-02-10 10:41:51] [Info] Checking specified domain: bear.local
[2025-02-10 10:41:51] [Info] Processing domain: bear.local
[2025-02-10 10:41:51] [Info] Getting domain controllers for domain: bear.local
[2025-02-10 10:41:51] [Info] Found 6 domain controllers
[2025-02-10 10:41:51] [Info] Testing DC BearDC1.bear.local (1 of 6)
[2025-02-10 10:41:51] [Info] Getting OS version for: BearDC1.bear.local
[2025-02-10 10:41:51] [Info] OS Version: Microsoft Windows Server 2022 Standard
[2025-02-10 10:41:51] [Info] Checking machine type for: BearDC1.bear.local
[2025-02-10 10:41:52] [Info] Machine type: Physical
[2025-02-10 10:41:52] [Info] Performing NSLookup for: BearDC1.bear.local
[2025-02-10 10:41:52] [Info] NSLookup successful
[2025-02-10 10:41:52] [Info] Testing connectivity to: BearDC1.bear.local
[2025-02-10 10:41:52] [Info] Ping test result: Success
[2025-02-10 10:41:52] [Info] Checking uptime for: BearDC1.bear.local
[2025-02-10 10:41:52] [Info] Uptime: 142 hours
[2025-02-10 10:41:52] [Info] Checking DIT file drive space for: BearDC1.bear.local
[2025-02-10 10:41:53] [Info] DIT drive space free: 95%
[2025-02-10 10:41:53] [Info] Checking OS drive space for: BearDC1.bear.local
[2025-02-10 10:41:53] [Info] OS drive space free: 85%
[2025-02-10 10:41:53] [Info] Checking critical services for: BearDC1.bear.local
[2025-02-10 10:41:54] [Info] DNS service status: Success
[2025-02-10 10:41:54] [Info] NTDS service status: Success
[2025-02-10 10:41:54] [Info] Netlogon service status: Success
[2025-02-10 10:41:54] [Info] Running DCDiag tests for: BearDC1.bear.local
[2025-02-10 10:41:56] [Info] DCDiag test Connectivity : Passed
[2025-02-10 10:41:56] [Info] DCDiag test Advertising : Passed
[2025-02-10 10:41:56] [Info] DCDiag test KnowsOfRoleHolders : Passed
[2025-02-10 10:41:56] [Info] DCDiag test Services : Passed
[2025-02-10 10:41:56] [Info] Results for BearDC1.bear.local:
[2025-02-10 10:41:56] [Info] DNS Status: Success
[2025-02-10 10:41:56] [Info] Ping Status: Success
[2025-02-10 10:41:56] [Info] DIT Free Space: 95%
[2025-02-10 10:41:56] [Info] OS Free Space: 85%
[2025-02-10 10:41:56] [Info] Services Status - DNS: Success, NTDS: Success, NetLogon: Success
[2025-02-10 10:41:56] [Info] DCDiag Results - Advertising: Passed, FSMO: Passed, Services: Passed
[2025-02-10 10:41:56] [Info] Processing Time: 5.7128293 seconds
Unhealthy Example
Then when the tests fail you want more information about what has failed and for this example I have purposely blocked TCP:389 on the local firewall which means many of the checks should fail to give you an example of how this looks, this can be seem below:
Running the Code
Running the Code
When you want to run the code you need to run this code for this HTML report using the command below:
.\ADDSHealthCards.ps1 -DomainName "bear.local" -ReportFile
If you do not want a HTML report and you would rather just see the log file then you can run this:
.\ADDSHealthCards.ps1 -DomainName "bear.local"
Script : ADDSHeathCards.ps1
.\ADDSHealthCards.ps1 -DomainName "bear.local" -ReportFile
If you do not want a HTML report and you would rather just see the log file then you can run this:
.\ADDSHealthCards.ps1 -DomainName "bear.local"
Script : ADDSHeathCards.ps1
# Domain Controller Health Check Script
[CmdletBinding()]
Param(
[Parameter(Mandatory=$false)]
[string]$DomainName,
[Parameter(Mandatory=$false)]
[switch]$ReportFile
)
#...................................
# Global Variables
#...................................
$now = Get-Date
$date = $now.ToShortDateString()
[array]$allDomainControllers = @()
$reportime = Get-Date
$logFile = Join-Path $PSScriptRoot "DCHealthCheck.log"
# Clear/create log file and ensure it's not locked
if (Test-Path $logFile) {
Remove-Item $logFile -Force
}
# Create fresh log file
Set-Content -Path $logFile -Value "[$((Get-Date).ToString('yyyy-MM-dd HH:mm:ss'))] [Info] Starting new health check run" -Force
#...................................
# Functions
#...................................
Function Write-Log {
param(
[Parameter(Mandatory=$true)]
[string]$Message,
[Parameter(Mandatory=$false)]
[ValidateSet('Info', 'Warning', 'Error')]
[string]$Level = 'Info',
[Parameter(Mandatory=$false)]
[switch]$NoConsole
)
$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
$logEntry = "[$timestamp] [$Level] $Message"
# Always write to log file
try {
Add-Content -Path $logFile -Value $logEntry -ErrorAction Stop
}
catch {
Write-Warning "Failed to write to log file: $_"
}
# Write to console unless suppressed
if (-not $NoConsole) {
switch ($Level) {
'Info' { Write-Host $logEntry -ForegroundColor White }
'Warning' { Write-Host $logEntry -ForegroundColor Yellow }
'Error' { Write-Host $logEntry -ForegroundColor Red }
}
}
}
Function Get-AllDomains() {
Write-Log "Running function Get-AllDomains"
try {
$allDomains = (Get-ADForest).Domains
Write-Log "Retrieved domains: $($allDomains -join ', ')"
return $allDomains
}
catch {
Write-Log "Failed to retrieve domains: $_" -Level Error
throw
}
}
Function Get-AllDomainControllers ($domainNameInput) {
Write-Log "Getting domain controllers for domain: $domainNameInput"
try {
[array]$allDomainControllers = Get-ADDomainController -Filter * -Server $domainNameInput
Write-Log "Found $($allDomainControllers.Count) domain controllers"
return $allDomainControllers
}
catch {
Write-Log "Failed to get domain controllers: $_" -Level Error
return @()
}
}
Function Get-DomainControllerNSLookup($domainNameInput) {
Write-Log "Performing NSLookup for: $domainNameInput" -NoConsole
try {
$null = Resolve-DnsName $domainNameInput -Type A -ErrorAction Stop
Write-Log "NSLookup successful" -NoConsole
return 'Success'
}
catch {
Write-Log "NSLookup failed: $_" -Level Warning -NoConsole
return 'Fail'
}
}
Function Get-DomainControllerPingStatus($domainNameInput) {
Write-Log "Testing connectivity to: $domainNameInput" -NoConsole
try {
$pingResult = Test-Connection $domainNameInput -Count 1 -Quiet
$status = if ($pingResult) { "Success" } else { "Fail" }
Write-Log "Ping test result: $status" -NoConsole
return $status
}
catch {
Write-Log "Ping test failed: $_" -Level Warning -NoConsole
return 'Fail'
}
}
Function Get-DomainControllerUpTime($domainNameInput) {
Write-Log "Checking uptime for: $domainNameInput" -NoConsole
if (-not (Test-Connection $domainNameInput -Count 1 -Quiet)) {
Write-Log "Server not responding - cannot check uptime" -Level Warning -NoConsole
return '0'
}
try {
$W32OS = Get-WmiObject -Class Win32_OperatingSystem -ComputerName $domainNameInput -ErrorAction Stop
$timespan = $W32OS.ConvertToDateTime($W32OS.LocalDateTime) – $W32OS.ConvertToDateTime($W32OS.LastBootUpTime)
[int]$uptime = "{0:00}" -f $timespan.TotalHours
Write-Log "Uptime: $uptime hours" -NoConsole
return $uptime
}
catch {
Write-Log "Failed to get uptime via WMI: $_" -Level Warning -NoConsole
return 'WMI Failure'
}
}
Function Get-DITFileDriveSpace($domainNameInput) {
Write-Log "Checking DIT file drive space for: $domainNameInput" -NoConsole
if (-not (Test-Connection $domainNameInput -Count 1 -Quiet)) {
Write-Log "Server not responding - cannot check DIT space" -Level Warning -NoConsole
return '0'
}
try {
$key = "SYSTEM\CurrentControlSet\Services\NTDS\Parameters"
$valuename = "DSA Database file"
$reg = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey('LocalMachine', $domainNameInput)
$regkey = $reg.opensubkey($key)
$NTDSPath = $regkey.getvalue($valuename)
$NTDSPathDrive = $NTDSPath.ToString().Substring(0,2)
$NTDSDiskDrive = Get-WmiObject -Class Win32_LogicalDisk -ComputerName $domainNameInput -ErrorAction Stop |
Where-Object {$_.DeviceID -eq $NTDSPathDrive}
$NTDSPercentFree = [math]::Round($NTDSDiskDrive.FreeSpace/$NTDSDiskDrive.Size*100)
Write-Log "DIT drive space free: $NTDSPercentFree%" -NoConsole
return $NTDSPercentFree
}
catch {
Write-Log "Failed to check DIT drive space: $_" -Level Warning -NoConsole
return 'WMI Failure'
}
}
Function Get-DomainControllerServices($domainNameInput) {
Write-Log "Checking critical services for: $domainNameInput" -NoConsole
$services = @{
DNS = "DNS"
NTDS = "NTDS"
NETLOGON = "Netlogon"
}
$results = New-Object PSObject
$results | Add-Member NoteProperty -name DNSService -Value "Fail"
$results | Add-Member NoteProperty -name NTDSService -Value "Fail"
$results | Add-Member NoteProperty -name NETLOGONService -Value "Fail"
if (-not (Test-Connection $domainNameInput -Count 1 -Quiet)) {
Write-Log "Server not responding - cannot check services" -Level Warning -NoConsole
return $results
}
foreach ($service in $services.GetEnumerator()) {
try {
$svc = Get-Service -ComputerName $domainNameInput -Name $service.Value -ErrorAction Stop
$status = if ($svc.Status -eq 'Running') { 'Success' } else { 'Fail' }
Write-Log "$($service.Value) service status: $status" -NoConsole
switch ($service.Key) {
'DNS' { $results.DNSService = $status }
'NTDS' { $results.NTDSService = $status }
'NETLOGON' { $results.NETLOGONService = $status }
}
}
catch {
Write-Log "Failed to check $($service.Value) service: $_" -Level Warning -NoConsole
}
}
return $results
}
Function Get-DomainControllerDCDiagTestResults($domainNameInput) {
Write-Log "Running DCDiag tests for: $domainNameInput" -NoConsole
$DCDiagTestResults = New-Object Object
$DCDiagTestResults | Add-Member -Type NoteProperty -Name "ServerName" -Value $domainNameInput
$DCDiagTestResults | Add-Member -Name Advertising -Value 'Failed' -Type NoteProperty -Force
$DCDiagTestResults | Add-Member -Name KnowsOfRoleHolders -Value 'Failed' -Type NoteProperty -Force
$DCDiagTestResults | Add-Member -Name Services -Value 'Failed' -Type NoteProperty -Force
if (-not (Test-Connection $domainNameInput -Count 1 -Quiet)) {
Write-Log "Server not responding - cannot run DCDiag" -Level Warning -NoConsole
return $DCDiagTestResults
}
try {
$DCDiagTest = (Dcdiag.exe /s:$domainNameInput /test:services /test:KnowsOfRoleHolders /test:Advertising) -split ('[\r\n]')
$TestName = $null
$TestStatus = $null
$DCDiagTest | ForEach-Object {
Switch -RegEx ($_) {
"Starting" { $TestName = ($_ -Replace ".*Starting test: ").Trim() }
"passed test|failed test" {
If ($_ -Match "passed test") {
$TestStatus = "Passed"
}
Else {
$TestStatus = "Failed"
}
}
}
If ($TestName -ne $Null -And $TestStatus -ne $Null) {
Write-Log "DCDiag test $TestName : $TestStatus" -NoConsole
$DCDiagTestResults | Add-Member -Name $("$TestName".Trim()) -Value $TestStatus -Type NoteProperty -Force
$TestName = $Null
$TestStatus = $Null
}
}
}
catch {
Write-Log "Failed to run DCDiag tests: $_" -Level Warning -NoConsole
}
return $DCDiagTestResults
}
Function Get-DomainControllerOSVersion ($domainNameInput) {
Write-Log "Getting OS version for: $domainNameInput" -NoConsole
try {
$osVersion = (Get-WmiObject -Class Win32_OperatingSystem -ComputerName $domainNameInput -ErrorAction Stop).Caption
Write-Log "OS Version: $osVersion" -NoConsole
return $osVersion
}
catch {
Write-Log "Failed to get OS version: $_" -Level Warning -NoConsole
return "Unknown"
}
}
Function Get-DomainControllerMachineType ($domainNameInput) {
Write-Log "Checking machine type for: $domainNameInput" -NoConsole
try {
$computerSystem = Get-WmiObject -Class Win32_ComputerSystem -ComputerName $domainNameInput -ErrorAction Stop
$machineType = switch ($computerSystem.Model) {
"Virtual Machine" { "VM" }
"VMware Virtual Platform" { "VM" }
"VirtualBox" { "VM" }
default { "Physical" }
}
Write-Log "Machine type: $machineType" -NoConsole
return $machineType
}
catch {
Write-Log "Failed to determine machine type: $_" -Level Warning -NoConsole
return "Unknown"
}
}
Function Get-DomainControllerOSDriveFreeSpace ($domainNameInput) {
Write-Log "Checking OS drive space for: $domainNameInput" -NoConsole
try {
$osDrive = (Get-WmiObject Win32_OperatingSystem -ComputerName $domainNameInput -ErrorAction Stop).SystemDrive
$diskDrive = Get-WmiObject -Class Win32_LogicalDisk -ComputerName $domainNameInput -ErrorAction Stop |
Where-Object {$_.DeviceID -eq $osDrive}
$percentFree = [math]::Round($diskDrive.FreeSpace/$diskDrive.Size*100)
Write-Log "OS drive space free: $percentFree%" -NoConsole
return $percentFree
}
catch {
Write-Log "Failed to check OS drive space: $_" -Level Warning -NoConsole
return 'WMI Failure'
}
}
Function Generate-HealthDashboard {
param(
[Parameter(Mandatory=$true)]
[array]$allTestedDomainControllers
)
$html = @"
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Domain Controller Health Dashboard</title>
<style>
:root {
--success-color: #10B981;
--warning-color: #F59E0B;
--danger-color: #EF4444;
--text-primary: #1F2937;
--text-secondary: #6B7280;
--bg-card: #ffffff;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
background: #F3F4F6;
margin: 0;
padding: 2rem;
color: var(--text-primary);
}
.dashboard {
max-width: 1200px;
margin: 0 auto;
}
.dashboard-header {
margin-bottom: 2rem;
}
.dashboard-header h1 {
font-size: 1.875rem;
font-weight: 600;
margin: 0;
}
.dashboard-header p {
color: var(--text-secondary);
margin: 0.5rem 0;
}
.cards-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 1.5rem;
}
.health-card {
background: var(--bg-card);
border-radius: 0.75rem;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
padding: 1.5rem;
transition: transform 0.2s;
}
.health-card:hover {
transform: translateY(-2px);
}
.card-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 1rem;
}
.server-info h2 {
font-size: 1.25rem;
margin: 0;
font-weight: 600;
}
.server-info p {
color: var(--text-secondary);
margin: 0.25rem 0;
font-size: 0.875rem;
}
.status-badge {
padding: 0.5rem 1rem;
border-radius: 9999px;
font-weight: 500;
font-size: 0.875rem;
}
.status-badge.healthy {
background: #DEF7EC;
color: var(--success-color);
}
.status-badge.warning {
background: #FEF3C7;
color: var(--warning-color);
}
.status-badge.error {
background: #FEE2E2;
color: var(--danger-color);
}
.issues-list {
margin: 1rem 0 0;
padding: 0;
list-style: none;
}
.issue-item {
display: flex;
align-items: center;
padding: 0.5rem 0;
color: var(--text-secondary);
font-size: 0.875rem;
}
.issue-item::before {
content: "•";
margin-right: 0.5rem;
color: var(--danger-color);
}
.summary-section {
margin-top: 2rem;
padding: 1rem;
background: var(--bg-card);
border-radius: 0.75rem;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
</style>
</head>
<body>
<div class="dashboard">
<div class="dashboard-header">
<h1>Domain Controller Health Status</a></h1>
<p>Generated: $($reportime.ToString("yyyy-MM-dd HH:mm:ss"))</p>
<p>Forest: $((Get-ADForest).Name)</p>
<p><a href="DCHealthCheck.log" style="color: var(--text-secondary); text-decoration: underline;">View Log File</a></p>
<p></p>
</div>
<div class="cards-grid">
"@
foreach ($dc in $allTestedDomainControllers) {
[int]$uptimeHours = if ($dc.'Uptime (hrs)' -eq 'WMI Failure') { 0 } else { $dc.'Uptime (hrs)' }
$isHealthy = $dc.'DIT Free Space (%)' -gt 20 -and
$dc.'OS Free Space (%)' -gt 20 -and
$dc.'DNS Service' -eq 'Success' -and
$dc.'NTDS Service' -eq 'Success' -and
$dc.'NetLogon Service' -eq 'Success' -and
$dc.'DCDIAG: Advertising' -eq 'Passed' -and
$dc.'DCDIAG: FSMO' -eq 'Passed' -and
$dc.'DCDIAG: Services' -eq 'Passed' -and
$uptimeHours -gt 1 -and
$uptimeHours -lt 4320 # 180 days
$statusClass = if ($isHealthy) { "healthy" } else { "error" }
$statusText = if ($isHealthy) { "Healthy" } else { "Unhealthy" }
$html += @"
<div class="health-card">
<div class="card-header">
<div class="server-info">
<h2>$($dc.Server)</h2>
<p>Site: $($dc.Site)</p>
</div>
<span class="status-badge $statusClass">$statusText</span>
</div>
"@
if (-not $isHealthy) {
$html += @"
<ul class="issues-list">
"@
if ($dc.'DIT Free Space (%)' -le 15) {
$html += "<li class='issue-item'>Critical: DIT Drive Space $($dc.'DIT Free Space (%)')% remaining</li>"
}
elseif ($dc.'DIT Free Space (%)' -le 20) {
$html += "<li class='issue-item'>Warning: DIT Drive Space $($dc.'DIT Free Space (%)')% remaining</li>"
}
if ($dc.'OS Free Space (%)' -le 15) {
$html += "<li class='issue-item'>Critical: OS Drive Space $($dc.'OS Free Space (%)')% remaining</li>"
}
elseif ($dc.'OS Free Space (%)' -le 20) {
$html += "<li class='issue-item'>Warning: OS Drive Space $($dc.'OS Free Space (%)')% remaining</li>"
}
if ($dc.'DNS Service' -ne 'Success') {
$html += "<li class='issue-item'>DNS Service is not running</li>"
}
if ($dc.'NTDS Service' -ne 'Success') {
$html += "<li class='issue-item'>NTDS Service is not running</li>"
}
if ($dc.'NetLogon Service' -ne 'Success') {
$html += "<li class='issue-item'>NetLogon Service is not running</li>"
}
if ($dc.'DCDIAG: Advertising' -ne 'Passed') {
$html += "<li class='issue-item'>DCDIAG Advertising test failed</li>"
}
if ($dc.'DCDIAG: FSMO' -ne 'Passed') {
$html += "<li class='issue-item'>DCDIAG FSMO test failed</li>"
}
if ($dc.'DCDIAG: Services' -ne 'Passed') {
$html += "<li class='issue-item'>DCDIAG Services test failed</li>"
}
if ($uptimeHours -le 1) {
$html += "<li class='issue-item'>Server recently rebooted - Uptime: $uptimeHours hours</li>"
}
if ($uptimeHours -ge 4320) {
$html += "<li class='issue-item'>Excessive uptime - Server has been up for $([math]::Round($uptimeHours/24)) days</li>"
}
$html += @"
</ul>
"@
}
$html += @"
</div>
"@
}
$html += @"
</div>
</div>
</body>
</html>
"@
return $html
}
#...................................
# Main Script Execution
#...................................
Write-Log "Starting Domain Controller Health Check Script" -Level Info
Write-Log "Script Version: 1.1" -Level Info
try {
if (!($DomainName)) {
Write-Log "No domain specified - checking all domains in forest"
$allDomains = Get-AllDomains
$reportFileName = "forest_health_report_$((Get-ADForest).Name).html"
}
Else {
Write-Log "Checking specified domain: $DomainName"
$allDomains = $DomainName
$reportFileName = "dc_health_report_$DomainName.html"
}
[array]$allTestedDomainControllers = @()
foreach ($domain in $allDomains) {
Write-Log "Processing domain: $domain" -Level Info
$domainControllers = Get-AllDomainControllers $domain
if ($domainControllers.Count -eq 0) {
Write-Log "No domain controllers found in domain $domain" -Level Warning
continue
}
Write-Log "Found $($domainControllers.Count) domain controllers"
$currentDC = 0
foreach ($dc in $domainControllers) {
$currentDC++
$stopWatch = [system.diagnostics.stopwatch]::StartNew()
Write-Log "Testing DC $($dc.HostName) ($currentDC of $($domainControllers.Count))"
$dcInfo = New-Object PSObject
$dcInfo | Add-Member NoteProperty -name Server -Value ($dc.HostName).ToLower()
$dcInfo | Add-Member NoteProperty -name Site -Value $dc.Site
$dcInfo | Add-Member NoteProperty -name "OS Version" -Value (Get-DomainControllerOSVersion $dc.HostName)
$dcInfo | Add-Member NoteProperty -name "Machine Type" -Value (Get-DomainControllerMachineType $dc.HostName)
$dcInfo | Add-Member NoteProperty -name "Operation Master Roles" -Value $dc.OperationMasterRoles
$dcInfo | Add-Member NoteProperty -name "DNS" -Value (Get-DomainControllerNSLookup $dc.HostName)
$dcInfo | Add-Member NoteProperty -name "Ping" -Value (Get-DomainControllerPingStatus $dc.HostName)
$dcInfo | Add-Member NoteProperty -name "Uptime (hrs)" -Value (Get-DomainControllerUpTime $dc.HostName)
$dcInfo | Add-Member NoteProperty -name "DIT Free Space (%)" -Value (Get-DITFileDriveSpace $dc.HostName)
$dcInfo | Add-Member NoteProperty -name "OS Free Space (%)" -Value (Get-DomainControllerOSDriveFreeSpace $dc.HostName)
$services = Get-DomainControllerServices $dc.HostName
$dcInfo | Add-Member NoteProperty -name "DNS Service" -Value $services.DNSService
$dcInfo | Add-Member NoteProperty -name "NTDS Service" -Value $services.NTDSService
$dcInfo | Add-Member NoteProperty -name "NetLogon Service" -Value $services.NETLOGONService
$dcDiag = Get-DomainControllerDCDiagTestResults $dc.HostName
$dcInfo | Add-Member NoteProperty -name "DCDIAG: Advertising" -Value $dcDiag.Advertising
$dcInfo | Add-Member NoteProperty -name "DCDIAG: FSMO" -Value $dcDiag.KnowsOfRoleHolders
$dcInfo | Add-Member NoteProperty -name "DCDIAG: Services" -Value $dcDiag.Services
$stopWatch.Stop()
$dcInfo | Add-Member NoteProperty -name "Processing Time" -Value $stopWatch.Elapsed.TotalSeconds
# Log the results for this DC
Write-Log "Results for $($dc.HostName):" -NoConsole
Write-Log "DNS Status: $($dcInfo.DNS)" -NoConsole
Write-Log "Ping Status: $($dcInfo.Ping)" -NoConsole
Write-Log "DIT Free Space: $($dcInfo.'DIT Free Space (%)')%" -NoConsole
Write-Log "OS Free Space: $($dcInfo.'OS Free Space (%)')%" -NoConsole
Write-Log "Services Status - DNS: $($dcInfo.'DNS Service'), NTDS: $($dcInfo.'NTDS Service'), NetLogon: $($dcInfo.'NetLogon Service')" -NoConsole
Write-Log "DCDiag Results - Advertising: $($dcInfo.'DCDIAG: Advertising'), FSMO: $($dcInfo.'DCDIAG: FSMO'), Services: $($dcInfo.'DCDIAG: Services')" -NoConsole
Write-Log "Processing Time: $($dcInfo.'Processing Time') seconds" -NoConsole
# Add to collection
[array]$allTestedDomainControllers += $dcInfo
}
}
if ($ReportFile) {
Write-Log "Generating HTML report"
try {
$dashboardHTML = Generate-HealthDashboard -allTestedDomainControllers $allTestedDomainControllers
$dashboardHTML | Out-File $reportFileName -Encoding UTF8
Write-Log "Report successfully saved as $reportFileName" -Level Info
}
catch {
Write-Log "Failed to generate or save report: $_" -Level Error
throw
}
}
# Log summary statistics
Write-Log "Health Check Summary:" -Level Info
Write-Log "Total Domain Controllers Tested: $($allTestedDomainControllers.Count)" -Level Info
# Calculate and log statistics
$failedDCs = $allTestedDomainControllers | Where-Object {
$_.'DNS Service' -eq 'Fail' -or
$_.'NTDS Service' -eq 'Fail' -or
$_.'NetLogon Service' -eq 'Fail' -or
$_.'DCDIAG: Advertising' -eq 'Failed' -or
$_.'DCDIAG: FSMO' -eq 'Failed' -or
$_.'DCDIAG: Services' -eq 'Failed'
}
$lowDiskDCs = $allTestedDomainControllers | Where-Object {
($_.'DIT Free Space (%)' -ne 'WMI Failure' -and [int]$_.'DIT Free Space (%)' -lt 20) -or
($_.'OS Free Space (%)' -ne 'WMI Failure' -and [int]$_.'OS Free Space (%)' -lt 20)
}
Write-Log "DCs with Failed Services or Tests: $($failedDCs.Count)" -Level $(if ($failedDCs.Count -gt 0) { 'Warning' } else { 'Info' })
Write-Log "DCs with Low Disk Space: $($lowDiskDCs.Count)" -Level $(if ($lowDiskDCs.Count -gt 0) { 'Warning' } else { 'Info' })
# Return results for potential further processing
return $allTestedDomainControllers
}
catch {
Write-Log "Critical error during script execution: $_" -Level Error
throw
}
finally {
Write-Log "Script execution completed" -Level Info
Write-Log "Log file location: $logFile" -Level Info
}
Unable to view the log file?
If you are hosting this appliation on IIS then remember you need to a MIME type for the ".log" extension for it work, if this is not done then you will get a "File not Found" error when you try to access this file.
If you are hosting this appliation on IIS then remember you need to a MIME type for the ".log" extension for it work, if this is not done then you will get a "File not Found" error when you try to access this file.
If you need to add this MIME type, then from IIS Manager, find the subdirectory where the data is located then choose MIMIE type as below:
Then you need to choose "Add" then as below you need to enter the following data:
File Name extension : .log
MIME Type : application/octet-stream
When this is added, you will notice there will now be a web.config file in that folder that looks similar to this, this will confirm the setting is now live:
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<system.webServer>
<staticContent>
<mimeMap fileExtension=".log" mimeType="application/octet-stream" />
</staticContent>
</system.webServer>
</configuration>
If you want the version where the health cards tell you the status of the key checks as below, which here you can see the "Backup" has failed on 2 x of the Domain Controllers and the "more critical" checks always have a status on the main health card.
The log also reports the updated metrics:
[2025-02-13 13:42:05] [Info] Starting new health check run
[2025-02-13 13:42:05] [Info] Starting Domain Controller Health Check Script
[2025-02-13 13:42:05] [Info] Script Version: 1.2
[2025-02-13 13:42:05] [Info] No domain specified - checking all domains in forest
[2025-02-13 13:42:05] [Info] Running function Get-AllDomains
[2025-02-13 13:42:05] [Info] Retrieved domains: bear.local
[2025-02-13 13:42:05] [Info] Processing domain: bear.local
[2025-02-13 13:42:05] [Info] Getting domain controllers for domain: bear.local
[2025-02-13 13:42:05] [Info] Found 3 domain controllers
[2025-02-13 13:42:05] [Info] Found 3 domain controllers
[2025-02-13 13:42:05] [Info] Testing DC BearDC1.bear.local (1 of 3)
[2025-02-13 13:42:05] [Info] Getting OS version for: BearDC1.bear.local
[2025-02-13 13:42:06] [Info] OS Version: Microsoft Windows Server 2022 Standard
[2025-02-13 13:42:06] [Info] Checking machine type for: BearDC1.bear.local
[2025-02-13 13:42:06] [Info] Machine type: Physical
[2025-02-13 13:42:06] [Info] Performing NSLookup for: BearDC1.bear.local
[2025-02-13 13:42:06] [Info] NSLookup successful
[2025-02-13 13:42:06] [Info] Testing connectivity to: BearDC1.bear.local
[2025-02-13 13:42:06] [Info] Ping test result: Success
[2025-02-13 13:42:06] [Info] Checking uptime for: BearDC1.bear.local
[2025-02-13 13:42:07] [Info] Uptime: 217 hours
[2025-02-13 13:42:07] [Info] Checking DIT file drive space for: BearDC1.bear.local
[2025-02-13 13:42:07] [Info] DIT drive space free: 95%
[2025-02-13 13:42:07] [Info] Checking OS drive space for: BearDC1.bear.local
[2025-02-13 13:42:08] [Info] OS drive space free: 85%
[2025-02-13 13:42:08] [Info] Checking critical services for: BearDC1.bear.local
[2025-02-13 13:42:08] [Info] DNS service status: Success
[2025-02-13 13:42:08] [Info] NTDS service status: Success
[2025-02-13 13:42:09] [Info] Netlogon service status: Success
[2025-02-13 13:42:09] [Info] Running DCDiag tests for: BearDC1.bear.local
[2025-02-13 13:42:11] [Info] DCDiag test Connectivity : Passed
[2025-02-13 13:42:11] [Info] DCDiag test Advertising : Passed
[2025-02-13 13:42:11] [Info] DCDiag test KnowsOfRoleHolders : Passed
[2025-02-13 13:42:11] [Info] DCDiag test Services : Passed
[2025-02-13 13:42:11] [Info] Checking replication health for: BearDC1.bear.local
[2025-02-13 13:42:12] [Info] Replication Status: Healthy, Failures: 0
[2025-02-13 13:42:12] [Info] Checking SYSVOL status for: BearDC1.bear.local
[2025-02-13 13:42:13] [Info] SYSVOL Share: Available, Replication: Normal
[2025-02-13 13:42:13] [Info] Checking backup status for: BearDC1.bear.local
[2025-02-13 13:42:13] [Info] Backup Event Check Results:
[2025-02-13 13:42:13] [Info] Windows Backup Event Found: False
[2025-02-13 13:42:16] [Info] AD Backup Event Found: True
[2025-02-13 13:42:16] [Info] AD Backup Event Time: 20250212225847.641390-000
[2025-02-13 13:42:16] [Info] Final Backup Status: Healthy, Successful backup confirmed
[2025-02-13 13:42:16] [Info] Checking AD database status for: BearDC1.bear.local
[2025-02-13 13:42:17] [Info] Database Status: Healthy, Database size: 3.84 GB
[2025-02-13 13:42:17] [Info] Checking time synchronization for: BearDC1.bear.local
[2025-02-13 13:42:18] [Info] Time Sync Status: Healthy, Source: st1w6044.bear.local
[2025-02-13 13:42:18] [Info] Checking domain functional level for: BearDC1.bear.local
[2025-02-13 13:42:18] [Info] Functional Level Status: Healthy, Domain: Windows2016Domain, Forest: Windows2012R2Forest
[2025-02-13 13:42:18] [Info] Results for BearDC1.bear.local:
[2025-02-13 13:42:18] [Info] Basic Health - DNS: Success, Ping: Success
[2025-02-13 13:42:18] [Info] Services - DNS: Success, NTDS: Success, NetLogon: Success
[2025-02-13 13:42:18] [Info] Storage - DIT Free: 95%, OS Free: 85%
[2025-02-13 13:42:18] [Info] Replication Status: Healthy - All replication healthy
[2025-02-13 13:42:18] [Info] SYSVOL Status: Available - DFSR State: 4
[2025-02-13 13:42:18] [Info] Backup Status: Healthy - Successful backup confirmed
[2025-02-13 13:42:18] [Info] Time Sync: Healthy - Time sync healthy, Stratum: 6
[2025-02-13 13:42:18] [Info] Processing Time: 12.9548416 seconds
[2025-02-13 13:42:05] [Info] Starting new health check run
[2025-02-13 13:42:05] [Info] Starting Domain Controller Health Check Script
[2025-02-13 13:42:05] [Info] Script Version: 1.2
[2025-02-13 13:42:05] [Info] No domain specified - checking all domains in forest
[2025-02-13 13:42:05] [Info] Running function Get-AllDomains
[2025-02-13 13:42:05] [Info] Retrieved domains: bear.local
[2025-02-13 13:42:05] [Info] Processing domain: bear.local
[2025-02-13 13:42:05] [Info] Getting domain controllers for domain: bear.local
[2025-02-13 13:42:05] [Info] Found 9 domain controllers
[2025-02-13 13:42:05] [Info] Found 9 domain controllers
[2025-02-13 13:42:05] [Info] Testing DC BearDC1.bear.local (1 of 9)
[2025-02-13 13:42:05] [Info] Getting OS version for: BearDC1.bear.local
[2025-02-13 13:42:06] [Info] OS Version: Microsoft Windows Server 2016 Standard
[2025-02-13 13:42:06] [Info] Checking machine type for: BearDC1.bear.local
[2025-02-13 13:42:06] [Info] Machine type: Physical
[2025-02-13 13:42:06] [Info] Performing NSLookup for: BearDC1.bear.local
[2025-02-13 13:42:06] [Info] NSLookup successful
[2025-02-13 13:42:06] [Info] Testing connectivity to: BearDC1.bear.local
[2025-02-13 13:42:06] [Info] Ping test result: Success
[2025-02-13 13:42:06] [Info] Checking uptime for: BearDC1.bear.local
[2025-02-13 13:42:07] [Info] Uptime: 217 hours
[2025-02-13 13:42:07] [Info] Checking DIT file drive space for: BearDC1.bear.local
[2025-02-13 13:42:07] [Info] DIT drive space free: 95%
[2025-02-13 13:42:07] [Info] Checking OS drive space for: BearDC1.bear.local
[2025-02-13 13:42:08] [Info] OS drive space free: 85%
[2025-02-13 13:42:08] [Info] Checking critical services for: BearDC1.bear.local
[2025-02-13 13:42:08] [Info] DNS service status: Success
[2025-02-13 13:42:08] [Info] NTDS service status: Success
[2025-02-13 13:42:09] [Info] Netlogon service status: Success
[2025-02-13 13:42:09] [Info] Running DCDiag tests for: BearDC1.bear.local
[2025-02-13 13:42:11] [Info] DCDiag test Connectivity : Passed
[2025-02-13 13:42:11] [Info] DCDiag test Advertising : Passed
[2025-02-13 13:42:11] [Info] DCDiag test KnowsOfRoleHolders : Passed
[2025-02-13 13:42:11] [Info] DCDiag test Services : Passed
[2025-02-13 13:42:11] [Info] Checking replication health for: BearDC1.bear.local
[2025-02-13 13:42:12] [Info] Replication Status: Healthy, Failures: 0
[2025-02-13 13:42:12] [Info] Checking SYSVOL status for: BearDC1.bear.local
[2025-02-13 13:42:13] [Info] SYSVOL Share: Available, Replication: Normal
[2025-02-13 13:42:13] [Info] Checking backup status for: BearDC1.bear.local
[2025-02-13 13:42:13] [Info] Backup Event Check Results:
[2025-02-13 13:42:13] [Info] Windows Backup Event Found: False
[2025-02-13 13:42:16] [Info] AD Backup Event Found: True
[2025-02-13 13:42:16] [Info] AD Backup Event Time: 20250212225847.641390-000
[2025-02-13 13:42:16] [Info] Final Backup Status: Healthy, Successful backup confirmed
[2025-02-13 13:42:16] [Info] Checking AD database status for: BearDC1.bear.local
[2025-02-13 13:42:17] [Info] Database Status: Healthy, Database size: 3.84 GB
[2025-02-13 13:42:17] [Info] Checking time synchronization for: BearDC1.bear.local
[2025-02-13 13:42:18] [Info] Time Sync Status: Healthy, Source: st1w6044.bear.local
[2025-02-13 13:42:18] [Info] Checking domain functional level for: BearDC1.bear.local
[2025-02-13 13:42:18] [Info] Functional Level Status: Healthy, Domain: Windows2012R2Domain, Forest: Windows2012R2Forest
[2025-02-13 13:42:18] [Info] Results for BearDC1.bear.local:
[2025-02-13 13:42:18] [Info] Basic Health - DNS: Success, Ping: Success
[2025-02-13 13:42:18] [Info] Services - DNS: Success, NTDS: Success, NetLogon: Success
[2025-02-13 13:42:18] [Info] Storage - DIT Free: 95%, OS Free: 85%
[2025-02-13 13:42:18] [Info] Replication Status: Healthy - All replication healthy
[2025-02-13 13:42:18] [Info] SYSVOL Status: Available - DFSR State: 4
[2025-02-13 13:42:18] [Info] Backup Status: Healthy - Successful backup confirmed
[2025-02-13 13:42:18] [Info] Time Sync: Healthy - Time sync healthy, Stratum: 6
[2025-02-13 13:42:18] [Info] Processing Time: 12.9548416 seconds
If you would like to use this version please contact me for more information.