Archiv der Kategorie: Tipps

Grafische Oberflächen für PowerShell-Skripte – ein WPF-Template (Teil 1)

Ein PowerShell-Skript mit einer kleinen Benutzeroberfläche auszustatten ist grundsätzlich nicht schwer – die größte Herausforderung für einen Admin, der kein Entwickler ist, ist es, das Prinzip zu verstehen und einen Einstieg zu finden. Natürlich gibt es seit vielen Jahren sehr viel Material im Web, unzählige Tutorials und Beispiele und mit ShowUI ein Modul, das einen genialen Ansatz verfolgt, aber leider seit Jahren nicht mehr weiterentwickelt wird. Aber, weniger wäre eventuell in diesem Fall mehr. Die Fülle des Angebots macht den Einstieg nicht unbedingt leichter, zumal viele der Beispiele bereits einige Jahre alt und etwas umständlich umgesetzt wurden.

Mit Windows Forms und WPF gibt es zwei Alternativen (mit HTML und JavaScript gibt es noch eine dritte Alternative, wie das sehr interessante Phosphor-Module zeigt). Beide haben ihre kleineren Vor- und Nachteile. Beide sind allerdings an Windows gebunden. Wer bereits „cross Plattform“ denkt und plant, sollte sich mit dem Phosphor-Modul von David Wilson beschäftigen: https://github.com/PowerShell/Phosphor. Der Autor hat es auf der letzten PoshConf Eu persönlich vorgestellt.

Ich bevorzuge in der Regel WPF, da mir der Umstand, dass das Fenster in XML, genauer gesagt in XAML, definiert wird, etwas besser gefällt. Auch wenn weder Eingabehilfen noch keinen Designer gibt (sieht man von Visual Studio ab), ist die Umsetzung etwas einfacher als bei Windows Forms, wenngleich ich seit vielen Jahren mit WPF programmiere, so dass mir die ganze Syntax bereits sehr vertraut ist (man muss lediglich daran denken, dass es auf die Groß-/Kleinschreibung ankommt).

Um nicht jedes Mal bei Null beginnen und sich von Irgendwo her im Web ein XAML-Grundgerüst besorgen zu müssen, stelle ich im Folgenden ein Grundgerüst für ein PowerShell-Skript vor, das ein Fenster mit einem Label, einer Textbox und einem Button anzeigt. Nach einem Klick auf den Button wird der Inhalt der Textbox in einer Messagebox angezeigt und das Fenster wird wieder geschlossen.

Das Template ist als Ausgangspunkt für eigene Dialogfelder gut geeignet. Das erforderliche Know-how zu WPF findet man an vielen Stellen im Web, z. B. http://wpftutorial.net und http://www.wpf-tutorial.com. Auf beiden Seiten findet man viele kleine XAML-Beispiele für die Standard-Controls, die man 1:1 in die XAML-Definition des PowerShell-Skripts übernehmen kann.

Kopiert das folgende Skript einfach in die ISE oder in die Konsole (dank PSReadline kein Problem) und startet das Ganze. Das Fenster sollte daraufhin angezeigt werden.

Auch wenn das natürlich eine Menge Text ist, ist das Ganze am Ende doch eine relativ überschaubare Angelegenheit.

Die Farbwahl es zugegeben etwas gewagt, aber es sollte kein Problem sein, dass sich jeder passendere Farben ausdenkt.

Tipp: Wer hauptsächlich mit der ISE arbeitet, kann den gesamten Text per New-ISESnippet-Cmdlet als Textausschnitt ablegen und ihn dadurch als Ausschnitt verfügbar machen. In Visual Studio Code ist es etwas mehr Arbeit, wenngleich die neueren Versionen der PowerShell Extensions auch Snippets unterstützen. Ein paar Infos findet ihr unter der folgenden Adresse: https://code.visualstudio.com/docs/editor/userdefinedsnippets

Abbildung: Das WPF Dialogbox-Template in Aktion

PowerShell-Tipp: Dateien umbenennen

Eigentlich ist das Umbenennen von Dateien per PowerShell keiner besonderen Erwähnung wert, eigentlich aber doch. Vor kurzem musste ich im Rahmen der Überarbeitung meines Windows 10-Buches auf das Creators Update (eine Menge Arbeit, aber das nur nebenbei, vor allem, wenn man zu spät anfängt;) eine Ordner mit Dateien umbenennen. Ich war gerade dabei den Ordner mit F2-Taste drücken, neuen Namen eintragen, Enter-Taste drücken, F2-Taste drücken, neuen Namen eintragen usw. durchzugehen bis mir einfiel, dass es dafür doch die PowerShell gibt. Leider ist das Rename-Item-Cmdlet etwas seltsam, ich kann mir nie merken, ob man für den NewName-Parameter nur den Namen übergibt oder den gesamten Pfad, so dass es gerade für einen PowerShell-Neuling eine große Hürde darstellt.

