Private/Kinds/Server.ps1
|
# The Server Kind: whether the machines an application depends on can be reached. # # The first Kind whose parameters are genuinely per-Customer. Every other Kind asks the # same questions of every machine; this one cannot be performed at all without knowing # which Servers matter, and those differ per Customer by definition. It is therefore also # the first real exercise of a Check Definition configuring rather than merely tuning. # # The same Kind serves an application's Servers and Servers a Technician supplied directly. # There is no difference between them worth a second implementation: a Server is a host, # optionally with ports, and the questions are the same either way. # How many times each port is tried and how many pings are sent. Not Check Definition # parameters, for the reason Private/Sampling.ps1 gives: a Customer may disagree about a # threshold, not about how many attempts make a measurement. One connect is not evidence - # a firewall that drops one connection in five is exactly what makes an application feel # unreliable, and a single successful probe would report it healthy. $script:ServerTcpAttempts = 5 $script:ServerPingCount = 20 function ConvertTo-ServerTarget { <# .SYNOPSIS Parses one Server entry into a host and its ports. Pure, and reaches nothing. .DESCRIPTION A Server is written as "host", "host:port" or "host:port,port". Parsing is separate from probing so that what Gutcheck understood from a Check Definition can be tested without a network, which matters because a mistyped entry in the Checks Repo would otherwise surface as a Customer's server being unreachable. #> [CmdletBinding()] [OutputType([psobject])] param([AllowNull()][AllowEmptyString()][string]$Entry) if (-not $Entry -or -not "$Entry".Trim()) { return } $parts = "$Entry".Trim() -split ':', 2 $hostName = $parts[0].Trim() if (-not $hostName) { return } $ports = @() if ($parts.Count -gt 1) { $ports = @($parts[1] -split ',' | ForEach-Object { $_.Trim() } | Where-Object { $_ -match '^\d+$' } | ForEach-Object { [int]$_ } | Where-Object { $_ -ge 1 -and $_ -le 65535 }) } [pscustomobject]@{ PSTypeName = 'Gutcheck.ServerTarget' Entry = "$Entry".Trim() HostName = $hostName Ports = $ports # An address literal has nothing to resolve, and reporting DNS for one would be # reporting on a lookup that never happened. IsIpLiteral = $hostName -match '^\d{1,3}(\.\d{1,3}){3}$' } } function ConvertTo-ServerRoute { <# .SYNOPSIS Whether an address is in the machine's own network or beyond a gateway. Pure. .DESCRIPTION A route without a next hop is an address next door. How the machine is connected - by cable or by radio - is not asked here: that is the Situation, the same for every Server. Where a Server is, is this Server's own. #> [CmdletBinding()] [OutputType([psobject])] param([AllowNull()][AllowEmptyCollection()]$Route) $hops = @($Route | ForEach-Object { "$(Get-DataProperty $_ 'NextHop')" } | Where-Object { $_ }) # Nothing said about a next hop is nothing known, not an address next door. if (-not $hops.Count) { return } [pscustomobject]@{ OnLink = @($hops | Where-Object { $_ -notin '0.0.0.0', '::' }).Count -eq 0 } } function Get-ServerRoute { <# .SYNOPSIS Asks Windows how it would reach an address. Nothing where it cannot say: a Finding then says what fits either way. #> [CmdletBinding()] [OutputType([psobject])] param([AllowNull()][AllowEmptyString()][string]$Address) if (-not $Address -or -not (Get-Command -Name Find-NetRoute -ErrorAction SilentlyContinue)) { return } try { ConvertTo-ServerRoute -Route @(Find-NetRoute -RemoteIPAddress $Address -ErrorAction Stop) } catch { } } function Get-ServerMailboxTarget { <# .SYNOPSIS Which Servers to measure for the mailboxes that are set up, out of what the Autodiscover Check gathered: the Customer's own Exchange for a mailbox that is on it, the Servers of the Check Definition for one in Exchange Online. Pure. .DESCRIPTION "Laufzeitmessungen (Server)" under Outlook measured Microsoft's two Servers on every machine, also on one whose mailbox is on the Customer's own Exchange, and said nothing of the Server Outlook talks to. Each target says for which mail domains it is measured and as what (Origin). A Server two mailboxes share is measured once. Where no mailbox is known - the Autodiscover Check did not run, or found none - the Servers of the Check Definition are measured as before, and Reason says why. #> [CmdletBinding()] [OutputType([psobject])] param([AllowNull()]$Autodiscover, [AllowNull()][AllowEmptyCollection()][string[]]$Default = @()) $mailboxes = @() $reason = '' if ($null -eq $Autodiscover) { $reason = 'NoAutodiscover' } else { $mailboxes = @(Get-AutodiscoverMailboxServer -Data $Autodiscover) if (-not $mailboxes.Count) { $reason = 'NoMailbox' } } $order = New-Object System.Collections.Generic.List[string] $byEntry = @{} $add = { param([string]$Entry, [string]$Origin, [string]$Domain) $key = $Entry.ToLowerInvariant() if (-not $byEntry.ContainsKey($key)) { $order.Add($key) $byEntry[$key] = [pscustomobject]@{ Entry = $Entry; Origin = $Origin; Domains = (New-Object System.Collections.Generic.List[string]) } } if ($Domain -and -not $byEntry[$key].Domains.Contains($Domain)) { $byEntry[$key].Domains.Add($Domain) } } if ($reason) { foreach ($entry in @($Default)) { & $add $entry 'Default' '' } } foreach ($mailbox in $mailboxes) { if ($mailbox.Location -eq 'OnPremises') { $origin = if ($mailbox.Source -eq 'Cached') { 'OnPremises' } else { 'OnPremisesAutodiscover' } foreach ($hostName in @($mailbox.Hosts)) { & $add ('{0}:443' -f $hostName) $origin $mailbox.Domain } } else { # In Exchange Online, or not known where: Microsoft's, as the Definition names them. foreach ($entry in @($Default)) { & $add $entry 'ExchangeOnline' $mailbox.Domain } } } [pscustomobject]@{ Targets = @($order | ForEach-Object { $t = $byEntry[$_]; [pscustomobject]@{ Entry = $t.Entry; Origin = $t.Origin; For = (@($t.Domains) -join ', ') } }) Mailboxes = $mailboxes Reason = $reason } } function Get-ServerData { <# .PARAMETER Observed What the Checks before this one gathered. Read only where the Check Definition sets MailboxServers: then 'Autodiscover' says which mailboxes are set up and where, and their Servers are what is measured. The Check has to stand after the Autodiscover Check for that; where it does not, or that Check did not run, the Servers of the Definition are measured and the Finding says so. #> [CmdletBinding()] [OutputType([psobject])] param([hashtable]$Parameters = @{}, [AllowNull()][hashtable]$Observed = @{}) $entries = @(Get-Parameter $Parameters 'Servers' @()) $mailbox = $null $about = @{} if ([bool](Get-Parameter $Parameters 'MailboxServers' $false)) { $autodiscover = $null if ($Observed -and $Observed.ContainsKey('Autodiscover')) { $autodiscover = $Observed['Autodiscover'] } $chosen = Get-ServerMailboxTarget -Autodiscover $autodiscover -Default $entries $entries = @($chosen.Targets | ForEach-Object { $_.Entry }) foreach ($t in $chosen.Targets) { $about["$($t.Entry)".Trim()] = $t } $mailbox = [pscustomobject]@{ Requested = $true; Reason = $chosen.Reason; Mailboxes = @($chosen.Mailboxes) } } $servers = @(foreach ($entry in $entries) { $target = ConvertTo-ServerTarget -Entry $entry if (-not $target) { continue } $resolution = $null if (-not $target.IsIpLiteral) { $resolution = Resolve-HostAddress -HostName $target.HostName } # A host that does not resolve cannot be connected to or pinged by name, so the # remaining probes would only produce noise about a name that does not exist. $resolved = $target.IsIpLiteral -or $resolution.Resolved $portResults = @() $latency = $null $route = $null if ($resolved) { $route = Get-ServerRoute -Address $(if ($target.IsIpLiteral) { $target.HostName } else { $resolution.Address }) $portResults = @(foreach ($port in $target.Ports) { $times = @(1..$script:ServerTcpAttempts | ForEach-Object { Write-CheckProgress -Status ((Get-Text 'Console.Progress.Connect') -f ('{0}:{1}' -f $target.HostName, $port)) ` -Step ($_ - 1) -Of $script:ServerTcpAttempts $ms = Measure-TcpConnect -HostName $target.HostName -Port $port Start-Sleep -Milliseconds 200 $ms }) $ok = @($times | Where-Object { $null -ne $_ }) [pscustomobject]@{ Port = $port Attempts = $script:ServerTcpAttempts Successes = $ok.Count AverageMs = $(if ($ok.Count) { [math]::Round(($ok | Measure-Object -Average).Average, 1) } else { $null }) MaximumMs = $(if ($ok.Count) { ($ok | Measure-Object -Maximum).Maximum } else { $null }) } }) $latency = Measure-Latency -Target $target.HostName -Count $script:ServerPingCount } [pscustomobject]@{ Entry = $target.Entry HostName = $target.HostName Ports = $target.Ports IsIpLiteral = $target.IsIpLiteral Resolution = $resolution PortResults = $portResults Latency = $latency Route = $route # For which mailboxes this Server is measured and as what, where the Check # measures the Servers of the mailboxes; nothing otherwise. For = $(if ($about.ContainsKey($target.Entry)) { $about[$target.Entry].For }) Origin = $(if ($about.ContainsKey($target.Entry)) { $about[$target.Entry].Origin }) } }) [pscustomobject]@{ PSTypeName = 'Gutcheck.Data.Server' Servers = $servers Mailbox = $mailbox } } function Get-ServerForText { <# .SYNOPSIS Whose Server a measured one is, in a Technician's words; nothing for a Server that was not measured for a mailbox. Pure. #> [CmdletBinding()] [OutputType([string])] param([AllowNull()]$Server) $for = "$(Get-DataProperty $Server 'For')" switch ("$(Get-DataProperty $Server 'Origin')") { 'OnPremises' { return (Get-Text 'Value.Server.For.OnPremises') -f $for } 'OnPremisesAutodiscover' { return (Get-Text 'Value.Server.For.OnPremisesAutodiscover') -f $for } 'ExchangeOnline' { return (Get-Text 'Value.Server.For.ExchangeOnline') -f $for } 'Default' { return Get-Text 'Value.Server.For.Default' } } '' } function New-ServerMailboxFinding { <# .SYNOPSIS The Finding that says which mailbox is where and which Server was measured for it, or why the Servers of the Check Definition were measured. Pure. #> [CmdletBinding()] param([AllowNull()]$Data) $mailbox = Get-DataProperty $Data 'Mailbox' if (-not [bool](Get-DataProperty $mailbox 'Requested')) { return } $check = Get-Text 'Check.Server.MailboxServers' $reason = "$(Get-DataProperty $mailbox 'Reason')" if ($reason) { $value = if ($reason -eq 'NoAutodiscover') { Get-Text 'Value.Server.Mailbox.NoAutodiscover' } else { Get-Text 'Value.Server.Mailbox.NoMailbox' } return New-Finding -Category Network -Check $check -Severity INFO -Value $value ` -Meaning (Get-Text 'Meaning.Server.Mailbox') -Hint (Get-Text 'Hint.Server.Mailbox.Fallback') } $known = $true $lines = @((Get-DataCollection $mailbox 'Mailboxes') | ForEach-Object { $domain = "$(Get-DataProperty $_ 'Domain')" $hosts = @((Get-DataCollection $_ 'Hosts') | ForEach-Object { "$_" } | Where-Object { $_ }) -join ', ' # Read here: inside the switch below, $_ is what is switched on. $source = "$(Get-DataProperty $_ 'Source')" switch ("$(Get-DataProperty $_ 'Location')") { 'OnPremises' { if (-not $hosts) { $known = $false; (Get-Text 'Value.Server.Mailbox.OnPremisesUnknown') -f $domain } elseif ($source -eq 'Cached') { (Get-Text 'Value.Server.Mailbox.OnPremises') -f $domain, $hosts } else { $known = $false; (Get-Text 'Value.Server.Mailbox.OnPremisesAutodiscover') -f $domain, $hosts } } 'ExchangeOnline' { (Get-Text 'Value.Server.Mailbox.ExchangeOnline') -f $domain } default { $known = $false; (Get-Text 'Value.Server.Mailbox.Unknown') -f $domain } } }) New-Finding -Category Network -Check $check -Severity $(if ($known) { 'OK' } else { 'INFO' }) -Value ($lines -join ' | ') ` -Meaning (Get-Text 'Meaning.Server.Mailbox') -Hint (Get-Text 'Hint.Server.Mailbox.Fallback') } function ConvertTo-ServerFinding { [CmdletBinding()] [OutputType([psobject])] param( [AllowNull()]$Data, [hashtable]$Parameters = @{}, [AllowNull()]$Situation ) $servers = Get-DataCollection $Data 'Servers' $forMailboxes = [bool](Get-DataProperty (Get-DataProperty $Data 'Mailbox') 'Requested') if (-not $servers.Count -and $forMailboxes) { return New-ServerMailboxFinding -Data $Data } if (-not $servers.Count) { # A Check Definition that names a Kind but supplies it nothing has not been # written yet, and a Report that simply omits it lets that go unnoticed. return New-Finding -Category Network -Check (Get-Text 'Check.Server.Servers') -Severity INFO -Value (Get-Text 'Value.Server.NoneConfigured') ` -Hint (Get-Text 'Hint.Server.ThisCheckDefinitionNamesNo') } New-ServerMailboxFinding -Data $Data # Of the Servers of one mailbox on the own Exchange, which answer. Outlook is told # an address for inside the network and one for outside, and needs one of them. $answers = @{} foreach ($server in $servers) { if ("$(Get-DataProperty $server 'Origin')" -notlike 'OnPremises*') { continue } $group = "$(Get-DataProperty $server 'For')" $up = (Test-ServerResolved -Server $server) -and [bool]@((Get-DataCollection $server 'PortResults') | Where-Object { (ConvertTo-Number (Get-DataProperty $_ 'Successes')) -gt 0 }).Count if ($up) { $answers[$group] = $true } elseif (-not $answers.ContainsKey($group)) { $answers[$group] = $false } } foreach ($server in $servers) { # Whose Server it is, behind what was measured: "TCP mail...:443" alone does not # say that this is the Server of the User's mailbox. $whose = Get-ServerForText -Server $server $found = @( New-ServerDnsFinding -Server $server -Parameters $Parameters # Everything below needs a name that resolved. Asking anything else of a host that # does not exist produces Findings about a typo, dressed as Findings about a # network - which is how a Technician ends up looking at a firewall for an hour. if (Test-ServerResolved -Server $server) { New-ServerPortFinding -Server $server -Parameters $Parameters -Situation $Situation New-ServerPingFinding -Server $server -Parameters $Parameters -Situation $Situation } ) $onPremises = "$(Get-DataProperty $server 'Origin')" -like 'OnPremises*' $otherUp = $onPremises -and [bool]$answers["$(Get-DataProperty $server 'For')"] foreach ($finding in $found) { if (-not $finding) { continue } if ($whose) { $finding.Value = (@("$($finding.Value)", $whose) | Where-Object { $_ }) -join ' | ' } if ($onPremises -and "$($finding.Severity)" -in 'FAIL', 'WARN') { if ($otherUp) { # Not this Server's failing: another of the same mailbox answers. # Information, and no Signal - nothing is lost to the User. $finding.Severity = 'INFO' $finding.Value = '{0} | {1}' -f $finding.Value, (Get-Text 'Value.Server.Mailbox.OtherAnswers') $finding.Signals = @() } else { $finding.Hint = (@("$($finding.Hint)", (Get-Text 'Hint.Server.Mailbox.OutsideVpn')) | Where-Object { $_ }) -join ' | ' } } $finding } } } function ConvertTo-ServerSection { [CmdletBinding()] [OutputType([psobject])] param([AllowNull()]$Data) $measured = @((Get-DataCollection $Data 'Servers') | Where-Object { $_ -and (Get-DataProperty $_ 'Latency') }) if (-not $measured.Count) { return } $forMailboxes = [bool](Get-DataProperty (Get-DataProperty $Data 'Mailbox') 'Requested') New-Section -Title (Get-Text 'Title.Server.LatencyTestsServers') -Row @( foreach ($server in $measured) { $row = [ordered]@{} if ($forMailboxes) { $row[(Get-Text 'Column.Server.For')] = Get-ServerForText -Server $server } $_ = Get-DataProperty $server 'Latency' $row[(Get-Text 'Column.Server.Target')] = Get-DataProperty $_ 'Target' $row[(Get-Text 'Column.Server.Sent')] = Get-DataProperty $_ 'Sent' $row[(Get-Text 'Column.Server.Lost')] = Get-DataProperty $_ 'Lost' $row[(Get-Text 'Column.Server.LossPercent')] = Get-DataProperty $_ 'LossPercent' $row[(Get-Text 'Column.Server.AverageMs')] = Get-DataProperty $_ 'AverageMs' $row[(Get-Text 'Column.Server.MaximumMs')] = Get-DataProperty $_ 'MaximumMs' [pscustomobject]$row } ) } function Test-ServerResolved { <# .SYNOPSIS Whether this Server has a name that can be reached at all. #> [CmdletBinding()] [OutputType([bool])] param([Parameter(Mandatory)]$Server) if (Get-DataProperty $Server 'IsIpLiteral') { return $true } [bool](Get-DataProperty (Get-DataProperty $Server 'Resolution') 'Resolved') } function New-ServerDnsFinding { [CmdletBinding()] param([Parameter(Mandatory)]$Server, [hashtable]$Parameters) $warn = Get-Parameter $Parameters 'ServerDnsWarnMs' 500 $fail = Get-Parameter $Parameters 'ServerDnsFailMs' ([double]::MaxValue) # Nothing to resolve, so nothing to report. An address literal is not a DNS success. if (Get-DataProperty $Server 'IsIpLiteral') { return } $hostName = Get-DataProperty $Server 'HostName' $resolution = Get-DataProperty $Server 'Resolution' if (-not (Get-DataProperty $resolution 'Resolved')) { return New-Finding -Category Network -Check ((Get-Text 'Check.Server.Dns') -f $hostName) -Severity FAIL ` -Value (Get-Text 'Value.Server.CannotBeResolved') ` -Meaning (Get-Text 'Meaning.Server.NotResolved') ` -Hint (Get-Text 'Hint.Server.WrongNameDNSProblemOr') } $ms = ConvertTo-Number (Get-DataProperty $resolution 'Milliseconds') if ($null -eq $ms) { return New-UnavailableFinding -Category Network -Check ((Get-Text 'Check.Server.Dns') -f $hostName) ` -Hint (Get-Text 'Hint.Shared.NameResolvedNotTimed') } New-Finding -Category Network -Check ((Get-Text 'Check.Server.Dns') -f $hostName) ` -Severity (Get-Severity $ms $warn $fail) -Value ('{0:N0} ms' -f $ms) ` -Meaning (Get-Text 'Meaning.Server.SlowNameResolution') ` -Hint (Get-Text 'Hint.Server.SlowNameResolutionDelaysEvery') } function New-ServerPortFinding { [CmdletBinding()] param([Parameter(Mandatory)]$Server, [hashtable]$Parameters, [AllowNull()]$Situation) $latencyWarn = Get-Parameter $Parameters 'ServerTcpWarnMs' 150 $latencyFail = Get-Parameter $Parameters 'ServerTcpFailMs' ([double]::MaxValue) $hostName = Get-DataProperty $Server 'HostName' foreach ($port in (Get-DataCollection $Server 'PortResults')) { $check = 'TCP {0}:{1}' -f $hostName, $port.Port $successes = ConvertTo-Number $port.Successes $attempts = ConvertTo-Number $port.Attempts if ($null -eq $attempts -or $attempts -le 0) { $attempts = $script:ServerTcpAttempts } if (-not $successes) { # Refused outright: a distinct Finding from a port that answers slowly or # intermittently, because the thing to go and look at is different. New-Finding -Category Network -Check $check -Severity FAIL ` -Value ((Get-Text 'Value.Server.NoConnection') -f $attempts) ` -Meaning (Get-Text 'Meaning.Server.NoConnection') ` -Hint (Get-Text 'Hint.Server.NothingIsListeningOrA') ` -Signal 'server-unreachable' -Subject "$hostName" continue } $average = ConvertTo-Number $port.AverageMs # Two independent ways for a port to be bad: some attempts failed, or the ones that # succeeded were slow. Whichever reads worse decides. $missed = 'OK' if ($successes -lt $attempts) { $missed = 'WARN' } $slow = 'OK' if ($null -ne $average) { $slow = Get-Severity $average $latencyWarn $latencyFail } $severity = Get-WorstSeverity $missed $slow $signal = @() if ($severity -in 'WARN', 'FAIL') { $signal = @('server-slow') } New-Finding -Category Network -Check $check -Severity $severity ` -Value ((Get-Text 'Value.Server.ConnectResult') -f $average, $port.MaximumMs, $successes, $attempts) ` -Meaning (Get-Text 'Meaning.Server.FailedOrSlowConnects') ` -Hint ((Get-Text 'Hint.Server.FailedOrSlowConnectsFirewall') -f (Get-SituationLinkText -Situation $Situation)) ` -Signal $signal -Subject "$hostName" } } function Select-ServerPingText { <# .SYNOPSIS The wording about a slow or lossy ping that fits how the machine is connected and where the Server is. Pure. .PARAMETER Text A wording for each of Wireless, WiredLocal, WiredRemote and Other, and one for Unknown, which is said where the connection or the place of the Server is not known. #> [CmdletBinding()] [OutputType([string])] param([Parameter(Mandatory)]$Server, [AllowNull()]$Situation, [Parameter(Mandatory)][hashtable]$Text) # At a cable it depends on where the Server is; where that is not known either, # what fits both. $wired = $Text['Unknown'] $onLink = Get-DataProperty (Get-DataProperty $Server 'Route') 'OnLink' if ($onLink -eq $true) { $wired = $Text['WiredLocal'] } if ($onLink -eq $false) { $wired = $Text['WiredRemote'] } Select-SituationText -Situation $Situation -Fact Connection -Text @{ Wired = $wired Wireless = $Text['Wireless'] Vpn = $Text['Other'] Unknown = $Text['Unknown'] } } function Get-ServerPlaceText { <# .SYNOPSIS Where the machine and the Server are, as a statement of a Meaning. Nothing where that is not known. Pure. #> [CmdletBinding()] [OutputType([string])] param([Parameter(Mandatory)]$Server, [AllowNull()]$Situation) $adapter = Get-SituationAdapterName -Situation $Situation Select-ServerPingText -Server $Server -Situation $Situation -Text @{ Wireless = (Get-Text 'Meaning.Server.Place.Wireless') -f $adapter WiredLocal = (Get-Text 'Meaning.Server.Place.WiredLocal') -f $adapter WiredRemote = (Get-Text 'Meaning.Server.Place.WiredRemote') -f $adapter Other = (Get-Text 'Meaning.Server.Place.Other') -f $adapter Unknown = '' } } function Get-ServerPingMeaning { <# .SYNOPSIS What a slow or lossy ping means: where the machine and the Server are, and what follows from loss on that way. Pure. #> [CmdletBinding()] [OutputType([string])] param([Parameter(Mandatory)]$Server, [AllowNull()]$Situation) # Loss to a target beyond the own network is as a rule the target not answering. $lost = Get-Text 'Meaning.Server.Ping' $what = Select-ServerPingText -Server $Server -Situation $Situation -Text @{ Wireless = $lost; WiredLocal = $lost; Other = $lost; Unknown = $lost WiredRemote = Get-Text 'Meaning.Server.Ping.PublicTarget' } @(@((Get-ServerPlaceText -Server $Server -Situation $Situation), $what) | Where-Object { $_ }) -join ' | ' } function Get-ServerPingHint { <# .SYNOPSIS What to do about a slow or lossy ping, by how the machine is connected and by where the Server is. Pure. #> [CmdletBinding()] [OutputType([string])] param([Parameter(Mandatory)]$Server, [AllowNull()]$Situation) Select-ServerPingText -Server $Server -Situation $Situation -Text @{ Wireless = Get-Text 'Hint.Server.Ping.Wireless' WiredLocal = Get-Text 'Hint.Server.Ping.WiredLocal' WiredRemote = Get-Text 'Hint.Server.Ping.WiredRemote' Other = Get-Text 'Hint.Server.Ping.Other' Unknown = Get-Text 'Hint.Server.Ping.Unknown' } } function New-ServerPingFinding { [CmdletBinding()] param([Parameter(Mandatory)]$Server, [hashtable]$Parameters, [AllowNull()]$Situation) $lossWarn = Get-Parameter $Parameters 'ServerLossWarnPercent' 0 $lossFail = Get-Parameter $Parameters 'ServerLossFailPercent' 2 # One ping of twenty is five percent, and one ping goes missing on a healthy line: # a FAIL takes more than that. $lostFail = ConvertTo-Number (Get-Parameter $Parameters 'ServerLossFailMinimumLost' 2) $msWarn = Get-Parameter $Parameters 'ServerLatencyWarnMs' 50 $msFail = Get-Parameter $Parameters 'ServerLatencyFailMs' ([double]::MaxValue) $hostName = Get-DataProperty $Server 'HostName' $check = 'Ping {0}' -f $hostName $latency = Get-DataProperty $Server 'Latency' if (-not $latency) { return } $loss = ConvertTo-Number (Get-DataProperty $latency 'LossPercent') $average = ConvertTo-Number (Get-DataProperty $latency 'AverageMs') $maximum = ConvertTo-Number (Get-DataProperty $latency 'MaximumMs') if ($null -eq $average) { # No reply at all. Whether that matters depends on whether anything else got # through: plenty of Customer networks block ICMP to servers that work perfectly. $ports = Get-DataCollection $Server 'PortResults' $anyPortOk = @($ports | Where-Object { (ConvertTo-Number $_.Successes) -gt 0 }).Count -gt 0 if ($ports.Count -and $anyPortOk) { return New-Finding -Category Network -Check $check -Severity INFO ` -Value (Get-Text 'Value.Server.NoIcmpReply') } return New-Finding -Category Network -Check $check -Severity FAIL -Value (Get-Text 'Value.Server.NoReply') ` -Meaning (Get-Text 'Meaning.Server.NoReply') ` -Hint (Get-Text 'Hint.Server.HostUnreachableOrPingBlocked') ` -Signal 'server-unreachable' -Subject "$hostName" } $lossGrade = Get-Severity $loss $lossWarn $lossFail $lost = ConvertTo-Number (Get-DataProperty $latency 'Lost') if ($lossGrade -eq 'FAIL' -and $null -ne $lost -and $lost -lt $lostFail) { $lossGrade = 'WARN' } $severity = Get-WorstSeverity $lossGrade (Get-Severity $average $msWarn $msFail) # Slow or lossy is this Server's; loss to a Server next door is also something about # the network this machine is on. $signal = @() if ($severity -in 'WARN', 'FAIL') { $signal += 'server-slow' } if ($lost -gt 0 -and (Get-DataProperty (Get-DataProperty $Server 'Route') 'OnLink') -eq $true) { $signal += 'loss-on-own-network' } New-Finding -Category Network -Check $check -Severity $severity ` -Value ((Get-Text 'Value.Server.PingResult') -f $average, $maximum, $loss) ` -Meaning (Get-ServerPingMeaning -Server $Server -Situation $Situation) ` -Hint (Get-ServerPingHint -Server $Server -Situation $Situation) ` -Signal $signal -Subject "$hostName" } |