Basic DC health email report via PowerShell

Today I would like to share one of the scripts for basic DC health checks. Some time ago I added article about DCDIAG and Repadmin. This time I created html formatted report for DCDIAG, ADSystem and AD Services. You can modify this easily and add additional functions.

DC Health report

Script contains lots of lines so we need to break it apart to make it more understandable. For health checks I prepared three functions where final results will be returned in arrays. At the end you should receive the following report to your mailbox:

DC Health
DC Health
Get-ADSystem

First function is called Get-ADSystem and it’s for getting AD system information like CPU and Memory utilization:

ADSystem function
ADSystem function
    function Get-ADSystem {
         
    [CmdletBinding()]
    [OutputType([Array])] 
    param
    (
        [Parameter(Position=0, Mandatory = $true, HelpMessage="Provide server names", ValueFromPipeline = $true)]
        $Server
    )

    $SystemArray = @()

        $Server = $Server.trim()
        $Object = '' | Select ServerName, BootUpTime, UpTime, "Physical RAM", "C: Free Space", "Memory Usage", "CPU usage"
                        
        $Object.ServerName = $Server

        # Get OS details using WMI query
        $os = Get-WmiObject win32_operatingsystem -ComputerName $Server -ErrorAction SilentlyContinue | Select-Object LastBootUpTime,LocalDateTime
                        
        If($os)
        {
            # Get bootup time and local date time  
            $LastBootUpTime = [Management.ManagementDateTimeConverter]::ToDateTime(($os).LastBootUpTime)
            $LocalDateTime = [Management.ManagementDateTimeConverter]::ToDateTime(($os).LocalDateTime)

            # Calculate uptime - this is automatically a timespan
            $up = $LocalDateTime - $LastBootUpTime
            $uptime = "$($up.Days) days, $($up.Hours)h, $($up.Minutes)mins"

            $Object.BootUpTime = $LastBootUpTime 
            $Object.UpTime = $uptime
        }
        Else
        {
            $Object.BootUpTime = "(null)" 
                $Object.UpTime = "(null)"
        }

        # Checking RAM, memory and cpu usage and C: drive free space
        $PhysicalRAM = (Get-WMIObject -class Win32_PhysicalMemory -ComputerName $server | Measure-Object -Property capacity -Sum | % {[Math]::Round(($_.sum / 1GB),2)})
                        
        If($PhysicalRAM)
        {
            $PhysicalRAM = ("$PhysicalRAM" + " GB")
            $Object."Physical RAM"= $PhysicalRAM
        }
        Else
        {
            $Object.UpTime = "(null)"
        }
   
        $Mem = (Get-WmiObject -Class win32_operatingsystem -ComputerName $Server  | Select-Object @{Name = "MemoryUsage"; Expression = { “{0:N2}” -f ((($_.TotalVisibleMemorySize - $_.FreePhysicalMemory)*100)/ $_.TotalVisibleMemorySize)}}).MemoryUsage
                       
        If($Mem)
        {
            $Mem = ("$Mem" + " %")
            $Object."Memory Usage"= $Mem
        }
        Else
        {
            $Object."Memory Usage" = "(null)"
        }

        $Cpu =  (Get-WmiObject win32_processor -ComputerName $Server  |  Measure-Object -property LoadPercentage -Average | Select Average).Average 
                        
        If($PhysicalRAM)
        {
            $Cpu = ("$Cpu" + " %")
            $Object."CPU usage"= $Cpu
        }
        Else
        {
            $Object."CPU Usage" = "(null)"
        }

        $FreeSpace =  (Get-WmiObject win32_logicaldisk -ComputerName $Server -ErrorAction SilentlyContinue  | Where-Object {$_.deviceID -eq "C:"} | select @{n="FreeSpace";e={[math]::Round($_.FreeSpace/1GB,2)}}).freespace 
                        
        If($FreeSpace)
        {
            $FreeSpace = ("$FreeSpace" + " GB")
            $Object."C: Free Space"= $FreeSpace
        }
        Else
        {
            $Object."C: Free Space" = "(null)"
        }

        $SystemArray += $Object 
 
        $SystemArray 
} 
Get-DCDiag

Second function is called Get-DCDiag and it will display only passed or failed values for DC checks:

DCDIAG function
DCDIAG function
  function Get-DCDiag {
         
    [CmdletBinding()]
    [OutputType([Array])] 
    param
    (
        [Parameter(Position=0, Mandatory = $true, HelpMessage="Provide server names", ValueFromPipeline = $true)]
        $Computername
    )
    $DCDiagArray = @()

            # DCDIAG ===========================================================================================
            $Dcdiag = (Dcdiag.exe /s:$Computername) -split ('[\r\n]')
            $Results = New-Object Object
            $Results | Add-Member -Type NoteProperty -Name "ServerName" -Value $Computername
            $Dcdiag | %{ 
            Switch -RegEx ($_) 
            { 
                "Starting test"      { $TestName   = ($_ -Replace ".*Starting test: ").Trim() } 
                "passed test|failed test" { If ($_ -Match "passed test") {  
                $TestStatus = "Passed"  
                # $TestName 
                # $_ 
                }  
                Else  
                {  
                $TestStatus = "Failed"  
                # $TestName 
                # $_ 
                } 
                } 
            } 
            If ($TestName -ne $Null -And $TestStatus -ne $Null) 
            { 
                $Results | Add-Member -Name $("$TestName".Trim()) -Value $TestStatus -Type NoteProperty -force 
                $TestName = $Null; $TestStatus = $Null 
            } 
            } 
            $DCDiagArray += $Results

    $DCDiagArray
            
}
Get-ADServices

