Archiv der Kategorie: PowerShell-Praxis

PowerShell-Praxis: Umgang mit mehrdimensionalen Arrays – ein „Schiffe versenken“-Algorithmus

Der Umgang mit Arrays besitzt bei der PowerShell seine Eigenheiten. Auf der seinen Seite genial einfach und komfortabel, doch sobald es um mehrdimensionale Arrays geht lässt der Komfort etwas nach und es wird speziell.

Eines gleich vorweg: Wer bislang eine Programmiersprache oder eine traditionelle Skriptsprache wie VBScript (WSH) verwendet hat und es einfach gewohnt ist mit zweidimensionalen Arrays zu arbeiten, z.B. um tabellarische Daten abbilden zu können: In 95% alle Fälle ist ein simples Array, das per [PSCustonmObject] angelegte Werte aufnimmt, die beliebige Werte aufnehmen, die deutlich einfachere und vor allem Powershell-typische Variante.

Da man aber niemand zwingend sollte, eine vertraut gewordene Gewohnheit aufgeben zu müssen, lassen sich zwei- oder mehrdimensionale Arrays auch bei PowerShell anlegen. Zwei Dinge muss man wissen: Wie ein mehrdimensionales Array angelegt und wie ein mehrdimensionales Array von einer Function zurückgegeben wird.

Mehrdimensionale Arrays anlegen


Ein mehrdimensionales Array wird per New-Object-Cmdlet angelegt. Da das Cmdlet offenbar mit der [,]-Schreibweise nicht klar kommt, muss die Typbezeichnung in Apostrophen gesetzt werden – dadurch fallen aber die äußeren eckigen Klammern weg.

Der folgende Befehl legt ein zweidimensionales Array mit 10 Feldern in der ersten und 2 Feldern in der zweiten Dimension an.

Der folgende Befehl legt ein dreidimensionales Array mit 2 Feldern in jeder Dimension an.

Die Rank-Eigenschaft gibt die Anzahl der Dimensionen an. Per GetLength-Methode erhält man die Größe einer einzelnen Dimension.

Ein mehrdimensionale Array in einer Function zurückgeben


Soll eine Function ein mehrdimensionale Array zurückgeben, muss dem Ausdruck bzw. der Variablen einfach ein Komma vorangestellt werden.

Ein Algorithmus für das Belegen eines Schiffe versenken-Spielfeldes

Ein genialer Algorithmus, der ein zweidimensionales Feld für ein Schiffe versenken-Spiel mit Schiffen belegt und dabei darauf achtet, dass sich keine „Verbände“ überlappen bzw. die Spielfeldgrenzen berücksichtigt werden.

Eine Erklärung folgt in Kürze – bei Fragen einfach fragen;)

Umgang mit generischen Listen (Teil 2)

Im ersten Teil „Umgang mit generischen Listen“ ging es um ein erstes Kennenlernen der generischen Liste. Beantwortet wurde auch die Frage warum man sie überhaupt braucht. Die Antwort war: Für das administrative Skripten bringen sie keine Vorteile und sollten daher auch nicht verwendet werden. Sie sind immer dann praktisch bzw. notwendig, wenn Programmcode aus einem C#-Programm in ein PowerShell-Skript umgesetzt werden soll, oder wenn eine Methode einer .NET-Assembly eine generische Liste als Parameterwert erwartet.

In diesem Teil geht es um die Typenbezeichnung, die die PowerShell bei einer generischen Liste verwendet.

Ausgangspunkt für das Beispiel ist ein von mir definierter Typ mit dem Namen WTToken.

Ob dieser Typ per class-Befehl definiert wird, aus einem C#-Programm stammt oder es ein ganz anderer Typ ist, etwa PSCustomObject, spielt keine Rolle.

Mit dem Typ wird als nächstes eine generische Liste per new()-Methode angelegt.

Damit gibt es eine Liste $Tokenlist, die nur Objekte vom Typ WTToken aufnehmen kann.

Übergebe ich eine andere Sorte von Wert, etwa eine Zahl, kommt es wie zu erwarten zu einer Fehlermeldung. Die Meldung selber ist aber etwas irritierend. Anstatt „Wrong type error“ lautet die Fehlermeldung: Für „Add“ und die folgende Argumenteanzahl kann keine Überladung gefunden werden: „1“.. ??? WTF;)

