PowerShell als HTTP-Client
Es ist per PowerShell sehr einfach möglich, per HTTP verschiedene Dinge zu automatisieren. Bis PowerShell 2.0 mussten Sie dazu allerdings noch direkt auf .NET-Klassen wie "System.Net.WebClient" zurückgreifen. Mit PowerShell 3.0 gibt es die zwei neue Commandlets "Invoke-Webrequest" und "Invoke-RESTMethod".
Bewertungskriterien
Ehe ich nun gleich die verschiedenen Möglichkeiten vorstelle, möchte ich erst einmal die Kriterien für die Bewertung vorstellen und die Wege als Tabelle auflisten.
- Technik
Es gibt Lösungen als PowerShell, solche mit einer .NET Klasse oder die Nutzung von COM-Objekten - Restricted Header
Es gibt Header (z.B. "Referer"), die man nutzen möchte aber nicht alle Methoden erlauben die Vorgabe dieser Header. - Parallelität
Die meisten Varianten warten nach dem Absetzen der Anfrage auf die Rückkehr. Da macht die Programmierung einfach aber wer aus Performance-Gründen parallel arbeiten will, muss dann selbst Code darum schreiben. - Fehler-Ergebniscode und
Inhalt beim Fehler
Einige Wege liefern wirklich z.B. einen 401-Fehler, während andere eine Exception werden. - Ergebnisform
Interessant ist auch, ob beim Download einer Webseite z.B. nur der HTML-Body als Text kommt, ein Zugriff auf den kompletten Datenstrom möglich ist oder sogar ein fertig zerlegtes HTML-DOM-Objekt zu erwarten ist.
Bei meinen Versuchen zu UCWA - Unified Communication Web API habe ich alle Versionen durchprobiert, da ich hier eine URL mit einem 401 abrufen muss und dennoch die Header brauche.
- Restricted Header
Sagt aus, ob am Beispiel von "Referer" der Header angepasst werden kann - Seriell
Ja bedeutet, dass das Skript auf das Ergebnis wartet.
Hier die verschiedenen Methoden im Schnellüberblick
Methode | Technik |
Restricted Header |
Seriell | Tempo | Verhalten bei | |||||
---|---|---|---|---|---|---|---|---|---|---|
Ja |
2xx | 3xx | 4xx | NoDNS | Notreach | |||||
Invoke-Webrequest |
CMDLet |
Nein |
Ja |
Langsam |
OK |
Redir |
Ex |
Ex |
Ex |
Ex |
System.Net.WebClient |
NET |
Nein |
Ja |
Ja |
OK |
Redir |
Ex |
Ex |
Ex |
Ex |
System.Net.HTTPWebrequest |
NET |
Ja |
Nein |
Ja |
OK |
Redir |
Ex |
Ex |
Ex |
Ex |
System.Net.Http.HttpClient |
NET |
Ja |
Ja |
Nein |
OK |
Redir |
Ex |
Ex |
Ex |
Ex |
XMLHttpRequest |
COM |
Ja |
Ja |
200 |
300 |
400 |
502 |
Ex |
Ex |
Ex |
Bits-Request |
|
|
|
|
|
|
|
|
|
Hinweise zur Bedeutung:
- OK1
Der Aufruf gibt ein Objekt zurück - OK2
Der Aufruf gibt einen String mit den Inhalt zurück - ReDir
Wenn die 3xx Antwort ein Verweis auf eine andere URL ist, dann macht die Methode automatisch einen zweiten GET auf die neue URL. Der aufrufende Prozess muss sich also nicht um die Behandlung eines 3xx Reply kümmern - Ex
Die Methode wirft eine "Exception". Diese können Sie mit einem Try/Catch abfangen und die Excetption auslesen. Keine der Methoden liefert bei einer Exception aber den Fehler als Rückgabe des Aufrufs. Hier ein Bespiel so einer Try/Catch Konstruktion
Try { $result = Invoke-WebRequest ` -uri "http://www.msxfaq.de/.htaccess" ` -Method GET $httperrorcode = $result.Statuscode } Catch [System.Net.WebException]{ [System.Net.HttpWebResponse]$result = [System.Net.HttpWebResponse] $_.Exception.Response $httperrorcode = $result.StatusCode.Value__ $error.clear() }
Sie sehen wie unterschiedlich die verschiedenen Methoden und Klassen agieren. Da ist es nicht immer richtig den einfachen Weg zu wählen, wenn bestimmte Ergebnisse benötigt werden. Nun die einzelnen Methoden in Beispielcodes:
SSL und Zertifikat
Wenn Sie mit einem Browser eine HTTPS-Adresse ansprechen und es Probleme mit dem Zertifikat gibt. So könnte der Name nicht übereinstimmen, die Gültigkeit abgelaufen, die ausstellende CA nicht vertrauenswürdig oder das Zertifikat sogar zurückgezogen worden sein. Als interaktiver Anwender bekommen Sie in der Regel eine Rückfrage, ob sie dennoch weiter machen möchten. Ein Skript kann nur einen Fehler werfen, den sie abfangen müssen. Oder Sie hinterlegen eine Funktion, die den Umgang mit der Fehlermeldung regelt. Folgende kurze Zeile liefert immer ein "True" für Zertifikatwarnungen der aktuellen Powershell Session.
# disabled SSL Checks if required [System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true}
Sie sollten es aber nicht zur Gewohnheit werden lassen, Zertifikatwarnungen und Fehler zu ignorieren. Besser ist es das Zertifikat auf der Gegenseite zu korrigieren. Dieser Einzeiler kann aber nicht alle Fehler abfangen. Die umfangreichere Lösung erfordert einen eigenen Type.
- Powershell und Zertifikate Check
- Ignoring SSL trust in PowerShell
System.Net.WebClient
https://blogs.technet.microsoft.com/bshukla/2010/04/12/ignoring-ssl-trust-in-powershell-system-net-webclient/ - Windows PowerShell, Invalid Certificates,
and Automated Downloading
https://blogs.technet.microsoft.com/heyscriptingguy/2010/07/25/windows-powershell-invalid-certificates-and-automated-downloading/
TLS Protokoll mit PowerShell
Normalerweise handeln Client und Server ein "passendes" TLS-Protokoll aus und versucht das "bester" zu nutzen. Als Client können Sie aber nicht sicher sein, ob nicht auf dem Weg jemand das "Angebot" des Servers beim TLS-Handshake absichtlich verändert, so dass Sie gar nicht wissen, was der Server kann. Sie können aber ihrerseits z.B. TLS 1.2 erzwingen und damit keine schwächeren Verfahren annehmen. Das geht z.B. wie folgt:
[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12
Ab dem Moment nutzen Invoke-WebRequest und andere eben nur noch TLS 1.2. Wenn Sie aber auch ein Fallback auf schwächere Verfahren erlauben wollen, dann müssen Sie einfach mehrere Werte binär mit "oder" verbinden
[Net.ServicePointManager]::SecurityProtocol = ` [Net.SecurityProtocolType]::Tls ` -bor [Net.SecurityProtocolType]::Tls11 ` -bor [Net.SecurityProtocolType]::Tls12
Die Einstellungen gelten nur für die aktuelle Session
- TLS 1.2 Enforcement
- Force the Invoke-RestMethod PowerShell
CMDLet to use TLS 1.2
https://www.codyhosterman.com/2016/06/force-the-invoke-restmethod-powershell-cmdlet-to-use-tls-1-2/
Invoke-Webrequest / Invoke-RestMethod (PS 3.0 oder höher)
Ab PowerShell 3.0 können Sie sehr viele Aufgaben als HTTP-Client in einer Zeile lösen, z.B.: den Download einer Datei.
$data= Invoke-WebRequest ` -Uri "http://www.msxfaq.de" ` -Outfile msxfaq-homepage.htm
Analog dazu können Sie auch Daten per POST an eine Webseite hochladen.
$data= Invoke-WebRequest ` -Method post ` -Uri "http://localhost:81/post.txt" ` -Body "bodytest"
Das macht es natürlich schon sehr viel einfacher mit einem Webserver zu interagieren.
- Invoke-WebRequest
http://technet.microsoft.com/en-us/library/hh849901.aspx - Invoke-RestMethod
http://technet.microsoft.com/en-us/library/hh849971(v=wps.620).aspx
Die Antwort im Erfolgsfalle ist ein HtmlWebResponseObject.
- HtmlWebResponseObject Class
http://msdn.microsoft.com/en-us/library/microsoft.PowerShell.commands.htmlwebresponseobject(v=vs.85).aspx
Interessant sind hier bei die beiden Properties "StatusCode" und "StatusDescription". Der Inhalt ist in dem Property "Content". Wer dem kompletten HTML-Inhalt samt Header benötigt, nutzt "RawContent". Interessanter sind aber vor allem die vorgearbeiteten Properties wie z.B."Links", welches alle Hyperlinks ausgibt.
Hinweis
Die Rückgabe enthält nur dann ein Objekt, wenn
die Abfrage erfolgreich war. Fehler werden immer
mit einer "Exception" abgefangen. Erwarten Sie
also nie in $data.Statuscode einen 401 o.ä.
Achtung:
Invoke-Webrequest
nutzt per Default DOM des IE. Wenn dieser nicht
installiert oder limitiert ist, kann das Parsen
nicht funktionieren. Nutzen Sie dann den
Parameter "-UseBasicParsing". Leider werden dann nur Links, Forms,
Images und Header auseinander genommen aber das
Feld "ParsedHTML" bleibt leer.
Achtung:
Beide Commandlets
senden nicht zwingend den kompletten Request in
einem Paket. Es ist durchaus erlaubt den Request
auf mehrere Pakete aufzuteilen und dem Ziel über
das Feld "Content-Length" mitzuteilen, wie groß
die Daten sind. Das Ziel muss dann mit einem
"100 Continue" eine Zwischenbestätigung senden.
Leider "verstehen" das gerade kleine Geräte das
nicht immer, wie ich auf
PRTG Edimax SP2101W z.B. herausgefunden
habe.
Zudem scheint im Hintergrund ein Objekte bestehen zu bleiben, welches bei Folgeanfragen wieder genutzt wird. Im Wireshark ist schön zu sehen, dass die gleichen Anfragen im Sekundenabstand immer den gleichen Source-Port nutzen:
Hinweis:
Der Download ist deutlich schneller, wenn Sie
die Variable $ProgressPreference = 'SilentlyContinue'
setzen. Das erspart ihnen den "Progress-Balken"
Invoke-Webrequest und Performance
Eines muss man Invoke-Webrequest aber ankreiden: Es ist nicht besonders schnell. Hier ist mal ein ganz einfacher Code, der 100mal die Homepage der msxfaq.de herunter lädt:
1..100 | %{Invoke-WebRequest http://www.msxfaq.de}
Schon im Powershell-Fenster ist zu sehen, dass jeder Request ca. 1 Sekunde dauert und auch im Taskmanager ist zu sehen, dass die PowerShell hier nur eine Verbindung zur 217.160.0.234 pflegt aber mit 100kbit/Sek ist das alles andere als schnell:
Invoke-Webrequest ist also der "Quickie" um eine HTML-Seite zu bekommen. Das stimmt so aber nicht. Das Commandlet macht sich nach dem Empfang der Seite nämlich noch die Mühe diese zu parsen und auf dem Bildschirm auszugeben. Beides lässt sich abschalten oder reduzieren mit deutlichen Auswirkungen auf die Performance.
Durch den Einsatz des Parameters "-UseBasicParsing" werden die Felder "Forms", "InputFields" und "ParsedHTML" des Ergebnisses nicht gefüllt. "Content" und "RAW-Content" gibt es aber ebenso wie "Links", "Images" und "Headers". BasicParsing liefert also schon viele Daten für weitergehende Downloads aber verzichtet auf das Rendern des Inhalts, was aber kaum mehr Zeit braucht.
Diese Werte habe ich in PowerShell per "Single Thread" ermittelt. Wenn Sie in ihrem Code Multithreading nutzen, können die Werte abweichen.
Aufruf | 100 Mal | Durchsatz 23771 bytes |
---|---|---|
Der einfache Aufruf mit Bildschirmausgabe und vollem Parsing. $start=get-date 1..100 | %{Invoke-WebRequest http://www.msxfaq.de} (get-date) - $start |
150,0 Sek |
158 kBit/Sec |
Aufruf mit UserBasicParsing $start=get-date 1..100 | %{Invoke-WebRequest http://www.msxfaq.de -UseBasicParsing} (get-date) - $start |
12,3 Sek |
1,932 kBit/Sek |
Unterdrücken der Ausgabe mit Out-Null $start=get-date 1..100 | %{Invoke-WebRequest http://www.msxfaq.de | out-null} (get-date) - $start |
7,7 Sek |
3087 kBit/Sek |
UseBasicParsing und Ausgabe mit Out-Null verwerfen $start=get-date 1..100 | %{Invoke-WebRequest http://www.msxfaq.de -UseBasicParsing | out-null} (get-date) - $start |
9,4 Sek |
2528 kBit/Sek |
Als Gegencheck habe ich mal eine native .NET-Klasse genutzt. $webclient = New-Object Net.WebClient $webclient.Headers.Add("User-agent", "Mozilla/5.0 (Windows NT; Windows NT 10.0; de-DE)"); $webclient.CachePolicy = new-object System.Net.Cache.RequestCachePolicy ("bypasscache") # Skip Cache $start=get-date 1..100 | %{$null=$webclient.DownloadString("http://www.msxfaq.de")} (get-date) - $start $start=get-date 1..100 | %{$webclient.DownloadString("http://www.msxfaq.de") | out-null} (get-date) - $start Dieser Weg ist also nicht schneller und sogar die Ausgabe nach "null" ist ein deutlicher Unterschied. Allerdings tippe ich hier eher auf meine Messfehler, denn Invoke-Webrequest nutzt wohl auch den gleichen Unterbau. |
14 Sek |
|
Mittlerweile liefert Microsoft auch "CURL.EXE" mit, was aber deutlich langsamer ist. measure-command {1..100 | %{curl https://www.msxfaq.de}} Das dürfte aber am Start der CMD-Shell liegen. |
30s |
|
Es ist offensichtlich, dass die Ausgabe der Daten auf den Bildschirm der Bremser ist. Wer die Ausgaben nicht braucht und nur Last erzeugen will, kann das mit "| Out-Null" oder der Zuweisung in eine Variable unterbinden. Anscheinend verlangsamt das "-UseBasicParsing" sogar die Verarbeitung. Allerdings muss man wissen, dass bei automatisierten Prozessen der Zugriff auf die Browser DOM-Objekte nicht immer möglich ist. Wenn ich die Dauer mit "Measure-Comand" messen will, welches auch die Ausgaben unterdrückt, ist auch alles schneller.
- Friday Fun: It’s PowerShell,
Baby!
http://jdhitsolutions.com/blog/2013/06/friday-fun-its-PowerShell-baby/ - Web Scraping with PowerShell
http://teusje.wordpress.com/2012/12/29/web-scraping-with-PowerShell/ - InvokeRestMethod für the
Rest of us
http://blogs.technet.com/b/heyscriptingguy/archive/2013/10/21/invokerestmethod-for-the-rest-of-us.aspx - Use PowerShell to Work with
SkyDrive für Powerful Automation
http://blogs.technet.com/b/heyscriptingguy/archive/2013/07/02/use-PowerShell-to-work-with-skydrive-for-powerful-automation.aspx
Invoke-Webrequest und 301 Moved Permanently
Immer mehr Webseiten erfordern eine SSL-Verschlüsselung und lösen dies dahingehend, dass Sie einen Zugriff per HTTP mit einem Redirect auf die SSL-Verbindung forcieren. Ich habe das mal mit frankysweb.de nachgespielt. Mit Fiddler lässt sich das Verhalten sehr gut analysieren.
Invoke-Webrequest unterstützt die Umleitung und liefert die Zieladresse als Ergebnis zurück. Per Default folgt Invoke-WebRequest bis zu 5 Weiterleitungen. Die Anzahl kann mit dem Parameter -MaximumRedirection angepasst werden.
Mit der PowerShell 7 hat sich das verhalten etwas geändert. Hier scheint Invoke-Webrequest nicht mehr zu folgen sondern einen Fehler nach $error zu melden.
Ursache ist hier die Umleitung von HTTPS zu HTTP, die das darunterliegende NET Core Framework nicht macht.
Der Fehler ist also nicht in der PowerShell selbst. Es wäre nett gewesen, wenn das Handling dieses Sonderfalles z.B.: durch einen Parameter "-Allow HTTPStoHTTPredirect:$true" o.ä. möglich wäre. Dem ist aber nicht so. Ich muss ich dann selbst z.B. mit einem Try/Catch arbeiten und die Location nachverfolgen.
- Invoke-Webrequest
https://msdn.microsoft.com/en-us/powershell/reference/5.1/microsoft.powershell.utility/invoke-webrequest - Invoke-WebRequest / Invoke-RestMethod
fails to follow HTTP redirects from HTTPS
https://GitHub.com/PowerShell/PowerShell/issues/2896 - Getting Redirected (301/302) URI’s in
PowerShell using Invoke-WebRequest Method
https://dotnet-helpers.com/powershell/getting-redirected-301-302-uris-in-powershell-using-invoke-webrequest-method/
Invoke-Webrequest und UTF-8
Eine sehr unschöne Fehlermeldung bekommen Sie manchmal, wenn Sie Invoke-Webrequest gegen einen Webserver nutzen, der Daten als UTF-8 ausliefert. Auch dieses Verhalten ist mir bei frankysweb.de aufgefallen, was aber keinesfalls als Fehler der Webseite angesehen werden darf.
Die Fehlermeldung in der Powershell liefert darauf hin folgendes:
Invoke-WebRequest : ""UTF-8"" ist kein unterstützter Codierungsname. Informationen zum Definieren einer benutzerdefinierten Codierung entnehmen Sie der Dokumentation zur Encoding.RegisterProvider-Methode. Parametername: name
Wenn ich bei der Anforderung der Daten kein Encoding vorgebe, liefert der WebServer einfach aus, was er als Default hat und Invoke-Webrequest weiß einfach nicht, wie man mit UTF-8 als Objekt umgeht. Ich habe versucht über die Header "Content-Type" und "Accept-Charset" versucht eine Codierung vorzugeben aber der WebServer hat das einfach ignoriert.
$headers = New-Object "System.Collections.Generic.Dictionary[[String],[String]]" $headers.Add("Accept-Charset", 'UTF-8') Invoke-WebRequest -headers $headers
Sobald der WebServer "UTF-8" sendet, scheint Invoke-Webrequest als auch Invoke-RESTMethod zu streiken. Diverse Quellen behaupten, dass dies ein Bug wäre. Interessanteweise kann man mit dem Parameter "-outfile" den Content problemlos in eine Datei schreiben lassen.
Eine Lösung für Invoke-WebRequest habe ich nicht. Ich nutzt stattdessen dann die NET.WebClient-Klasse:
$webclient= New-Object Net.WebClient $rssdata = $webclient.DownloadString("https://www.frankysweb.de/feed/")
Der Inhalt ist weiterhin UTF-8 mit Umlauten:
Wenn ich die Rückgabe aber einfach verarbeiten würde, dann sind die Umlaute korrupt. PowerShell interpretiert den String als UNICODE
Auch diese Klasse erkennt anhand des Content-Type nicht automatisch das Encoding. Das kann ich hier aber vorab spezifizieren, so dass der komplette Code dann wie folgt aussieht:
$webclient = New-Object Net.WebClient #Optional Proxy $webclient.Proxy = New-Object System.net.WebProxy("http://squid:3128/", $true) $webclient.Proxy.UseDefaultCredentials= $true $webclient.Encoding = [System.Text.Encoding]::UTF8 $rssdata = $webclient.DownloadString("https://www.frankysweb.de/feed/") #Ausgabe der Items an die Pipeline ([xml]$rssdata).rss.channel.Item
Natürlich ist es schade, dass Invoke-Webrequest nicht mit UTF-8 umgehen kann und selbst Der WebClient braucht etwas Hilfe, da er ansonsten die Rückgabe als ISO-8859-1 (Windows-1252) codiert, selbst wenn der Content-Type in der Antwort etwas anderes mitteilt.
- UTF8 Encoding call
https://msdn.microsoft.com/en-us/library/system.text.utf8encoding.aspx - UnicodeEncoding Class
https://msdn.microsoft.com/en-us/library/system.text.unicodeencoding(v=vs.110).aspx - #PSTip Access web from PowerShell console
http://www.powershellmagazine.com/2012/11/02/pstip-access-web-from-powershell-console/
Exception Handling
Wenn Sie mit Invoke-Webrequest oder Invoke-RestMethod eine URL ansprechen, und der Server z.B. mit einem 4xx Fehler antwortet, dann liefert PowerShell eine Exception.
Hier noch mal als Text für Google und Co:
PS C:\> $result = invoke-webrequest https://www.msxfaq.de/gibtesnicht.htm invoke-webrequest : MSXFAQ.DE:404 Fehlerseite Die von ihnen angesurfte Seite ist anscheinend nicht mehr vorhanden oder wurde an eine andere Stelle umgezogen. Moment bitte, wir leiten Sie weiter In Zeile:1 Zeichen:11 + $result = invoke-webrequest https://www.msxfaq.de/gibtesnicht.htm + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + CategoryInfo : InvalidOperation: (System.Net.HttpWebRequest:HttpWebRequest) [Invoke-WebRequest], WebException + FullyQualifiedErrorId : WebCmdletWebResponseException,Microsoft.PowerShell.Commands.InvokeWebRequestCommand
Das Problem dabei ist, dass die Variable "$Result" in dem Fall nicht belegt wird. Wenn ihnen der Fehler reicht, ist das zu vertreten. Wenn Sie aber z.B. eine REST-Methode aufrufen, dann ist der HTTP-Fehler ziemlich unwichtig. Dann interessiert mich schon die detaillierte Meldung im Payload. Hier am Beispiel eines Graph-Aufrufs:
Diese Information kann ich aber über eine Try/Catch-Konstruktion abfangen:
try { $result =Invoke-WebRequest https://www.msxfaq.de/gibtesnicht.htm } catch { $resultstream = $_.Exception.Response.GetResponseStream() $reader = New-Object System.IO.StreamReader($resultstream) $errorbody = $global:reader.ReadToEnd(); }
So kann ich über $errorbody dann auch die Details des Fehlers weiter verarbeiten.
Achtung: Powershell 2und PowerShell 5+ verhalten sich hier unterschiedlich!
- Invoke-RestMethod should
return the full error response
from the remote endpoint
https://GitHub.com/PowerShell/PowerShell/issues/2193 - Using Try and Catch with
PowerShell’s Invoke-WebRequest
https://wahlnetwork.com/2015/02/19/using-try-catch-powershells-invoke-webrequest/
System.Net.WebClients (bis PS 2.0)
Per PowerShell können auch HTTP-Anfragen an Webserver gestellt werden. So können z.B. automatisch Daten von Webservern herunter geladen werden. Dreh und Angelpunkt ist die System.Net.WebClient-Klasse mit ihren Properties.
PS C:\> $webclient = new-object System.Net.WebClient PS C:\> $webclient AllowReadStreamBuffering : False AllowWriteStreamBuffering : False Encoding : System.Text.SBCSCodePageEncoding BaseAddress : Credentials : useDefaultCredentials : False Headers : {} QueryString : {} ResponseHeaders : Proxy : System.Net.WebRequest+WebProxyWrapper CachePolicy : IsBusy : False Site : Container :
Hier können Sie dann natürlich auch die Header komplett flexibel setzen:
$webclient.Headers.Add("UserAgent", "User agent to send"); $webclient.Headers.Add("Referer", "string");
Und interessanter noch die Methoden. Hier eine Auswahl:
TypeName: System.Net.WebClient Name MemberType Definition ---- ---------- ---------- DownloadData Method byte[] DownloadData(string address), by... DownloadDataAsync Method void DownloadDataAsync(uri address), vo... DownloadFile Method void DownloadFile(string address, strin... DownloadFileAsync Method void DownloadFileAsync(uri address, str... DownloadString Method string DownloadString(string address), ... DownloadStringAsync Method void DownloadStringAsync(uri address), ... OpenRead Method System.IO.Stream OpenRead(string addres... OpenReadAsync Method void OpenReadAsync(uri address), void O... OpenWrite Method System.IO.Stream OpenWrite(string addre... OpenWriteAsync Method void OpenWriteAsync(uri address), void ... UploadData Method byte[] uploadData(string address, byte[... UploadDataAsync Method void uploadDataAsync(uri address, byte[... UploadFile Method byte[] uploadFile(string address, strin... UploadFileAsync Method void uploadFileAsync(uri address, strin... UploadString Method string uploadString(string address, str... UploadStringAsync Method void uploadStringAsync(uri address, str... UploadValues Method byte[] uploadValues(string address, Sys... UploadValuesAsync Method void uploadValuesAsync(uri address, Sys...
Damit ist die Basis gelegt und der Abruf einer einfachen HTTP-Information ohne Proxy ist in zwei Zeilen geschafft:
$webclient = new-object System.Net.WebClient $webclient.DownloadString("http://www.msxfaq.de")
Einen Fehler müssen Sie natürlich mit Try/Catch-Konstruktionen selbst abfangen
System.Net.HTTPWebrequest
Diese Klasse ist eine auf HTTP "optimierte" Version der generischen Webrequest-Klasse. Während ein Webrequest quasi jede URI bedienen kann, ist diese Klasse auf HTTP/HTTPS fokussiert. Die Klasse kennt dabei als Attribute auch HTTP-spezifische Eigenschaften.
$URL = "http://www.msxfaq.de" $httprequest=[system.Net.HttpWebRequest]::Create($URL); $data = $httprequest.getresponse(); $stat = $data.statuscode; $data.Close();
- HttpWebRequest-Eigenschaften
http://msdn.microsoft.com/de-de/library/system.net.httpwebrequest_properties(v=vs.110).aspx - HttpWebRequest-Methoden
http://msdn.microsoft.com/de-de/library/system.net.httpwebrequest_methods(v=vs.80).aspx - HttpWebRequest.GetResponse-Methode
http://msdn.microsoft.com/de-de/library/system.net.httpwebrequest.getresponse(v=vs.80).aspx - WebException-Klasse
http://msdn.microsoft.com/de-de/library/system.net.webexception(v=vs.80).aspx
System.Net.Http.HttpClient
Zuletzt gibt es auch noch diese Klasse im .NET-Framework. Dazu gleich ein Hinweis:
Hinweis Die Namespaces
System.Net.Http und System.Net.Http.Headers sind
in zukünftigen Windows-Versionen möglicherweise
nicht mehr zur Verwendung in Windows Store-Apps
verfügbar. Verwenden Sie ab Windows 8.1 und
Windows Server 2012 R2 stattdessen
Windows.Web.Http.HttpClient im Windows.Web.Http-Namespace und den zugehörigen Namespaces
Windows.Web.Http.Headers und
Windows.Web.Http.Filters für Windows Store-Apps.
Quelle:
http://msdn.microsoft.com/de-de/library/windows/apps/hh781239.aspx
Wenn Sie keine "DefaultRequestHeader" addieren, dann ist der Request wirklich "leer". Einige Webserver lehnen so etwas gerne mit einen "400 Bad Request" ab. Meist reicht es einen "User-Agent" mit zu senden.
Allerdings geht das wirklich erst ab Windows 8.1 und Windows Server 2012R2- Dann bleibe ich erst mal beim System.net.http.httpClient. Im Gegensatz zu den anderen drei Optionen läuft diese Klasse asynchron, d.h. die Kontrolle wird sofort an den aufrufenden Prozess zurück gegeben. Das ist von Vorteil, wenn man parallelisieren muss aber bei einem Aufruf müssen wir warten.
Add-Type -AssemblyName System.Net.Http # Optional Handler erstellen, z.B. fuer Proxy, Credentials, Redirect, SSLProperties $HttpClientHandler =New-Object System.Net.Http.HttpClientHandler # Optional Proxy und ProxyAuthentication $HttpClientHandler.Proxy = New-Object System.net.WebProxy("http://proxyserver:3128/", $true) $HttpClientHandler.Proxy.UseDefaultCredentials=$true $httpclient = New-Object System.Net.Http.HttpClient($HttpClientHandler) # oder ohne HttpClientHandler #$httpclient = New-Object System.Net.Http.HttpClient #Optionale Header $httpclient.DefaultRequestHeaders.add("User-Agent", "Mozilla/5.0 MSXFAQSample"); write-verbose "Start Request" $response = $httpclient.GetStringAsync($URL) Try { write-host "Wait für completion" $response.wait() # wait für end of http-tasks add-member -InputObject $response -Name LastStatus -MemberType noteproperty -Value "OK" -Force } Catch { write-host "Download ERROR $URL" write-host ("Error:" + $response.Exception.InnerException.message) add-member -InputObject $response -Name LastStatus -MemberType noteproperty -Value ($winhttptask.Exception.InnerException.message) -Force $error.removerange(0,1) #remove last error } if ($winhttptask.LastStatus -eq "OK") { write-host "Download OK" write-host ("Download Data 40char " + $response.result.substring(1,40)) } else { write-host ("Found error:" + $response.LastStatus) }
Aber auch dieser Request liefert als Response nur dann eine Antwort, wenn dieser fehlerfrei war. Wenn auch hier die Abfrage mit einem 4xx Fehler bedient wurde, dann ist auch dieser Response leer.
Ich habe noch keinen Weg gefunden, bei einem 407 neben dem Fehlercode auch die Response-Header des 407 zu erhalten.
- HttpClient-Klasse
http://msdn.microsoft.com/de-de/library/system.net.http.httpclient.aspx - Schnellstart: Herstellen
einer Verbindung mit "System.Net.Http.HttpClient"
(Windows Store-Apps mit C#/VB und XAML)
http://msdn.microsoft.com/de-de/library/windows/apps/hh781239.aspx - HttpClient.GetAsync-Methode
(String)
http://msdn.microsoft.com/de-de/library/hh158944(v=vs.118).aspx - Task<TResult>-Klasse
http://msdn.microsoft.com/de-de/library/dd321424(v=vs.118).aspx - Task.Wait-Methode
http://msdn.microsoft.com/de-de/library/dd235635(v=vs.110).aspx - HttpResponseMessage-Klasse
http://msdn.microsoft.com/de-de/library/system.net.http.httpresponsemessage(v=vs.118).aspx
XMLHTTPRequest
Zwar ist dies ein COM-Objekt und kann auch aus der PowerShell heraus genutzt werden. Allerdings ist diese Schnittstelle ein universeller Zugang für verschiedene Browser und durchaus mit den gleichen Methoden und Eigenschaften über die verschiedenen Betriebssysteme vorhanden.
$XMLHTTPRequest = New-Object -ComObject Msxml2.XMLHTTP $XMLHTTPRequest.open('GET', $URL, $false) #$XMLHTTPRequest.open('POST', $URL, $false) $XMLHTTPRequest.setRequestHeader("Referer", "http://www.msxfaq.de") #$XMLHTTPRequest.setRequestHeader("Content-type", "application/x-www-form-URLencoded") #$XMLHTTPRequest.setRequestHeader("Content-length", $parameters.length) $XMLHTTPRequest.setRequestHeader("Referer", "http://www.msxfaq.de") $XMLHTTPRequest.setRequestHeader("Test", "http://www.msxfaq.de") Try { write-host "Download START $URL" $XMLHTTPRequest.send($null) write-host "Download DONE $URL" } Catch { write-host "Download ERROR $URL" write-host ("Error:" + $_.Exception.InnerException.Message) $error.removerange(0,1) # remove last error } write-host ("Status :" + $XMLHTTPRequest.status) write-host ("Statustext:" + $XMLHTTPRequest.statusText)
Hier die Information über das Objekt selbst:
PS C:\> $XMLHTTPRequest |gm TypeName: System.__ComObject#{ed8c108d-4349-11d2-91a4-00c04f7969e8} Name MemberType Definition ---- ---------- ---------- abort Method void abort () getAllResponseHeaders Method string getAllResponseHeaders () getResponseHeader Method string getResponseHeader (string) open Method void open (string, string, Variant, Variant... send Method void send (Variant) setRequestHeader Method void setRequestHeader (string, string) onreadystatechange Property IDispatch onreadystatechange () {set} readyState Property int readyState () {get} responseBody Property Variant responseBody () {get} responseStream Property Variant responseStream () {get} responseText Property string responseText () {get} responseXML Property IDispatch responseXML () {get} status Property int status () {get} statusText Property string statusText () {get}
- XMLHttpRequest object
http://msdn.microsoft.com/de-DE/library/ie/ms535874.aspx - IXMLHTTPRequest Members
http://msdn.microsoft.com/de-de/library/ms760305.aspx - XMLHttpRequest-Erweiterungen
http://msdn.microsoft.com/de-de/library/ie/hh673569(v=vs.85).aspx - XMLHttpRequest
http://de.wikipedia.org/wiki/XMLHttpRequest - Sending POST Data via
PowerShell
http://PowerShell.com/cs/blogs/tips/archive/2010/04/29/sending-post-data-via-PowerShell.aspx
BITS-Transfer
Bits wird schon länger von Windows angeboten und verschiedene Programme, wie z.B. Outook nutzen Bits um den Download von Dateien zu delegieren. Bits ist ein Hintergrunddienst, der angeblich auch die Bandbreite berücksichtigt und den Download drosselt, wenn andere Dienste die Bandbreite benötigen.
Der Download kann sowohl Synchron also auch mit dem Schalter "-Asynchronous" in den Hintergrund gelegt werden. Mit Start-BITSTransfer, u.a.
PS C:\> get-command *-bits* CommandType Name ModuleName ----------- ---- ---------- Cmdlet Add-BitsFile BitsTransfer Cmdlet Complete-BitsTransfer BitsTransfer Cmdlet Get-BitsTransfer BitsTransfer Cmdlet Remove-BitsTransfer BitsTransfer Cmdlet Resume-BitsTransfer BitsTransfer Cmdlet Set-BitsTransfer BitsTransfer Cmdlet Start-BitsTransfer BitsTransfer Cmdlet Suspend-BitsTransfer BitsTransfer
Entsprechend kann Auch per PowerShell die BITS-Funktion genutzt werden.
$sourceurl = "http://www.msxfaq.de" $Zieldatei = "c:\temp\msxfaq.html" Import-Module BitsTransfer # Synchrone Uebertragung Start-BitsTransfer -Source $sourceurl -Destination $Zieldatei # ASyncrhjone übertragung Start-BitsTransfer -Source $sourceurl -Destination $Zieldatei -Asynchronous
Bei der asynchronen Nutzung sehen Sie natürlich keinen Indikator, wie weit der Download schon ist und sie müssen mit Get-BitsTransfer bei Gelegenheit nachschauen, ob ihr Download schon abgeschlossen ist.
Wenn Sie die PowerShell als Administrator starten, können Sie mit "Get-BITSTransfer -AllUsers" auch sehen, was andere Prozesse (z.B. WSUS, Windows Update, Outlook etc) gerade so im Hintergrund per BITS übertragen.
Einschränkung: Error-Handing
Sowohl die seit PowerShell 3.0 verfügbaren Commandlets "Invoke-WebRequest" und "Invoke-RESTMethod" als auch der System.Net.WebClient machen es einfach per HTTP mit einem Server zu kommunizieren. Aber Sie haben eine Einschränkungen gemeinsam, die Sie können sollten: Die Fehlerbehandlung:
Solange ein Abruf fehlerfrei funktioniert, d.h. der Webserver erreichbar ist und man mit einer 2xx/3xx-Meldung quittiert wird, dann erhalten Sie auch ein Objekt mit der Antwort zurück. Warum auch immer passiert die so nicht, wenn Sie auf einen Fehler (4xx/5xx) laufen. Dann wird nämlich nicht ein Antwortobjekt zurück gegeben, welches als Statuscode dann den entsprechenden Fehlercode hat, sondern es wird eine Exception geworfen. Hier am Beispiel eines 401:
Ich hätte es anders logischer angesehen, denn auch ein 401 o.ä. stellt zwar ein Fehler innerhalb der Aktion dar, aber der "Abruf" als solches war schon erfolgreich. Auch die 401-Fehlerseite enthält Informationen, Header etc., der z.B. bei UCWA - Unified Communication Web API erforderlich ist. Laut MSDN kann eigentlich nur eine "WebException" kommen.
- WebClient.DownloadString
Method (String)
http://msdn.microsoft.com/en-us/library/fhd1f0sw.aspx - WebException Class
http://msdn.microsoft.com/en-us/library/system.net.webexception.aspx
Wir benötigen also eine "Try/Catch"-Anweisung, die solche Fehler abfängt. Ich habe mir das so aufbereitet, dass ich auf jeden Fall einen eigenen Status an das Objekt anfüge, welche ich später dann einfach abfragen kann.
Try { $result = Invoke-WebRequest -uri $test Add-member -InputObject $result -Name Status -MemberType noteproperty -Value $result.Statuscode } Catch { [System.Net.HttpWebResponse]$result = [System.Net.HttpWebResponse] $_.Exception.Response Add-member -InputObject $result -Name Status -MemberType noteproperty -Value $result.StatusCode.Value__ $error.clear() }
So landet in "$Result" entweder die erfolgreiche Antwort oder eben die partielle Fehlerantwort. Allerdings sind es zwei unterschiedliche Objekte, die zwar beide das Property "Statuscode" haben, aber einmal als Objekt und einmal als Enumeration. Daher habe ich hier den Staus als eigenes Feld addiert. Folgende Fälle habe ich mal getestet.
Hinweis: Die Variable $data wird nur belegt, wenn die Antwort ein 200/300-Code ist. Bricht die Anforderung mit einem Fehler ab, dann wird die Variable nicht verändert.
Fehlerfall | Verhalten | $webclient | Exception StatusMessage |
Exception StatusCode |
$data |
---|---|---|---|---|---|
200 OK |
Daten wurden abgerufen |
Enthält Response Header |
OK |
Kein |
HTML-Content |
302 Redirect |
Daten wurden vom Redirect-Ziel abgerufen ! |
Enthält Response Header |
OK |
kein |
HTML-Content |
401 Error |
Exception wird geworfen |
Null$ |
ProtocolError |
7 |
$null |
Keine Verbindung |
Abbruch nach 20Sek |
unverändert! |
ConnectFailure |
2 |
|
Kein DNS |
Abbruch nach 3 Sek |
unverändert! |
NameResolutionFailure |
1 |
|
HTTPS auf ungültiges Cert |
Exception wird geworfen |
$null |
TrustFailure |
9 |
$null |
Eine komplette Liste der verschiedenen Statuscodes bekommt man mit
PS C:\> [System.Enum]::GetValues([System.Net.WebExceptionStatus]) Success NameResolutionFailure ConnectFailure ReceiveFailure SendFailure PipelineFailure RequestCanceled ProtocolError ConnectionClosed TrustFailure SecureChannelFailure ServerProtocolViolation KeepAliveFailure Pending Timeout ProxyNameResolutionFailure unknownError MessageLengthLimitExceeded CacheEntryNotFound RequestProhibitedByCachePolicy RequestProhibitedByProxy
-
WebExceptionStatus-Enumeration
http://msdn.microsoft.com/de-de/library/system.net.webexceptionstatus(v=vs.110).aspx
Allerdings konnte ich den "Success" noch nicht sehen, da beim einem Erfolg ja keine Exception geworfen wird.
Aus meiner Sicht eine sehr unglückliche Definition der WebClient-Klasse. Ich hätte mir gewünscht, dass Sie immer ein Objekt zurück gibt, welcher die Antwort des Servers enthält. Eine Exception wäre sicher möglich, wenn der Server nicht erreichbar ist aber auch ein 4xx oder 5xx Fehler des Servers sollte dennoch als "erfolgreiche Antwort" angesehen werden. Es würde das weitere Programmieren schon einfacher machen.
Aber es ist so, wie es ist und ich habe noch keinen Weg gefunden den vom Webserver bei einem Fehler mitgelieferten Content zu erhalten. Zumindest ist es mir aber schon gelungen z.B. die Header der Fehlermeldung zu sammeln.
- Error Reporting Concepts
http://msdn.microsoft.com/en-us/library/ms714414(v=vs.85).aspx - An Introduction to Error
Handling in PowerShell
http://blogs.msdn.com/b/kebab/archive/2013/06/09/an-introduction-to-error-handling-in-PowerShell.aspx - Effective PowerShell Item
16: Dealing with Errors
http://rkeithhill.wordpress.com/2009/08/03/effective-PowerShell-item-16-dealing-with-errors/
Einschränkung: Restricted Header
Manchmal ist es erforderlich im HTTP-Request auch Felder im Header zu setzen. Sowohl "Invoke-Webrequest" als auch "Invoke-RESTMethod" erlauben über den Parameter "-headers" auch die Übergabe eines Dictionary mit eigenen Headern.
$data = Invoke-WebRequest ` -uri $URL ` -Method GET ` -Header @{Referer = "http://www.msxfaq.de"} `
Allerdings gibt es auch hier Beschränkungen, so dass Sie nicht alle Felder frei setzen können. Mich hat es bei Experimenten mit UCWA - Unified Communication Web API erwischt, bei denen ich das Feld "Referer" setzen wollte, was aber mit folgendem Fehler abgelehnt wurde:
Der Wert "System.ArgumentException: Der 'Referer'-Header muss mit der entsprechenden Eigenschaft oder Methode geändert werden. Parametername: name bei System.Net.WebHeaderCollection.ThrowOnRestrictedHeader(String headerName)
- WebRequestSession Class
http://msdn.microsoft.com/en-us/library/microsoft.PowerShell.commands.webrequestsession(v=vs.85).aspx
Wer also z.B. den "Referer" setzen will, kann den Parameter "Headers" beiden Commandlets getrost vergessen. Aber beide Commandlets erlauben die Angabe einer "WebSession" beim Aufruf. Sie wird vom Commandlet beim Aufruf gefüllt so dass man sie nachfolgenden Aufrufen mitgeben kann. Sie kann aber auch vorher instanziert und gefüllt werden.
PS C:\> $websession= new-object Microsoft.PowerShell.Commands.WebRequestSession PS C:\> $websession Headers : {} Cookies : System.Net.CookieContainer useDefaultCredentials : False Credentials : Certificates : UserAgent : Mozilla/5.0 (Windows NT; Windows NT 6.1; de-DE) WindowsPowerShell/3.0 Proxy : MaximumRedirection : -1
Und entsprechend können die Parameter erweitert oder geändert werden. Parameter, die Sie dann aber per Commandlet angeben, überschreiben diese Angaben wieder.
$websession= new-object Microsoft.PowerShell.Commands.WebRequestSession $websession.UserAgent = "MSXFAQ Test"" $websession.Headers.Add("Referer","http://www.msxfaq.de") $data = Invoke-WebRequest ` -uri $URL ` -Method GET ` -WebSession websession
Aber auch hier kommen Sie nicht weiter. Auch hier wird dieser Versuch einen "Referer" mit zu übergeben mit einem Fehler quittiert
Es gibt noch einige weitere Felder wie z.B.
- Accept Connection
- Content-Length
- Content-Type
- Date Expect Host
- If-Modified-Since
- Range
- Referer
- Transfer-Encoding
- User-Agent
- Proxy-Connection
Ein Teil davon kann man aber schon über Parameter der PowerShell Commandlets setzen. Andere nicht. Den vollen Zugriff auf die Felder erhalten Sie nur über die NET-API. Der WebClient liefert als Stream aber nur den Inhalte, also den Body der Abfrage. Die Header der Abfrage landen als Dictionary im Feld "ResponseHeaders". Ein Zugriff auf den Statuscode (2xx/3xx) habe ich so nicht nicht erhalten.
- C# WebClient
http://www.dotnetperls.com/webclient - Query HTTP status codes and
headers with PowerShell
http://scriptolog.blogspot.de/2007/08/query-http-status-codes-and-headers.html
Einschränkung: Response Header und 401
Um per UCMA auf Dienste von Lync zuzugreifen, muss ich aber nicht nur bestimmte Header setzen, sondern auch "Response Header" auslesen. Das ist insbesondere knifflig mit einer provozierten 401-Meldungen, die im "WWW-Authenticate"-Header eine erforderliche URL als Antwort enthält.
- WebClient
Das Feld "ResponseHeader" ist leider leer, wenn die Gegenseite mit einem 401 quittiert. Allerdings enthält die Exception die gewünschten Informationen in $_.Exception.InnerException.Response.Headers - Invoke-Webrequest
Die Antwort mit allen Daten ist leider "$null", wenn die Gegenseite mit einem 401 quittiert. Allerdings enthält die Exception die gewünschten Informationen im Feld $_.Exception.Response.Headers - System.Net.HTTPWebrequest
Liefert bei einem 401 keine Daten und damit auch kein ResponseHeader mit aus.
Es ist nur unschön, dass wir die Daten nicht aus der Antwort zum Request sondern über die Exception abfangen müssen.
- Test-Bearer
- Using Try and Catch with PowerShell’s
Invoke-WebRequest
http://wahlnetwork.com/2015/02/19/using-try-catch-powershells-invoke-webrequest/ - WebClient not sending credentials?
Here’s why!
https://kristofmattei.be/2013/02/20/webclient-not-sending-credentials-heres-why/ - Using Try and Catch with PowerShell’s
Invoke-WebRequest
https://wahlnetwork.com/2015/02/19/using-try-catch-powershells-invoke-webrequest/
Einschränkung Session Reusing
Bei der Nutzung von PowerShell für End2End-HTTP ist mir aufgefallen, dass die Antwortzeiten sehr schnell waren. Ich habe dann mit Wireshark nachgeschaut und gesehen, dass eine TCP-Verbindung nur beim ersten mal aufgebaut wird.
Das ist beim Einsatz von HTTPS noch deutlicher zu merken, da der TLS-Handshake bei den Folgeverbindungen ebenfalls unterbleibt. Die PowerShell instanziert da wohl im Hintergrund ein Objekt und lässt es noch weiter laufen für folgende Requests an die gleichen Ziele. Erst nach ca. 80-100 Sekunden später baut das Objekt die Verbindung eigenständig ab. Die Verbindung wird sofort beendet, wenn Sie die PowerShell selbst beenden.
- HTTP persistent connection
https://en.wikipedia.org/wiki/HTTP_persistent_connection
Also habe ich die verschiedenen Optionen verglichen:
Die Ergebnisse stimmen nur in dem Maße, wie der Server auch das Keep-Alive unterstützt und die Verbindung nichts selbst beendet. Das ist bei 4xx-Fehlerseiten oder bei einem Redirect sehr oft der Fall. Hier geht der Web-Server oft davon aus dass der Client nicht wiederkommt.
Methode | Wiederverwendung der Connection |
---|---|
Invoke-Webrequest |
Ja, kann aber mit dem Parameter "-DisableKeepAlive" verhindert werden. |
Invoke-RestMethod |
Ja, kann aber mit dem Parameter "-DisableKeepAlive" verhindert werden. |
System.Net.WebClient |
Ja. Auch ein Aufruf der Dispose-Methode gibt die TCP-Connection nicht frei. |
System.Net.HttpWebRequest |
Ja, Selbst wenn ich die Variable neu instanziere bleibt die TCP-Connection erhalten |
System.Net.Http.HttpClient |
Nein, wenn ich jedes mal das Objekt neu instanzieren |
Wenn ich also wirklich mit jedem Aufruf eine neue Verbindung nutzen möchte um z.B.: die Ports von Proxy-Server zu verbrauchen, dann muss ich das mit der "System.Net.Http.HttpClient "-Klasse machen. Wer also möglich viele IP-Ports konsumieren möchte kann einen kleinen DoS gegen den NAT-Router fahren:
Add-Type -AssemblyName System.Net.Http; write-host " Start TCP Connections $((get-nettcpconnection).count)" 1..1000|% { write-progress "Loop $($_) of 50000" $winhttpclient = new-object System.Net.Http.HttpClient; $winhttpclient.DefaultRequestHeaders.add("User-Agent", "Mozilla/5.0"); $null = $winhttpclient.GetStringAsync("https://outlook.office365.com/favicon.ico"); } write-host " End TCP Connections $((get-nettcpconnection).count)"
In Wireshark sieht man schön, dass wirklich viele Connections genutzt werden:
Und auch auf dem Client sehe ich den "Portverbrauch":
Doe 65535 Ports kann ich so aber kaum erreichen, da ein Idle-Verbindung nach 2 Min wieder abgebaut wird und so ein PowerShell-Script ist nicht schnell genug. Natürlich sollte man die Rückgaben auch "prüfen", ob denn noch was zurück kommt. Auf dem WebServer gibt es hier abgesehen von der Last kein Problem. Es könnte aber ihren Proxy oder NAT-Router eher überlasten als dass ihrem lokalen PC die Ports ausgehen. Allerdings müssen Sie bei den meisten WebServern noch einen User-Agent mitliefern, damit sie keinen "400 bad Request" sendet.
Bsp: Einfacher anonymer Download einer URL
Der einfachste Schritt ist der Download einer anonym erreichbaren Quelle per HTTP. Es muss sich dabei nicht zwingend um eine "HTML-Datei handelt.
$webclient = new-object System.Net.WebClient $webpage = $webclient.DownloadString("http://www.msxfaq.de")
Dieser Zweizeiler lädt die angegebene URL herunter und speichert Sie in der Variable "$webpage". Interessant ist dieser Prozess, wenn Sie z.B. eine XML-Datei per HTTP herunter laden und die Variable gleich mit einem "XML" versehen.
#Variante 1 [xml]$configxml = $webclient.DownloadString("http://autodiscover.netatwork.de/autodiscover.xml") #Variante 2 $configxml = [xml]($webclient.DownloadString("http://autodiscover.netatwork.de/autodiscover.xml")
Dann können Sie direkt auf die XML-Datei zugreifen.
Bsp: Authentifizierung
Die nächste Steigerung ist natürlich die Authentifizierung, da die meisten interessanten Dienste nicht immer anonym erreichbar sind. Die entsprechenden Anmeldedaten muss man natürlich entweder mitgeben oder Windows nutzt die integrierte Authentifizierung (NTLM/Kerberos)
$webclient = new-object System.Net.WebClient $webclient.Credentials = New-Object System.Net.NetworkCredential("frank","meinkennwort") $webpage = $webclient.DownloadString("http://sicher.msxfaq.net")
So wird die Anfrage dann authentifiziert versendet, zumindest wenn es sich um eine HTTP-Authentifizierung handelt. Aber auch mit dem neuen PowerShell 3 Befehl "Invoke-Webrequest" kann die Anmeldung über den Parameter "Credential" gesetzt werden. Wenn Sie die Anmeldedaten aber nicht mit "Get-Credential" abfragen wollen, müssen Sie einen kleinen Umweg gehen:
[string] $strUser = 'domain\Username' [System.Security.SecureString]$strPass = ConvertTo-SecureString -String "password" -AsPlainText -Force $Cred = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList ($strUser, $strPass) Invoke-Webrequest -uri $URL -credential $cred
Der Vorteil gegenüber WebPage ist, dass das Ergebnis kein "String" sondern ein "HtmlWebResponseObject (Microsoft.PowerShell.Commands.WebResponseObject )" ist, die einfacher als Objekt angesprochen werden kann.
Die Anmeldung über die Commandlets funktionieren nach meinem Wissen aber immer erst, wenn die Gegenseite auch eine Authentifizierung anfordert. Die PowerShell scheint dazu nämlich zuerst einmal einen anonymen Request zu senden, um sich dann einen 401 einzufangen. In den 401 steht aber dann auch drin, welche Authentifizierungsverfahren die Gegenseite unterstützt. Hier einmal ein IIS/Exchange, der auf eine anonyme Anfrage einen 401 mit "WWW-Authenticate:" liefert, während BMW dies nicht tut.
Hier müssen Sie ggfls. "blind" die Anmeldedaten senden, wenn die PowerShell Module das richtige Authentifizierungsverfahren nicht ermitteln können. Die meisten WebServer liefern im 401 die möglichen Anmeldeverfahren mit aber gerade in der IoT-Welt oder mit Kleinst-Prozessoren, z.B. ESP8266, ist dies nicht immer möglich. Dann muss man sich selbst um den "Authentication Header" kümmern:
# Rest-Aufruf mit vorgegebener Basic Auth $User = "Username" $pass = "kennwort" $basicauth=[System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($User+":"+$pass)) $headers=@{} $headers.Add("Authorization","Basic $basicauth") $result= Invoke-RestMethod ` -Method GET ` -Headers $headers ` -Uri https://test.msxfaqnet ` -Headers $headers"
Webseiten mit formularbasierter Anmeldung müssen natürlich anders abgewickelt werden. Das ist dann ein mehrstufiger Prozess, bei dem Sie Formulardaten senden und z.B. den Cookie oder das Webticket in der Antwort bei folgenden Requests mit senden. Aber auch das geht.
$body = @{ Username = 'Username' password = 'kennwort' } $authrequest = invoke-webrequest ` -method POST ` -usebasicparsing ` -URI "https://test.msfaq.de/logon.aspx" ` -Body $body
Hier muss man eben die Anmeldedaten als Formularfeld "blind" mitgeben. Das ist quasi bei allen "Form Based"-Anmeldungen der Fall.
- PowerShell v3.0:
Invoke-WebRequest
http://psscripts.blogspot.de/2012/09/PowerShell-v30-invoke-webrequest.html - .NET
WebRequest.PreAuthenticate – not
quite what it sounds like
http://weblog.west-wind.com/posts/2010/Feb/18/NET-WebRequestPreAuthenticate-not-quite-what-it-sounds-like - Use PowerShell to call HTTP
Endpoint using Basic Auth when
you already have a Basic Auth
token in hand (not Username +
password)
https://gist.GitHub.com/codingoutloud/7033235 - Basic Auth
http://en.wikipedia.org/wiki/Basic_access_authentication - WebClient not sending
credentials? Here’s why!
https://kristofmattei.be/2013/02/20/webclient-not-sending-credentials-heres-why/
Bsp: Dateidownload
Wenn es nicht darum geht eine HTML-Datei als String für weitere Verarbeitungen zu nutzen, sondern eine auch größere Datei herunter zu laden, dann geht das auch über den System.Net.Webclient. Allerdings nutzen Sie dann besser die "DownloadFile"-Methode
$webclient = new-object System.Net.WebClient $webclient.DownloadFile($sourceURL,$targetfile)
Die herunter geladene Datei landet als Datei mit dem angegeben Namen (hier $targetfile). Die "DownloadFile"-Methode liefert keinen Ergebniscode zurück. Ein Fehler wird als "Error" gemeldet,
Entsprechend sollten Sie so einen Download in einem "Try/Catch"-Block einschließen und den Fehler abhandeln.
Bsp: HTTPProxy nutzen
In den meisten Firmen dürfen Clients nicht direkt das Internet erreichen, sondern müssen einen Proxy verwenden. Auch dies ist mit dem System.Net.WebClient möglich.
Die meisten PowerShell Module bedienen sich den WinHTTP-Einstellungen
Die "Proxy"-Eigenschaft ist der passenden Platz.
- WebClient.Proxy-Eigenschaft
http://msdn.microsoft.com/de-de/library/system.net.webclient.proxy(v=vs.110).aspx
Entsprechend muss der WebClient erweitert werden:
$WebClient = New-Object System.Net.WebClient $WebProxy = New-Object System.Net.WebProxy("http://proxy:8080",$true) $Credentials = New-Object Net.NetworkCredential("Username,"Password","domain") $WebProxy.Credentials = $Credentials $WebClient.Proxy = $WebProxy $result = $WebClient.DownloadString("http://www.msxfaq.net")
Sie können aber auch in der Shell einfach einen Proxy hinterlegen. Das kann erforderlich sein, wenn Sie in einer PowerShell-Shell weitere Commandlets nutzen.
[system.net.webrequest]::defaultwebproxy = new-object system.net.webproxy('http://proxyserver:port') [system.net.webrequest]::defaultwebproxy.credentials = [System.Net.CredentialCache]::DefaultNetworkCredentials [system.net.webrequest]::defaultwebproxy.BypassProxyOnLocal = $true
Alternativ geht es auch in einer Admin CMD-Shell zum setzen des Systemproxy anhand der Browser/WinHTTP-Einstellungen
netsh winhttp import proxy source=ie
Achtung: Der Proxy muss die Anfragen anonym oder mittels integrierter Anmeldung (NTLM/Kerberos) erlauben. Die PowerShell startet keinen "Anmeldedialog", wenn der Proxy mit einem "407 Proxy Authentication Required" antwortet.
Damit lassen sich dann auch Proxy-Server nutzen.
- WinHTTP
- PowerShell Proxyerkennung
- Proxy 407 Handling
- HTTP Proxy Authentication
- Working behind a proxy
http://www.zerrouki.com/working-behind-a-proxy/ - Setting Up Powershell gallery And Nuget
gallery
https://copdips.com/2018/05/setting-up-powershell-gallery-and-nuget-gallery-for-powershell.html - Installing PowerShell modules behind
corporate proxy
https://daveshap.github.io/DavidShapiroBlog/powershell/kb/2021/03/12/install-powershell-modules.html
Bsp: GET und POST
Das HTTP-Protokoll kennt verschiedene Methoden des Zugriffs. Die beiden wichtigsten sind GET und POST. Die meisten Anfragen an Webseiten sind einfache "GET-Befehle, bei denen der Pfad und ggfls. Parameter mit angegeben werden. Dieses Verfahren ist sehr einfach zu implementieren, aber eignet sich natürlich nicht für das Übertragen größerer Datenmengen vom Client zum Server. In den Anfangszeiten des Internet konnten im Browser z.B. Formulare ausgefüllt werden, die dann per POST hochgeladen werden. Mit system.net.webclient ist dann die Methode uploadData z.B. nutzbar
- WebClient.UploadData-Methode
(String, String, bByte[])
http://msdn.microsoft.com/de-de/library/ktfa4fek(v=vs.110).aspx
Der zweite Parameter kann auf "POST" gesetzt, werden.
Net.Webclient mit UserAgent und Serverantwort
Bei der Entwicklung zu End2End-HTTP habe ich versucht sehr oft und schnell immer wieder die gleiche HTTP_URL zu laden und beim Test gegen https://outlook.office365.com/favicon.ico ist mir ein unterschiedliches Verhalten des Servers je nach Client aufgefallen. per Fiddler habe ich die HTTPS-Anfragen analysiert:
$url = "https://outlook.office365.com/favicon.ico $webclient = New-Object Net.WebClient $webclient.Downloadstring($url) Invoke-WebRequest $url
Der Mitschnitt ist aber interessant und überraschend.
- Beide Methoden unterstützen Proxy
Auch ohne explizite Konfiguration nutzt die PowerShell den "System Proxy" - Beide Methoden verstehen einen 302
redirect
Die abgefragte URL wird vom Server auf /owa/favicon.ico umgeleitet. Beide Aufrufen folgen alleine der Umleitung und fragen eigenständig in einem zweiten Request die neue URL ab.. Mein Skript sieht den 302 gar nicht - Connection Pooling
Obwohl es unterschiedliche Commandlets sind, wird nur einmal ein SSL-Handshake aufgebaut. Das könnte nun aber auch in der Nutzung eines Proxy und Fiddler begründet sein.
Aber obwohl sich bis dahin beide Abrufe gleich verhalten, bekommt nur der "Invoke-Webrequest" auch das Bild. Der einfache "net.webclient" wird vom Office 365 Service nicht bedient. Also habe ich mir den Request genauer angeschaut
Der einzig sichtbare Unterscheid ist hier der User-Agent. Sollte Office 36 einen Abruf ohne diesen Header ablehnen?. Das kann ich ja schnell ändern
$url = "https://outlook.office365.com/favicon.ico $webclient = New-Object Net.WebClient; $webclient.Headers.Add("User-Agent", "Mozilla/5.0 (Windows NT; Windows NT 10.0; de-DE) "); $webclient.Downloadstring($url);
Und schon gelingt auch der Abruf. Achten Sie also darauf, dass Sie beim Einsatz von Net.WebClient auch einen UserAgent setzen, da Webserver sich dann unterschiedlich verhalten.
Weitere Links
- PowerShell als HTTPServer
- PowerShell
- PS ErrHandling
- Test-Bearer - Schnell prüfen, ob die Gegenseite BEARER anbietet
- Script to Backup AudioCodes
Gateways
http://blog.lyncdialog.com/2013/08/script-to-backup-audiocodes-gateways.html - An Introduction to Error
Handling in PowerShell
http://blogs.msdn.com/b/kebab/archive/2013/06/09/an-introduction-to-error-handling-in-PowerShell.aspx - 3 ways to download files
with PowerShell
https://blog.jourdant.me/post/3-ways-to-download-files-with-powershell - Html Agility
Pack HTML
SelectSingleNode
https://html-agility-pack.net/select-single-node - Webscraping mit
PowerShell - Eine
Einführung
https://www.scip.ch/?labs.20210211 - XPath Tutorial
https://www.w3schools.com/xml/xpath_intro.asp - FakeIE
https://www.heise.de/ct/projekte/machmit/webautomatisieren/wiki/FakeIE