Invoke-PingLog.ps1
# Invoke-Pinglog v1.0 (c) Chris Redit <#PSScriptInfo .VERSION 1.0 .GUID 9c508ea0-00d2-4ad6-8d3e-e8d60a3de7f3 .AUTHOR Chris Redit .COPYRIGHT (c) 2017 Chris Redit. All rights reserved. .TAGS ping monitoring .PROJECTURI https://github.com/chrisred/posh-scripts #> <# .SYNOPSIS Test and monitor network connectivity using ICMP echo packets. .DESCRIPTION The Invoke-Pinglog cmdlet tests and monitors network connectivity using ICMP echo packets. Behavior is similar to the Windows ping command and essentially wraps the Win32_PingStatus WMI class. Failure and success responses can be filtered by a chosen threshold to aid monitoring of connections, and logging to a CSV format file is possible. Logs are not effected by filtering. If a domain name resolves to both an IPv4 and an IPv6 address then the IPv6 address will be preferred, this is a behaviour of the Win32_PingStatus class with no option to force either protocol. .PARAMETER Address Specifies the host to send ICMP echo packets to. .PARAMETER Count Specifies the number of ICMP echo packets to send, default is 0 (unlimited). .PARAMETER Timeout Specifies the time to wait in seconds before no response is assumed, default is 4. .PARAMETER BufferSize Specifies the number of data bytes to be sent in the ICMP echo packet, this does not include the 8 byte ICMP header or the IP header (typically 20 bytes), default is 32. .PARAMETER TimeToLive Specifies the TTL (time to live) value for the IP packet which is effectively a maximum hop count, the TTL field will be decremented by each router the packet passes through, default is 128. .PARAMETER WriteThreashold Specifies the number of "success" or "failure" responses to output before silencing them base on the -WriteType parameter, default is 4. .PARAMETER WriteType Specifies which responses are output, "Both" with output all responses, "Failed" will output only failed responses, "Success" only successful responses, default is "Both". .PARAMETER ResolveAddress Include this parameter to enable IP address to domain name resolution attempts for the address specified. .PARAMETER NoFragmentation Include this parameter to set the "Do not fragment" bit in the IP header, this will trigger "Packet too big" ICMP responses if the -BufferSize parameter causes the total packet size to exceed the MTU of the first hop. .PARAMETER DisableFormat Include this parameter to remove the custom formatting for the "Win32_Pingstatus" object, this will also stop formatting data for all the .NET types being reloaded when the script exits. .PARAMETER EnableLog Include this parameter to enable CSV logging to a file, default is the console working directory. .PARAMETER LogPath Specifies the location to save the CSV log file to, default is the console working directory. .PARAMETER ComputerName Runs the cmdlet on the specified computer using remote WMI (DCOM), default is the local computer. .OUTPUTS System.Management.ManagementObject#Win32_PingStatus on success. A non-terminating error when a W32_PingStatus object can not be created. A terminating error when the log file can not be written to. .EXAMPLE .\Invoke-Pinglog.ps1 8.8.8.8 -ResolveAddress -Count 4 .EXAMPLE .\Invoke-Pinglog.ps1 google.com -WriteType Failed -EnableLog -LogPath "C:\Users\Administrator\Documents" #> Param( [Parameter(Position=0,Mandatory=$true)] [String]$Address, [Int]$Count = 0, [Int]$Timeout = 4, [Int]$BufferSize = 32, [Int]$TimeToLive = 128, [Int]$WriteThreashold = 4, [ValidateSet('Both','Failed','Success')] [String]$WriteType = 'Both', [Switch]$ResolveAddress, [Switch]$NoFragmentation, [Switch]$DisableFormat, [Switch]$EnableLog, [String]$LogPath = '.', [String]$ComputerName = $env:COMPUTERNAME ) $PingStatusFormat = @" <?xml version="1.0" encoding="utf-8" ?> <Configuration> <ViewDefinitions> <View> <Name>System.Management.ManagementObject#root\cimv2\Win32_PingStatus</Name> <ViewSelectedBy> <TypeName>System.Management.ManagementObject#root\cimv2\Win32_PingStatus</TypeName> </ViewSelectedBy> <TableControl> <TableHeaders> <TableColumnHeader> <Width>20</Width> </TableColumnHeader> <TableColumnHeader> <!--<AutoSize/>--> </TableColumnHeader> <TableColumnHeader> <Width>10</Width> </TableColumnHeader> <TableColumnHeader> <Width>12</Width> </TableColumnHeader> <TableColumnHeader> <Label>ResponseTTL</Label> <Width>12</Width> </TableColumnHeader> <TableColumnHeader> <Width>30</Width> </TableColumnHeader> </TableHeaders> <TableRowEntries> <TableRowEntry> <TableColumnItems> <TableColumnItem> <PropertyName>Timestamp</PropertyName> </TableColumnItem> <TableColumnItem> <PropertyName>ProtocolAddress</PropertyName> </TableColumnItem> <TableColumnItem> <PropertyName>ReplySize</PropertyName> </TableColumnItem> <TableColumnItem> <PropertyName>ResponseTime</PropertyName> </TableColumnItem> <TableColumnItem> <PropertyName>ResponseTimeToLive</PropertyName> </TableColumnItem> <TableColumnItem> <PropertyName>Status</PropertyName> </TableColumnItem> </TableColumnItems> </TableRowEntry> </TableRowEntries> </TableControl> </View> </ViewDefinitions> </Configuration> "@ $StatusCodeText = @{ 0 = 'Success' 11001 = 'Buffer Too Small' 11002 = 'Destination Net Unreachable' 11003 = 'Destination Host Unreachable' 11004 = 'Destination Protocol Unreachable' 11005 = 'Destination Port Unreachable' 11006 = 'No Resources' 11007 = 'Bad Option' 11008 = 'Hardware Error' 11009 = 'Packet Too Big' 11010 = 'Request Timed Out' 11011 = 'Bad Request' 11012 = 'Bad Route' 11013 = 'TimeToLive Expired Transit' 11014 = 'TimeToLive Expired Reassembly' 11015 = 'Parameter Problem' 11016 = 'Source Quench' 11017 = 'Option Too Big' 11018 = 'Bad Destination' 11032 = 'Negotiating IPSEC' 11050 = 'General Failure' } Function Write-Log ($Value) { if ($EnableLog) { Out-File -InputObject $Value -FilePath (Join-Path -Path $LogPath -ChildPath "pinglog-$($ScriptTime).csv") -Append } } $ScriptTime = Get-Date -Format yyMMddHHmm $TimeoutMilliseconds = $Timeout * 1000 $SuccessTotal = 0 $SuccessCurrent = 0 $FailedTotal = 0 $FailedCurrent = 0 $ResponseMaximum = $null $ResponseMinimum = $null $ResponseSum = 0 try { if (-not $DisableFormat) { # Create a temporary format data file, and load in to the current session, this works from PSv2 onwards. Using a # custom object and "DefaultDisplayPropertySet" which would be an alternative does not work for PSv2 for example. $FormatPath = "$($env:Temp)\pinglog.format.ps1xml" Set-Content -Value $PingStatusFormat -Path $FormatPath -Encoding 'UTF8' -Force # PSv2 raises a non-terminating error about the format data file being "already present" Update-FormatData -PrependPath $FormatPath -EA SilentlyContinue } for ($i=1; $i -le $Count -or $Count -eq 0; $i++) { if ($ResolveAddress) {$ResolveAddressString = " AND ResolveAddressNames='TRUE'"} else {$ResolveAddressString = ''} if ($NoFragmentation) {$NoFragmentationString = " AND NoFragmentation='TRUE'"} else {$NoFragmentationString = ''} $PingStatusQuery = "SELECT * FROM Win32_PingStatus WHERE Address='{0}' AND Timeout={1} AND BufferSize={2} AND TimeToLive={3}{4}{5}" $PingStatus = Get-WmiObject -ComputerName $ComputerName -Query ($PingStatusQuery -f $Address, $TimeoutMilliseconds, $BufferSize, $TimeToLive, $ResolveAddressString, $NoFragmentationString) -EA Continue if ($PingStatus -ne $null) { if ($PingStatus.PrimaryAddressResolutionStatus -gt 0) { throw "Invalid IP address or domain name." } # add TimeStamp and Status properites to the $PingStatus object $StatusCode = [Int]$PingStatus.StatusCode Add-Member -InputObject $PingStatus -Name 'TimeStamp' -Value (Get-Date -Format 'yyyy-MM-dd HH:mm:ss') -MemberType NoteProperty Add-Member -InputObject $PingStatus -Name 'Status' -Value $StatusCodeText[$StatusCode] -MemberType NoteProperty # calculate the success/failed min/max/avg totals for display on termination if ($StatusCode -lt 1) { $SuccessTotal++ $SuccessCurrent++ $FailedCurrent = 0 $ResponseSum += $PingStatus.ResponseTime if ($i -gt 1) { # run the calculations for all except the first iteration if($PingStatus.ResponseTime -gt $ResponseMaximum -or $ResponseMaximum -eq $null) {$ResponseMaximum = $PingStatus.ResponseTime} if($PingStatus.ResponseTime -lt $ResponseMinimum -or $ResponseMinimum -eq $null) {$ResponseMinimum = $PingStatus.ResponseTime} $ResponseAverage = $ResponseSum / $SuccessTotal } else { $ResponseMaximum = $PingStatus.ResponseTime $ResponseMinimum = $PingStatus.ResponseTime $ResponseAverage = $PingStatus.ResponseTime } } else { $FailedTotal++ $FailedCurrent++ $SuccessCurrent = 0 } if ($WriteType -eq 'Both') { Write-Output $PingStatus } elseif ($WriteType -eq 'Failed' -and $WriteThreashold -ge $SuccessCurrent) { Write-Output $PingStatus if ($WriteThreashold -eq $SuccessCurrent) { Write-Warning "Silencing success messages after $WriteThreashold replies." } } elseif ($WriteType -eq 'Success' -and $WriteThreashold -ge $FailedCurrent) { Write-Output $PingStatus if ($WriteThreashold -eq $FailedCurrent) { Write-Warning "Silencing failed messages after $WriteThreashold failures." } } # log to a file in CSV format when logging is enabled if ($i -gt 1) { # skip the header for all except the first iteration Write-Log ($PingStatus | select TimeStamp,Address,ProtocolAddress,ReplySize,ResponseTime,ResponseTimeToLive,Status | ConvertTo-Csv -NoTypeInformation | select -Last 1) } else { Write-Log ($PingStatus | select TimeStamp,Address,ProtocolAddress,ReplySize,ResponseTime,ResponseTimeToLive,Status | ConvertTo-Csv -NoTypeInformation) } if ($StatusCode -ne 11010 -or $StatusCode -ne 11003) { # delay next iteration for 1 second unless the request timed out or host unreachable, these get the timeout applied Start-Sleep -Seconds 1 } } else { # hardware/driver error or use of reserved address space if $PingStatus is $null Write-Log $Error[0].ToString() Start-Sleep -Seconds 1 } } } catch { throw } finally { if (-not $DisableFormat) { # reload the built-in format data file to revert the temp file loaded previously Update-FormatData -PrependPath "$PSHOME\DotNetTypes.format.ps1xml" -EA SilentlyContinue } $PacketTotal = $SuccessTotal + $FailedTotal if ($PacketTotal -gt 0) { # display ping-like stats on termination, must have at least one response $FailedPercentage = "{0:P0}" -f ($FailedTotal / $PacketTotal) if ($PingStatus.ProtocolAddress -eq '') {$AddressText = $Address} else {$AddressText = $PingStatus.ProtocolAddress} if ($ResolveAddress) {$ResolveAddressText = " [$($PingStatus.ProtocolAddressResolved)]"} else {$ResolveAddressText = ''} Write-Host "" Write-Host "Ping statistics for $($AddressText)$($ResolveAddressText):" Write-Host " Packets: Sent = $PacketTotal, Received = $SuccessTotal, Lost = $FailedTotal ($FailedPercentage loss)" if ($SuccessTotal -gt 0) { $ResponseAverageFormat = "{0:D1}" -f [Int]$ResponseAverage Write-Host "Approximate round trip time in milli-seconds:" Write-Host " Minimum = $($ResponseMinimum)ms, Maximum = $($ResponseMaximum)ms, Average = $($ResponseAverageFormat)ms" Write-Host "" } } } |