# .SYNOPSIS Test-ExchangeServerHealth.ps1 - Exchange Server Health Check Script. .DESCRIPTION Performs a series of health checks on Exchange servers and DAGs and outputs the results to screen, and optionally to log file, HTML report, and HTML email. Use the ignorelist.txt file to specify any servers, DAGs, or databases you want the script to ignore (eg test/dev servers). .OUTPUTS Results are output to screen, as well as optional log file, HTML report, and HTML email .PARAMETER Server Perform a health check of a single server .PARAMETER ReportMode Set to $true to generate a HTML report. A default file name is used if none is specified. .PARAMETER ReportFile Allows you to specify a different HTML report file name than the default. .PARAMETER SendEmail Sends the HTML report via email using the SMTP configuration within the script. .PARAMETER AlertsOnly Only sends the email report if at least one error or warning was detected. .PARAMETER Log Writes a log file to help with troubleshooting. .EXAMPLE .\Test-ExchangeServerHealth.ps1 Checks all servers in the organization and outputs the results to the shell window. .EXAMPLE .\Test-ExchangeServerHealth.ps1 -Server HO-EX2010-MB1 Checks the server HO-EX2010-MB1 and outputs the results to the shell window. .EXAMPLE .\Test-ExchangeServerHealth.ps1 -ReportMode -SendEmail Checks all servers in the organization, outputs the results to the shell window, a HTML report, and emails the HTML report to the address configured in the script. .LINK https://practical365.com/exchange-server/powershell-script-exchange-server-health-check-report/ .NOTES Written by: Paul Cunningham Find me on: * My Blog: http://paulcunningham.me * Twitter: https://twitter.com/paulcunningham * LinkedIn: http://au.linkedin.com/in/cunninghamp/ * Github: https://github.com/cunninghamp For more Exchange Server tips, tricks and news check out Exchange Server Pro. * Website: https://practical365.com * Twitter: https://twitter.com/practical365 Additional Credits (code contributions and testing): - Chris Brown, http://twitter.com/chrisbrownie - Ingmar Brückner - John A. Eppright - Jonas Borelius - Thomas Helmdach - Bruce McKay - Tony Holdgate - Ryan - Rob Silver - andrewcr7, https://github.com/andrewcr7 License: The MIT License (MIT) Copyright (c) 2017 Paul Cunningham Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Change Log V1.00, 05/07/2012 - Initial version V1.01, 05/08/2012 - Minor bug fixes and removed Edge Tranport checks V1.02, 05/05/2013 - A lot of bug fixes, updated SMTP to use Send-MailMessage, added DAG health check. V1.03, 04/08/2013 - Minor bug fixes V1.04, 19/08/2013 - Added Exchange 2013 compatibility, added option to output a log file, converted many sections of code to use pre-defined strings, fixed -AlertsOnly parameter, improved summary sections of report to be more readable and include DAG summary V1.05, 23/08/2013 - Added workaround for Test-ServiceHealth error for Exchange 2013 CAS-only servers V1.06, 28/10/2013 - Added workaround for Test-Mailflow error for Exchange 2013 Mailbox servers. - Added workaround for Exchange 2013 mail test. - Added localization strings for service health check errors for non-English systems. - Fixed an uptime calculation bug for some regional settings. - Excluded recovery databases from active database calculation. - Fixed bug where high transport queues would not count as an alert. - Fixed error thrown when Site attribute can't be found for Exchange 2003 servers. - Fixed bug causing Exchange 2003 servers to be added to the report twice. V1.07, 24/11/2013 - Fixed bug where disabled content indexes were counted as failed. V1.08, 29/06/2014 - Fixed bug with DAG reporting in mixed Exchange 2010/2013 orgs. V1.09, 06/07/2014 - Fixed bug with DAG member replication health reporting for mixed Exchange 2010/2013 orgs. V1.10, 19/08/2014 - Fixed bug with E14 replication health not testing correct server. V1.11, 11/02/2015 - Added queue length to Transport queue result in report. V1.12, 05/03/2015 - Fixed bug with color-coding in report for Transport Queue length. V1.13, 07/03/2015 - Fixed bug with incorrect function name used sometimes when trying to call Write-LogFile V1.14, 21/05/2015 - Fixed bug with color-coding in report for Transport Queue length on CAS-only Exchange 2013 servers. V1.15, 18/11/2015 - Fixed bug with Exchange 2016 version detection. V1.16, 13/04/2017 - Fixed bugs with recovery DB detection, invalid variables, shadow redundancy queues, and lagged copy detection. V1.17, 17/05/2017 - Fixed bug with auto-suspended content index detection #> #requires -version 2 [CmdletBinding()] param ( [Parameter( Mandatory=$false)] [string]$Server, [Parameter( Mandatory=$false)] [string]$ServerList, [Parameter( Mandatory=$false)] [string]$ReportFile="exchangeserverhealth.html", [Parameter( Mandatory=$false)] [switch]$ReportMode, [Parameter( Mandatory=$false)] [switch]$SendEmail, [Parameter( Mandatory=$false)] [switch]$AlertsOnly, [Parameter( Mandatory=$false)] [switch]$Log ) #................................... # Variables #................................... $now = Get-Date #Used for timestamps $date = $now.ToShortDateString() #Short date format for email message subject [array]$exchangeservers = @() #Array for the Exchange server or servers to check [int]$transportqueuehigh = 100 #Change this to set transport queue high threshold. Must be higher than warning threshold. [int]$transportqueuewarn = 80 #Change this to set transport queue warning threshold. Must be lower than high threshold. $mapitimeout = 10 #Timeout for each MAPI connectivity test, in seconds $pass = "Green" $warn = "Yellow" $fail = "Red" $ip = $null [array]$serversummary = @() #Summary of issues found during server health checks [array]$dagsummary = @() #Summary of issues found during DAG health checks [array]$report = @() [bool]$alerts = $false [array]$dags = @() #Array for DAG health check [array]$dagdatabases = @() #Array for DAG databases [int]$replqueuewarning = 8 #Threshold to consider a replication queue unhealthy $dagreportbody = $null $myDir = Split-Path -Parent $MyInvocation.MyCommand.Path #................................... # Modify these Variables (optional) #................................... $reportemailsubject = "Exchange Server Health Report" $ignorelistfile = "$myDir\ignorelist.txt" $logfile = "$myDir\exchangeserverhealth.log" #................................... # Modify these Email Settings #................................... $smtpsettings = @{ #To = hcornet@fichorga.fr,jmdefossez@fichorga.fr From = "Rapports@cloud-fichorga.fr" Subject = "$reportemailsubject - $now" SmtpServer = "10.101.10.2" } #................................... # Modify these language # localization strings. #................................... # The server roles must match the role names you see when you run Test-ServiceHealth. #$casrole = "Client Access Server Role" $casrole = "Rôle serveur d'accès au client" #$htrole = "Hub Transport Server Role" $htrole = "Rôle serveur de transport Hub" #$mbrole = "Mailbox Server Role" $mbrole = "Rôle serveur de boîtes aux lettres" #$umrole = "Unified Messaging Server Role" $umrole = "Rôle serveur de messagerie unifiée" # This should match the word for "Success", or the result of a successful Test-MAPIConnectivity test $success = "Success" #................................... # Logfile Strings #................................... $logstring0 = "=====================================" $logstring1 = " Exchange Server Health Check" #................................... # Initialization Strings #................................... $initstring0 = "Initializing..." $initstring1 = "Loading the Exchange Server PowerShell snapin" $initstring2 = "The Exchange Server PowerShell snapin did not load." $initstring3 = "Setting scope to entire forest" #................................... # Error/Warning Strings #................................... $string0 = "Server is not an Exchange server. " $string1 = "Server is not reachable. " $string3 = "------ Checking" $string4 = "Could not test service health. " $string5 = "required services not running. " $string6 = "Could not check queue. " $string7 = "Public Folder database not mounted. " $string8 = "Skipping Edge Transport server. " $string9 = "Mailbox databases not mounted. " $string10 = "MAPI tests failed. " $string11 = "Mail flow test failed. " $string12 = "No Exchange Server 2003 checks performed. " $string13 = "Server not found in DNS. " $string14 = "Sending email. " $string15 = "Done." $string16 = "------ Finishing" $string17 = "Unable to retrieve uptime. " $string18 = "Ping failed. " $string19 = "No alerts found, and AlertsOnly switch was used. No email sent. " $string20 = "You have specified a single server to check" $string21 = "Couldn't find the server $server. Script will terminate." $string22 = "The file $ignorelistfile could not be found. No servers, DAGs or databases will be ignored." $string23 = "You have specified a filename containing a list of servers to check" $string24 = "The file $serverlist could not be found. Script will terminate." $string25 = "Retrieving server list" $string26 = "Removing servers in ignorelist from server list" $string27 = "Beginning the server health checks" $string28 = "Servers, DAGs and databases to ignore:" $string29 = "Servers to check:" $string30 = "Checking DNS" $string31 = "DNS check passed" $string32 = "Checking ping" $string33 = "Ping test passed" $string34 = "Checking uptime" $string35 = "Checking service health" $string36 = "Checking Hub Transport Server" $string37 = "Checking Mailbox Server" $string38 = "Ignore list contains no server names." $string39 = "Checking public folder database" $string40 = "Public folder database status is" $string41 = "Checking mailbox databases" $string42 = "Mailbox database status is" $string43 = "Offline databases: " $string44 = "Checking MAPI connectivity" $string45 = "MAPI connectivity status is" $string46 = "MAPI failed to: " $string47 = "Checking mail flow" $string48 = "Mail flow status is" $string49 = "No active DBs" $string50 = "Finished checking server" $string51 = "Skipped" $string52 = "Using alternative test for Exchange 2013 CAS-only server" $string60 = "Beginning the DAG health checks" $string61 = "Could not determine server with active database copy" $string62 = "mounted on server that is activation preference" $string63 = "unhealthy database copy count is" $string64 = "healthy copy/replay queue count is" $string65 = "(of" $string66 = ")" $string67 = "unhealthy content index count is" $string68 = "DAGs to check:" $string69 = "DAG databases to check" #................................... # Functions #................................... #This function is used to generate HTML for the DAG member health report Function New-DAGMemberHTMLTableCell() { param( $lineitem ) $htmltablecell = $null switch ($($line."$lineitem")) { $null { $htmltablecell = "
Database Availability Group $($dag.Name) Health Summary:
" $dagdetailintro = "Database Availability Group $($dag.Name) Health Details:
" $dagmemberintro = "Database Availability Group $($dag.Name) Member Health:
" $dagdbcopyReport = @() #Database copy health report $dagciReport = @() #Content Index health report $dagmemberReport = @() #DAG member server health report $dagdatabaseSummary = @() #Database health summary report $dagdatabases = @() #Array of databases in the DAG $tmpstring = "---- Processing DAG $($dag.Name)" Write-Verbose $tmpstring if ($Log) {Write-Logfile $tmpstring} $dagmembers = @($dag | Select-Object -ExpandProperty Servers | Sort-Object Name) $tmpstring = "$($dagmembers.count) DAG members found" Write-Verbose $tmpstring if ($Log) {Write-Logfile $tmpstring} #Get all databases in the DAG if ($HasE15) { $tmpdatabases = @(Get-MailboxDatabase -Status -IncludePreExchange2013 | Where-Object {$_.Recovery -ne $true -and $_.MasterServerOrAvailabilityGroup -eq $dag.Name} | Sort-Object Name) } else { $tmpdatabases = @(Get-MailboxDatabase -Status | Where-Object {$_.Recovery -ne $true -and $_.MasterServerOrAvailabilityGroup -eq $dag.Name} | Sort-Object Name) } foreach ($tmpdatabase in $tmpdatabases) { if (!($ignorelist -icontains $tmpdatabase.name)) { $dagdatabases += $tmpdatabase } } $tmpstring = "$($dagdatabases.count) DAG databases will be checked" Write-Verbose $tmpstring if ($Log) {Write-Logfile $tmpstring} if ($Log) {Write-Logfile $string69} if ($Log) { foreach ($database in $dagdatabases) { Write-Logfile "- $database" } } foreach ($database in $dagdatabases) { $tmpstring = "---- Processing database $database" Write-Verbose $tmpstring if ($Log) {Write-Logfile $tmpstring} $activationPref = $null $totalcopies = $null $healthycopies = $null $unhealthycopies = $null $healthyqueues = $null $unhealthyqueues = $null $laggedqueues = $null $healthyindexes = $null $unhealthyindexes = $null #Custom object for Database $objectHash = @{ "Database" = $database.Identity "Mounted on" = "Unknown" "Preference" = $null "Total Copies" = $null "Healthy Copies" = $null "Unhealthy Copies" = $null "Healthy Queues" = $null "Unhealthy Queues" = $null "Lagged Queues" = $null "Healthy Indexes" = $null "Unhealthy Indexes" = $null } $databaseObj = New-Object PSObject -Property $objectHash $dbcopystatus = @($database | Get-MailboxDatabaseCopyStatus) $tmpstring = "$database has $($dbcopystatus.Count) copies" Write-Verbose $tmpstring if ($Log) {Write-Logfile $tmpstring} foreach ($dbcopy in $dbcopystatus) { #Custom object for DB copy $objectHash = @{ "Database Copy" = $dbcopy.Identity "Database Name" = $dbcopy.DatabaseName "Mailbox Server" = $null "Activation Preference" = $null "Status" = $null "Copy Queue" = $null "Replay Queue" = $null "Replay Lagged" = $null "Truncation Lagged" = $null "Content Index" = $null } $dbcopyObj = New-Object PSObject -Property $objectHash $tmpstring = "Database Copy: $($dbcopy.Identity)" Write-Verbose $tmpstring if ($Log) {Write-Logfile $tmpstring} $mailboxserver = $dbcopy.MailboxServer $tmpstring = "Server: $mailboxserver" Write-Verbose $tmpstring if ($Log) {Write-Logfile $tmpstring} $pref = ($database | Select-Object -ExpandProperty ActivationPreference | Where-Object {$_.Key -ieq $mailboxserver}).Value $tmpstring = "Activation Preference: $pref" Write-Verbose $tmpstring if ($Log) {Write-Logfile $tmpstring} $copystatus = $dbcopy.Status $tmpstring = "Status: $copystatus" Write-Verbose $tmpstring if ($Log) {Write-Logfile $tmpstring} [int]$copyqueuelength = $dbcopy.CopyQueueLength $tmpstring = "Copy Queue: $copyqueuelength" Write-Verbose $tmpstring if ($Log) {Write-Logfile $tmpstring} [int]$replayqueuelength = $dbcopy.ReplayQueueLength $tmpstring = "Replay Queue: $replayqueuelength" Write-Verbose $tmpstring if ($Log) {Write-Logfile $tmpstring} if ($($dbcopy.ContentIndexErrorMessage -match "is disabled in Active Directory")) { $contentindexstate = "Disabled" } else { $contentindexstate = $dbcopy.ContentIndexState } $tmpstring = "Content Index: $contentindexstate" Write-Verbose $tmpstring if ($Log) {Write-Logfile $tmpstring} #Checking whether this is a replay lagged copy $replaylagcopies = @($database | Select-Object -ExpandProperty ReplayLagTimes | Where-Object {$_.Value -gt 0}) if ($($replaylagcopies.count) -gt 0) { [bool]$replaylag = $false foreach ($replaylagcopy in $replaylagcopies) { if ($replaylagcopy.Key -ieq $mailboxserver) { $tmpstring = "$database is replay lagged on $mailboxserver" Write-Verbose $tmpstring if ($Log) {Write-Logfile $tmpstring} [bool]$replaylag = $true } } } else { [bool]$replaylag = $false } $tmpstring = "Replay lag is $replaylag" Write-Verbose $tmpstring if ($Log) {Write-Logfile $tmpstring} #Checking for truncation lagged copies $truncationlagcopies = @($database | Select-Object -ExpandProperty TruncationLagTimes | Where-Object {$_.Value -gt 0}) if ($($truncationlagcopies.count) -gt 0) { [bool]$truncatelag = $false foreach ($truncationlagcopy in $truncationlagcopies) { if ($truncationlagcopy.Key -eq $mailboxserver) { $tmpstring = "$database is truncate lagged on $mailboxserver" Write-Verbose $tmpstring if ($Log) {Write-Logfile $tmpstring} [bool]$truncatelag = $true } } } else { [bool]$truncatelag = $false } $tmpstring = "Truncation lag is $truncatelag" Write-Verbose $tmpstring if ($Log) {Write-Logfile $tmpstring} $dbcopyObj | Add-Member NoteProperty -Name "Mailbox Server" -Value $mailboxserver -Force $dbcopyObj | Add-Member NoteProperty -Name "Activation Preference" -Value $pref -Force $dbcopyObj | Add-Member NoteProperty -Name "Status" -Value $copystatus -Force $dbcopyObj | Add-Member NoteProperty -Name "Copy Queue" -Value $copyqueuelength -Force $dbcopyObj | Add-Member NoteProperty -Name "Replay Queue" -Value $replayqueuelength -Force $dbcopyObj | Add-Member NoteProperty -Name "Replay Lagged" -Value $replaylag -Force $dbcopyObj | Add-Member NoteProperty -Name "Truncation Lagged" -Value $truncatelag -Force $dbcopyObj | Add-Member NoteProperty -Name "Content Index" -Value $contentindexstate -Force $dagdbcopyReport += $dbcopyObj } $copies = @($dagdbcopyReport | Where-Object { ($_."Database Name" -eq $database) }) $mountedOn = ($copies | Where-Object { ($_.Status -eq "Mounted") })."Mailbox Server" if ($mountedOn) { $databaseObj | Add-Member NoteProperty -Name "Mounted on" -Value $mountedOn -Force } $activationPref = ($copies | Where-Object { ($_.Status -eq "Mounted") })."Activation Preference" $databaseObj | Add-Member NoteProperty -Name "Preference" -Value $activationPref -Force $totalcopies = $copies.count $databaseObj | Add-Member NoteProperty -Name "Total Copies" -Value $totalcopies -Force $healthycopies = @($copies | Where-Object { (($_.Status -eq "Mounted") -or ($_.Status -eq "Healthy")) }).Count $databaseObj | Add-Member NoteProperty -Name "Healthy Copies" -Value $healthycopies -Force $unhealthycopies = @($copies | Where-Object { (($_.Status -ne "Mounted") -and ($_.Status -ne "Healthy")) }).Count $databaseObj | Add-Member NoteProperty -Name "Unhealthy Copies" -Value $unhealthycopies -Force $healthyqueues = @($copies | Where-Object { (($_."Copy Queue" -lt $replqueuewarning) -and (($_."Replay Queue" -lt $replqueuewarning)) -and ($_."Replay Lagged" -eq $false)) }).Count $databaseObj | Add-Member NoteProperty -Name "Healthy Queues" -Value $healthyqueues -Force $unhealthyqueues = @($copies | Where-Object { (($_."Copy Queue" -ge $replqueuewarning) -or (($_."Replay Queue" -ge $replqueuewarning) -and ($_."Replay Lagged" -eq $false))) }).Count $databaseObj | Add-Member NoteProperty -Name "Unhealthy Queues" -Value $unhealthyqueues -Force $laggedqueues = @($copies | Where-Object { ($_."Replay Lagged" -eq $true) -or ($_."Truncation Lagged" -eq $true) }).Count $databaseObj | Add-Member NoteProperty -Name "Lagged Queues" -Value $laggedqueues -Force $healthyindexes = @($copies | Where-Object { ($_."Content Index" -eq "Healthy" -or $_."Content Index" -eq "Disabled" -or $_."Content Index" -eq "AutoSuspended") }).Count $databaseObj | Add-Member NoteProperty -Name "Healthy Indexes" -Value $healthyindexes -Force $unhealthyindexes = @($copies | Where-Object { ($_."Content Index" -ne "Healthy" -and $_."Content Index" -ne "Disabled" -and $_."Content Index" -ne "AutoSuspended") }).Count $databaseObj | Add-Member NoteProperty -Name "Unhealthy Indexes" -Value $unhealthyindexes -Force $dagdatabaseSummary += $databaseObj } #Get Test-Replication Health results for each DAG member foreach ($dagmember in $dagmembers) { $replicationhealth = $null $replicationhealthitems = @{ ClusterService = $null ReplayService = $null ActiveManager = $null TasksRpcListener = $null TcpListener = $null ServerLocatorService = $null DagMembersUp = $null ClusterNetwork = $null QuorumGroup = $null FileShareQuorum = $null DatabaseRedundancy = $null DatabaseAvailability = $null DBCopySuspended = $null DBCopyFailed = $null DBInitializing = $null DBDisconnected = $null DBLogCopyKeepingUp = $null DBLogReplayKeepingUp = $null } $memberObj = New-Object PSObject -Property $replicationhealthitems $memberObj | Add-Member NoteProperty -Name "Server" -Value $($dagmember.Name) $tmpstring = "---- Checking replication health for $($dagmember.Name)" Write-Verbose $tmpstring if ($Log) {Write-Logfile $tmpstring} if ($HasE15) { $DagMemberVer = ($GetExchangeServerResults | Where-Object {$_.Name -ieq $dagmember.Name}).AdminDisplayVersion.ToString() } if ($DagMemberVer -like "Version 14.*") { if ($Log) {Write-Logfile "Using E14 replication health test workaround"} $replicationhealth = Test-E14ReplicationHealth $dagmember } else { $replicationhealth = Test-ReplicationHealth -Identity $dagmember } foreach ($healthitem in $replicationhealth) { if ($($healthitem.Result) -eq $null) { $healthitemresult = "n/a" } else { $healthitemresult = $($healthitem.Result) } $tmpstring = "$($healthitem.Check) $healthitemresult" Write-Verbose $tmpstring if ($Log) {Write-Logfile $tmpstring} $memberObj | Add-Member NoteProperty -Name $($healthitem.Check) -Value $healthitemresult -Force } $dagmemberReport += $memberObj } #Generate the HTML from the DAG health checks if ($SendEmail -or $ReportFile) { ####Begin Summary Table HTML $dagdatabaseSummaryHtml = $null #Begin Summary table HTML header $htmltableheader = "
Database | Mounted on | Preference | Total Copies | Healthy Copies | Unhealthy Copies | Healthy Queues | Unhealthy Queues | Lagged Queues | Healthy Indexes | Unhealthy Indexes | ||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
$($line.Database) | " #Warn if mounted server is still unknown switch ($($line."Mounted on")) { "Unknown" { $htmltablerow += "$($line."Mounted on") | " $dagsummary += "$($line.Database) - $string61" } default { $htmltablerow += "$($line."Mounted on") | " } } #Warn if DB is mounted on a server that is not Activation Preference 1 if ($($line.Preference) -gt 1) { $htmltablerow += "$($line.Preference) | " $dagsummary += "$($line.Database) - $string62 $($line.Preference)" } else { $htmltablerow += "$($line.Preference) | " } $htmltablerow += "$($line."Total Copies") | " #Show as info if health copies is 1 but total copies also 1, #Warn if healthy copies is 1, Fail if 0 switch ($($line."Healthy Copies")) { 0 {$htmltablerow += "$($line."Healthy Copies") | "} 1 { if ($($line."Total Copies") -eq $($line."Healthy Copies")) { $htmltablerow += "$($line."Healthy Copies") | " } else { $htmltablerow += "$($line."Healthy Copies") | " } } default {$htmltablerow += "$($line."Healthy Copies") | "} } #Warn if unhealthy copies is 1, fail if more than 1 switch ($($line."Unhealthy Copies")) { 0 { $htmltablerow += "$($line."Unhealthy Copies") | " } 1 { $htmltablerow += "$($line."Unhealthy Copies") | " $dagsummary += "$($line.Database) - $string63 $($line."Unhealthy Copies") $string65 $($line."Total Copies") $string66" } default { $htmltablerow += "$($line."Unhealthy Copies") | " $dagsummary += "$($line.Database) - $string63 $($line."Unhealthy Copies") $string65 $($line."Total Copies") $string66" } } #Warn if healthy queues + lagged queues is less than total copies #Fail if no healthy queues if ($($line."Total Copies") -eq ($($line."Healthy Queues") + $($line."Lagged Queues"))) { $htmltablerow += "$($line."Healthy Queues") | " } else { $dagsummary += "$($line.Database) - $string64 $($line."Healthy Queues") $string65 $($line."Total Copies") $string66" switch ($($line."Healthy Queues")) { 0 { $htmltablerow += "$($line."Healthy Queues") | " } default { $htmltablerow += "$($line."Healthy Queues") | " } } } #Fail if unhealthy queues = total queues #Warn if more than one unhealthy queue if ($($line."Total Queues") -eq $($line."Unhealthy Queues")) { $htmltablerow += "$($line."Unhealthy Queues") | " } else { switch ($($line."Unhealthy Queues")) { 0 { $htmltablerow += "$($line."Unhealthy Queues") | " } default { $htmltablerow += "$($line."Unhealthy Queues") | " } } } #Info for lagged queues switch ($($line."Lagged Queues")) { 0 { $htmltablerow += "$($line."Lagged Queues") | " } default { $htmltablerow += "$($line."Lagged Queues") | " } } #Pass if healthy indexes = total copies #Warn if healthy indexes less than total copies #Fail if healthy indexes = 0 if ($($line."Total Copies") -eq $($line."Healthy Indexes")) { $htmltablerow += "$($line."Healthy Indexes") | " } else { $dagsummary += "$($line.Database) - $string67 $($line."Unhealthy Indexes") $string65 $($line."Total Copies") $string66" switch ($($line."Healthy Indexes")) { 0 { $htmltablerow += "$($line."Healthy Indexes") | " } default { $htmltablerow += "$($line."Healthy Indexes") | " } } } #Fail if unhealthy indexes = total copies #Warn if unhealthy indexes 1 or more #Pass if unhealthy indexes = 0 if ($($line."Total Copies") -eq $($line."Unhealthy Indexes")) { $htmltablerow += "$($line."Unhealthy Indexes") | " } else { switch ($($line."Unhealthy Indexes")) { 0 { $htmltablerow += "$($line."Unhealthy Indexes") | " } default { $htmltablerow += "$($line."Unhealthy Indexes") | " } } } $htmltablerow += "
Database Copy | Database Name | Mailbox Server | Activation Preference | Status | Copy Queue | Replay Queue | Replay Lagged | Truncation Lagged | Content Index | ||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
$($line."Database Copy") | " $htmltablerow += "$($line."Database Name") | " $htmltablerow += "$($line."Mailbox Server") | " $htmltablerow += "$($line."Activation Preference") | " Switch ($($line."Status")) { "Healthy" { $htmltablerow += "$($line."Status") | " } "Mounted" { $htmltablerow += "$($line."Status") | " } "Failed" { $htmltablerow += "$($line."Status") | " } "FailedAndSuspended" { $htmltablerow += "$($line."Status") | " } "ServiceDown" { $htmltablerow += "$($line."Status") | " } "Dismounted" { $htmltablerow += "$($line."Status") | " } default { $htmltablerow += "$($line."Status") | " } } if ($($line."Copy Queue") -lt $replqueuewarning) { $htmltablerow += "$($line."Copy Queue") | " } else { $htmltablerow += "$($line."Copy Queue") | " } if (($($line."Replay Queue") -lt $replqueuewarning) -or ($($line."Replay Lagged") -eq $true)) { $htmltablerow += "$($line."Replay Queue") | " } else { $htmltablerow += "$($line."Replay Queue") | " } Switch ($($line."Replay Lagged")) { $true { $htmltablerow += "$($line."Replay Lagged") | " } default { $htmltablerow += "$($line."Replay Lagged") | " } } Switch ($($line."Truncation Lagged")) { $true { $htmltablerow += "$($line."Truncation Lagged") | " } default { $htmltablerow += "$($line."Truncation Lagged") | " } } Switch ($($line."Content Index")) { "Healthy" { $htmltablerow += "$($line."Content Index") | " } "Disabled" { $htmltablerow += "$($line."Content Index") | " } default { $htmltablerow += "$($line."Content Index") | " } } $htmltablerow += "
Server | Cluster Service | Replay Service | Active Manager | Tasks RPC Listener | TCP Listener | Server Locator Service | DAG Members Up | Cluster Network | Quorum Group | File Share Quorum | Database Redundancy | Database Availability | DB Copy Suspended | DB Copy Failed | DB Initializing | DB Disconnected | DB Log Copy Keeping Up | DB Log Replay Keeping Up |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
$($line."Server") | " $htmltablerow += (New-DAGMemberHTMLTableCell "ClusterService") $htmltablerow += (New-DAGMemberHTMLTableCell "ReplayService") $htmltablerow += (New-DAGMemberHTMLTableCell "ActiveManager") $htmltablerow += (New-DAGMemberHTMLTableCell "TasksRPCListener") $htmltablerow += (New-DAGMemberHTMLTableCell "TCPListener") $htmltablerow += (New-DAGMemberHTMLTableCell "ServerLocatorService") $htmltablerow += (New-DAGMemberHTMLTableCell "DAGMembersUp") $htmltablerow += (New-DAGMemberHTMLTableCell "ClusterNetwork") $htmltablerow += (New-DAGMemberHTMLTableCell "QuorumGroup") $htmltablerow += (New-DAGMemberHTMLTableCell "FileShareQuorum") $htmltablerow += (New-DAGMemberHTMLTableCell "DatabaseRedundancy") $htmltablerow += (New-DAGMemberHTMLTableCell "DatabaseAvailability") $htmltablerow += (New-DAGMemberHTMLTableCell "DBCopySuspended") $htmltablerow += (New-DAGMemberHTMLTableCell "DBCopyFailed") $htmltablerow += (New-DAGMemberHTMLTableCell "DBInitializing") $htmltablerow += (New-DAGMemberHTMLTableCell "DBDisconnected") $htmltablerow += (New-DAGMemberHTMLTableCell "DBLogCopyKeepingUp") $htmltablerow += (New-DAGMemberHTMLTableCell "DBLogReplayKeepingUp") $htmltablerow += "
No database availability groups found.
" } ### End DAG Health Report Write-Host $string16 ### Begin report generation if ($ReportMode -or $SendEmail) { #Get report generation timestamp $reportime = Get-Date #Create HTML Report #Common HTML head and styles $htmlhead="The following server errors and warnings were detected.
No Exchange server health errors or warnings.
" } #Check if the DAG summary has 1 or more entries if ($($dagsummary.count) -gt 0) { #Set alert flag to true $alerts = $true #Generate the HTML $dagsummaryhtml = "The following DAG errors and warnings were detected.
No Exchange DAG errors or warnings.
" } #Exchange Server Health Report Table Header $htmltableheader = "
Server | Site | Roles | Version | DNS | Ping | Uptime (hrs) | Client Access Server Role Services | Hub Transport Server Role Services | Mailbox Server Role Services | Unified Messaging Server Role Services | Transport Queue | PF DBs Mounted | MB DBs Mounted | MAPI Test | Mail Flow Test |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
$($reportline.server) | " $htmltablerow += "$($reportline.site) | " $htmltablerow += "$($reportline.roles) | " $htmltablerow += "$($reportline.version) | " $htmltablerow += (New-ServerHealthHTMLTableCell "dns") $htmltablerow += (New-ServerHealthHTMLTableCell "ping") if ($($reportline."uptime (hrs)") -eq "Access Denied") { $htmltablerow += "Access Denied | " } elseif ($($reportline."uptime (hrs)") -eq $string17) { $htmltablerow += "$string17 | " } else { $hours = [int]$($reportline."uptime (hrs)") if ($hours -le 24) { $htmltablerow += "$hours | " } else { $htmltablerow += "$hours | " } } $htmltablerow += (New-ServerHealthHTMLTableCell "Client Access Server Role Services") $htmltablerow += (New-ServerHealthHTMLTableCell "Hub Transport Server Role Services") $htmltablerow += (New-ServerHealthHTMLTableCell "Mailbox Server Role Services") $htmltablerow += (New-ServerHealthHTMLTableCell "Unified Messaging Server Role Services") #$htmltablerow += (New-ServerHealthHTMLTableCell "Transport Queue") if ($($reportline."Transport Queue") -match "Pass") { $htmltablerow += "$($reportline."Transport Queue") | " } elseif ($($reportline."Transport Queue") -match "Warn") { $htmltablerow += "$($reportline."Transport Queue") | " } elseif ($($reportline."Transport Queue") -match "Fail") { $htmltablerow += "$($reportline."Transport Queue") | " } elseif ($($reportline."Transport Queue") -eq "n/a") { $htmltablerow += "$($reportline."Transport Queue") | " } else { $htmltablerow += "$($reportline."Transport Queue") | " } $htmltablerow += (New-ServerHealthHTMLTableCell "PF DBs Mounted") $htmltablerow += (New-ServerHealthHTMLTableCell "MB DBs Mounted") $htmltablerow += (New-ServerHealthHTMLTableCell "MAPI Test") $htmltablerow += (New-ServerHealthHTMLTableCell "Mail Flow Test") $htmltablerow += "