Die Fehlermeldung will uns Folgendes sagen: Es kann für den Typ, den der Wert (1 Argument) besitzt, der übergeben werden soll, keine Methodenvariante gefunden werden, die diesen Typ akzeptiert. Eigentlich ganz einfach.

Bis jetzt ist hoffentlich noch alles nachvollziehbar.

Im Folgenden wird es kurzzeitig etwas spezieller. Beim Herumexperimentieren mit generischen Listen erhielt ich die obige Fehlermeldung auch dann, wenn der Wert vom Typ WTToken war und damit passen sollte.

Nach ein wenig Herumprobieren kam ich eher per Zufall auf die Lösung. Die PowerShell fügt in die Typenbezeichnung auch den Pfad der Ps1-Datei ein, in der der Typ definiert wird. Vermutlich aus der Überlegung heraus, dass die Typenbezeichnung damit eindeutig wird, da es keine zwei identischen Pfade geben kann.

Legt man jetzt in der ISE ein neues Fenster an und kopiert den Skriptcode, der eben noch funktioniert hatte, in das neue Fenster, kommt es zu obigen Fehler, da die generische Liste einen Typ erwartet, in dem der Pfad des alten Skripts noch enthalten ist. Wird das Objekt in dem neuen Fenster angelegt, erhält sein Typnname nicht den alten Ps1-Pfad und es entsteht ein neuer Typ, der nicht mehr in die generische Liste eingefügt werden kann.

Ein „Problem“ ist dieses Verhalten in der Praxis natürlich nicht. Der Fehler kann nur beim Herumprobieren in der ISE auftreten, da hier Variablen globale Variablen sind und mit ihrem aktuellen Wert und vor allem Typ in jedem neuen Fenster automatisch verwendet werden.

Im Folgenden möchte ich noch einmal zeigen, wie sich das von mir beschriebene Verhalten nachvollziehen lässt.

Schritt 1:

Starte die ISE, gib den folgenden PowerShell-Code ein und speichere das Ganze in einer Datei, z.B. „Test.ps1“.

