ZebiDiskHealthReport.ps1
Param ( [bool]$Debug = $false, [bool]$Console = $false ) <#============================================================================== File Name : ZebiDiskHealthReport.ps1 Original Author : Kenneth C. Mazie (kcmjr @ kcmjr.com) : Description : This script will query multiple Tegile Zebi Controllers, : parse the output from "zpool list", and email a report. : Arguments : Named commandline parameters: (all are optional) : "-console" - Displays console output during run. : "-debug" - Switches email recipient. : Notes : Settings are loaded from an XML file located in the script folder. : See the end of the script for config file example. : Requirements : Requires PS v5. Requires Posh-SSH module. : Warnings : Make absolutely sure the proper user is in the confix file AND on each Zebi : as well as is listed in the sshd_config file on the zebi. : Legal : Public Domain. Modify and redistribute freely. No rights reserved. : SCRIPT PROVIDED "AS IS" WITHOUT WARRANTIES OR GUARANTEES OF : ANY KIND. USE AT YOUR OWN RISK. NO TECHNICAL SUPPORT PROVIDED. : Credits : Code snippets and/or ideas came from many sources around the web. : Last Update by : Kenneth C. Mazie (email kcmjr AT kcmjr.com for comments or to report bugs) Version History : v1.00 - 02-03-16 - Original Change History : v1.10 - 02-14-17 - Retooled from Zebi replication report script. : v1.20 - 02-24-17 – Added separate triggers for pool and meta use. : v1.30 - 03-02-17 - Fixed error caused by meta free less than 10 without leading zero : v1.40 - 09-22-17 - Added color to % used : v1.50 - 10-18-17 - Tweaked colors to be more logarithmic. Added notification for SSH failure : v1.51 - 03-02-18 - Minor notation tweak for PS Gallery upload : v1.52 - 01-11-19 - Fixed config file load error. : #===============================================================================#> <#PSScriptInfo .VERSION 1.52 .GUID aaebd4f5-e60d-4d4b-b530-4242db7843aa .AUTHOR Kenneth C. Mazie (kcmjr AT kcmjr.com) .DESCRIPTION This script will query multiple Tegile Zebi Controllers, parse the output from "zpool list", and email a report. #> #requires -version 5.0 clear-host #--[ Bypass for testing ]-- # [bool]$Debug = $true # [bool]$Console = $true #--[ Store all the start up variables so you can clean up when the script finishes. ]-- if ($startupvariables) { try {Remove-Variable -Name startupvariables -Scope Global -ErrorAction SilentlyContinue } catch { } } New-Variable -force -name startupVariables -value ( Get-Variable | ForEach-Object { $_.Name } ) If (!(Get-Module Posh-SSH)){Import-Module "Posh-SSH" -ErrorAction SilentlyContinue} If ($Debug){$Script:Debug = $true} If ($Console){$Script:Console = $true} $ErrorActionPreference = "silentlycontinue" $Computer = $Env:ComputerName $Script:ScriptName = ($MyInvocation.MyCommand.Name).split(".")[0] $Script:LogFile = $PSScriptRoot+"\"+$ScriptName+"_{0:MM-dd-yyyy_HHmmss}.log" -f (Get-Date) $Script:ConfigFile = "$PSScriptRoot\$ScriptName.xml" $Script:Datetime = Get-Date -Format "MM-dd-yyyy_HH:mm" #--[ Current date $ time ]-- $Script:Today = Get-Date -Format "MM-dd-yyyy" #--[ Current date ]-- $Script:ThisYear = Get-Date -Format "yyyy" #--[ Current year ]-- $EpochDiff = New-TimeSpan '01 January 1970 00:00:00' $(Get-Date) #--[ Seconds since 01-01-1970 ]-- $EpochSecs = [INT] $EpochDiff.TotalSeconds #--[ Rounded ]-- $EpochDays = [INT] (($EpochDiff.TotalSeconds)/86400) #--[ Converted to days ]-- $Script:Span = 11 #--[ HTML colm count ]-- $Script:BadMetaPercent = 85 #--[ Meta use percent after which report turns red ]-- $Script:BadPoolPercent = 80 #--[ Pool use percent after which report turns red ]-- #--[ Functions ]---------------------------------------------------------------- Function ResetVariables { Get-Variable | Where-Object { $startupVariables -notcontains $_.Name } | ForEach-Object { try { Remove-Variable -Name "$($_.Name)" -Force -Scope "global" -ErrorAction SilentlyContinue -WarningAction SilentlyContinue}catch{ } } } Function SendEmail { If ($Script:Debug){$ErrorActionPreference = "stop"} $email = New-Object System.Net.Mail.MailMessage $email.From = $Script:EmailFrom $email.IsBodyHtml = $Script:EmailHTML if ($Script:Debug){ $email.To.Add($Script:DebugEmail) }else{ $email.To.Add($Script:EmailTo) } $email.Subject = $Script:EmailSubject $email.Body = $Script:ReportBody $smtp = new-object Net.Mail.SmtpClient($Script:SmtpServer) $smtp.Send($email) If ($Script:Console){Write-Host "`nEmail sent...`n"} } #--[ Read and load configuration file ]----------------------------------------- If (!(Test-Path "$PSScriptRoot\$ScriptName.xml")){ #--[ Error out if configuration file doesn't exist ]-- Write-host "MISSING CONFIG FILE. Script aborted." -forgroundcolor red break }Else{ [xml]$Script:Configuration = Get-Content $Script:ConfigFile #--[ Load configuration ]-- $Script:DebugEmail = $Script:Configuration.Settings.Email.Debug $Script:EmailTo = $Script:Configuration.Settings.Email.To $Script:EmailHTML = $Script:Configuration.Settings.Email.HTML $Script:EmailSubject = $Script:Configuration.Settings.Email.Subject $Script:EmailFrom = $Script:Configuration.Settings.Email.From $Script:SmtpServer = $Script:Configuration.Settings.Email.SmtpServer $Script:UserName = $Script:Configuration.Settings.Credentials.Username $Script:Password = $Script:Configuration.Settings.Credentials.Password [array]$Script:Targets = $Script:Configuration.Settings.General.Targets $Script:ReportName = $Script:Configuration.Settings.General.ReportName } #--[ Add header to html output file ]-- $Script:ReportBody = @() $Script:ReportBody += ' <style type="text/css"> table.myTable { border:5px solid black;border-collapse:collapse;} table.myTable td { border:2px solid black;padding:5px;white-space:nowrap;} table.myTable tr { border:2px solid black;padding:5px;white-space:nowrap;} table.myTable th { border:2px solid black;padding:5px;background:white-space:nowrap;} table.bottomBorder { border-collapse:collapse; } table.bottomBorder td, table.bottomBorder th { border-bottom:1px dotted black;padding:5px; } tr.noBorder td {border:0} td.auto { border:2px solid black;padding:5px;white-space:nowrap;} </style>' $Script:ReportBody += '<table class="myTable"> <tr class="noBorder"><td colspan='+$Script:Span+'><center><h1>- ' + $Script:ReportName + ' -</h1></td></tr> <tr class="noBorder"><td colspan='+$Script:Span+'><center>The following report displays statistics for the disk pools on all Tegile SAN controllers.</td></tr> <tr class="noBorder"><td colspan='+$Script:Span+'><center>Metadata is only used for pools that host data, not system pools. Not all controllers host data.</td></tr> <tr class="noBorder"><td colspan='+$Script:Span+'><center>If Metadata useage exceeds 85% manual cleanup is recommended. Above 95% writes will begin to fail.</td></tr> <tr class="noBorder"><td colspan='+$Script:Span+'><center>If Pool data useage exceeds 80% cell color will change to indicate manual inspection is recommended.</td></tr> <tr class="noBorder"><td colspan='+$Script:Span+'></tr> ' Foreach ($Target in $Script:Targets.Target){ if ($Console){Write-Host "`n--[ Processing Target: $Target ]-----------------------------------" -ForegroundColor Yellow } $Cmd = '/usr/sbin/zpool list' #--[ If you change this command keep the path or SSH will not record data ]-- Remove-SSHSession -SessionId 0 -ErrorAction SilentlyContinue | Out-Null #--[ Clear out previous session if it still exists ]-- $SecPassword = ConvertTo-SecureString $Script:Password -AsPlainText -Force $Creds = New-Object System.Management.Automation.PSCredential ($Script:UserName, $SecPassword) Try{ $SSH = New-SshSession -ComputerName $Target -Credential $Creds -AcceptKey:$true #| Out-Null #--[ Open new SSH session ]-- $Script:Return = $(Invoke-SSHCommand -SSHSession $SSH -Command $Cmd).Output #--[ Invoke SSH command and capture the output as a string ]-- }Catch{ $Script:Return = "SSH_Failure" } #If ($Script:Console){$Script:Return} #--[ Display raw output ]-- #--[ HTML Row Color Settings ]------------------------------------------ $ColorGrey = "#dfdfdf" #--[ Grey default cell background ]-- $ColorRed = "#ff0000" #--[ Red background for alerts ]-- $ColorOra = "#ff9900" #--[ Orange background for alerts ]-- $ColorYel = "#ffd900" #--[ Yellow background for alerts ]-- $ColorBla = "#000000" #--[ Black default cell foreground ]-- $lineCount = 0 $color = 10 #--[ Build target table ]-- $Script:HTMLData = @() $Script:HTMLData += '<tr class="myTable"><th>Controller</th><th>Pool</th><th>Pool Size</th><th>Pool Used</th><th>Pool Free</th><th>Pool % Used</th><th>Dedup %</th><th>Meta Size</th><th>Meta Used</th><th>Meta Free</th><th>Meta % Used</th></tr>' $Script:RowData = "" Foreach ($Line in $Script:Return){ If ($Console){write-host $Line -ForegroundColor $color } #--[ Display parsed data for debugging ]-- If ($Line -eq "SSH_Failure"){ $LineCount = 1 #$Script:RowData += '<tr>' #--[ Start table row ]-- #$Script:RowData += '<td class="myTable" bgcolor=' + $ColorGrey + '><font color=' + $ColorBla + '>' + $Target + '</td>' #$Script:RowData += '<td class="myTable" bgcolor=' + $ColorGrey + '><font color=' + $ColorBla + '>Logon Failure</td>' } If ($LineCount -ge 1){ #--[ Ignore Output Header ]-- $Line = $Line -replace "\s+", "," $Script:RowData += '<tr>' #--[ Start table row ]-- $Script:RowData += '<td class="myTable" bgcolor=' + $ColorGrey + '><font color=' + $ColorBla + '>' + $Target + '</td>' #--[ Host (controller) $Script:RowData += '<td class="myTable" bgcolor=' + $ColorGrey + '><font color=' + $ColorBla + '>' + $Line.Split(",")[0] + '</td>' #--[ Pool $Script:RowData += '<td class="myTable" bgcolor=' + $ColorGrey + '><font color=' + $ColorBla + '>' + $Line.Split(",")[1] + '</td>' #--[ Pool Size $Script:RowData += '<td class="myTable" bgcolor=' + $ColorGrey + '><font color=' + $ColorBla + '>' + $Line.Split(",")[2] + '</td>' #--[ Pool Used $Script:RowData += '<td class="myTable" bgcolor=' + $ColorGrey + '><font color=' + $ColorBla + '>' + $Line.Split(",")[3] + '</td>' #--[ Pool Free if (($Console) -and ($Debug)){write-host 'Pool % Used: '(($Line.Split(",")[4]).Split("%")[0])} [int]$Percentage = ($Line.Split(",")[4]).Split("%")[0].ToString() #--[ Apply background cell color according to percent used ]-- if ($Percentage -gt 95){$BGColor = "#FF0000"} if ($Percentage -le 95){$BGColor = "#ff1a00"} if ($Percentage -le 91){$BGColor = "#ff2600"} if ($Percentage -le 87){$BGColor = "#ff3300"} if ($Percentage -le 79){$BGColor = "#ff4000"} if ($Percentage -le 76){$BGColor = "#ff5900"} if ($Percentage -le 73){$BGColor = "#ff7300"} if ($Percentage -le 71){$BGColor = "#ff8c00"} if ($Percentage -le 69){$BGColor = "#ffa600"} if ($Percentage -le 66){$BGColor = "#ffbf00"} if ($Percentage -le 63){$BGColor = "#ffd900"} if ($Percentage -le 60){$BGColor = "#ffff00"} if ($Percentage -le 55){$BGColor = "#d9e800"} if ($Percentage -le 50){$BGColor = "#bfd900"} if ($Percentage -le 45){$BGColor = "#a6c900"} if ($Percentage -le 40){$BGColor = "#8cba00"} if ($Percentage -le 35){$BGColor = "#73ab00"} if ($Percentage -le 30){$BGColor = "#599c00"} if ($Percentage -le 20){$BGColor = "#408c00"} if ($Percentage -le 10) {$BGColor = "#0d6e00"} if ($Percentage -le 1) {$BGColor = "#006600"} $Script:RowData += '<td class="myTable" bgcolor=' + $BGColor + '><font color=' + $ColorBla + '>' + $Line.Split(",")[4] + '</td>' #--[ Pool % Used #If (($Line.Split(",")[4]).Split("%")[0] -ge $Script:BadPoolPercent){ # $Script:RowData += '<td class="myTable" bgcolor=' + $ColorYel + '><font color=' + $ColorRed + '>' + $Line.Split(",")[4] + '</td>' #--[ Pool % Used BAD #}Else{ # $Script:RowData += '<td class="myTable" bgcolor=' + $ColorGrey + '><font color=' + $ColorBla + '>' + $Line.Split(",")[4] + '</td>' #--[ Pool % Used #} $Script:RowData += '<td class="myTable" bgcolor=' + $ColorGrey + '><font color=' + $ColorBla + '>' + $Line.Split(",")[5] + '</td>' #--[ Dedup $Script:RowData += '<td class="myTable" bgcolor=' + $ColorGrey + '><font color=' + $ColorBla + '>' + $Line.Split(",")[8] + '</td>' #--[ Meta Size $Script:RowData += '<td class="myTable" bgcolor=' + $ColorGrey + '><font color=' + $ColorBla + '>' + $Line.Split(",")[9] + '</td>' #--[ Meta Used $Script:RowData += '<td class="myTable" bgcolor=' + $ColorGrey + '><font color=' + $ColorBla + '>' + $Line.Split(",")[10] + '</td>' #--[ Meta % Used BAD If ($Line -eq "SSH_Failure"){ $Script:RowData += '<td class="myTable" bgcolor=' + $ColorGrey + '><font color=' + $ColorBla + '>' + $Line.Split(",")[11] + '</td>' #--[ Filler for erros }Else{ If ("{0:D2}" -f [int](($Line.Split(",")[11]).Split("%")[0]) -ge $Script:BadMetaPercent){ $Script:RowData += '<td class="myTable" bgcolor=' + $ColorYel + '><font color=' + $ColorRed + '>' + $Line.Split(",")[11] + '</td>' #--[ Meta % Used $Script:BadMetaPercent If ($Console -and $Debug){write-host "Meta % Used: "("{0:D2}" -f [int](($Line.Split(",")[11]).Split("%")[0])) -ForegroundColor Red } }Else{ $Script:RowData += '<td class="myTable" bgcolor=' + $ColorGrey + '><font color=' + $ColorBla + '>' + $Line.Split(",")[11] + '</td>' #--[ Meta Cap If ($Console -and $Debug){write-host "Meta % Used: "("{0:D2}" -f [int](($Line.Split(",")[11]).Split("%")[0]))} } } $Script:RowData += '</tr>' #--[ Start table row ]-- } $LineCount++ $color++ } $Script:HTMLData += $Script:RowData $Script:ReportBody += $Script:HTMLData $Script:ReportBody += '<tr class="noBorder"><td colspan='+$Script:Span+'></tr>' } $Script:ReportBody += '</table><br><br>' $Script:ReportBody += '<tr class="noBorder"><td colspan=8><font color=#909090>Script "'+$MyInvocation.MyCommand.Name+'" executed from server "'+$env:computername+'".</td></tr>' $Script:ReportBody += "<br>Script executed on "+$Datetime+"<br><br>" $Script:ReportBody | Out-File "$Script:FullFileName.html" SendEmail [gc]::Collect() [gc]::WaitForPendingFinalizers() ResetVariables if ($Console){Write-Host "--- Completed ---" -ForegroundColor Red } <#-----------------------------[ Config File ]--------------------------------- The configuration file must be named identically to the script and must reside in the same folder as the script. Below is the format and element list: <!-- Settings & Configuration File --> <Settings> <General> <ReportName>Zebi Disk Pool Health Report</ReportName> <ScriptName>ReplicationReport</ScriptName> <Targets> <Target>10.100.1.1</Target> <Target>10.100.1.2</Target> <Target>10.100.1.3</Target> <Target>10.100.1.4</Target> <Target>10.100.1.5</Target> <Target>10.100.1.6</Target> <Target>10.100.1.7</Target> <Target>10.100.1.8</Target> </Targets> </General> <Email> <From>WeeklyReports@mydomain.com</From> <To>me@mydomain.com,you@yourdomain.com</To> <Subject>Zebi Disk Pool Status Report</Subject> <HTML>$true</HTML> <SmtpServer>10.100.1.10</SmtpServer> </Email> <Credentials> <UserName>zebiadminuser</UserName> <Password>zebiadminpwd</Password> </Credentials> </Settings> #> |