Ping mit PowerShell

An verschiedenen Stellen nutze ich "Ping" oder besser das "Test-Connection"-Commandlet um die Erreichbarkeit von Systemen vorab zu prüfe oder auch einfache LAN/WAN-Checks zu machen. Dabei gilt es einige Besonderheiten zu beachten, die ich hier beschreibe.

Basis Ping

Wenn man sich in der PowerShell bewegt, sollte man auch die Vorteile der Pipeline und von Rückgabeobjekten nutzen. Wer also aus einer PowerShell einen "PING" machen will, sollte sicher nicht "PING.EXE" aufrufen und dann die Bildschirmausgabe auswerten. Aber auch der Umweg über WMI-Klassen ist heute eigentlich nicht mehr erforderlich. Ein einfaches "Test-Connection" sendet per Default 4 ICMP-Pakete a 32 Byte an die angegebene Gegenstelle und liefert sehr ausführliche Daten zurück. Hier mal ein Beispiel:

PS C:\> Test-Connection -Count 1 192.168.178.1 | fl

__GENUS                        : 2
__CLASS                        : Win32_PingStatus
__SUPERCLASS                   :
__DYNASTY                      : Win32_PingStatus
__RELPATH                      : Win32_PingStatus.Address="192.168.178.1",BufferSize=32,NoFragmentation=FALSE,RecordRoute=0,Resolve
                                 AddressNames=FALSE,SourceRoute="",SourceRouteType=0,Timeout=4000,TimestampRoute=0,TimeToLive=80,Ty
                                 peofService=0
__PROPERTY_COUNT               : 24
__DERIVATION                   : {}
__SERVER                       : NAWNBFC
__NAMESPACE                    : root\cimv2
__PATH                         : \\NAWNBFC\root\cimv2:Win32_PingStatus.Address="192.168.178.1",BufferSize=32,NoFragmentation=FALSE,
                                 RecordRoute=0,ResolveAddressNames=FALSE,SourceRoute="",SourceRouteType=0,Timeout=4000,TimestampRou
                                 te=0,TimeToLive=80,TypeofService=0
Address                        : 192.168.178.1
BufferSize                     : 32
NoFragmentation                : False
PrimaryAddressResolutionStatus : 0
ProtocolAddress                : 192.168.178.1
ProtocolAddressResolved        :
RecordRoute                    : 0
ReplyInconsistency             : False
ReplySize                      : 32
ResolveAddressNames            : False
ResponseTime                   : 3
ResponseTimeToLive             : 64
RouteRecord                    :
RouteRecordResolved            :
SourceRoute                    :
SourceRouteType                : 0
StatusCode                     : 0
Timeout                        : 4000
TimeStampRecord                :
TimeStampRecordAddress         :
TimeStampRecordAddressResolved :
TimestampRoute                 : 0
TimeToLive                     : 80
TypeofService                  : 0
PSComputerName                 : NAWNBFC
IPV4Address                    : 192.168.178.1
IPV6Address                    :

Viele Daten sind rein informativ aber einige Felder sind natürlich für eine weitere Verarbeitung interessant wie z.B.

  • ResponseTime
    Die Zeit in Millisekunden für die Antwort
  • ResponseTimeToLive
    Wenn man davon ausgeht, dass der Absender eine Pakete mit einem TTL=64 sendet, kann man aus der Differenz so die Anzahl der Hops auf dem Rückweg ermitteln. Das ist aber nicht sicher, da der Absender den Startwert für TTL vorgibt.

Übrigens können Sie beim Versenden einen TTL mitgeben und damit die Entfernung abschätzen. Einfach ein PING mit hohem TTL senden um die generelle Erreichbarkeit zu prüfen und dann mit aufsteigendem TTL die Entfernung ermitteln.

Wenn die Gegenstelle aber nicht erreichbar ist, dann kommt gar nichts zurück und es gibt eine Exception, die Sie natürlich abfangen müssen:

PS D:\> Test-Connection -Count 1 192.168.178.2 | fl
Test-Connection : Fehler beim Testen der Verbindung mit dem Computer "192.168.178.2": Fehler aufgrund von zu wenigen Ressourcen
In Zeile:1 Zeichen:1
+ Test-Connection -Count 1 192.168.178.2 | fl
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : ResourceUnavailable: (192.168.178.2:String) [Test-Connection], PingException
    + FullyQualifiedErrorId : TestConnectionException,Microsoft.PowerShell.Commands.TestConnectionCommand

Ein Try/Catch ist angebracht, wenn Sie auf den zurückgegebenen Daten weitere Informationen erwarten. Alternativ können Sie

Test-Connection -Count 1 192.168.178.2 -ErrorAction silentlycontinue -ErrorVariable c

Dann sollten Sie aber die Variable "$c" danach abfragen und auch nicht vergessen, dass der Fehler zusätzlich auch in $Error hinterlegt wird. Nicht dass eine spätere Anfrage nach $Error sie irrtümlich einen Fehler annehmen lässt.

"Stiller" Ping

Eine Sonderform erhalten Sie mit dem Parameter "-quiet". Dann gibt es keine Details aber auch keinen Fehler sondern nur ein "False" oder "True" als Antwort.

Test-Connection -Count 1 192.168.178.2 -Quiet
False

Test-Connection -Count 1 192.168.178.1 -Quiet
True

Diese Funktion erfordert auch keine Fehlerbehandlung und es wird auch nichts in $error geloggt. Insofern ist dies der bessere Weg um die Erreichbarkeit eines Hosts abzufragen wenn die Dauer nicht relevant ist.

Paralleles Ping

Wenn ich mehrere Systeme prüfen will, kann ich natürlich mehrere "Test-Connection"-Aufrufe nacheinander ketten. Der Parameter "Computer" erlaubt aber auch die Angabe einer Liste von Gegenstellen. Das können Sie recht einfach testen mit:

PS D:\> Test-Connection -ComputerName www.msxfaq.de,www.netatwork.de,www.heise.de -Count 2

Source  Destination      IPV4Address    IPV6Address Bytes Time(ms)
------  -----------      -----------    ----------- ----- --------
NAWNBFC www.heise.de     193.99.144.85              32    25
NAWNBFC www.msxfaq.de    217.160.0.234              32    28
NAWNBFC www.netatwork.de 178.77.109.224             32    38
NAWNBFC www.heise.de     193.99.144.85              32    27
NAWNBFC www.msxfaq.de    217.160.0.234              32    30
NAWNBFC www.netatwork.de 178.77.109.224             32    38

Leider macht Test-Connection hier keine Parallelisierung und pro "Ping" wartet er am Ende auch wieder 1 Sekunde, so dass die Abfrage der drei Hosts drei Sekunden dauert. Test-Connection erlaubt allerdings auch kein Übergabe der Hostnamen als Pipeline, so dass Sie mit einer Loop erst zum Ziel kommen.

