Skip to content

Instantly share code, notes, and snippets.

@changbowen
Forked from kiwi-cam/Start-Monitoring.ps1
Last active September 11, 2021 07:32
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save changbowen/9dc1c9910740e7474354a6018adefade to your computer and use it in GitHub Desktop.
Save changbowen/9dc1c9910740e7474354a6018adefade to your computer and use it in GitHub Desktop.
A simple Powershell script that monitors a remote hosts connection. Loops constantly reporting to the screen when the status changes. Optionally also records to the event log, makes an audible beep, and/or sends emails.
<#
.SYNOPSIS
Tests the connection to the supplied host or IP and reports back on changes to its status.
.DESCRIPTION
This script will check the connection to the supplied hostname or IP address every 5 second's to
monitor its status. If the status changes, a message is recorded, the Eventlog is update (optional), and an email is sent
to a supplied email address (optional).
If a TCPPort parameter is suppied the script will attempt to connect to this port. Otherwise a simple ICMP Ping is used.
To end monitoring, press CTRL-C
.PARAMETER RemoteHost
A String specifying host or IP address you'd like to monitor. You can also specify a TCPport here using host:port syntax.
.PARAMETER EmailAddress
A String specifying the email address to notify of status changes. If not supplied, or empty, emails are not sent.
In order to use this, first edit the script to supply a valid SMTP server, port, and from address.
.PARAMETER SMTPServer
The SMTP server address. Port 25 will be used.
.PARAMETER SMTPFromAddress
The From address of the notification email.
.PARAMETER EventLog
If true, status changes are also recorded in the Windows Application EventLog.
In order to use this, first run this command at an Administrator Powershell Prompt:
> New-EventLog -LogName Application -Source MonitoringConnection
.PARAMETER NoNotify
If true, status changes will not show up as Windows notifications.
.PARAMETER Silent
If true, the script will not create any beeps as the status changes.
.PARAMETER Pause
The number of seconds to wait between tests. Default 5 seconds.
.PARAMETER ConsecThreshold
Hold alert until the specified number of consecutive success or failure.
Set to 0 will alert each time the status changes. Default is 3.
.PARAMETER FailRateThreshold
Send alert when status check failures reaches the specified percentage during a period of time set by FailWindowMin.
Set to 0 to disable failure check. Default is 10.
.PARAMETER FailWindowMin
See FailRateThreshold. Default is 10.
.PARAMETER FlipRateThreshold
Send alert when status flips reaches the specified percentage during a period of time set by FlipWindowMin.
Set to 0 to disable flipping check. Default is 10.
.PARAMETER FlipWindowMin
See FlipRateThreshold. Default is 10.
.PARAMETER TCPPort
If supplied, the script will test a connection to the supplied TCP port. If not supplied a ICMP Ping test is used.
.PARAMETER TCPTimeoutMs
Time in milliseconds to wait for response on TCP connect. Default is 2000 ms.
.PARAMETER Namespace
If supplied, the script will run a query on this WMI Namespace on the supplied Host
.PARAMETER Class
If supplied, the script will run a query on this WMI Class of the supplied namespace on the supplied Host
.PARAMETER WMIProperty
The WMI Property to monitor. Any changes in value will be reported
.PARAMETER WMIFilter
The Filter used to identify the correct result in the WMI query.
.PARAMETER WMIValidator
A scriptblock that returns bool to indicate success or failure. Use $args[0] to access the WMI property value.
.EXAMPLE
./Start-Monitoring google.com
.EXAMPLE
./Start-Monitoring -RemoteHost 8.8.8.8 -EmailAddress alertme@gmail.com
.EXAMPLE
./Start-Monitoring -RemoteHost 8.8.8.8 -EmailAddress alertme@gmail.com -EventLog $true
.EXAMPLE
./Start-Monitoring -RemoteHost 8.8.8.8 -TCPPort 53
.EXAMPLE
./Start-Monitoring -RemoteHost 8.8.8.8:53
.EXAMPLE
./Start-Monitoring -RemoteHost @("www.google.com:443","8.8.8.8:53")
.EXAMPLE
./Start-Monitoring -RemoteHost WebServer1 -Namespace "root\cimv2" -Class Win32_PerfRawData_APPPOOLCountersProvider_APPPOOLWAS -WMIProperty CurrentApplicationPoolState -WMIFilter "{$_.Name -like 'SLSS'}"
#>
[CmdletBinding()]
Param(
[Parameter(Mandatory=$True,Position=1)]
[ValidateScript({
if ($_ -like "*:*") {
if($_.Split(":")[0] -As [IPAddress] -and $_.Split(":")[1] -As [Int]){
$True
}else {
if(([System.Net.Dns]::GetHostEntry($_.Split(":")[0])) -and $_.Split(":")[1] -As [Int]){
$True
}else{
Throw "$($_) is not a valid IP or hostname. Try again."
}
}
} elseif($_ -As [IPAddress]){
$True
} else {
if([System.Net.Dns]::GetHostEntry($_)){
$True
}else{
Throw "$($_) is not a valid IP or hostname. Try again."
}
}
})]
[string[]]$RemoteHost,
[ValidateScript({
if($_ -Match "\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b" -or $_ -eq $null){
$True
} else {
Throw "$($_) is not a valid email address. Try again, or remove this parameter to skip email alerts."
}
})]
[mailaddress]$EmailAddress = $null,
[string]$SMTPServer,
[string]$SMTPFromAddress,
[switch]$EventLog,
[switch]$NoNotify,
[switch]$Silent,
[int]$Pause = 5,
[int]$ConsecThreshold = 3,
[int]$FailRateThreshold = 10,
[int]$FailWindowMin = 10,
[int]$FlipRateThreshold = 10,
[int]$FlipWindowMin = 10,
[Parameter(Mandatory=$True, ParameterSetName = 'Port')]
[int]$TCPPort,
[Parameter(ParameterSetName = 'Port')]
[int]$TCPTimeoutMs = 2000,
[Parameter(Mandatory=$true, ParameterSetName = 'WMI')]
[string]$Namespace,
[Parameter(Mandatory=$true,ParameterSetName = 'WMI')]
[string]$Class,
[Parameter(Mandatory=$true, ParameterSetName = 'WMI')]
[string]$WMIProperty,
[Parameter(Mandatory=$False, ParameterSetName = 'WMI')]
[scriptblock]$WMIFilter,
[Parameter(Mandatory=$True, ParameterSetName = 'WMI')]
[scriptblock]$WMIValidator
)
$SMTPPort = 25
#Error Check
#Confirm EventLog Source is configured if required
if($EventLog){
try {
[System.Diagnostics.EventLog]::SourceExists("MonitoringConnection") | Out-Null
} catch{
throw "In order to record to the EventLog, first run this command at an Administrator Powershell Prompt: `n`
> New-EventLog -LogName Application -Source MonitoringConnection"
}
}
#notification / alert function
function Alert {
param (
[bool]$Success,
[string]$Hostname,
[string]$StatusMsg,
[switch]$StatusOnly # only update status in the console and skip all other forms of notification
)
$resultTimeStr = [datetime]::UtcNow.ToString('u');
$statusLabel = if ($Success) { 'UP' } else { 'DOWN' }
$statusColor = if ($Success) { 'green' } else { 'red' } #needs to be css-compatible
Write-Host "$Hostname $StatusMsg at $resultTimeStr" -ForegroundColor $statusColor
if ($Host.Name -ne "ServerRemoteHost") { $Host.PrivateData.ProgressBackgroundColor = $statusColor }
Write-Progress -Id $PID -Activity " Monitoring connection status of $Hostname" -Status "$StatusMsg at $resultTimeStr"
if ($StatusOnly) { return }
if (-not $Silent) {
if ($Success) { [console]::beep(1000, 300) } else { [console]::beep(500, 300) }
}
if (-not $NoNotify) {
Write-Debug "Showing notification"
$XmlString = @("
<toast duration='short'>
<visual>
<binding template='ToastGeneric'>
<text>$Hostname $StatusMsg</text>
<text>$Hostname $StatusMsg at $resultTimeStr</text>
<image src='$(if ($Success) { $InfoLogo } else { $WarningLogo })' placement='appLogoOverride' hint-crop='circle' />
</binding>
</visual>
<audio src='ms-winsoundevent:Notification.Default' />
</toast>")
$ToastXml = [Windows.Data.Xml.Dom.XmlDocument]::new()
$ToastXml.LoadXml($XmlString)
$Toast = [Windows.UI.Notifications.ToastNotification]::new($ToastXml)
[Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier($AppId).Show($Toast)
}
if ($EmailAddress -ne $null) {
Send-MailMessage -To $EmailAddress -BodyAsHtml -From $SMTPFromAddress -SmtpServer $SMTPServer -Port $SMTPPort `
-Subject "$Hostname $statusLabel - $resultTimeStr" `
-Body `
"<p>
<b>Host Name: </b>$Hostname<br/>
<b>Timestamp: </b>$resultTimeStr<br/>
<b>Status: </b><span style='color: $statusColor'>$StatusMsg</span></br>
</p>"
}
if ($EventLog) {
Write-EventLog -LogName Application -Source MonitoringConnection -Message "$Hostname $StatusMsg" `
-EventId $(if ($Success) { 8000 } else { 8001 }) `
-EntryType $(if ($Success) { 'Information' } else { 'Warning' })
}
}
#Deal with an Array first
if ($RemoteHost.Count -gt 1) {
Write-Host "Monitoring the connection status of $($RemoteHost -join ', ')" -ForegroundColor Blue
Write-Host "Press CTRL-C to stop" -ForegroundColor Blue
Write-Host
Write-Progress -Activity "Monitoring connection status of $($RemoteHost -join ', ')" -Id $PID -Status "..."
# Change the default behavior of CTRL-C so that the script can intercept and use it versus just terminating the script.
[Console]::TreatControlCAsInput = $True
# Sleep for 1 second and then flush the key buffer so any previously pressed keys are discarded and the loop can monitor for the use of
# CTRL-C. The sleep command ensures the buffer flushes correctly.
Start-Sleep -Seconds 1
$Host.UI.RawUI.FlushInputBuffer()
$Jobs = @()
$ScriptPath = $MyInvocation.MyCommand.Definition
#Shared args
$ScriptArgs = @{
EventLog = $EventLog
NoNotify = $NoNotify
Silent = $Silent
Pause = $Pause
ConsecThreshold = $ConsecThreshold
FlipRateThreshold = $FlipRateThreshold
FlipWindowMin = $FlipWindowMin
FailRateThreshold = $FailRateThreshold
FailWindowMin = $FailWindowMin
SMTPServer = $SMTPServer
SMTPFromAddress = $SMTPFromAddress
}
if ($EmailAddress) { $ScriptArgs.Add("EmailAddress", $EmailAddress) }
switch ($PSCmdlet.ParameterSetName) {
'Port' {
$ScriptArgs.Add("TCPPort", $TCPPort)
$ScriptArgs.Add("TCPTimeoutMs", $TCPTimeoutMs)
}
'WMI' {
$ScriptArgs.Add("Namespace", $Namespace)
$ScriptArgs.Add("Class", $Class)
$ScriptArgs.Add("WMIProperty", $WMIProperty)
$ScriptArgs.Add("WMIFilter", $WMIFilter)
$ScriptArgs.Add("WMIValidator", $WMIValidator)
}
Default {}
}
$RemoteHost | ForEach-Object {
$Jobs += Start-Job -Name "Monitor-$($_)" -ScriptBlock {
param($p1, $p2, $p3)
$p3.Add("RemoteHost", $p2)
& $p1 @p3
} -ArgumentList @($ScriptPath, $_, $ScriptArgs)
}
While ((Get-Job | Where-Object {$_.Name -like "Monitor-*"}).Count -gt 0) {
Receive-Job -Job $Jobs
# If a key was pressed during the loop execution, check to see if it was CTRL-C (aka "3"), and if so exit the script after clearing
# out any running jobs and setting CTRL-C back to normal.
if ($Host.UI.RawUI.KeyAvailable -and ($Key = $Host.UI.RawUI.ReadKey("AllowCtrlC,NoEcho,IncludeKeyUp"))) {
if ([Int]$Key.Character -eq 3) {
Write-Host ""
Write-Warning "CTRL-C was used - Shutting down any running jobs before exiting."
Get-Job | Where-Object {$_.Name -like "Monitor-*"} | Remove-Job -Force -Confirm:$False
[Console]::TreatControlCAsInput = $False
Return
}
# Flush the key buffer again for the next loop.
$Host.UI.RawUI.FlushInputBuffer()
}
#Add a small pause to ease CPU load
Start-Sleep -Milliseconds 200
}
}
#Extract TCP Port if encoded in Host
if ($RemoteHost -like "*:*") {
$TCPPort = [int]$RemoteHost.Split(":")[1]
$RemoteHost = $RemoteHost.Split(":")[0]
}
if ($Host.Name -ne "ServerRemoteHost"){
Write-Host "Monitoring the connection status of $RemoteHost" -ForegroundColor Blue
Write-Host "Press CTRL-C to stop" -ForegroundColor Blue
Write-Host
if ($Host.Name -ne "ServerRemoteHost") { $Host.PrivateData.ProgressForegroundColor = 'Black' }
}
#Setup Nofitications
if (-not $NoNotify) {
$AppId = '{1AC14E77-02E7-4E5D-B744-2EB1AE5198B7}\WindowsPowerShell\v1.0\powershell.exe'
$null = [Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime]
$null = [Windows.Data.Xml.Dom.XmlDocument, Windows.Data.Xml.Dom.XmlDocument, ContentType = WindowsRuntime]
$WarningLogo = "$env:TEMP\ToastWarningLogo.png"
if (-not (Test-Path $WarningLogo)){
$Logo = "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"
[byte[]]$Bytes = [convert]::FromBase64String($Logo)
[System.IO.File]::WriteAllBytes($WarningLogo,$Bytes)
}
$InfoLogo = "$env:TEMP\ToastInfoLogo.png"
if (-not (Test-Path $InfoLogo)){
$Logo = "iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAG20lEQVR4Xu2ZbYwdZRXH/+fMzL1LpC3t3rnbCLE1QQH7wotGS4zKBz5oK5UPkLRBCkKsIVgsoDUmu9PZe69+IBUFGo2EtyovRRNF5S3RLw2RaCjEFsuLSqQJNHRnttotNnvvzJxjnru70q2WOy93u8nunW8395znnPN7zjnPeWYI8/yheR4/egB6GTDPCfRKYJ4nQK8J9kqgVwLznECvBOZ5AvROgdNaAlW/uVoSvpwIawQ43xI5WxkLTBaS4JiA3yLG66r4owX53eF6+eWZztAZB1DZpgvQF28m4EYAF2QJiBSvCHA/Wfa9gU/vZtFNKztzAHy1K0l8C5EMArzYOCTQw0z8FAn2CORAyXIOHgLGzH8fBBa2kmgZEa8UwecAWcdM1YnskCNCVA9fc+7BLyhJG1wauRkBUBkcP09g7bYYF006sUeZdoSwnoVPcRrHsFmdytLk8wT9FoDPtHUEL6mdbAz9vr+mWiOFUNcBVPx4vcbJI8x8poDeYNavB77zbApfTini+vFaFewk6IcBOaawNoY1+6kia07pdhVA1YuvVSQPAmwp6cNEzk3dql3TS7gU/USZNkIkVrauD2v2I0UhdA2A2XmS5Jft4AE/rNk1gLSog9P1lapePKzA0CSEK4tmQlcAmJpX0F6T9hPBO8PdDXz6au72qA7FoCkHiH48aPT9La+94gB8tZfE8Qum4Zm0D4edTWl2frmvfe9Kcich+bI5HsDWzxb907r97/dQs3MwSu726FEobQDwYvCq/am8p0NhABWvdSuB7pxoeNZFaWu+4sU/IuhN04Il2hkM21s6AwCW+LqQomgfW7Qcqt8I6qW70+idLFMIgGlM1Nc62D7nGV/I0u1drzkGcHsKfO+Ro0GtfFbaQCpefAVBfwPIKB8vLTu8g/6dVrcrp0DFi24nYAeAPUHNuSyLcXeweRTMC6fHL/8KGuX20JTuUap48XMEfFpVbw3rpR+m03tPqlAGuF70ihlvlemK0LefzGLc9eKdgN58ks5dQc3ZmmWdidNHfw3BX4KGsyqLrpHNDcBcbFR4nxlvR9k5J/WEN+nhuVu0fHRJsgOaXDvRBHlXwPY2+NTKFMRmdfqXNt9mWK6lsvKdevlAFv3cACpD0W1E+D6IHgiGbXPRmbXHHWw9BKbrAN0a1Ep3ZXEkNwDXi34O4GpSum6kbv80i9Fuy7pe6ysAPQDRx4NGyRyNqZ/cAPq9aD8Dq1T1E2G99GJqizMg2O+3PslCf0oEfz7ScC7OYiI3gOpgc1SZlzhsu4d8CrMY7bbsgK9VkfiwShKGjT43y/q5AbheswWwE7BdztK4XC/6v/eDoObk9qXdUBfH4yTSGmmUyz0AGQjkpt6tEpjKiCIZMCslUBmM9hFjddEm2A0As9IEp45BgK4PavauDFk3TbQbAGblGJwahETowdGGfcOsAtje2gWlTXluhbl7wMBQc5UQ7xfRkVHbOTvrKDwFrHAG+FqqxM23ia1KwrLiiF8295PUT24AxkJ1KDqghI8paH1Ys3+b2uoJgkUBVP34ShX9lQAvj9ac1Vl9KATgv/cB4Lmg5nw2q3EjXwyAkuvFzwNYk+ceYOwXAuD6eibFrYNmIgTTusC3n84KoQiAAS/+kkCfMBNgyS4vO+TT8az2CwEwxipDra1E9AMF/QPj1oXhHXTs/Zw41SR4sk6nuWDxt3WR40T7lelDUN0S1Es7swZfOAPaBq9Wy70gfgHAxST62EjDueb9Xop2B0A79R831gHsDV6118zaS9F2FvjjHyWhveYdHwH1kZrj5dmNtDquF30PwHcgMkbQS0YafW+k1T1ZrnAJTC1Y8eJ1JMkTYLZBaATDtpfm9Xg2x9s7/93J4GOwtT6o2c9kW2O6dNcAtDPBi68hSR6agKC7E3K+dsSn9tffoo+peS7F9zHhqsmvQpvCmv1Y0XW7CmCiHOIvkiSPmnKQRN8ki28Ja9aT+bNBacBL1qvI3e2GJzIGtjYU3fkpcF0H0D7bB8c/Ali7wbjE/FbgD2C6IzxkPYN7KUq1a76WqkjWqug2AJdO6uwlSTYUqfkZ6wH/E5Q5Hc6PbgapB3C/+V+QBCz8NBh7hHHAhvPmglEcNf8d68eiGNFyFqwA4TJNZK0Zb9sAJQmJeDh4zflx3m5/KugzkgEnGhv4pn4gOSP6KindCMbKVLs/KWTGW4bez8ed+/J89Ulja8YBnOjE0qHmioTocgguTUDnMZJzGNT+OiTQMYH1lgV9HYTnE0t/n/Vikybg01cCebyZBZ3TmgGzEF9Hkz0AHRHNcYFeBszxDe4YXi8DOiKa4wK9DJjjG9wxvF4GdEQ0xwX+A/WJ+l+1sXcwAAAAAElFTkSuQmCC"
[byte[]]$Bytes = [convert]::FromBase64String($Logo)
[System.IO.File]::WriteAllBytes($InfoLogo,$Bytes)
}
}
$lastResult = $true
# result of the last sent alerts
$lastConsecAlert = $true
$lastFailRateAlert = $true
$lastFlipRateAlert = $true
$consecCount = 0
$failTimes = @()
$flipTimes = @()
$flipCountThld = [int](($FlipWindowMin * 60 / $Pause) * ($FlipRateThreshold / 100))
$failCountThld = [int](($FailWindowMin * 60 / $Pause) * ($FailRateThreshold / 100))
while ($true) {
$statusMessage = ''
Write-Verbose "Performing tests..."
# perform test
if ($TCPPort) {
$Result = $false
$tcpclient = [System.Net.Sockets.TcpClient]::new()
$tcpStartTime = [datetime]::Now
try {
$task = $tcpclient.ConnectAsync($RemoteHost, $TCPPort)
if ($task.Wait($TCPTimeoutMs) -and $tcpclient.Connected) {
$Result = $true
}
}
catch {}
finally {
$tcpclient.Close()
}
$tcpTripTime = ([datetime]::Now - $tcpStartTime).TotalMilliseconds
$statusMessage = "TCP $TCPPort Connection $(if ($Result) {'Succeeded'} else {'Failed'}) in $tcpTripTime ms"
}
elseif ($WMIProperty) {
if ($Pause -lt 30) {
Write-Host "A pause of only $($Pause) between WMI queries will strain the remote host. Adjusting to 30 seconds."
$Pause = 30
}
$WMIResult = Get-WmiObject -Namespace $Namespace -Class $Class -ComputerName $RemoteHost
if ($WMIFilter) {
$WMIResult = $WMIResult.Where($WMIFilter)
}
if ($WMIResult.Count -gt "1") {
throw "Multiple WMI results found, check you're using a valid filter."
} elseif ($WMIResult.Count -eq "0") {
$WMIResult = "NotFound"
} else {
#If a single result found, update the result to its value
$WMIResult = $WMIResult | Select-Object -ExpandProperty $WMIProperty
}
$Result = $WMIValidator.InvokeReturnAsIs(@(,$WMIResult))
$statusMessage = "$WMIProperty $(if ($Result) {'Success'} else {'Failure'}) with value $WMIResult"
}
else {
$Result = Test-Connection -Count 1 -ComputerName $RemoteHost -Quiet
$statusMessage = if ($Result) {'UP'} else {'DOWN'}
}
Write-Verbose "Test result: $Result."
# process test result
if ($Result -ne $lastResult) {
#status changed
$consecCount = 0
if ($flipCountThld -gt 0) { $flipTimes += [datetime]::Now }
}
else {
# using -le instead of -lt can avoid sending alert every loop after reaching threshold without knowing $lastConsecAlert
if ($consecCount -le $ConsecThreshold) {
$consecCount += 1
}
}
if ($failCountThld -gt 0 -and !$Result) { $failTimes += [datetime]::Now }
$lastResult = $Result
if ($consecCount -eq $ConsecThreshold -and $lastConsecAlert -ne $Result) {
Write-Verbose "Consecutive Threshold ($consecCount/$ConsecThreshold): Perform alert actions..."
Alert -Success $Result -Hostname $RemoteHost -StatusMsg $statusMessage
$lastConsecAlert = $Result
}
else {
Write-Verbose "Consecutive Threshold ($consecCount/$ConsecThreshold): Skip alert actions..."
}
if ($failCountThld -gt 0) {
$failTimes = $failTimes.Where{$_ -gt [datetime]::Now.AddMinutes(-$FailWindowMin)}
$failRateResult = $failTimes.Count -lt $failCountThld
if ($lastFailRateAlert -ne $failRateResult) {
Write-Verbose "Fail Threshold ($($failTimes.Count)/$failCountThld): Perform alert actions..."
Alert -Success $failRateResult -Hostname $RemoteHost -StatusMsg "Fail rate $(if ($failRateResult) {'under'} else {'over'}) $FailRateThreshold% in the last $FailWindowMin mins"
$lastFailRateAlert = $failRateResult
}
else {
Write-Verbose "Fail Threshold ($($failTimes.Count)/$failCountThld): Skip same alert actions..."
}
}
if ($flipCountThld -gt 0) {
$flipTimes = $flipTimes.Where{$_ -gt [datetime]::Now.AddMinutes(-$FlipWindowMin)}
$flipRateResult = $flipTimes.Count -lt $flipCountThld
if ($lastFlipRateAlert -ne $flipRateResult) {
Write-Verbose "Filp Threshold ($($flipTimes.Count)/$flipCountThld): Perform alert actions..."
Alert -Success $flipRateResult -Hostname $RemoteHost -StatusMsg "Flip rate $(if ($flipRateResult) {'under'} else {'over'}) $FlipRateThreshold% in the last $FlipWindowMin mins"
$lastFlipRateAlert = $flipRateResult
}
else {
Write-Verbose "Filp Threshold ($($flipTimes.Count)/$flipCountThld): Skip same alert actions..."
}
}
Write-Verbose "Waiting $Pause sec for next test..."
Write-Verbose ""
Start-Sleep -Seconds $Pause
}
@changbowen
Copy link
Author

Added multiple metrics for alerting and lots of other improvements.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment