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 mit PowerShell 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
  • Ex1
    Die Methode wirft eine "Exception", die nicht immer einen HTTP-Error-Code enthält.
  • Ex2

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 ienen 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.

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 "bodtest"

Das macht es natürlich schon sehr viel einfacher mit einem Webserver zu interagieren.

Die Antwort im Erfolgsfalle ist ein HtmlWebResponseObject.

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".
Dummerweise werden dann wohl 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.

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 "-UserBasicParsing" werden die Felder "Forms", "InputFields" und "ParsedHTML" des Ergebnisses nicht gefüllt. "Content" und "RAWContent" 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.

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

UserBasicParsing 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

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.

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(); 

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

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
$winhttpclient = new-object System.Net.Http.HttpClient
$winhttpclient.DefaultRequestHeaders.add("Referer", "http://www.msxfaq.de");
write-host "Start Request"
$winhttptask = $winhttpclient.GetStringAsync($URL)

Try {
	write-host "Wait für completion"
	$winhttptask.wait() # wait für end of http-tasks
	add-member -InputObject $winhttptask -Name LastStatus -MemberType noteproperty -Value "OK" -Force
} 
Catch {
	write-host "Download ERROR $URL"
	write-host ("Error:" + $winhttptask.Exception.InnerException.message)
	add-member -InputObject $winhttptask -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 " +$winhttptask.result.substring(1,40))
}
else {
	write-host ("Found error:" +$winhttptask.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.

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}

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 dosselt, 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.

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
403 Forbidden

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ülties 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

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.

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)

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.

Einschränkung: Response Header

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.He aders
  • 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.

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.

$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.

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.

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: Proxy 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 "Proxy"-Eigenschaft ist der passenden Platz.

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")

Damit lassen sich dann auch Proxy-Server nutzen.

Beispiel: 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

Der zweite Parameter kann auf "POST" gesetzt, werden.

Weitere Links