"www.msxfaq.de","www.netatwork.de","www.heise.de" `
| %{Test-Connection $_ -Count 1}

So geht es schneller aber noch nicht "parallel". Wenn ein Host nicht erreichbar ist, dann wartet Test-Connection auf den Timeout.

Test-Connection und "-AsJob"

Sehr viele Kommandlets lassen sich einfach mit dem Parameter "-asJob" in den Hintergrund schicken. Da Test-Connection auch diesen Parameter kennt, habe ich das doch gleich mal ausprobiert. mit mehreren Hosts wäre der Aufruf dann ja sehr einfach

Ich habe mal schnell versucht, mit der Liste einfach entsprechende "Dauerping"-Jobs loszusetzen. Das geht ja recht schnell mit

foreach ($entry in import-csv .\hostlist.csv) {
   Test-connection `
      -asjob `
      -buffersize 512 `
      -delay 1 `
      -count 300
}

Dann müsste ich mit einem Receive Job einfach nur die Daten einsammeln und visualisieren. Ich habe es dann doch erst mal manuell gemacht. Einmal manuell und einmal als "job

Test-Connection www.msxfaq.de -Count 4
Test-Connection www.msxfaq.de -Count 4 -asjob

Der erste Aufruf hat wie erwartet 4 "PING" mit einem Abstand von 1 Sekunde versendet und empfangen. Der zweite Befehl hat einen Job angelegt, der das Gleiche im Hintergrund tun sollte. Ich sage absichtlich "sollte", denn als ich mit get-job den Status überprüfen wollte, war er schon "Completed". Als ich die Rückgabe mit "Receive-Job" abgeholt habe, habe ich aber vier Ergebnisse gesehen. Irgendwas war falsch gelaufen. Ich habe die gleiche Prozedur noch mal mit WireShark mitgeschnitten.

Der Aufruf mit "AsJob" feuert ganz schnell sogar parallel die PINGs. ab. Ich habe dann mit den Parametern experimentiert aber keine Besserung erreicht. In der Betriebsart mit "-asJob" scheint der Parameter "-Delay" fest mit "0" hinterlegt zu sein und das Kommandlet flutet das Netzwerk. Wenn ich aber den Parameter "Delay" angebe, dann muss er in Sekunden angegeben werden und "0" ist nicht erlaubt. Sehr suspektes Verhalten.

Wenn ich aber z.B. 10 Pings in einer Sekunden absenden will, dann geht das nur wie folgt:

1..10  | % {
   test-connection www.msxfaq.de -count 1
   start-sleep -milliseconds 100
}

So kommt "TestConnection" sofort wieder zurück und kann wieder gestartet werden. Allerdings sollten Sie das nicht mit jeder Gegenstelle machen, da Firewalls natürlich sehr schnell so einen "Ping Flood" unterbrechen, was die PowerShell so meldet

Passend dazu das Paket in WireShark:

Im internen LAN/WAN kann man aber wohl damit arbeiten. So darf ich das Skript also nicht starten. Wenn ich viele Host "Parallel" abprüfen will, dann muss ich das mit einem Start-Job machen.

foreach ($entry in import-csv .\hostlist.csv) {
   Start-Job `
      -ArgumentList @($entry) `
      -ScriptBlock { `
            Test-Connection `
               -ComputerName $Args[0] 
               -buffersize 512 `
               -delay 1 `
               -count 300
}

Die einzelnen Jobs laufen in dem Beispiel per Default schon einmal 5 Minuten aber stoppen dann alleine. Allerdings ist Start-Job nicht immer besonders Ressourcenschonend.

Messen

Ein Ping ist für eine Messung nicht repräsentativ aber 5 Minuten möchte ich auch nicht auf ein Ergebnis warten. Daher habe ich mir überlegt, dass ich alle 5 Sekunden ein Update mache und dazu 5 Pings mit 1KB Buffergröße sende. Die Auswertung kann darüber denn den Mittelweg, Max, Min erstellen und ausgeben. Ich erstelle also alle 5 Sekunden für jedes Ziel einen Job, der Pings versendet und die Ergebnisse dann liefert.

1..50 `
| %{ `
   Test-Connection 192.168.178.1 `
      -Count 1  `
      -BufferSize 1kb ;  `
   start-sleep  `
      -Milliseconds 50}  `
| Measure
   -Object Responsetime `
   -Minimum `
   -Maximum `
   -Average

Dieser Code wird einmal pro Ziel aufgerufen und am Ende sammle ich von allen Jobs die Ergebnisse ein und zeige sie an. Sie können.

Get-Content `
   -path C:\Utilities\servers.txt `
| ForEach-Object { `
      Test-Connection `
         -ComputerName $_ `
         -Count 1 `
         -AsJob `
   }`
| Get-Job `
| Receive-Job `
   -Wait `
| Select-Object @{Name='ComputerName';Expression={$_.Address}},`
                @{Name='Reachable';Expression={if ($_.StatusCode -eq 0) { $true } else { $false }}} `
| ft -AutoSize ResponseTimeToLive

Weitere Links