Der folgende Befehl nennt alle Dateien im aktuellen Verzeichnis um, in dem er ein „PM_“ zu Beginn des Dateinamens entfernt:

Soll nicht nur der Name, sondern gleich der Pfad der Datei geändert werden, nimmt man dafür das Move-Item-Cmdlet.

Tipp: Datei- und Verzeichnisberechtigungen hinzufügen – es wird etwas einfacher bei 5.0/5.1

Dass Hinzufügen einer Datei- oder Verzeichnisberechtigungen war mit den Hausmitteln der PowerShell alleine in der Vergangenheit etwas unnötig „kompliziert“ – mit der Version 5.0 wird es dank der statischen New-Methode etwas einfacher da kürzer:

Im Folgenden soll eine FullControl-Berechtigung für den User winbu für das Verzeichnis C:\Temp hinzugefügt werden:

Lässt man die Variable $r weg, fällt auch ein Befehl weg:

Soll eine Berechtigung für alle angemeldeten Benutzer gelten, muss bei einem deutschsprachigen Windows „Jeder“ und nicht „Everyone“ angegeben werden.

Dank Tab-Vervollständigung ist das Ganze relativ schnell eingegeben.

Tipp: Skripte der ganzen Welt zur Verfügung stellen per Gists und dem GistProvider von Doug Finke

Wer anderen seinen Skripte zur Verfügung stellen möchte oder selber nach einer einfachen Technik sucht, um seine Skripte jederzeit vor Ort beim Kunden, in einer Schulung oder im Urlaub abrufen zu können, hatte in der Vergangenheit viele Alternativen, keine war wirklich optimal. Ein Ftp-Verzeichnis ist naheliegend, einfach und robust, aber die Dateien können dort nicht direkt editiert werden und vieles mehr. Ein Ort, an dem ein maximaler Komfort für das Bereitstellen von Dateien geboten wird, ist natürlich GitHub. Nur wie greift man auf dort abgelegte Dateien zu? Vermutlich über eine Web-API. Sehr viel einfacher geht es mit Gists, durch das sich die Dateien eines GitHub-Repos direkt ansprechen lassen und dem GistProvider von Doug Finke.

Vorausesetzung ist die PowerShell ab Version 5.0 oder das installierte PackageManagement Preview vom März 2015 (!) für die Versionen 3.0 und 4.0.

Schritt 1: GistProvider installieren

Schritt 2: Erneutes Importieren

Aufgrund einer unterschiedlichen Benennung kann ein erneuter Import erforderlich sein:

Jetzt lassen sich Gists durchsuchen:

Wer doch lieber ein Ftp-Verzeichnis bevorzugt, kann Dateien sehr einfach per Wget (dahinter steckt das Invoke-WebRequest-Cmdlet) herunterladen:

Dieser Befehl ist nur ein Anschauungsbeispiel, da ich die Zugangsdaten zu meinem Ftp-Server nicht verraten habe;)

Famous „Einzeiler“ in PowerShell

Es muss nicht immer gleich ein Skript sein, auch mit einem Einzeiler lässt sich bereits einiges bewerkstelligen. Auf Stackoverflow.com hat vor einigen Zeit eine Frage eine Vielzahl interessanter Einzeiler hervorgebracht:

http://stackoverflow.com/questions/615287/useful-powershell-one-liners?rq=1

Vergleichbar mit den „famous quotes“ gibt es natürlich auch ein paar „famous onliner“ in PowerShell. Hier ein paar Kostproben.

1) Auflisten aller Dateien, die ich heute geändert habe

2) Bei Visual Studio-Menüs die Großschreibung deaktivieren (11.0 steht für VS 2012)

3) Dateien auf den Desktop kopieren

Originell sind Beiträge wie „exit“ oder „gps programThatIsAnnoyingMe | kill“.

Ich habe natürlich selber auch jede Menge Einzeiler, die ich an dieser Stelle in naher Zukunft zusammenstellen werde (alleine in jeder Schulung fallen ein paar dieser Einzeiler an, die dann wieder in Vergessenheit geraten).

Tipp: Die Registry gekonnt durchsuchen

Der Umstand, dass die Registry-Hives wie HKey_Local_Machine als Laufwerk angesprochen werden, ist nur auf den ersten Blick eine Vereinfachung. Das wird spätestens dann deutlich, wenn man die Registry nach Einträgen (Names) eines Schlüssels und deren Werten (Values) durchsuchen möchte. Zwar zeigt ein dir hklm:\Software\Microsoft\Windows\CurrentVersion\Uninstall auch die Einträge der Schlüssel und ihre Werte an, liefert diese aber nicht zurück.

Der folgende Befehl funktioniert daher nicht.

Es gibt bei den zurückgegebenen Objekten ganz einfach keine Eigenschaft DisplayName. Bevor man jetzt allzu viel Zeit mit einer Suche nach einer Lösung verlierrt, was es gibt sind Methoden-Members wie GetValueNames und GetValue.

Der folgende Befehl gibt für alle installierten Anwendungen Anzeigename und Versionsnummer aus.

Weiß man nicht, welcher Schlüssel einen bestimmten Eintrag (Name) besitzen könnte, kommt die Methode GetValueNames in Frage.

Der folgende Befehl gibt alle Schlüssel aus, in denen eines Eintrag „ExecutionPolicy<" gibt.

Es stellt sich heraus, dass die Einstellung für die Ausführungsrichtlinie für den gesamten Computer unter HKEY_LOCAL_MACHINE\Software\Microsoft\PowerShell\1\ShellIds\Microsoft.PowerShell aufgehängt ist.

Der folgende Aufruf von Powershell.exe ändert die Einstellung z.B. auf „Restricted“.

Tipp: Zu welchem Namespace gehört ein Parametertyp?

Sobald man sich mit der PowerShell ein wenig besser auskennt (das kann unter Umständen ein paar Jahre dauern;) achtet man mehr auf die Feinheiten. Dazu gehört z.B. die Frage auf welchem (Daten-) Typ bestimmte Parameter basieren. Nehmen wir als Beispiel den Parameter ExecutionPolicy von Set-ExecutionPolicy. Ist sein Wert ein String oder eine Zahl? Weder noch, die Syntaxbeschreibung verrät, dass der Datentyp, große Überraschung, ExecutionPolicy ist. Da für den Parameter lediglich bestimmte Werte in Frage kommen, steht der Datentyp für eine Konstantenliste, eine Enum. Die Konstantenliste umfasst eine Reihe von Zahlenwerten, die durch Namen repräsentiert werden.

Es ist typisch für die PowerShell, dass eine automatische Typenkonvertierung durchgeführt wird. Übergebe ich für den Parameter z.B. „Unrestricted“, also einen String, wird dadurch automatisch die gleichnamige Konstante ausgewählt.

Was sicher nicht jeder weiß, es ist grundsätzlich sehr einfach die geladenen Assemblies nach einem bestimmten Typ zu durchsuchen, um sich z.B. den Namespace ausgeben zu lassen. Kennt man den Namespace, erfährt man alles über den Typ des Enum und kann sich z.B. alle Felder ausgeben lassen.

Der folgende Befehl führt eine Suche unter allen geladenen Typen durch.

Es stellt sich heraus, dass die vollständige Typenbezeichnung Microsoft.PowerShell.ExecutionPolicy ist.

Ein [Microsoft.PowerShell.ExecutionPolicy].GetFields().Name gibt z.B. die Namen der einzelnen Konstanten aus.

Da wir gerade dabei sind, wie erhalte ich den Zahlenwert einer Konstanten wie z.B. RemoteSigned? Ganz einfach per Typenkonvertierung z.B. in einen Int.

Also, keine „Angst“ vor speziellen PowerShell-Typen, sie lassen sich wie gezeigt sehr einfach in den geladenen Assemblies lokalisieren.

Endlich: Farbige Ausgaben (aber nur unter Windows 10 und Windows Server 2016)

Wenn die PowerShell in einem Punkt im Vergleich zu den Linux-Shells etwas blass aussieht, dann aufgrund der nüchternen Schwarz-Weiß-Optik. Es ist einfach nichts bunt. Klar, per Write-Host lässt sich jede Ausgabe farbig machen, aber wir wissen ja, dass wir Write-Host nicht verwenden sollen, da es die Ausgabe direkt in die Konsole schreibt – es ist in diesem Fall eine reine Textausgabe. Weiterverarbeitung nur sehr eingeschränkt möglich.

