Powershell : ADDS Health Card Report

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:

[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 

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

# 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 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>

Professional Version?

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.
Previous Post Next Post

نموذج الاتصال