And the third function is just checking AD services and it’s called Get-ADServices:

ADServices function
ADServices function
function Get-ADServices {
         
    [CmdletBinding()]
    [OutputType([Array])] 
    param
    (
        [Parameter(Position=0, Mandatory = $true, HelpMessage="Provide server names", ValueFromPipeline = $true)]
        $Computername
    )

    $ServiceNames = "HealthService","NTDS","NetLogon","DFSR"
    $ErrorActionPreference = "SilentlyContinue"
    $report = @()

        $Services = Get-Service -ComputerName $Computername -Name  $ServiceNames

        If(!$Services)
        {
            Write-Warning "Something went wrong"
        }
        Else
        {
            # Adding properties to object
            $Object = New-Object PSCustomObject
            $Object | Add-Member -Type NoteProperty -Name "ServerName" -Value $Computername

            foreach($item in $Services)
            {
                $Name = $item.Name
                $Object | Add-Member -Type NoteProperty -Name "$Name" -Value $item.Status 
            }
            
            $report += $object 
        }
    
    $report
}
Test-Cred

As a verification step I used Test-Cred function from previous article – link.

If($credentials.GetNetworkCredential().password -eq $null )
{
    Write-Warning "Credential validation failed"
    pause
    Break
}
Else
{
    $CredCheck = $Credentials  | Test-Cred
    If($CredCheck -ne "Authenticated")
    {
        Write-Warning "Credential validation failed"
        pause
        Break
    }     
}
SendMail

As my environment is very complex I had to run this script on server where I had connectivity towards all Domain Controllers. Unfortunately there were no SMTP so I had to prepare workaround for this. I created SendMail script on other remote server with SMTP enabled.

# Parameters in main script ===========================================
#Servers
$RemoteServer = "Server01"
$ServerNames = Get-Content -Path "D:\scripts\servers.txt" 

#Setup email parameters
$SubDate = (Get-Date).ToString('MMMM-dd')
$mailTo = ((Get-ADUser -Identity $ENV:Username -Properties mail).mail ) 
$subject = ”Domain Controllers - ” + $SubDate
$priority = ”Normal”
$smtpServer = "smtp.powershellbros.com"
$emailFrom = ”Reports@powershellbros.com”
$emailTo = $mailTo
$port = 25 
#=======================================================================

To run remote script I used Invoke-Command and passed three parameters param($Emailto,$Subject,$Output). For this operation I had to use $Credentials:

    # Connect to server where SMTP is set and pass parameters for sending email
    Invoke-Command $RemoteServer -Scriptblock{param($Emailto,$Subject,$Output)

        D:\scripts\SendMail\SendMail.ps1 -EmailTo $EmailTo -Subject $Subject -Output $Output

    } -ArgumentList $EmailTo,$Subject,$Output -Credential $Credentials

SendMail script located on $RemoteServer:

    Param (
        [Parameter(Position=0, Mandatory = $true, HelpMessage="Provide mail recipeint", ValueFromPipeline = $true)]
        $EmailTo,

        [Parameter(Position=1, Mandatory = $true, HelpMessage="Provide Subject", ValueFromPipeline = $true)]
        $Subject,

        [Parameter(Position=2, Mandatory=$true, HelpMessage="Provide output", ValueFromPipeline=$false)] 
        $Output
    )

    $priority = ”Normal”
    $smtpServer = "smtp.powershellbros.com"
    $emailFrom = ”Reports@PowershellBros.com”
    $port = 25 

    # Use loop to do 5 attempts in case of failure
    $n=1
    do 
    {
        $ErrorMessage = $null
        Write-Host "Attempt $n - Sending email"
                        
        #Send email
        Send-MailMessage -ErrorAction SilentlyContinue -ErrorVariable SendError -To $emailTo -Subject $subject -BodyAsHtml $output -SmtpServer $smtpServer -port $port -From $emailFrom -Priority $priority
                        
        $ErrorMessage = $SendError.exception.message
        If($ErrorMessage)
        {
            Write-Warning "$ErrorMessage"
            Start-Sleep -Seconds 5
        }
        
        $n++
    } 
    until ( $SendError.exception.message -eq $null -or $n -eq 6 )

    If($SendError.exception.message -eq $null)
    {
        Write-Host "`nEmail has been sent to $emailto" -ForegroundColor Yellow
    }
    Else
    {
        Write-Host "`nFailed to sent email to $emailto" -ForegroundColor Yellow
    } 


Final script:
DCHealth

Send mail script on remote server with SMTP enabled:
SendMail

If you have SMTP on your machine you can just use the following code:
DCHealthSMTP

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.