Seit Windows 10 und Windows Server 2016 und WMF 5.1 gibt es eine endlich eine Lösung. Sie besteht darin, dass das Konsolenfenster VT100-Escape-Sequenzen erkennt und entsprechend umsetzen kann. Unter anderem gibt es Esc-Seqenzen für eine farbige Ausgabe. Jetzt muss man nur noch eine Typenformatierungsdatei für den auszugebenden Typ anlegen, dort die Esc-Sequenzen einbauen und schon wird die Eigenschaft in einer anderen Farbe ausgegeben.

Hier ist ein „kleines“ Beispiel, das die Formatierung für Process-Objekte ersetzt und bewirkt, dass ein Get-Process mit Name und WS(MB) nur noch zwei Spalten anzeigt. Die Ausgabe in der Spalte WS(MB) wird immer dann rot angezeigt, wenn der Wert größer 50MB ist – denkt bitte daran, dass das nur ab Windows Server 2016 und Windows 10 ab Anniversary Update und WMF 5.1 funktioniert.

Wer viel Zeit hat, kann natürlich weitere Spalten definieren bzw. eine benannte View anlegen, die bei Format-Table per View-Parameter ausgewählt wird. Möglich ist, wie immer, vieles. Man muss nur die Zeit haben und experimentierfreudig sein…

Farbige Ausgaben dank VT100-Unterstützung in der modernen Konsole

Praktisch: Dateien kopieren mit Fortschrittsanzeige

Das „Problem“ beim Kopieren von mehreren Dateien ist, dass die Anzahl der zu kopierenden Dateien am Anfang in der Regel nicht bekannt ist. Möchte man Dateien mit Hilfe einer Function pipelinebasierend kopieren, gibt es in der Regel keine Möglichkeit an die Anzahl der Dateien, die der Function übergeben werden, heranzukommen. Der „Trick“ mit $Input.Count funktioniert ab der Version 3.0 nicht in einer Pipeline-Function.

Eine einfache Lösung besteht darin, die zu kopierenden Dateien im Process-Block zu sammeln und die Dateien erst im End-Block zu kopieren. In dieser Aufteilung werden die Dateien im Process-Block lediglich gezählt, um dann im End-Block im Rahmen einer ForEach-Wiederholung kopiert zu werden.

Das folgende Beispiel veranschaulicht das Prinzip.

Aufgerufen werden kann die Function wie folgt:

Die DSC-Script-Ressource etwas flexibler einsetzen

Zu den knapp ein Dutzend DSC-Ressourcen, die bei der PowerShell 5.0 von Anfang an dabei sind, gehört auch die Script-Ressource. Mit ihrer Hilfe ist es möglich, auf einem Node bei der Anwendung der Ressource durch den LCM beliebige Skriptbefehle auszuführen. Das hört sich zunächst einmal gut an, etwas kniffliger ist es die Ressource so einzusetzen, dass sie wirklich etwas bringt. Das bedeutet konkret, Parameterwerte, die Teil der Konfigurationsdaten sind, in die per SetScript ausgeführten Befehle einzubauen.

Die Script-Ressource umfasst drei Eigenschaften: TestScript, GetScript und SetScript. Alle drei Eigenschaften sind vom Typ String, ihnen kann aber auch ein ScriptBlock zugewiesen werden was im Allgemeinen die etwas flexiblere Variante ist. TestScript enthält einen Befehl/Befehlsfolge, die einen $true/$false-Wert zurückgeben muss. Nur wenn der Wert $false ist, wird der Befehl ausgeführt, der über SetScript festgelegt wird. GetScript erfüllt keine echte Funktion. Die Befehlsfolge muss eine Hashtable mit einer Result-Eigenschaft zurückgeben, die einen beliebigen Wert besitzen kann.

Soweit, so gut. In der Regel sollen die per SetScript auszuführenden Befehle Werte verarbeiten, die Teil der Konfigurationsdaten sind. Dazu muss die Variable Node in die Befehlskette eingebaut werden. Am einfachsten geschieht dies, in dem der Node-Variablen ein „using:“ vorangestellt wird. Per Using kann auch eine Variable angesprochen werden, die außerhalb der Konfiguration definiert ist.

Im folgenden Beispiel soll eine Datei über das Web herunterladen und in einem lokalen Verzeichnis abgelegt werden. Die Url ist Teil der Konfigurationsdaten und wird daher über die Node-Variable angesprochen.

Der folgende Aufruf setzt die Konfiguration um.

Der folgende Aufruf überträgt die Konfiguration auf den über die Konfigurationsdaten festgelegten Computer (in diesem Beispiel ist es der lokale Computer).