End2End ClientCheck

Diese Skriptsammlung hilft mir zum einen dabei unklare Fehlersituationen eines Clients bei Netzwerkverbindungen analysieren und zweites eine Performance-Messung von Clients und Servern erlauben. Die erste Version ist ein PowerShell-Script mit statischen Einstellungen, die lokal protokollieren. Sie führt verschiedene Checks im Hintergrund aus, die alle als eigener Thread jede Sekunden einen Check durchführen und an das Hauptprogramm melden. Das Hauptprogramm sammelt die Ergebnisse wieder ein. Die Lösung ist modular aufgebaut und wird je nach Client immer wieder angepasst.

Viele der hier beschrieben Tests können Sie auch mit Rimscout (https://www.rimscout.com) auf vielen Clients ausführen und zentral auswerten lassen.

 

Warum?

Der Auslöser für das Skript war ein Problem mit Exchange bei verschiedenen Clients. Anwender haben sich immer mal wieder über schlechte Performance des Exchange Servers mit Outlook beschwert, was im Cached-Mode eigentlich nicht bemerkt werden sollte. Ich konnte auf dem Server aber keine Probleme erkennen und wie so oft sind Aussagen aus der LAN/WAN-Fraktion eher nichtssagend. Die Auslastung der Leitung ist gering aber das sagt natürlich nicht viel über die Performance einer Applikation aus. Wenn eine Leitung voll ist, ist alles langsam aber eine wenig belastete Leitung ist noch kein Garant für eine gute Anwendungsperformance. Daher habe ich dieses Skript so gestaltet, das es möglich viele Tests parallel und kontinuierlich durchführt.

Verschiedene parallele Jobs prüfen entlang der Verbindung zu Office 365 oder auch interne Services unterschiedliche Dienste und sammeln entsprechende Messdaten, anhand der ich das Problem hoffentlich besser einkreisen kann. Das Skript ist natürlich erst recht bei der Überprüfung von Office 365 hilfreich da hier die meisten Stationen ja nicht mehr in ihrem Verantwortungsbereich sind. Daher sind einige Tests auch auf Office 365 ausgerichtet.

Auf der Webseite https://connectivity.office.com/ hat Microsoft auch ein "Test" für die Office 365 Performance veröffentlicht, welche DNS-Anfragen stellen, ProxySserver und einen VoIP-Call simulieren.

Bausteine

Zuerst habe ich mir überlege, welche Daten ich ermitteln will. Folgende Bausteine sind zusammen gekommen. All die Bausteine können Sie direkt so in einer PowerShell starten und sollten einfach einen Wert zurück geben.

Check

PowerShell

Computername

Der lokale Computername kann über mehrere Wege ausgelesen werden. Ich nutze gerne die Information über den FQDN

# Kurzer Name
$env:computername

# kurzer Name per DNS
[system.net.dns]::GetHostName()

# FQDN 
([system.net.dns]::GetHostEntry("localhost")).hostname

Hyper-V-Host

(get-item "HKLM:\SOFTWARE\Microsoft\Virtual Machine\Guest\Parameters").GetValue("HostName")
if ($error) {"noHyper-V";$error.clear()}

Aktuelle CPU-Last

(Get-WmiObject win32_processor).loadpercentage

Abfrage dauert aber relative lange.

Aktuelle RAM-Free

$os = Get-WmiObject Win32_OperatingSystem
$pctFree = [math]::Round(($os.FreePhysicalMemory/$os.TotalVisibleMemorySize)*100,2)

https://www.petri.com/display-memory-usage-powershell

Netzwerk

Diese Abfrage macht man wohl am besten per Performance Counter wobei die Aufgabe wohl die ist, die Karte mit dem Default Gateway zu finden. Aber auch der TCPIP-Stack kann interessant Daten liefern, z.B. Retransmissions, Packetloss

# Englische Clients
Get-Counter "Segments Retransmitted/s"

# Deutsche Clients
Get-Counter "\TCPv4\erneut übertragene Segmente/s"

Primäre Netzwerkverbindung

LAN, WLAN, UMTS ? wäre nett, wenn man das wüsste um z.B. auch "Changes" zu erkennen.

Default GW finden (ca. 34ms)

Für die Ausgabe möchte ich die IP-Adresse des Default Gateway haben. In einem Firmen-LAN ist das ein guter Indikator für den Standort. Aber auch Home-Office-Anwender kann man so leichter ermitteln. Vor allem aber sieht man einen Netzwerk-Change.

(Get-WmiObject -Class Win32_IP4RouteTable `
   | where { $_.destination -eq '0.0.0.0' -and $_.mask -eq '0.0.0.0'} `
   | Sort-Object metric1).nexthop

Ping DefGw less 800ms and 10k

Wenn ich das Default Gateway habe, dann kann ich das ja mal mit einem Ping pro Sekunde belasten um die Laufzeit zu messen. Mein Ziel war es ja "einen" Ping zu senden, der nach maximal 1 Sek auch wieder die Kontrolle zurück gibt. Das geht mit PING.EXE aber die Zeit ist damit nicht gut zu messen. Test-Connection macht alles aber unterstützt kein Timeout aber kommt zumindest nach 2,5 Sekunden zurück. Ein direkter WMI-Aufruf hat bei mir aber den angegebenen Timeout nicht respektiert. Daher bleibe ich mal bei Test-Connection

Um etwas "Musik" auf die Leitung zu bringen, sende ich einen 1kByte Paket. So lassen sich sehr langsame Verbindungen, z.B. schlechtes WLAN ermitteln. Um die Daten später besser anzeigen zu können, beschränke ich mich auf ganze Millisekunden.

# send single ping
$gw=(Get-WmiObject -Class Win32_IP4RouteTable `
      | where { $_.destination -eq '0.0.0.0' -and $_.mask -eq '0.0.0.0'} `
      | Sort-Object metric1).nexthop;
#$filter="Address="""+$gw+""" and timeout=800 and buffersize=1500"
#$time = [int]((Get-WmiObject -Class Win32_PingStatus -Filter $filter).ResponseTime)
#$time=[int](measure-command {$result= ping -w 800 -l 10000 -n 1 $gw}).totalmilliseconds;
$result = (test-connection -Computername $gw -BufferSize 1000 -Count 1 -erroraction silentlycontinue).responsetime
if ($error) {
   "Error";
   $error.clear();
}
else
if ($result) {
   $result
} 
else {
   "noreply"
}

Proxy-Server, Exit-IP

Ein wichtiger Punkt ist zu wissen, welchen Weg der Client zum Internet nimmt. Hier kann z.B. geprüft werden, ob ein Proxy genutzt wird und wie schnell der ist. Einige Cloud-Proxy-Dienstleister liefern z.B. "ProbeURL". Wer z.B. über ZScaler im Internet surft, kann über die URL schnell feststellen, ob der Requests über den Proxy läuft.

Es gibt im Internet auch sehr viele URLs um die eigene IP zu ermitteln. Selbst das RIPE zeigt oben die Adresse direkt an.

Allerdings ist das dann nicht zwingend auch die Adresse in Richtung Office 365.

Ich habe noch keinen von Microsoft in Office 365 betriebenen Service gesehen, der mir die IP-Adresse meiner Anfrage zurückliefert.

Outlook Entry Point

Hinsichtlich der Cloud möchte ich natürlich wissen, welchen Office 365 "Eingangspunkt" der Client bei einer DNS-Anfrage geliefert bekommt.

([system.net.dns]::GetHostEntry("outlook.office365.com")).hostname

Alternativ kann man auch PowerShell nutzen

# Erste Anfrageliefert CNAME auf outlook.ms-acdc.office.com 
(Resolve-DnsName -Name outlook.office365.com)[0].NameHost

# Liefert Frontend CNAME auf Standort   z.B. FRA-efz.ms-acdc.office.com
(Resolve-DnsName -Name outlook.ms-acdc.office.com)[0].NameHost

Office 365 CAS-Server

Und natürlich interessiert mich dann auch der Exchange Server, bei dem der Client landet.

try {
   $error.clear();
   $httprequest=[system.Net.HttpWebRequest]::Create("https://outlook.office365.com/owa/favicon.ico");
   $httprequest.timeout=[int]800;
   $data=$httprequest.getresponse();
   if ($data.statuscode -eq 200){
      $data.headers["X-FEServer"];
   }
   $data.Close();
}
catch [system.Net.WebException] {
   $error.exception.message;
}
catch {
   "Failed";
}
# Alternativer Weg
(Invoke-WebRequest https://msxfaq.sharepoint.com/).headers."x-ms-ests-server"
(Invoke-WebRequest https://outlook.office365.com/owa/healthcheck.htm).headers."x-FEServer"

Office 365 CAS Latenz

Die Dauer, bis Office 365 den Request bedient, möchte ich auch gerne wissen. Da jeder Bausteine aber unabhängig sein soll und immer nur eine Information zurückgeben kann, ist der Code fast zum CAS-Server identisch. Um die Daten später besser anzeigen zu können, beschränke ich mich auf ganze Millisekunden.

try {
   $error.clear();
   $httprequest=[system.Net.HttpWebRequest]::Create("https://outlook.office365.com/owa/healthcheck.htm");
   $duration=[int](measure-command {$data=$httprequest.getresponse()}).milliseconds;
   if ($data.statuscode -eq 200){
      $duration;
   }
   $data.Close(); 
}
catch [system.Net.WebException] {
   $error.exception.message;
}
catch {
   "Failed";
}

Internet Connection

Jeder Windows Client "prüft", ob er Zugriff auf das Internet hat. Dazu fragt der Client im Rahmen des Network Location Awareness die URL http://www.msftncsi.com/ncsi.txt ab und prüft den Inhalt. Sicher kann ein böser Provider oder Netzwerkadministrator diese URL auf einen internen Server lenken und die Antwort "spoofen". Aber es ist durchaus ein legitimer Check um eine Aussage zur Erreichbarkeit diesbezüglich zu machen:

try {
   $result = Invoke-WebRequest http://www.msftncsi.com/ncsi.txt -UseBasicParsing
   if ($result.content -eq "Microsoft NCSI"){
      "OK"
   }
   else{
      "NOK"
   }
}
catch {
   "failed"
}

Outlook Performance

Outlook erfasst durchaus interessante Kennzahlen und meldet diese wohl auch zur Cloud. Bei Exchange 2010 On-Premises konnte ich die Werte sogar selbst noch auslesen. Aber vielleicht gibt es einen API um auf dem Client diese Daten abzufragen

Windows Connection Performance

Auch der Ressourcenmonitor in Windows liefert pro Verbindung Daten zu Latenz und Paketloss. Diese Daten sind nützlich um Probleme bei der echten Nutzung zu erkennen. Aus Datenschutzgründen wird man natürlich nicht alle Verindnugen tracken sondern vielleicht nur Durchschnittswerte ermitteln und Ausreißer melden.

Weitere Applikationen

Ich habe noch nicht gezielt weiter gesucht, aber vielleicht lassen sich auch von Browsers, Word, Excel und anderen Applikationen solche "Performance-Indikatoren" abfragen.

Die Bausteine muss ich dann später so starten, dass Sie permanent laufen und Ergebnisse liefern. Daher bette ich jeden Baustein in folgenden While-Schleife ein:

While ($true) {
   #
   #  hier muss dann der Check-Code rein
   #
   #Warten auf nächste voll Sekunde
   start-sleep -Milliseconds (1000-(get-date).millisecond)
}

In einer ersten Version habe ich den Prüf-Code als TXT-Datei abgespeichert und per Get-Content geladen und als SkriptBlock dann dem Start-Job vorgeworfen. Das ist aber unschön, da der Code im Textfile keinen Umbruch haben darf oder jede Zeile mit ";" getrennt sein muss und ich konnte den Code alleine nicht mal eben ausführen. Daher sind die Module nun PS1-Dateien inklusive der While-Schliefe

Es sind durchaus noch andere Checks denkbar, wie z.B.:

  • Zertifikatnamencheck (erkennt Inspection Firewalls)
  • Bandbreitencheck (z.B. UDP-Pings)
  • Get-PublicIP  um die externe Adresse (NAT/Proxy) zu berichten (mit WebService)
  • Erreichbarkeit LDAP von AD-Servern
  • Weitere HTTPS-Dienste

Bei der Analyse der URL https://outlook.office365.com/owa/healthcheck.htm ist mit aufgefallen, dass das ausgelieferte Zertifikat auch die Namen für Hotmail im SAN-Eintrag enthalten. Anscheinend hat Microsoft nun wirklich die Welten verschmolzen.

Start-Job und Last

Damit die Checks skalieren, kann ich diese nicht sequentiell von einem Skript ausführen lassen. Ein "blockender" Aufruf würde alle anderen Checks verzögern. Daher startet das Hauptskript die einzelnen Checks als "Job". Die Jobs arbeiten dann komplett autark ihre Prüfung ab und melden die Ergebnisse über die Pipeline zurück. Das Hauptprogramm muss dann nur noch die Jobs überwachen und die Ergebnisse einsammeln.

Dazu habe ich natürlich ein paar Checks gemacht. Für Zeitmessungen nutze ich z.B. sehr intensiv das "Get-Date"-Commandlet Daher habe ich mal geprüft, ob das signifikant zu Verzögerungen führt. Dem ist aber wohl nicht so.

(measure-command {get-date}).TotalMilliseconds
3,0074

(measure-command {1..1000| %{get-date}}).TotalMilliseconds
127,2234

Dann stellt sich natürlich die Frage, wie "belastend" die vielen Job sein können. Auch hier habe ich auf meinem PC einfach die oben genannten Jobs gestartet.

Id Name                      PSJobTypeName State   HasMoreData Location  Command
-- ----                      ------------- -----   ----------- --------  -------
38 dns-office365outlook      BackgroundJob Running True        localhost ...
40 host-computername         BackgroundJob Running True        localhost ...
42 host-cpuload              BackgroundJob Running True        localhost ...
44 host-Hyper-Vhost           BackgroundJob Running True        localhost ...
46 host-ramfree              BackgroundJob Running True        localhost ...
48 http-o365casserverlatency BackgroundJob Running True        localhost ...
50 http-o365casservername    BackgroundJob Running True        localhost ...
52 net-defaultgateway        BackgroundJob Running True        localhost ...
54 net-defgatewayping        BackgroundJob Running True        localhost ...

Die Jobs sind ja die meiste Zeit im "Schlafmodus" und warten auf die nächste Sekunde. Und wenn Sie dann diesen einen Befehl ausführen, dann haben sie ein klein wenig CPU-Last. Nur die PowerShell-"Umgebung" braucht natürlich etwas RAM.

Wir brauchen also nicht allzu viel CPU-Last aber aufgrund des Speicherbedarfs sollten wir allzu viele Jobs starten. ein C#- Code könnte mit Threads das vielleicht noch optimieren.

Ausführung

Ich starte also in einer PowerShell das Hauptmodul. Es lädt alle Checks, die als TXT-Datei vorliegen und startet dieses als Job.

Nach ein paar Sekunden Einschwingzeit startet es dann die Sammlung der Rückgaben und speichert diese in einer CSV-Datei. Wenn Sie das Skript per CTRL-C abbrechen, dann laufen die Jobs weiter!. Mit einem "X" hat das Skript noch die Zeit wieder aufzuräumen. Sollte das doch mal passiert sein, dann können Sie entweder die PowerShell schließen oder die Jobs manuell wie folgt entfernen:

get-job | remove-job -force

Ich habe das Skript noch nicht öffentlich gemacht. Ich möchte erst noch einige Tage meine Erfahrungen damit sammeln um es noch zu optimieren. Insbesondere bei den Langzeit -Tests mit den Prüfungen gegen Office 365-Dienste möchte ich sehen, ob diese von Microsoft vielleicht künstlich "gedrosselt" werden o.ä.

Ein Problem, welches ich sicher noch lösen muss, ist der Speicherbedarf. Nach einigen Tagen Laufzeit hat die aufrufende PowerShell sich richtig "voll" gefressen.

Das Beenden der "Jobs" mit einem "X" bringt aber noch keine Besserung. "Jobs" sind zumindest nicht sichtbar der Speicherfresser. Das Schließen der steuernden PowerShell gibt den Speicher sofort wieder frei:

Das "Vollfressen" auf etwas über 3 GB hat bei mir aber 7 Tage gedauert. In der Zeit habe ich mehrfach das Netzwerk gewechselt und den Schlafmode genutzt. Dabei wurde ein 36MB Protkolldatei geschrieben. Das Skript ist aktuell also besser kein Dauerläufer sondern nur für den überwachten kurzfristigen Betrieb gedacht.

Auswerten

Ein 8 Stunden-Lauf produziert generiert eine 5 MB CSV-Datei. Eine 7-Tage-Analyse kommt so schon mal auf ca. 100 Megabyte. Die Ergebnis-CSV ist der Menge (eine Zeile pro Sekunde) schnell zu groß, um mit den RAW-Daten genutzt zu werden. Hier ist dann eine Auswertung und Gruppierung erforderlich. Das kann per PowerShell passieren, z.B.: eine Liste der Outlook Zugriffspunkte zu erhalten.

import-csv ClientConnectionCheck.result.20171123173528.csv | group dns-office365outlook -NoElement | ft -AutoSize

Count Name
----- ----
  665
 3329 outlook-emeaeast.office365.com
 6631 outlook.ms-acdc.office.com
 2975 outlook-emeaeast2.office365.com
 3201 outlook-emeaeast3.office365.com

Diese wechseln nämlich schon das ein oder andere mal. Auch die "Pingzeit" des Default Gateway ist interessant. Ist es doch der erste Hop und der sollte immer "schnelle" sein, es sei denn es ist wieder mal ein WLAN

import-csv ClientConnectionCheck.result.20171123173528.csv | measure-object net-defgatewayping -Average -Minimum -Maximum

Count    : 16822
Average  : 363,385150398288
Sum      :
Maximum  : 850
Minimum  : 0
Property : net-defgatewayping

Mindestens ein Paket war 850ms unterwegs. und selbst ein Average von 363,4 ist echt nicht gut. Da brauchen wir über WAN gar nicht mehr zu reden. Interessanter ist natürlich  eine grafisch Aufbereitung, z.B. mit Power Bi. Dann können Sie auch über die Zeit hinweg, z.B. einen Tag sehen, was passiert ist und wie die Verteilung der Antwortzeiten ist. Mit dem Timestamp auf der X-Achse können Sie sehen, wann die Ping-Zeit (rot) und die Dauer eines HTTP-Abrufs (grün) erhöht war.

Es gibt hier fast nur ganz wenig Übereinstimmung zwischen "langen PING-Zeiten" und langsamen HTTP-Requests. Insofern könnte man sagen, dass zumindest der erste Hob hier kein Problem hat. Interessant wird es natürlich, wenn ein Check auf dem Client den gesamten Weg durch das firmeneigene LAN bis zur Firewall prüft und so auch Transferstrecken mit erfasst werden. Allerdings würde ich solch einen Check dann nicht mehr auf dem Client sehen, sondern wieder im Bereich Netzwerk-Management.

Weiterentwicklung

Ich nutze das Skript aktuell immer interaktiv mit meiner Kennung, um z.B. die Leistung eines Clients Richtung Office 365 und andere Netzwerkprobleme zu diagnostizieren. Dazu passe ich die Skripte und Module natürlich auch entsprechend an, um die richtigen Ziele mit geeigneten Methoden und Intervallen zu messen. Ich will mit dem Skript ja nicht eine Gegenstelle oder Leitung überlasten. Für meine Anforderungen reicht dieses Funktion erst einmal. Es sind natürlich viele Optionen einer Weiterentwicklung möglich, z.B. eine zentrale Konfiguration, zentrales Reporting als auch eine Anzeige für den Anwender. Ein Tray-Icon könnte dem Anwender ja signalisieren, wenn die Verbindung zu gewissen Diensten gar nicht oder nur eingeschränkt möglich ist oder die Qualität, speziell bei VoIP, nicht für große Sprünge geeignet ist.

Eine zweite Komponente könnte ein Benachrichtigungsdienst sein. Wenn ich eh immer wieder per HTTP eine Testseite abhole, dann könnte die Seite auch Konfigurationsinformationen für das Skript und Hinweise für den Anwender enthalten, z.B. geplante Wartungen, Wichtige Nahrichten. o.ä. Die Anzeige könnte wie eine Ticker-Meldung am Bildschirmrand durchlaufen. Der Vorteil wäre neben der Aktualität und Unabhängigkeit von anderen Diensten auch die Entfernung aus dem Blickfeld, wenn das Problem nicht mehr akut ist. Daher sollten solche Infos auch nicht im Windows Benachrichtigungsfenster erscheinen.

Weitere Links