Führe das Ganze aus. Die Liste in der Variablen Tokenlist besitzt den Typ „System.Collections.Generic.List`1[[WTToken, ⧹E։⧹2017⧹Projekte⧹BoolscherService⧹test.ps1, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null]]“ (der Ps1-Pfad lautet natürlich immer anders).

Schritt 2:

Lege in der ISE ein neues Fenster an und füge den folgenden Code ein

Führe das Ganze aus. Es kommt zu der besagten Fehlermeldung, da WToken jetzt eine Typenbezeichnung erhält, in der anstelle des Ps1-Pfades nur „\powershell“ enthalten ist. Auch wenn es nur eine Kleinigkeit ist, ist es damit ein anderer Typ.

PowerShell-Tipp: switch-Überprüfung mit continue abbrechen

Der switch-Befehl der PowerShell ist leistungsfähig, besitzt aber seine kleinere „Besonderheiten“. Eine davon ist, dass eine Überprüfung von mehreren Werten nicht per break, sondern per continue-Befehl abgebrochen wird. Während break die switch-Abfrage komplett abbricht, bricht continue nur den aktuellen Durchlauf ab. Zwingend erforderlich ist aber keiner der beiden Befehle. Es hängt von der Abfragelogik ab, ob nach der ersten Übereinstimmung nach weiteren Übereinstimmungen gesucht werden soll oder nicht.

Hier ein kleines Beispiel.

Ohne continue würden jeweils drei Meldungen ausgegeben werden. In der Hilfe ist das alles unter about_switch schön beschrieben;)

Eine kurze Einführung in die PowerShell für erfahrene Skripter

Wer sein Leben lang eine klassische Skriptsprache (VBScript, Perl usw.) verwendet hat und nun mit der PowerShell arbeiten möchte, hat man Anfang oft unnötige Probleme und Startschwierigkeiten. Da bei der PowerShell vertraute Formalismen wie eine explizite Variablendeklaration oder ein expliziter AUsgabebfehl keine ROlle spielen bzw. optional sind, wirkt die PowerShell-Syntax für „Umsteiger“ ungewohnt, unlogisch und man tendiert schnell zu alles nur noch negativ zu sehen.

Ich muss zugegeben, dass ich am Anfang auch meine kleineren Problemee hatte, aber nach ein paar Jahren kam ich dann ganz gut mit der PowerShell-Syntax klar;)

Angeregt wurde ich für diesen Blog-Eintrag durch einen längeren Thread in einem Forum auf Administrator.de, in der der Fragesteller (offensichtlich ein älterer Mensch mit sehr viel VBScript-Erfahrung) mit der Art und Weise wie in PowerShell Arrays verwendet nicht klar kam und in jeder „geht leider immer noch nicht“-Antwort auf am Anfang sehr hilfsbereite Antworten immer mehr der Frust über die vermeintlich „grausame“ PowerShell-Syntax anklang. Am Ende schlug die Hilfsbereitschaft natürlich in Kritik um und er musste sich ein „Du wetterst hier seit Tagen über
PowerShell – irgenwie alles sch… aber allem Anschein nach fehlen dir die elementarsten Grundlagen“. Der Fragesteller gab dann auch später zu, dass er noch sehr viel lesen, lesen und lesen müsste, aber das ist für mich weder die Lösung noch die Ursache der offenkunding vorhandenen Unzufriedenheit.

Das grundsätzliche Problem ist eine überzogene Erwartungshaltung („mit PowerShell soll ja alles einfacher werden“ – wird meines Wissens nirgendwo behauptet) und die (falsche) Annahme, dass Techniken, die z.B. bei VBScript seit 20 Jahren angewendet wurden, sich 1:1 auf PowerShell übertragen lassen. Und wenn dann noch eine gewisse Ungeduld hinzu kommt, ist die Unzufriedenheit groß. Und wenn dann der Unzufriendende eine Multiplikator-Funktion besitzt und sich in der Firma oder in Foren über die vermmeintlichen Schwächen der PowerShell auslässt….

Die PowerShell ist nicht perfekt, aber sie ist nicht nur enorm leistungsfähig (das hilft Anfängern im Allgemeinen wenig), sondern weitestgehend konsistent. Das Problem, sofern man es als solches betrachtet ist, dass die Syntax keine Rücksicht auf VBScript&Co genommen wurde, Bruce Payette (der Kopf der PowerShell-Spprache) vielleicht nicht ein ganz so großes Genie ist wie Guido van Rossum (Python) oder Larry Wall (Perl) und die Version 1.0 unter einem enormen Zeitdruck veröffentlicht wurde. Der Vater der PowerShell, Jeffrey Snover, hat es auf der PowerShell Konferenz 2017 in Hannover so umschrieben, dass der Vorgesetzte bei Microsoft das ganze Projekt am liebsten wieder eingestampt hätte und dem Team irgendwann ein Ultimatum gestellt hatte – entwweder ihr bringt jetzt eine Version 1.0 oder das Projekt ist tot. Keine ganz optimale Voraaussetzung und eventuell eine Erklärung für einige der Inkosistenzen.

Hier sind meine persönlichen Regeln/Empfehlungen für einen Einstieg, der sich gerade an Anwender richtet, die sehr viel Erfahrung mit VBScript und anderen formalen Skriptsprachen besitzen.

Regel 1: Variablen müssen nicht deklariert werden

Regel 2: Es gibt keine Notwendigkeit Write-Host zu benutzen

Regel 3: Der Umgang mit Arrays ist einfach, man darf nur nicht wie ein Programmierer denken

Regel 4: Der Umgang mit mehrdimensionalen Arrays ist inkosistent und ein Schwachpunkt der PowerShell-Syntax

Regel 5: Zweidimensionale Arrays werden im Allgemeinen nicht benötigt

Regel 6: Es gibt ein Pendant zu Option Explizit

Regel 7: Halte es einfach

Regel 8: Verwende die Eingabehilfen und vor allem PSREadline

Regel 9: Freunde Dich mit Subexpressions an – am besten sofort

Regel 10: Aktualisiere die Hilfe und schau Dir die Beispiele zu Cmdlets und PowerShell-Befehlen an

Regel 11: Schreibe immer zuerst einen Test

Ok, diese Regel ist nicht ganz ernst gemeint.

Ich werde zu allen Regeln in naher Zukunft noch einiges schhreiben. Ich wollte die Regel erst einmal los werden, da sie frustrierten PowerShell-Anfängern eventuell helfen können. Und noch etwas: Keine Panik und alles wird gut;)

Umgang mit generischen Listen (Teil 1)

Eine generische Collection ist der Fachbegriff für ein Array bzw. allgemein eine Liste, die nur eine bestimmte „Sorte“ von Werten aufnehmen kann. Zum Beispiel Zahlen (Int32), Zeichenketten (String) oder bestimmte Objekttypen. Der Vorteil ist, dass eine generische Liste immer nur eine Sorte von Werten besitzt. Einen zwigenden Grund gibt es für diesen Speziallfall im PowerShell-Alltag nicht. Dennoch ist es gut, dass man auch in PowerShell generische Listen anlegen kann, da es einige Methoden gibt, die diese Sorte von Parameterwert erwarten. Außerdem wäre es schade, wenn das Umsetzen von C#-Code auf PowerShell-Skript an einem solchen Detail scheitern würde.

Das Anlegen einer generischen Liste ist grundsätzlich einfach.

WTToken ist lediglich irgendein Typ, der z.B. als Klasse definiert wird.

Das Hinzufügen eines Elements geschieht per Add-Methode:

Das war einfach, jetzt wird es etwas anspruchsvoller und leider unnötig kompliziert. Der letzte Befehl fügt ein WTToken-Objekt zu einer Liste hinzu. Über den Konstruktor werden die Properties Typ und Name mit Werten belegt. Für die Property Wert gibt es keinen Konstruktorparameter, da diese Property nicht immer belegt werden soll. Möchte man der Property einen Wert zuweisen, müsste man formal zuerst eine Variable anlegen:

Das ist aber etwas umständlich. In C# gibt es dafür Objektinitialisierer mit einer einfachen Syntax:

Bei der PowerShell 6.0 gibt es diese Möglichkeit noch nicht („noch“ weil es ja nur eine Frage der Zeit ist, bis ein Vorschlag im Projektportal eingereicht bzw. eventuell sogar fertig als Pull Request eingereicht wird).

Eine eventuell naheliegende Schreibweise funktioniert leider nicht.

Der Grund ist, dass die Zuweisung zwar grundsätzlich funktioniert, dabei aber kein WTToken-Objekt resultiert, das der Add-Methode übergeben werden könnte. Auch ein weiteres in runde Klammern setzen bringt nichts, da in diesem Fall lediglich der zugewiesene Wert zurückgegeben wird. In diesem Punkt gibt es also noch Verbesserungsbedarf was das Thema Objektinitialisierer beim Anlegen eines Objekts über die statische New-Methode angeht.

Es gibt einen „Workaround“, doch leider ist dieser arg umständlich und damit keine echte Verbesserung. Dieser besteht darin, einen Scriptblock zu übergeben, in dem das Objekt angelegt wird. Da aber auch hier eine Variable benötigt wird, damit am Ende das Objekt zurückgegeben werden kann, ist das Ganze nicht besonders elegant. Im Folgenden geht es daher nur um das Prinzip bzw. ein Anschauungsbeispiel.

Wer bis hierhin mitgelesen hat (dafür erst einmal einen herzlichen Glückwunsch angesichts der etwas trockenen Thematik, es lief wohl gerade nichts Passendes im Fernsehen, auf Netflix oder irgendwo anders;), natürlich gibt es eine einfache Lösung: Das New-Object-Cmdlet und seinen Propert-Parameter, mit dessen Hilfe sich ein Objekt gleichzeitig anlegen und was einzelne Eigenschaften betrifft initialisieren lässt.

Vielleicht nicht ganz so elegant wie die moderne Schreibweise per New, aber auf alle Fälle nachvollziehbar und trotzdem alles in allem relativ einfach in der Umsetzung.

Tipp: Mehrdimensionale Arrays als Parameter übergeben

Grundsätzlich ist der Umgang mit mehrdimensionalen Arrays bei der PowerShell kein Problem, man muss sich allerdings an die etwas spezielle Schreibweise beim Anlegen eines mehrdimensionalen Arrays gewöhnen. Da hätten sich die Väter der PowerShell etwas überlegen können.

Der folgende Befehl legt ein leeres zweidimensionales Array vom Typ 2×10 an. Die erste Dimension ist damit 2, die zweite Dimension 10.

Wird ein mehrdimensionales Array einer Function als Parameter übergeben, sieht die Datentyp-Deklaration fast genauso aus.

Http.sys Url Reservierungen per PowerShell und ein wenig „String-Vodoo“ löschen

Eine URL-Reservierung ist eine vorgegebene URL-Schreibweise, die z.B. für den Aufruf eines Webservice verwendet wird. URL-Servierungen spielen z.B. beim Aufruf eines WCF-Dienstes per HTTP/HTTPS eine Rolle. Möchte ich einen WCF-Dienst nicht über Port 80, sondern z.B. über Port 88 aufrufen, muss zuvor eine URL-Reservierung angelegt werden. Soll der Dienst per HTTPS aufrufbar sein, muss eine SSL-Bindung angelegt werden. Beides wird offiziell per Netsh durchgeführt.

Zu den wenigen Bereichen, für die es aktuell (Stand: Oktober 2017) nur wenige PowerShell-Commands gibt, gehören die Kommandos der Netsh-Shell. Ein Auflisten aller URL-Reservierungen geschieht per

Die Textausgabe ist relativ umfangreich und vor allem nicht sehr regelmäßig strukturiert. Grundsätzlich sollte man auch ohne einen Doktor in „Regex-Kunde“ zu haben, das Zerlegen des Ausgabetextes per Select-String, dem Match-Operator oder per [Regex] hinbekommen, da es lediglich darum geht, eine bestimmte Zeile herauszufischen.

Ein weniger einfacher und vor allem ganz ohne Regex-Zauber geht es unter Umständen mit dem mit der Version 5.0 eingeführten ConvertFrom-String-Cmdlet, wenngleich die folgende kleine Lösung auch nicht perfekt ist. Die Idee ist, dass der zu konvertierende Text anhand eines Musters in seine Bestandteile zerlegt wird. Das Muster besteht aus der ersten Zeilen des zu konvertierenden Textes, in dem die zu extrahierenden Bereiche einfach in geschweifte Klammern gesetzt werden. In dem in den geschweiften Klammern jeweils ein Name angegeben wird, kann der extrahierte Text später über diesen Namen angesprochen werden. Bei sich wiederholenden Bereichen folgt auf den Namen noch ein *.

Im Idealfall liefert ConvertFrom-String pro Übereinstimmung ein Objekt, bei dem der über eine Markierung markierte Text über die Eigenschaft geliert wird, die dem Namen der Markierung entspricht.

Das folgende Beispiel zerlegt den Output von netsh urlacl nach den Http-Adressen und löscht jede Reservierung. Vorsicht: Die Reservierungen werden tatsächlich gelöscht. Die Nutzung erfolgt daher auf eigenes Risiko.

Das kleine Beispiel versteht sich in erster Linie als Anschauungsbeispiel für das enorm leistungsfähige, leider aber auch immer noch recht kryptische wirkende ConvertFrom-String-Cmdlet.

Wer nur eine komfortable Möglichkeit sucht, URL-Reservierungen und SSL-Bindungen zu löschen ohne die Befehlszeile bemühen zu müssen, sollte den praktischen Http.sys-Manager von Nicolas Dorier verwenden:

https://www.codeproject.com/Articles/437733/Demystify-http-sys-with-HttpSysManager
bzw.

http://httpsysmanager.codeplex.com/

Das kleine WPF-Tool verwendet .NET-Klassen aus dem Namespace HttpSysManager.Core und HttpSysManager.Native. Theoretisch ließen sich die Klassen direkt in einem PowerShell-Skript verwenden. Wer also viel Zeit hat, kann sich in dieser Nische einen Namen machen, den ein PowerShell-Command scheint es noch nicht zu geben.