PowerShell 1-2-3

Vorwort

Liebe(r) Leser(in),

herzlichen Dank an Ihrem Interesse für dieses PowerShell-Buch. Die erste Frage die Sie sich stellen ist wohl ob Sie weiterlesen sollen. Daher möchte ich kurz und knapp beschreiben an wen sich dieses Buch richtet und ob es für Sie geeignet ist. PowerShell ist eine Skriptsprache die auf allen aktuellen Microsoft Betriebssystemen zur Verfügung steht. Sicher ist Ihnen schon einmal eine sogenannte DOS-Box oder auch Eingabeaufforderung genannt in Windows begegnet. Diese DOS-Box ist ein Relikt aus den Urzeiten als Microsoft mit den ersten Betriebssystem DOS an den Start ging. Die PowerShell hat sich nun langsam als Ablösung etabliert und wird über kurz oder lang die gute, alte DOS-Box komplett ablösen. Als normaler Anwender hatten Sie damit bislang wohl weniger zu tun, aber als IT-Profi kommt man nicht drum herum den einen oder anderen Befehl auch einmal auf der Konsole (in die DOS-Box) einzutippen. Administratoren hingegen automatisieren viele tägliche Aufgaben mit Hilfe von Skripten. Dieses Buch erhebt den Anspruch sowohl Administratoren mit langjähriger Skripterfahrung für die Möglichkeiten mit der PowerShell zu begeistern, als auch interessierte Anwender, durch die für Jedermann verständlichen Formulierungen in die Materie einzuführen. Facebook-Maus-Schubser sollten jetzt bitte zuklappen. Ja, jetzt - fertig!

Die 2. Frage dürfte wohl sein, wer das eigentlich ist, der dieses Buch geschrieben hat. Nun, mein Name ist Martin Lehmann und ich hatte mit 14 Jahren das erste Mal einen Computer unter dem Weihnachtsbaum liegen. Die Winterferien waren somit verplant…RTFM for Basic-Programming, oder zu Deutsch: erst einmal Basic programmieren lernen, denn Spiele waren nicht mit dabei. Da Basic zu langsam war, habe ich ein Jahr später ein Assembler-Modul und sogar eine „Datasette“ bekommen. Den ersten PC in Form eines 386er habe ich mir dann selbst zusammen gebastelt um festzustellen, dass die Programmierung auf dem PC als Hobby nicht durchführbar ist. Basic war damals viel zu langsam und allein um meine Grafikkarte in Assembler zu programmieren habe ich ½ Jahr gebraucht. Inzwischen sind die Computer aber so rasend schnell, dass man sogar mit einer Skriptsprache wie PowerShell schon ganze Anwendungen im Handumdrehen zusammen programmieren kann und diese sogar recht flott ablaufen. Da man hier in Deutschland ohne Zeugnis oder Dipl. nicht besonders weit kommt, habe ich ein Fernstudium zum Dipl. PC-Betreuer bei der SGD belegt. Da stellte man fest, dass ich wohl in der Lage sein könnte selbst für die SGD Seminare durchzuführen. So bin ich seit 1995 als freiberuflicher IT-Trainer unterwegs. Begonnen hat es mit Hardware- und Microsoft-Office-Schulungen und später dann Ausbildungen zu Netzwerkadministratoren und dem inzwischen verfügbaren Ausbildungsberuf Mechatroniker. 1997 habe ich den Segen von Microsoft erhalten, das Evangelium zu verkünden, sprich ich wurde MCT. 1998 wurde ich aufgrund meines Erfolgs vom Deutschen Fernschulverband zum Fernschüler des Jahres gewählt. Unter Windows 2000 war ich dann einer der „Early-Achiever“, musste allerdings feststellen, dass Windows 2000 viele Mängel aufwies und so habe ich erst einmal Microsoft den Rücken gekehrt und habe mich den LINUX-Betriebssystemen zugewandt. So habe ich dann bei Novell den Status CNI und distributionsunabhängig den Status LPIC Level 2 erreicht. So kam ich dann auch zur Skriptsprache Perl. Als XP und Server 2003 veröffentlicht wurden, stellte ich fest, dass Microsoft einen großen Teil seiner Hausaufgaben gemacht hat und Windows wieder praktikabel einsetzbar wurde. PowerShell in Version 1.0 hat mich nicht gerade begeistert, aber seit Version 2.0 würde ich sogar Perl in der Ecke stehen lassen, wenn PowerShell auch plattformunabhängig (der Anfang ist gemacht, mehr dazu im Buch) zur Verfügung stehen würde. In reinen Windows Umgebungen gibt es einfach nichts Besseres als die PowerShell, um schnell, einfach und effizient Alltagsprobleme am PC zu lösen.


 

Danksagung

Zunächst einmal möchte ich mich noch recht herzlich bei meinen vielen Schulungsteilnehmern für die interessanten Fragen zum Thema PowerShell bedanken, deren Lösungen teilweise auch in dieses Buch eingeflossen sind.

Darüber hinaus einen besonderen Dank an Gunnar Brandes, der während der frühen Entwicklung des Buches auf erste Fehler hingewiesen hat.

Da ich mich dazu entschlossen habe, dieses Buch als OpenBook zu veröffentlichen hier die Liste mit den freundlichen, finanziellen Unterstützern, die es mir möglich gemacht haben dieses Werk immer weiter voran zu bringen.

Spender

Fam. Schlauch, Rolf Egner, Elke Trautmann, Christian Weber, Jochen Huehne, Claus Schwab, Sebastian Rubretus, Stefan Kurajew, Sebastian Loof, FrankHietzig, Corvin Jonigk, Uwe Karhof, Tobias Duske, Guido Fedeler, Jens Brinkschmidt, Markus Buecker, Christian Erwin, Andreas Huther, Sascha Zimmer, Sven Scholz, Eike Schwoeppe, Martin Klar, Tobias Berger, Bärbel Koslowski, Mike Schaarschmid, Sven Eybe, Dr. Doris Schnabel, Peter Kriegel, Alex Leistner, Peter Rüttinger, Fam. Fertig, Dominik Gülland, Helmut Knies, Lutz Draeger, Wolfgang Gandre, Thomas Peter, Wilfried Dessin, Helge Rohrbein, Thorsten Oeltjen, Mark Vorländer, Volker Baumann, Jens-Peter Lock, Joseph Collé, Angelo Castro, Sebastian Franz,  Alexander Brückner, Thorsten Schmidt, Jörg  Meuthen, Oliver Braun, Frank Mühlenhart, Philipp Schach, Wai Pang Mak, Uwe Bauer, Andreas Marquardt, Christoph Langer, Markus Kern, Sven Bergau, Daniel Plump, Carsten Zuber, Stephan Rumelt, Martin Wecker, Daniel Biermann, Thomas Frank, Alfred Haack, Rainer Friedrich, Kai Uwe Hepke, Klaus Oberdalhoff, Rene Wild, Herbet Winter, Andreas Fuchs, Laura Martini, Fam. Fölsch, Gerd Fischer, Michael Santeler, Andreas Bahner, Michael Wagner, Steffen Montag, Daniel Grams, Stefan Jacobs, Leo Kohl.


 

Inhalt

1       Einführung. 1

1.1        Wie lesen Sie dieses Buch optimal?. 1

1.2        Warum gibt es die PowerShell und weshalb sollten Sie sie einsetzen?. 1

1.3        Auf welchen Betriebssystemen Sie die PowerShell nutzen können. 1

1.3.1         Windows 10 und Server 2016, 2019, 2022. 1

1.3.2         Windows 8.1 und Server 2012 R2  1

1.3.3         Windows 8 und Server 2012  1

1.3.4         Windows 7 und Server 2008 R2  1

1.3.5         Windows Vista und Server 2008  1

1.3.6         Windows XP, Server 2003 und Server 2003 R2. 1

1.3.7         Ältere Betriebssysteme. 1

1.3.8         Plattformunabhängige Versionen (auch für Linux & Mac) 1

1.4        Kontrollfragen. 1

2       Versionsunabhängig. 1

2.1        Arbeitsumgebung einrichten. 1

2.1.1         PowerShell starten und die Version ermitteln. 1

2.1.2         Einsetzen von Hilfsmitteln für PowerShell 1

2.1.3         Anpassung der Standard Shell 1

2.1.4         Kontrollfragen. 1

2.2        Übersicht der grundlegenden Befehle  1

2.2.1         Eingaben an der PowerShell Konsole. 1

2.2.2         Kompatibilität 1

2.2.3         Anatomie des PowerShell Cmdlets  1

2.2.4         Hilfefunktionen der PowerShell 1

2.2.5         Alias Cmdlets. 1

2.2.6         Navigation. 1

2.2.7         Das EVA-Prinzip und seine Anwendung. 1

2.2.8         Pipelineverarbeitung und Umleitung von Ein- und Ausgaben. 1

2.2.9         Eingabe Befehle und Variablen kennenlernen und verstehen  1

2.2.10       Kontrollfragen. 1

2.2.11       Die Grundlagen der Verarbeitung. 1

2.2.12       Ausgabe Befehle kennenlernen und verstehen. 1

2.2.13       Quoting Regeln. 1

2.2.14       Verwenden von Rechen- und Zuweisungs-Operatoren. 1

2.2.15       Kontrollfragen. 1

2.3        Objektorientierung. 1

2.3.1         Theoretische Einführung in objektorientierte Programmierung. 1

2.3.2         Objekt-Eigenschaften in der Praxis  1

2.3.3         Daten Objekt orientiert filtern  1

2.3.4         Messen wie viel Zeit eine Verarbeitungskette in Anspruch nimmt  1

2.3.5         Objekte miteinander vergleichen. 1

2.3.6         Vergleichs-Operatoren und reguläre Ausdrücke. 1

2.3.7         Logische Verkettung von Vergleichs-Operatoren mit Logikoperatoren. 1

2.3.8         Objektmethoden in der Praxis  1

2.3.9         .NET-Objekte. 1

2.3.10       Kontrollfragen. 1

2.3.11       WMI Objekte. 1

2.3.12       Kontrollfragen. 1

2.3.13       COM Objekte. 1

2.3.14       Kontrollfragen. 1

2.4        Mit Skripten arbeiten. 1

2.4.1         Grundlagen zur Skriptausführung. 1

2.4.2         Gutes Skripting. 1

2.4.3         Kommentare. 1

2.4.4         Einfache Parameterübergabe an Skripte. 1

2.4.5         Im Skript den Skriptpfad herausfinden. 1

2.4.6         Kontrollfragen. 1

2.4.7         Schleifenfunktionen. 1

2.4.8         Kontrollfragen. 1

2.4.9         Bedingte Ausführung. 1

2.4.10       Funktionen. 1

2.4.11       Profil Skripte. 1

2.4.12       Skripttuning. 1

2.4.13       Kontrollfragen. 1

2.4.14       Troubleshooting. 1

2.4.15       Kontrollfragen. 1

3       Unterschiede zwischen PowerShell Version. 1

3.1        PowerShell 1.0. 1

3.1.1         Module in Version 1.0 (Snap-ins) 1

3.2        PowerShell 2.0. 1

3.2.1         ISE. 1

3.2.2         Troubleshooting in Version 2.0. 1

3.2.3         Kontrollfragen. 1

3.2.4         Allgemeine zusätzliche Befehle der Version 2.0. 1

3.2.5         Module in Version 2.0. 1

3.2.6         Kontrollfragen. 1

3.2.7         PowerShell Remote Zugriff 1

3.2.8         Kontrollfragen. 1

3.2.9         Jobs. 1

3.2.10       Kontrollfragen. 1

3.2.11       Ereignisse. 1

3.2.12       Transaktionen. 1

3.3        PowerShell 3.0. 1

3.3.1         Hilfe. 1

3.3.2         Show-Command. 1

3.3.3         Vereinfachte Schreibweisen. 1

3.3.4         Neue Systemvariable. 1

3.3.5         Workflows. 1

3.3.6         Scheduled Jobs. 1

3.3.7         PSSession Disconnect 1

3.3.8         New-PSDrive -Persist 1

3.3.9         Out-Gridview –Passthru. 1

3.3.10       Module in Version 3.0. 1

3.3.11       PowerShell Remoting. 1

3.3.12       WMI vs. CIM... 1

3.3.13       Anti-Malware Scan Interface (AMSI) 1

3.4        PowerShell 4.0. 1

3.4.1         ISE Steuerung. 1

3.4.2         Vereinfachte Schreibweisen, auch in Workflows. 1

3.4.3         Zusätzliche Cmdlets, Parameter  1

3.4.4         Desired State Configuration. 1

3.5        PowerShell 5.0. 1

3.5.1  Paketmanager/Softwareinstallation. 1

3.5.2         RunSpaces - Multithreaded statt Multiprocessed Jobs  1

3.5.3         PowerShell Direct 1

3.5.4         Umgang mit komprimierten Dateien/Verzeichnissen. 1

3.5.5         PowerShell 5.0 Klassen und Enumeratoren. 1

3.6        PowerShell Core (Versionen: 6.x & 7.x) 1

3.6.1         Foreach -Parallel 1

3.6.2         Ternärer Operator. 1

3.6.3         Pipeline-Kettenoperatoren. 1

3.6.4         Null-Sammeloperator. 1

3.6.5         Fehlermeldungen. 1

4       Praxisbeispiele. 1

4.1        Allgemeine Tipps rund um PowerShell 1

4.1.1         Rückfragen automatisch mit Ja beantworten lassen. 1

4.1.2         WMI Events (Might & Magic) 1

4.2        Windows Verwaltung. 1

4.2.1         Computermanagement 1

4.2.2         Filesystem.. 1

4.2.3         Registry. 1

4.2.4         Internet Explorer kontrollieren. 1

4.2.5         32 oder 64 Bit?. 1

4.3        Microsoft-Office. 1

4.3.1         Allgemein. 1

4.3.2         Word. 1

4.3.3         Excel 1

4.3.4         Outlook. 1

4.4        Datenbankzugriffe. 1

4.4.1         Datenbankzugriffe auf  Access  1

4.4.2         Datenbankzugriffe auf  SQL  1

4.5        GUI – Grafische Oberflächen mit PowerShell 1

4.5.1         Simple Messagebox. 1

4.5.2         Vom einfachen Fenster zu aufwendigen Gestaltungsdetails. 1

4.5.3         Was sind Controls?. 1

4.5.4         Fensterrahmen und Texte. 1

4.5.5         Knöpfe. 1

4.5.6         Tool-Tips (Hilfetexte) 1

4.5.7         Ladebalken. 1

4.5.8         Eingabefelder. 1

4.5.9         Aktives Element eines Fensters festlegen. 1

4.5.10       Listen Auswahl und DropDown Felder. 1

4.5.11       Kalender. 1

4.5.12       Karteikartenreiter. 1

4.5.13       Gruppieren von Radiobuttons und Checkboxen. 1

4.5.14       Dateien öffnen und speichern Dialoge. 1

4.5.15       Explorer ähnliche Ansichten  1

4.5.16       Durch Events andere Controls im Fenster beeinflussen. 1

4.5.17       Fensterinhalt mittels Timer aktualisieren. 1

4.5.18       Mit XAML erstellte GUIs einbetten  1

4.5.19       GUIHelper-Modul 1

4.5.20       Auf Tastendruck reagieren. 1

4.5.21       Online Dokumentation zu allen GUI-Elementen. 1

4.6        Internet-Information-Server (IIS) 1

4.6.1         Grundlagen zu IIS. 1

4.6.2         Technische Details. 1

4.6.3         Navigieren im IIS PS-Drive. 1

4.6.4         Websites und ApplicationPools verwalten. 1

4.7        Hyper-V. 1

4.7.1         Allgemeine Informationen über die Gastsysteme. 1

4.7.2         Snapshot von mehreren VMs gleichzeitig erstellen. 1

4.7.3         Komplettes Backup von VMs erstellen (VHDs, Config, Snapshots) 1

4.8        Lokale clientseitige Benutzerwerwaltung. 1

4.8.1         Wer bin ich, oder WhoAmI 1

4.8.2         Benutzer Konten Steuerung UAC (User Account Control) 1

4.9        Active Directory. 1

4.9.1         Navigieren im AD PS-Drive. 1

4.9.2         AD-Objekt-Verwaltung. 1

4.9.3         AD, Powershell und mmc in einer Oberfläche kombinieren. 1

4.9.4         Eigene AD – Papierkorb Cmdlets  1

4.9.5         GUI für AD Papierkorb. 1

4.9.6         Gruppenrichtlinien. 1

4.10      Mit PowerShell fremden XML-Dateien arbeiten. 1

4.10.1       XML-Datei laden. 1

4.10.2       XML-Objekt untersuchen. 1

4.10.3       XPath Syntax um auf einzelne Knoten zu verweisen. 1

4.10.4       Neues XML-Element erstellen  1

4.10.5       Einem XML-Element Attribute und Werte zuweisen. 1

4.10.6       XML-Element in Dokument plazieren. 1

4.10.7       XML-Attribut löschen. 1

4.10.8       XML-Element löschen. 1

4.10.9       XML-Dokument speichern. 1

4.10.10         XML-Dokument wie ein PSObject verwenden. 1

5       Anhang. 1

5.1        Lösungen. 1

5.1.1         Kontrollfragen. 1

5.1.2         Übungen. 1

5.1.3         Skripte. 1

5.2        Formatierungen Übersicht 1

5.2.1         -f Format-Operatoren. 1

5.2.2         Datentypen. 1

5.3        Variablen Übersicht 1

5.3.1         Automatische Variablen. 1

5.3.2         Umgebungs Variablen. 1

5.3.3         Vordefinierte PowerShell Variablen  1

5.4        Operatoren Übersicht 1

5.4.1         Vergleichs-Operatoren. 1

5.4.2         Zuweisungs-Operatoren. 1

5.5        Sonderzeichen Übersicht 1

5.6        Datenbank-Typen. 1

6       Glossar. 1

7       Index. 1

 


 

Typos

 

Normaler Text                               Beschreibung/Erklärung.

Simplified Arabic Fixed                                                   Einzutippende Befehle/Skripte.

Fett                                                             Wichtige Text/Skriptpassagen und Klick-Reihenfolgen.

Unterstrichen                                                             Wichtige Textpassagen.

Kursiv                                                                     Hier sind sinngemäß Ersetzungen vorzunehmen.
                                                             Beispiel: Laufwerk:\Pfad wird zu C:\Programme

Fett+Kursiv                                                                         Zu drückende Tasten auf der Tastatur, oder anzuklickende                                                                 Knöpfe innerhalf von Fenstern.

1                                                                       Bei Skripten sind die Zeilennummern nicht mit abzutippen!                                                                      Sind bei Skripten keine Zeilennummern vorhanden, so ist                                                                      jede Zeile mit Enter zu bestätigen.


 

1        Einführung

1.1     Wie lesen Sie dieses Buch optimal?

Im ersten Kapitel erfahren Sie wie Sie für sich am effizientesten durch die vielen Seiten dieses Buches kommen.

Das Buch gliedert sich grob in 6 Teile. Den ersten Teil, die „Einführung“ lesen Sie gerade. Hier geht es lediglich um ein paar einleitende Worte, die der gestandene Admin ruhig überspringen kann. Abgesehen vielleicht von dem Unterpunkt über die unterschiedlichen Betriebssystem-Versionen die PowerShell unterstützen. Hier steht im Großen und Ganzen nur ein bisschen Hintergrundwissen, dass für die tägliche Arbeit aber eher unwichtig ist.

Der 2. Teil, „Versionsunabhängig“  führt ganz sacht in die PowerShell Thematik ein und endet mit der Skriptprogrammierung. Ganz nebenbei wird der Ansatz der Objektorientierung vermittelt. In diesem Teil geht es nicht um Anwendungsbeispiele, sondern um das Verstehen der grundsätzlichen Möglichkeiten und Techniken. Alles in diesem Teil können Sie in jeder PowerShell Version gleichermaßen verwenden. Der 2. Teil ist mit seinen unzähligen Übungen auf sehr gut als Schulungsunterlage für einen Kurs zu verwenden.

Im 3. Teil handelt es sich ähnlich wie im zweiten, um grundsätzliche Möglichkeiten und Techniken, kehrt aber die Unterschiede zwischen den einzelnen Versionen hervor. Insbesondere PowerShell- Cracks sollten sich natürlich die Kapitel zur neuen Version 3.0 durchlesen.

Der 4. Teil enthält viele Einsatzbeispiele für den praktischen Gebrauch im Alltag. Den sollten Sie nicht durcharbeiten, sondern sich anhand der Überschriften die für Sie interessanten Skripte herauspicken.

Im 5. Teil, dem Anhang finden Lösungen zu den Kontrollfragen und Skriptübungen, sowie eine Übersicht über die Möglichkeiten des –f Operators. Eine Auflistung unterstützer Datentypen mit Beschreibung, Operatoren und vordefinierte Variablen ist ebenfalls in diesem Abschnitt.

Wenn irgendwo im Text ein Begriff auftaucht den Sie nicht auf Anhieb verstehen bzw. kennen, schauen Sie doch einfach einmal im 6. Teil, dem Glossar nach. Dort werden viele Begriffe und Abkürzungen erläutert.

Im 7. Teil finden Sie einen Index mit Schlagworten.

Eine Befehlszeilenreferenz enthält dieses Buch nicht, da sich die PowerShell sehr gut selbst dokumentiert. Dazu können Sie z.B. für den Einstieg den Befehl Get-Command eintippen, doch dazu später mehr.

1.2     Warum gibt es die PowerShell und weshalb sollten Sie sie einsetzen?

Die PowerShell ist eine mächtige Skriptsprache mit der sich alle Aufgaben an einem PC  auf einfache Weise automatisieren lassen. Theoretisch könnten Sie sogar eine X-beliebige Anwendung damit erstellen. Um Computerspiele zu programmieren gibt es sicherlich bessere Möglichkeiten, da sie in der Ablaufgeschwindigkeit schnellere Programme produzieren. Ein einfaches Programm zur Kundenverwaltung mit Datenbankzugriff, oder gar ein Programm zur Auftragsabwicklung von der Lieferung bis hin zu Abrechnung wäre ohne weiteres machbar.

Die PowerShell selbst ist komplett .NET basiert. Sie erlaubt uns daher den Zugriff auf das komplette .NET-Framework. Wem das nicht reicht, der kann gerne auch auf COM oder WMI zurückgreifen. Was das alles ist, dazu mehr unter dem Punkt Arbeitsumgebung einrichten. Mit diesen 3 Programmierschnittstellen, stehen Ihnen fast alle Möglichkeiten offen, die auch ein C Programmierer hat. Ein fertig kompiliertes C-Programm würde schneller ablaufen, doch mit der PowerShell haben wir es 100 Mal schneller zusammen programmiert. Umständliche Variablendeklarationen und Typkonvertierungen, instanziieren von Objekten aus Objektklassen, komplizierte Vererbung, Polymorphismus und andere böse Wörter brauchen wir hier zunächst einmal nicht. Hier schreiben Sie Ihre Programme fast so einfach, wie wenn Sie sich mit einem anderen Menschen unterhalten. Selbst die gute alte BOS-Box macht uns die Verwaltung des PCs im Vergleich mit der PowerShell ziemlich schwer. Mit Hilfe dieses Buches haben Sie einen sehr schnellen Einstieg in die PowerShell und können schon nach wenigen Kapiteln intuitiv Ihre eigenen Probleme aus der Praxis lösen. Microsoft hat nicht nur versucht die schnelle und einfache Benutzung der Kommandozeile mit den Möglichkeiten des VBS zu vereinen, sondern dieses Ziel sogar noch weit übertroffen.

Seit Windows 7 und Server 2008 R2 ist die PowerShell auf Microsoft Betriebssystemen standardmäßig installiert und aktiviert. In Windows 8 und Server 2012 gibt es zwar aus Gründen der Kompatibilität noch eine DOS-Box (cmd.exe), aber die Frage ist: Wie lange noch? Die zukunftsträchtigste Möglichkeit etwas zu automatisieren ist also die PowerShell. Die komplette Systemcenter Produktfamilie basiert auf PowerShell Skripten und auch in Exchange und SQL ist Sie eine wichtige Grundlage. Auch der IIS und viele andere Standardwindowsfunktionen können damit administriert werden. Mehr dazu im nachfolgenden Kapitel.

1.3     Auf welchen Betriebssystemen Sie die PowerShell nutzen können

In diesem Kapitel erfahren Sie auf welchen Betriebssystemen Sie die PowerShell unter welchen Umständen einsetzen können und welche Voraussetzungen ggf. geschaffen werden müssen.

Grundvoraussetzung für PowerShell ist ein installiertes .NET-Framework in der Version 2.0 (was das genau ist, dazu mehr im Kapitel Arbeitsumgebung einrichten). Bei Windows 7/Server 2008 R2 oder neueren Microsoft-Betriebssystemen ist dies bereits vorhanden. Bei älteren Betriebssystemen können Sie es in der Regel über die automatischen Updates beziehen oder manuell unter diesem Hyper-Link: https://www.microsoft.com/de-de/download/details.aspx?id=6523

1.3.1   Windows 10 und Server 2016, 2019, 2022

Windows 10 und Server 2016 sind mit PowerShell Version 5.X ausgestattet. Um PowerShell zu benutzen drücken Sie kurz auf die Windows-Taste (die Taste mit dem Fahnensymbol links unten auf der Tastatur) und tippen Sie einfach den Begriff PowerShell ein und klicken auf das PowerShell Icon. PowerShell Direct erlaubt den direkten Zugriff eines Hyper-V Hosts auf die Shell der Gäste (ohne den Umweg über PS-Remoting). Ab

1.3.2   Windows 8.1 und Server 2012 R2

Windows 8.1 und Server 2012 R2 haben die PowerShell Version 4.0 an Bord. Sie können jedoch das Windows Management Framework 5.0 (mit PowerShell 5.0) herunterladen und installieren. Dies benötigt zuvor die Installation des .NET-Framework 4.5. Auf dem Server können Skripte standardmäßig remote ausgeführt werden.

1.3.3   Windows 8 und Server 2012

Windows 8 und Server 2012 haben die PowerShell Version 3.0 vorinstalliert. Auch hier ist ein Update auf 4.0 oder gar 5.0 möglich. Auf dem Server ist die Skriptausführung lokal standardmäßig erlaubt, auf dem Client nicht.

1.3.4   Windows 7 und Server 2008 R2

Bei Windows 7 und Server 2008 R2 ist Version 2.0 der PowerShell schon direkt bei der Betriebssysteminstallation mit dabei und kann „Out-of-the-Box“ benutzt werden. Ausnahme Server Core Installationen: Hier müssen Sie erst über den Servermanager das .NET-Framework und dann die PowerShell als Feature nachinstallieren. Dazu tippen Sie an der Eingabeaufforderung des Server-Core die folgenden beiden Befehle jeweils mit Enter bestätigt ein:

ocsetup “NetFx2-ServerCore”

ocsetup “MicrosoftWindowsPowerShell”

Um in die PowerShell in der Version 3.0 zu verwenden, laden Sie sich unter http://www.microsoft.com/en-us/download/details.aspx?id=34595 die Datei Windows6.1-KB2506143-x86.msu für 32-Bit Windows Installationen herunter und installieren Sie diese. Für 64-Bit Windows Installationen ist es die Datei Windows6.1-KB2506143-x64.msu. Ab Service Pack 1 für Windows 7 können Sie sogar PowerShell 5.0 installieren. Allerdings bringen die Updates nicht allzuviel, da die zusätzlichen Befehle wie z. B. Set-NetIPAddress leider nur auf den neueren Betriebssystemen verfügbar sind. Warum auch immer sind diese leider nicht auf älteren Systemen vorhanden. Selbst wenn man die neuste PowerShell-Version installiert.

1.3.5   Windows Vista und Server 2008

Vista und Server 2008 haben die PowerShell in der Version 1.0 zwar mit an Bord, diese ist aber nicht aktiviert und müsste erst mittels des Servermanagers bei 2008 oder bei Vista über die Systemsteuerung aktiviert werden. Details dazu verrate ich nicht, denn wenn Sie Version 1.0 aktiviert haben müssen Sie diese erst wieder deaktivieren bevor Sie Version 2.0 benutzen können. Version 1.0 macht sowieso niemand Spaß, von daher laden Sie sich bitte über den nachfolgend bei XP angegeben Link Ihre Version 2.0 für Vista bzw. Server 2008 herunter und installieren Sie die.

Alternativ können Sie auch hier Version 3.0 verwenden. Laden Sie sich unter http://www.microsoft.com/en-us/download/details.aspx?id=34595 die Datei Windows6.0-KB2506146-x86.msu für 32-Bit Windows Installationen herunter und installieren Sie diese. Für 64-Bit Windows Installationen ist es die Datei Windows6.0-KB2506146-x64.msu.

Für die Fans von Windows Server 2008 in der Core Installation, habe ich leider eine schlechte Nachricht. Server 2008 in der Core Installation unterstütz kein .NET und daher funktioniert hier auch keine PowerShell.

1.3.6   Windows XP, Server 2003 und Server 2003 R2

Diese Betriebssysteme haben von Haus aus erst einmal keine PowerShell mit an Bord. Allerdings können Sie sich die Version 2.0 unter http://support.microsoft.com/kb/968929 sowohl für 32, als auch 64 Bit Betriebssysteme herunterladen. Den Download finden Sie so ziemlich in der Mitte der Internetseite unter der Überschrift Windows Management-Framework Core (WinRM 2.0 und Windows PowerShell 2.0).

Version 3.0 wird für XP und 2003 leider nicht mehr angeboten.

1.3.7   Ältere Betriebssysteme

Auf älteren Betriebssystemen wie Windows 2000, NT 4.0 oder gar Windows 3.11 oder DOS steht Ihnen die PowerShell nicht zur Verfügung. Wer so etwas noch einsetzt, oder gar administrieren muss hat mein aufrichtiges Beileid.

1.3.8   Plattformunabhängige Versionen (auch für Linux & Mac)

Für Linux und Mac gibt es inzwischen von Microsoft selbst PowerShell Core ab Version 6.x und 7.x als OpenSource-Projekt auf GitHub. Allerdings sollte man sich von der Versionsnummer nicht täuschen lassen. Diese basiert zurzeit auf .NET Core 2.0 und hat daher nicht ganz den Charme eines PowerShell (ohne Core) 2.0, z.B. fehlen in den frühen Versionen noch die WMI Cmdlets – dafür aber z.B. CIM. Ziel ist es PowerShell Core möglichst kompatibel zu PowerShell zu halten. Daher ist auch mit einer zeitnahen Implementierung der WMI Cmdlets zu rechnen (ist ja letztlich nicht wirklich was anderes als CIM). Auch PowerShell 1.0 war nicht so prikelnd hat aber verdammt schnell zugelegt. Ähnlich erwarte ich das auch für PowerShell Core. Möglicher Weise wird es in Zukunft dann nur noch ein PowerShell Core geben und die „normale“ PowerShell wird eingestellt. Auch lassen sich nicht alle Module nutzen, allen voran Active-Directory und Exchange. Auch wenn Module geladen werden können, bedeutet dies nicht, dass diese in vollem Umfang auch unter PowerShell Core genutzt werden können. Hier soll das ebenfalls auf Github zur Verfügung stehende WindowsPowerShellCompatibilityPack weiterhelfen.

Die Installation kann manuell über GitHub erfolgen oder besser von entsprechende Repositories wie hier am Beispiel für Ubuntu 17.04:

# Importieren der public repository GPG keys

curl https://packages.microsoft.com/keys/microsoft.asc | sudo apt-key add -

# Registrieren des Microsoft Ubuntu repository (ab hier muss alles als Benutzer root erfolgen)

sudo curl -o /etc/apt/sources.list.d/microsoft.list https://packages.microsoft.com/config/ubuntu/17.04/prod.list

# Updaten

sudo apt-get update

# Installieren der PowerShell

sudo apt-get install -y powershell

# …und starten der PowerShell

Pwsh

# zum Abschluß kann man auch die Installation noch auf den neusten Stand bringen:

sudo apt-get upgrade powershell

Der Aufruf erfolgt auf Linux Systemen mittels pwsh und auf Windows mit pwsh.exe, statt mit powershell.exe und befindet sich standardmäßig in $env:ProgramFiles\PowerShell\.

Weitere Details zu den Core-Versionen erfahren Sie in Kapitel PowerShell Core.

1.4     Kontrollfragen

Die Kontrollfragen sollen der Festigung Ihres Wissens dienen. Die Antworten finden Sie im ersten Kapitel des Anhangs. Bis jetzt haben Sie noch nicht viel kennengelernt, daher an dieser Stelle nur 2 Fragen:

Auf welchen Betriebssystemen können Sie die PowerShell verwenden?

Was müssen Sie bei Windows Server 2008 R2 in der Core Installation tun um PowerShell verwenden zu können?

2        Versionsunabhängig

Dies ist der wichtigste Abschnitt im Buch, der zunächst mit einer kleinen Einleitung über die Positionierung der PowerShell im Betriebssystem beginnt. Man könnte auch den Begriff Architektur heranziehen, aber ich halte es für übertrieben und nicht zielführend tatsächlich die komplette Architektur darzustellen und hier so weit ins Detail zu gehen. Im weiteren Verlauf kommen Sie dann Stück für Stück tiefer in die Geheimnisse der PowerShell-Welt. Angefangen beim Start der PowerShell Konsole und Eingabe der ersten Befehle über eine Einführung in die Objektorientierte Programmierung bis hin zum Erstellen von Skripten mit der Powershell. Wie der Titel dieses Buchteils schon sagt, können Sie dies hier in jeder Powershell nutzen – egal welche Version Ihnen gerade zur Verfügung steht. Daher werden Sie sich u. U. an der einen oder anderen Stelle fragen warum eine bessere Möglichkeit hier nicht erwähnt wird. Wenn Sie mögen lunzen Sie doch dann einmal im Inhaltsverzeichnis im Abschnitt Versionsunterschiede, ob dort nicht vielleicht gerade genau Ihr Gedanke beschrieben steht.

2.1     Arbeitsumgebung einrichten

In diesem Kapitel werden ein paar Hintergründe zu PowerShell erläutert. Dieses Kapitel ist nur dann für Sie interessant, wenn Sie mit den Begriffen wie .NET, WMI und COM nichts anfangen können.

Wie bereits im Kapitel Auf welchem Betriebssystemen können Sie die PowerShell wie einsetzen erwähnt setzt die PowerShell auf .NET auf.
.NET who the … is .NET? Oder frei übersetzt: Was ist eigentlich .NET? Ein kleiner geschichtlicher Abriss:
Damals, als ich das letzte Mal von meinem Dinosaurier gefallen bin, da war DOS. Das Disk-Operating-System. Multitasking und so komische Sachen gab es noch nicht. In der Regel war der PC schon mit einer einzelnen Aufgabe hoffnungslos überfordert. Wenn man damals ein Spiel programmieren wollte, musste man das am besten in Assembler (Nullen und Einsen durch die Gegend scheuchen) oder C tun. Das DOS bot einem an über dokumentierte Software Interrupts bestimmte Dinge zu tun, wie z. B. die Grafikkarte anzuweisen Pixel in einer bestimmten Farbe zum Glimmen zu bringen. Als Programmierer konnte man also (in echt natürlich viel umständlicher) einen Befehl schreiben wie: Pixel(100,10,16,16,16) und dann hat das fertig übersetzt Programm nach dem Start einen Bildschirmpunkt an der angegebenen Position und Farbe zum Leuchten gebracht. Das war in der Regel aber immer noch zu langsam für Spiele, von daher hat man sich dann nicht mit dem Betriebssystem über den Funktionsaufruf verständigt, sondern man hat direkt mit der Grafikkarten Hardware über die Darstellung des Punktes verhandelt. Den Code für einen einzelnen Bildschirmpunkt würde wohl hier 3-4 Seiten im Buch mit wilden Befehlsabkürzungen wie z.B. LDA, ROL gefolgt von vielen Zahlen und anderen kryptischen Zeichen ausmachen. Im Laufe der Zeit wurden die PCs schneller und mehr und mehr konnte man die Betriebssystemfunktionen einsetzen um sich viel Tipparbeit zu sparen.
Dann kam die Epoche in der wir noch die Hexen geröstet haben mit Windows for Workgroups, welches Multitasking erlaube. Kurze Zeit darauf wurden wir mit der ersten Office-Version beglückt. Nun hatten die Entwickler von Excel und Word die gleiche Aufgabe, wie z.B. den Datei-öffnen-Dialog. Warum sollte sich das Word Team dazu Gedanken machen wie man von Festplatte Dateien kratzt und das Excel Team auch noch einmal? Dies war die Geburtsstunde der DLL-Dateien, den dynamic-link-libraries. Die DLLs waren keine ausführbaren Programme, sie waren auch keine Programmiersprache, sie waren so etwas Ähnliches wie zuvor die Software Interrupts unter DOS, nur einfacher zu bedienen. Nun konnte der Word-Programmierer einen DLL Aufruf in Form von $a=openfile(„*.doc“) machen und der Excel-Programmierer $a=openfile(„*.xls“). Wie man auf das darunterliegende Dateisystem FAT/NTFS oder gar auf die Hardware (Diskette, Festplatte, etc) zugreift, darum musste sich weder der Excel, noch der Word-Programmierer kümmern. Das hat der Programmierer der DLL für die beiden erledigt, ebenso wie die Anzeige des dazugehörigen Bildschirmdialogs.
Dann kam der Trend der objektorientierten Programmierung und aus einfachen DLLs wurde COM, das Component Object Model. Platt gesagt, eine für alle Anwendungsprogrammierer offenstehende Basis von Funktionen um sich die Arbeit zu erleichtern und einmal geschrieben Code wieder zu verwenden.
Wieder einen Schritt weiter ging dann DCOM, das Distributed Component Object Model, welches die Funktionen dann auch über das Netzwerk für andere PCs bereitstellte.
Mit dem heutigen .NET haben wir dank der OpenSource Unterstützung eine weitere Ergänzung um die plattformübergreifenden Austauschmöglichkeiten. Böse Zungen nennen .NET auch einfach das JAVA von Microsoft.

Die PowerShell selbst wurde auf dem Fundament des .NET-Frameworks erstellt, stellt aber auch die komplette Funktionalität des .NET den Programmierern von PowerShell Skripten zur Verfügung. Durch diese enge Bindung an .NET und die vollständige Weitergabe an den PowerShellSkripter (das werden Sie bald sein) haben Sie dank der .NET-Erweiterbarkeit sogar die Möglichkeit nicht nur eigene PowerShell-Befehle zu erstellen, sondern können sogar das .NET-Framework um Ihre selbst gestalteten Eigenschaften und Funktionen zu erweitern. Was es mit den Eigenschaften und Funktionen auf sich hat können Sie im Kapitel über Objektorientierung nachlesen.

Um die PowerShell noch mächtiger zu machen und auch den Zugang zu Informationen zu vereinfachen hat man noch zwei weitere Objekt-Modelle angeflanscht. Über .NET wäre es zwar möglich Zugriff auf die CPU zu bekommen, aber nicht gerade simpel. Durch die zusätzliche Unterstützung des WMI haben wir hier relativ einfachen Zugriff auf viele Systeminformationen und Einstellungen. WMI=Windows Mord Instrument?! Nein! WMI steht für Windows Management Instrumentation und ist die Microsoft Version des schon länger vorhandenen WBEM-Standards. WBEM steht für Web-based Enterprise Management und kann als Nachfolger des SNMP (Simple-Network-Management-Protokolls) angesehen werden. Einen guten Einstieg in WBEM liefert Wikipedia mit einigen weiterführenden Links zu m Thema unter:
http://de.wikipedia.org/wiki/Web_Based_Enterprise_Management
WBEM bietet einen plattformübergreifenden Zugriff auf grundlegende Systeminformationen und –einstellungen. Angefangen bei Informationen zur Hard- und Softwareausstattung über Lastangaben (z.B. Pakete/Sekunde) bis hin zu Funktionen die Routingtabellen ändern, oder das System rebooten. Wie man das benutzt wird im Kapitel WMI-Objekte erklärt und im Praxisteil finden Sie Anwendungsbeispiele.

Der dritte im Bunde ist das gute, alte COM bzw. DCOM Modell. Die meisten Anwendungen die Sie installieren und auch das Windows Betriebssystem selbst bringen solche COMponenten mit. Wenn Sie z.B. Excel installieren, wird eine Excel COMponente in der Registrierdatenbank veröffentlicht. Die COMponente bringt Ihnen einen einfachen Zugriff auf Excel-Funktionen. Obwohl Sie selbst nicht Excel programmiert haben, bekommen Sie mit Hilfe der PowerShell ganz schnell heraus, wie diese COMponente funktioniert und was Sie alles damit anstellen können. So ist es z. B. möglich damit Excel zu starten und auch gleich eine bestimmte Dokumentvorlage bereit zu stellen, oder eine XLS-Datei nach Ihren Vorgaben zu öffnen. Wie man das grundsätzlich macht wird im Kapitel COM Objekte erklärt und einige Anwendungsbeispiele finden Sie im Praxisteil zu MS-Office.

2.1.1   PowerShell starten und die Version ermitteln

Hier lernen Sie „nur“ wie Sie PowerShell starten können und wie Sie auf einem fremden System herausfinden welche PowerShell Version Ihnen dort zur Verfügung steht. Details zu einem parametrisierten Start werden Sie am Anfang eines guten Buches vergeblich suchen. Wenn Sie also schon einmal eine PowerShell gestartet haben und auch wissen wie man die Versionsnummer ermittelt, ab zum nächsten Kapitel…

Die PowerShell zu starten ist mannigfaltig. Natürlich führen auch hier wieder 1000 Wege zum Ziel. Etwas genauer werden wir uns im Kapitel Grundlagen zur Skriptausführung damit beschäftigen. Daher hier nur kurz die gebräuchlichsten Varianten:

Bei Windows 7, Server 2008 R2 und neueren Versionen finden Sie auf dem Bildschirm links, unten und dort rechts neben dem Windows-Knopf der das Startmenü öffnet das PowerShell Symbol, auf das Sie einfach drauf klicken:

Bei älteren Betriebssystemen, bei denen dieses Icon fehlt, finden Sie die PowerShell im Startmenü unter Alle Programme/Zubehör/Windows PowerShell.

Bei Windows 10 / Server 2016 finden Sie es im Startmenü unter „W“ wie „Windows PowerShell“.

Die folgende Variante klappt auf allen Microsoft Betriebssystemen gleichermaßen gut. Tippen Sie die Windows-Taste und dazu ein ‚R‘ (das sogenannte Run-Command). In die Zeile tippen Sie PowerShell und drücken Enter oder klicken auf den OK-Knopf.

Wenn Sie die PowerShell gestartet haben, ja nach Rechner kann das einen kleinen Augenblick länger dauern, als eine „normale Eingabeaufforderung“ zu starten, gibt es wiederum 2 Arten die Version herauszufinden. Entweder durch Eingabe von $host, oder aber get-host, wie hier in der Abbildung zu sehen:

Sowohl die  PowerShell Variable $host, als auch das Cmdlet get-host liefern jeweils in der 2. Zeile Version: 2.0. Besonders interessant ist aber die Tatsache, dass laut der Titelleiste, Powershell.exe angeblich in einem Verzeichnis v1.0 liegt. Halten wir es demokratisch - die Mehrheit siegt ;-). Die Angaben innerhalb der PowerShell sind korrekt. Lassen Sie sich durch den Pfad des Installationsverzeichnisses nicht ins Bockshorn jagen. Was Variablen und Cmdlets nun genau sind, wird in den weiteren Kapiteln genauer erläutert, hier kann Ihnen dies erst einmal egal sein.

Unter Linux tippen Sie einfach pwsh und wenn Sie PowerShell Scripte starten möchten:

(#!/usr/bin/env pwsh) ScriptName.ps1

2.1.2   Einsetzen von Hilfsmitteln für PowerShell

Sicherlich interessiert es Sie, welche Helferlein Ihnen für die PowerShell zur Verfügung stehen. Daher finden Sie bereits an dieser frühen Stelle eine Aufstellung über die wichtigsten, kostenlosen oder bei kostenpflichtigen Programmen im Lieferumfang enthaltenen Tools. Wenn Sie mögen, können Sie auch dieses Kapitel gerne erst einmal überspringen. Allerdings sollten Sie im Hinterkopf behalten, dass in diesem Abschnitt die Zusatzwerkzeuge erwähnt sind. Die nachfolgende Übersicht erhebt allerdings keinesfalls den Anspruch auf Vollständigkeit!

2.1.2.1   Befehlsumfang erweitern

Der Befehlsumfang von PowerShell kann beliebig erweitert werden, nicht zuletzt sogar durch Sie selbst. Wie das genau funktioniert können Sie im 3. Teil des Buches bei den Versionsunterschieden nachlesen. Einige dieser Befehlserweiterungen sind aber nicht Bestandteil von Windows und müssen daher erst heruntergeladen und installiert werden.

Windows eigene Standard Befehlserweiterungen werden detailliert im 3. Teil des Buches behandelt. Diese beinhalten ab Version 2.0 so interessante Dinge wie Trouble Shooting und Diagnose, Active-Directory-Verwaltung, Steuerung von Servermanager und IIS, um an dieser Stelle nur einmal die interessantesten zu erwähnen.

Je nach installiertem Produkt können die vorhandenen Befehle auch erweitert werden. Dazu zählt z. B. Exchange, SQL und die gesamten System Center Produkte von Microsoft, sowie auch von 3. Herstellern z.B. VMware.

PowerShell management Library for Hyper-V, PowerShellPack und PSCX, die PowerShell Community Extensions wurden bis Ende 2017 als OpenSource Projekt unter http://pscx.codeplex.com/ bereitgestellt. Leider wurde diese aufgrund aktuellerer Möglichkeiten der neuen PowerShell-Versionen eingestellt. D.h. wer tatsächlich noch irgendwo mit XP unterwegs ist, hat leider das Nachsehen und muss endlich über eine Aktualisierung nachdenken.

2.1.2.2   Skripting Werkzeuge

Hier sind Werkzeuge aufgelistet die Sie beim Schreiben von PowerShell Skripten unterstützen.

Notepad ist wohl die simpelste Art PowerShell Skripte zu schreiben und nicht explizit dafür gemacht und von daher weniger empfehlenswert. Dafür ist es aber immer und auf jedem Windows System verfügbar.  Zu finden ist dies im Startmenü unter Alle Programme/Zubehör/Editor oder einfach im Suchfeld des Startmenüs notepad eintippen. Wenn Sie einen Doppelklick auf eine Datei mit der Endung .PS1 machen und kein ISE installiert ist wird auch Notepad mit dem Skript geöffnet.

ISE, oder ausgeschrieben das Integrated Scripting Environment ist Bestandteil der PowerShell ab Version 2.0 und ist ein spezieller Skripteditor für PowerShell Skripte. Zeilennummerierung, Syntax Highlighting, die Hervorhebung durch Einfärbung von Befehlen und Parametern, sowie Debug Funktionen zum Aufspüren von Fehlern in Skripten erleichtern das Entwickeln ungemein. Inzwischen ist es sogar relativ einfach möchlich, den Editor durch eigene PowerShell-Skripte beliebig zu erweitern. Auch in diesem Buch werden wir uns auf dieses Standardwerkzeug verlassen.

PowerGUI von Quest ist noch etwas mächtiger als das ISE der PowerShell 2.0, braucht aber dafür beim Starten etwas länger. Als es noch kein ISE gab, war PowerGui der Skripteditor für PowerShell schlechthin. Vor allem auch durch die Befehlserweiterungen welche z. B. für Active-Directory schon in Version 1.0 zur Verfügung standen hat sich PowerGUI einen Namen gemacht. Aber auch in Version 2.0 der PowerShell kann PowerGUI noch mehr bieten als ISE, z.B. das Anzeigen der Variablenbelegung im Skript. Was Variablen sind, dazu wie schon geschrieben später mehr. PowerGUI stand früher mal unter http://powergui.org/ zum Download bereit. Inzwischen ist es aber von der Internetbühne verschwunden.

PowerShell Scriptomatic kann unter http://www.microsoft.com/download/en/details.aspx?displaylang=en&id=24121 heruntergeladen werden. Sparen Sie sich aber die Mühe, der Name des Werkzeugs lässt mehr erwarten, als es liefert. Ähnlich verhält es sich mit Scriptomatic 2.0 (ehem. WMI Scriptomatic), welches nur Perl, VBS, JScript und Python Abfragen für den WMI Namespace erstellt.

Visual Studio ist auch für PowerShell inzwischen der empfohlene Editor insbesondere in auch in Bezug auf PowerShell Core. Allerdings gibt es auch noch Visual Studio Code, welches schneller startet und auch alles mitbringt, was man sich von einem modernen PowerShell Scripting Tool wünscht.

Natürlich gibt es noch unzählige weitere Editoren wie Eclipse oder Visual-Studio von Microsoft, die Sie mit entsprechenden Plugins für PowerShell-skripting erweitern können. Alle hier aufzulisten, wäre ein eigenständiges, wenn auch kleines Buch.

2.1.2.3   Internet

Hier sind einige Internetadressen angegeben, die Ihnen im Alltag bei der Skripterstellung weiterhelfen können.

Auf http://www.codeplex.com/ werden Sie noch viele weitere „Schätzchen“ über die unter Befehlsumfang genannten Erweiterungen hinaus finden. Dazu geben Sie einfach im Suchfeld auf der Website PowerShell ein und schon werden Sie mit Erweiterungen überschüttet: SharePoint, Admin-Tools, Firewall, u.v.a.m.

http://poshcode.org/ bietet viele zusätzliche PowerShell Befehle, Skripte und Tipps und Tricks. Bevor man sich selbst an die Lösung eines Problems macht ist es auf jeden Fall mal einen Blick wert, ob hier vielleicht das Problem schon gelöst wurde und Sie sich einfach der Skripte bedienen und ggf. an Ihre Bedürfnisse anpassen. Die Website ist auf Englisch.

Von Microsoft bekommen wir den Einstieg zu allen möglichen PowerShell Themen an dieser Stelle: http://technet.microsoft.com/de-de/scriptcenter/dd742419. Dort ist auch ein Link zum Microsoft Script Repository für PowerShell: http://technet.microsoft.com/de-de/scriptcenter/dd742419.

Weiterhin gibt es noch die PowerShell Gallery, die ebenfalls von Microsoft betrieben wird. Hier finden Sie inzwischen jede Menge Skripte und Module für den Alltagsgebrauch. Wenn Sie mal was tolles geskriptet haben und es gerne dem Rest der Welt zugänglich machen möchten, können Sie es auch dort veröffentlichen.

Auch auf meiner Seite http://www.martinlehmann.de/wp/category/scripting/powershell/ kommen immer wieder neue Tipps zu PowerShell, aber auch anderen Themen der IT zusammen. Gerne können Sie meine neuesten Artikel als RSS-Feed abonnieren.

2.1.3   Anpassung der Standard Shell

An dieser Stelle finden Sie Informationen darüber wie Sie auf die Schnelle das Aussehen der PowerShell Oberfläche anpassen. Ich bin auch nicht mehr der Jüngste und hätte vielleicht gerne die Schrift etwas größer. Wie man diese Einstellungen zur Systemvorgabe macht, dass es bei jedem Aufruf der PowerShell automatisiert erfolgt, wird im Kapitel Profil Skripte behandelt. Wer mit den Standardeinstellungen aber zufrieden ist oder diese Möglichkeiten bereits, von der „DOS-Box“ her kennt, kann auch gleich zum nächsten Kapitel wechseln.

Wenn Sie die PowerShell gestartet haben finden Sie in der linken, oberen Fensterecke der PowerShell das Systemmenü, wenn Sie das PowerShell Symbol anklicken.

Bei dem Punkt Eigenschaften können Sie das Dialogfeld Eigenschaften von „Windows PowerShell“ durch Anklicken (wenn ich es nicht extra dazu schreibe ist immer die linke Maustaste gemeint) aufrufen.

Nach Karteikartenreitern sortiert finden Sie hier die Einstellmöglichkeiten:

Optionen enthält Einstellmöglichkeiten für die Größe des Textcursors (der Blinkemann an der Position wo Sie Ihren Text eintippen), für den Befehlsspeicher (History) und Berabeitungsoptionen, wie den Einfüge- oder Überschreibmodus, sowie den QuickEdit-Modus. Ist bei QuickEdit-Modus ein Häkchen gesetzt, fügen Sie direkt den Zwischenablage Inhalt an der Textcursorposition ein, wenn Sie die rechte Maustaste in der PowerShell Konsole betätigen. Wenn nicht, dann erscheint bei Rechtsklick erst ein Kontextmenü, indem Sie auswählen können, was Sie tun möchten. Die wenigsten Leser werden wohl in diesem Bereich Anpassungen vornehmen.

Schriftart ist da schon wesentlich interessanter. Hier können Sie die Schriftart und Größe festlegen. Die Größe dürfte hier wohl die interessantere Einstellmöglichkeit sein. Die Schriftart auf eine schicke Handschrift umzustellen, werden Sie sicher ganz schnell wieder bleiben lassen, wenn Sie merken, dass Sie sich mehr auf das Entziffern der Schrift konzentrieren müssen als auf das, was Sie machen möchten.

Layout ermöglicht Ihnen all das, was Sie mit der Maus direkt am Fenster nicht schon viel schneller erledigt hätten. Abgesehen von der Fensterpuffergröße sind die Einstellungen hier recht überflüssig. Mit der Fensterpuffergröße können Sie die interne Größe des Fensters festlegen. Ist Ihre Fenstergröße auf 100 Zeichen Breite eingestellt, aber der Puffer auf 200, so taucht am unteren Bildschirmrand eine Bildlaufleiste auf, da nicht das gesamte interne Fenster dargestellt werden kann. Das kann sehr tückisch sein, wenn Konsolenausgaben rechtsbündig erfolgen. Diese würde man erst sehen, wenn man die horizontale Bildlaufleiste verwendet um den rechten „internen“ Konsoleninhalt zu sehen. So denkt man oft im ersten Moment: „Hä?! Wieso macht der Befehl nichts? Nicht einmal ein Fehler ist zu sehen? Seltsam!“ In solchen Momenten sollte Ihnen schlagartig einfallen, dass vielleicht irgendwie der Fensterpuffer größer ist, als die Fensterbreite! Blick zum unteren Fensterrand. Ist da eine Bildlaufleiste? Wenn ja, haben wir sicher einen größeren Puffer, als das Fenster breit ist. Mit solchen Problemen kann man sich Stundenlang beschäftigen, wenn man da nicht daran denkt.

Farben ermöglicht Ihnen die Schrift- und Hintergrundfarbe der Konsole, sowie von Popups festzulegen. Vorsicht mit der Ostfriesischen Flagge!!! Weißer Adler, auf weißem Hintergrund ;-).

2.1.4   Kontrollfragen

Wie können Sie PowerShell starten?

Wie finden Sie die Version der PowerShell heraus, die auf dem PC installiert ist?

Wie können Sie die Hintergrundfarbe der aktuell geöffneten PowerShell ändern?

Link zu den Lösungen der Arbeitsumgebung

2.2     Übersicht der grundlegenden Befehle

So jetzt geht es aber endlich los! Ihre ersten Befehle an die PowerShell. Bis hier habe ich immer das Wort Befehl verwendet. Zeit für ein bisschen Besserwisserei. Bei PowerShell gibt es keine Befehle! Hier nennt sich ein Befehl Cmdlet (ausgesprochen: Kommandlet). Wenn man es ganz genau nimmt, dann sind Cmdlets eine besondere Form von Befehlen, nämlich die ureigenen PowerShell Anweisungen. Darüber hinaus gibt es dann noch Applikationen, Funktionen, Aliase u.v.a.m., aber wir wollen am Anfang ja nicht gleich übertreiben.

2.2.1   Eingaben an der PowerShell Konsole

Wie mache ich der PowerShell klar, was ich von ihr will?

Zunächst einmal können Sie sich auf der PowerShell wie zu Hause fühlen, sprich Sie können die PowerShell genauso verwenden, wie Sie bisher mit cmd.exe oder bash gearbeitet haben.

Das bedeutet Sie können Befehle wie dir, del, mkdir, ls oder rm direkt auch unter der PowerShell verwenden. Hüten Sie sich aber vor Schaltern wie /s unter DOS oder –r bei Linux. Warum, erfahren Sie im folgenden Kapitel Kompatibilität. Wenn Sie Pfadangaben machen, können Sie die Windows oder Linux Variante benutzen. Beispielsweise können Sie einen Verzeichniswechsel entweder so:
cd \windows
oder so
cd /windows
durchführen. Schon gewusst? Inzwischen geht das sogar in der cmd.exe! Da Sie sich in 99% der Fälle wohl an einem Windowssystem befinden würde ich daher raten, sich auch an der Windows Schreibweise zu orientieren. Weil wir gerade bei Unterschieden zwischen Linux und Windows sind…Groß- und Kleinschreibung dürfen Sie in der Regel ignorieren. Ob Sie also DIR, dir oder Dir tippen macht hier keinen Unterschied.

Probieren Sie die Ihnen bekannten Befehle doch gleich einmal aus.

Es gibt eine Tabulatorvervollständigung wie in den anderen beiden, oben genannten Konsolen auch. Wen Sie den Begriff Tabulatorvervollständigung bereits kennen, dürfen Sie den folgenden Absatz überspringen.

Tabulatorvervollständigung ist unter Linux ein alter Hut, auf Windowssystem kam dieses Feature irgendwann ab XP in die Kommandozeile. Bei PowerShell war dies wie bei der bash von Anfang an dabei. Bei der Tabulatorvervollständigung müssen Sie nur die ersten Buchstaben eines Kommandos, Parameters oder Verzeichnisses tippen bis es nicht mehr mit einem anderen Kommando, Parameter oder Verzeichnis verwechselt werden kann. Wenn Sie dann die Tabulator-Taste (das ist die Taste mit den zwei Pfeilen in entgegengesetzter Richtung, ganz links auf Ihrer Tastatur. Normaler Weise neben dem Buchstaben  Q) drücken, wird das begonnene Wort automatisch vervollständigt. Sie dürfen aber auch schon Tab (Kurzform von Tabulator) drücken, wenn der Begriff noch nicht eindeutig ist. In diesem Fall wird zunächst die im Alphabet zuerst stehende Variante angezeigt, wenn Sie noch einmal Tab drücken die Zweite usw… Probieren Sie das aus indem Sie nur:
get-
tippen und dann drücken Sie so lange die Tab Taste bis das Cmdlet Get-ChildItem angezeigt wird und dann drücken Sie Enter.

Mit den Cursor-Tasten Links, Rechts…kennt jeder. Aber haben Sie mal probiert dabei die Strg-Taste (auch Control-, Steuerungs- oder String-Taste genannt. Diese befindet sich normaler Weise ganz links und rechts unten auf Ihrer Tastatur. Sie ist wie die Taste für Großbuchstaben 2 x vorhanden) festzuhalten? Damit können Sie Wortweise weiter springen, was bei langen Befehlsketten recht hilfreich sein kann.

Cursor-Tasten hoch, runter lässt Sie in der Befehlshistorie blättern. Für die bash verwöhnten: Nein, Strg+R geht nicht und die Historie wird normaler Weise auch nicht in einer Datei gespeichert. Konsole zu, Konsole auf=Historie weg. Am nächsten kommt Strg+R einer Bash (kann in der Historie suchen) noch die Taste F7 (über den Zahlen, so ziemlich in der Mitte), welche die Historie als nummerierte Liste zum Auswählen anzeigt. Wenn Sie die Nummern schon auswendig kennen, können Sie auch F9 tippen und direkt die Nummer angeben (blind). Alt+F7 löscht die Historie. Die Alt-Taste finden Sie links von der Leertaste (das ist wiederum die Große, unten in der Mitte ;-).

Mit der Taste Pos1 springen Sie an den Zeilenanfang und mit Ende zum Zeilenende wie im Word. Diese beiden Tasten finden Sie normaler Weise entweder beim Ziffernblock, oder in einem eigenen Block zwischen dem Ziffern- und dem Buchstaben-Block über den Cursor-Tasten.

Mit Strg+Pos1 löschen Sie alles von der aktuellen Position bis zum Zeilenanfang. 3 x dürfen Sie raten, was Strg+Ende macht.

Wenn Sie etwas in die Zwischenablage bringen möchten, markieren Sie einfach mit der Maus (Linke Maustaste an einer Stelle festhalten und wo anders hin ziehen, dann Maustaste wieder los lassen) den gewünschten Textbereich. Durch einen anschließenden Rechtsklick wird der eben markierte Text in die Zwischenablage befördert. Wenn Sie jetzt noch einmal Rechtsklicken wird der Inhalt der Zwischenablage an der Position des Textcursors (nicht der Maus) eingefügt. Leider kann man nur rechteckige Bereich markieren und nicht wie in Linux Shells üblich zeilenweise. Wenn das Markieren nicht klappt, ist der Quickedit Modus nicht aktiviert, dann müssen Sie erst einen Rechtsklick machen und den Punkt Markieren im Kontextmenü auswählen (siehe Kapitel Anpassung der Standard Shell, falls Sie das übersprungen haben). Die Standard Shortcuts der Zwischenablage funktionieren hier nicht. Also weder Strg+C, E oder V noch wie in der bash Strg+Shift+C, E oder V. Shift+PageUp/Down geht auch nicht…hey, das ist ein Windowssystem…die Maus beißt nicht ;-). Shift ist die Taste um Großbuchstaben zu schreiben. Alternativ wird sie auch Umschalttaste oder einfach Großschreibtaste genannt. Die PageUp/Down Taste ist auf deutschen Tastaturen oft mit dem Wort Bild beschriftet und ein Pfeil nach oben bzw. unten ist darauf abgebildet.

Den Überschreibmodus können Sie jeder Zeit durch Drücken der Einfügen-Taste (Diese befindet sich in der Nähe der POS1- und Ende-Tasten. Manchmal steht nur Einf darauf, aber bitte nicht mit der Entf-Taste verwechseln!) aktivieren oder deaktivieren. Je nachdem werden die Zeichen die sich rechts von Textcursor befinden bei tippen weitergerückt oder überschrieben. Wie man die eine oder andere Variante zur dauerhaften Systemvorgabe macht steht im Kapitel Anpassung der Standard Shell.

2.2.2   Kompatibilität

Hier wird offengelegt, wie kompatibel die PowerShell mit anderen Shells ist.

Zu PowerShell 1.0 Zeiten hat Microsoft ganz gerne behauptet, die PowerShell sei kompatibel mit der herkömmlichen Eingabeaufforderung, ja und sogar klassische UNIX-Befehle lassen sich in der PowerShell ausführen. Bei genauerem Hinsehen fällt aber auf, dass dies nur die halbe Wahrheit ist.

Starten Sie doch einmal die PowerShell Konsole und tippen Sie dort ein paar Befehle ein, die Sie von DOS oder LINUX her kennen, wie z.B.:

dir
ls
ps
mkdir test
rm test

Klappt doch alles wunderbar, oder? Dann tippen Sie doch einmal:

dir *.txt /s.

Die Eingabeaufforderung schluckt den Befehl ohne Murren und verarbeitet ihn auch korrekt. Die PowerShell (auf der Abbildung im Hintergrund) hingegen spuckt eine Fehlermeldung aus. Der Befehl hätte vom aktuellen Verzeichnis C:\Users\Martin\test\ aus, im aktuellen und allen Unterverzeichnissen (wegen des Parameters /s) nach Dateien mit der Erweiterung .txt suchen sollen. Mit der von PowerShell dargestellten Fehlermeldung kommt man auch erst einmal nicht weiter, bis man dann vor lauter Verzweiflung einmal den ersten Begriff aus der Fehlermeldung get-childitem eintippt. Dabei passiert dasselbe, wie bei dir oder ls. Geben Sie nun

alias [dl]*

ein, stellen Sie in der Ausgabe fest, dass sowohl dir vom DOS, als auch ls vom LINUX einfach nur ein Alias (ein Hinweis oder alternativer Name) auf das PowerShell Cmdlet Get-ChildItem sind. Was ein Alias ist und was man damit anstellen kann finden Sie im Kapitel Alias Cmdlets. Da alle 3 Kommandozeilenbefehle naturgemäß vollkommen unterschiedliche Schalter haben, können Sie sich an Ihren 5 Fingern abzählen, dass Kompatibilität nicht einfach über einen Alias realisiert werden kann. Bei dir ist der Schalter /s, bei ls ist es –r und bei dem eigentlichen PowerShell Cmdlet –recurse. Da dir und ls nur Verweise (Aliase) auf das PowerShell Cmdlet sind, wird bei deren Eingabe quasi Get-ChildItem /s ausgeführt und dies führt eben zu Fehlermeldungen, da /s gar nicht als Parameter verstanden wird, weil Parameter bei PowerShell mit dem – Zeichen eingeleitet werden.

Zusammenfassend kann man also festhalten, dass die PowerShell definitiv eine komplett neue Shell ist, die nur sehr eingeschränkt Kompatibilität mit bestehenden Shells aufweist. Wer schon davon geträumt hat seine alten bat, cmd oder sh Skripte in der PowerShell 1:1 einsetzen zu können, muss sich von dem Gedanken leider verabschieden. Jeder einzelne Schalter in einem Skript muss überprüft werden, wie er in PowerShell umgesetzt werden kann. Dies ist offensichtlich so aufwändig, dass es bis heute meines Wissens nach keinen automatisierten Skriptkonverter gibt. Wenn Sie nach der Lektüre dieses Buches einmal Langeweile haben, können Sie ja einen mit PowerShell schreiben ;-).

2.2.3   Anatomie des PowerShell Cmdlets

Das wir nicht kompatibel sind tut erst einmal weh, aber es ist gut besser zu werden. Man muss eben ab und zu auch einmal ein bisschen leiden, um es hinterher besser zu haben. Ein paar Ihrer alten Gewohnheiten dürfen Sie ja auch erst einmal beibehalten.

Nun ist es Zeit für ein kleines didaktisches Wunder. Sie kennen eigentlich schon die PowerShell Cmdlets, Sie wissen es nur noch nicht!

Ein PowerShell Cmdlet sieht syntaktisch immer gleich aus:

Verb-Noun Anpassung Art

Oder als Praxisbeispiel:

Get-ChildItem Path c:\

Das Verb (oder Tätigkeitswort) bestimmt was Sie tun möchten. Möchten Sie etwas starten ist das Verb: Start. Möchten Sie etwas beenden ist das Verb: Stop. Möchten Sie Informationen haben ist das Verb: Get. Möchten Sie etwas einstellen ist das Verb: Set. Möchten Sie etwas Neues erstellen, ist das Verb: New. Raten Sie mal wie Sie z.B. etwas (de-)aktivieren! Ja, richtig! Enable bzw. Disable.

Das Noun (zu Deutsch: Hauptwort) gibt an, mit was Sie das im Verb festgelegte tun möchten. Also z.B. mit einem Dienst = Service, einem Prozess = Process oder der Ereignisanzeige = Eventlog.

Eigentlich brauchen Sie nur zu überlegen, was Sie tun möchten und die englischen Begriffe einsetzen – fertig! So, Sie wissen also nicht was ich meine, nein? Überlegen Sie doch einmal, wie das PowerShell Cmdlets heißen könnte, um den Computer herunterzufahren oder zu rebooten. Noch ein kleiner Tipp: Das Noun (Hauptwort) wird niemals in der Mehrzahl angegeben. Na, sind Sie darauf gekommen? Die Cmdlets lauten Stop-Computer, um den Computer herunterzufahren und Restart-Computer für den Reboot.

Am Anfang habe ich mich sehr über die langen PowerShell Cmdlet Namen geärgert. Aber durch diesen Aufbau ist es eben der Instinkt, der Ihnen sagt wie der Befehl lautet und nicht das Gedächtnis in das Sie zuerst jede Menge kryptische Kürzel reinprügeln muss. Durch die Aliase können Sie sich dann die Tipparbeit verkürzen. Da ls ein Alias auf Get-ChildItem ist und somit gleichzusetzen, können Sie im Laufe der Zeit immer mehr Alias einsetzen (auswendig lernen) um schneller beim tippen zu werden. Wobei Sie dir oder ls bestimmt nicht großartig büffeln müssen;-).

Die Anpassung wird im Fachjargon Parameter, Switch oder Schalter genannt. Mit den Schaltern legen Sie zusätzliche Informationen fest, die Sie dem Cmdlet mitteilen möchten, damit es seine Aufgabe eben angepasst verrichten kann.  Manche Schalter werden einfach nur angegeben, anderen muss die Art oder Form der Anpassung mitgeteilt werden. Wenn Sie nur Get-ChildItem tippen, haben Sie sich schon einmal Gedanken gemacht wie die PowerShell das auffasst? Stellen Sie sich vor Ihre Frau, oder Ihr Mann ruft Ihnen zu „Hol, die Kinder!“. OK, wenn ich zwei Töchter habe die gerade im Kindergarten sind, dann sind wohl die gemeint. Aber wenn Sie selbst gar keine Kinder haben, welche sollen Sie dann holen? Wie Sie sehen, hat’s die PowerShell schon schwer mit uns Knochensäcken. Wenn wir einfach nur Get-ChildItem schreiben, nimmt die PowerShell vor lauter Verzweiflung (bevor’s daheim wieder Ärger gibt) die nächstbesten Kinder mit, die Sie bekommen kann und das sind nun einmal diejenigen welche gerade im aktuellen Verzeichnis (am Straßenrand) stehen.

Hinweis: Nein, das Buch soll nicht zur Kindesentführung ermutigen! Das hier passiert nur im Computer, nur virtuell. Bitte holen Sie die Bits und Bytes nicht ins richtige Leben. Die ganzen Psychopaten die da draußen rumlaufen haben offensichtlich eine innere Stimme, die unpräzise Anweisungen gibt. Zu denen wollen Sie doch nicht gehören?

Also damit die PowerShell nicht wegen Kindesentführung in den Knast geht, sollten Sie als gute(r) Ehe(frau|mann) Ihre Anweisung möglichst präzise formulieren. Dafür haben Sie die Schalter. Also ein Get-ChildItem Path c:\ entspricht in etwa der Anweisung „Hol die Kinder, von Oma ab!“ Die Oma ist hier unser c:\. Nicht das da jetzt was durcheinander gerät, mit der eingangs erwähnten Syntax. Oma’s sind immer artig, oder? Die Oma=c:\=artig ;-)

Damit kommen wir zum ersten heiligen Cmdlet. 5 davon gibt es. Wenn Sie die kennen, haben Sie schon fast gewonnen.

2.2.3.1   Get-Command

Get-Command ist das 1. heilige Cmdlet. Mit dem Get-Command können Sie sich alle Cmdlets anzeigen lassen, die PowerShell kennt. Falls Sie nicht schon alleine drauf gekommen sind wie das entsprechende Cmdlet heißt, schauen Sie doch einfach einmal in diese Liste. Das Get-Command hat natürlich auch einen Sack voll Parameter. Die beiden wichtigsten will ich hier erwähnen und die sollten Sie sich auch unbedingt für die tägliche Arbeit merken.

Mit dem Schalter –Verb können Sie die ausgegebene Liste auf ein bestimmtes Verb einschränken.

Get-Command –Verb Start

zeigt nur noch die Cmdlets an die etwas starten.

Get-Command –Verb *et

zeigt alle Cmdlets die beim Verb mit et aufhören. Sie dürfen also auch gerne * als Platzhalterzeichen einsetzen. Die Liste würde nun alle Cmdlets mit Get, Set und ein Reset mogelt sich auch noch mit rein, anzeigen.

Mit dem Schalter –Noun können Sie dasselbe mit dem Hauptwort tun. Möchten Sie also wissen was man alles mit Diensten anstellen kann, dann tippen Sie einfach einmal:

Get-Command –Noun Service

und schon haben Sie alle Cmdlets zur Verwaltung von Diensten.

Eigentlich kennen Sie nun schon alle PowerShell Cmdlets. Jetzt müssen wir uns nur noch um den Feinschliff kümmern. ;-)

2.2.4   Hilfefunktionen der PowerShell

Get-Help ist das 2. heilige Cmdlet. Wenn Sie mit Get-Command herausgefunden haben welches Cmdlet für Sie interessant sein könnte, möchten Sie sicherlich wissen wie das jeweilige Cmdlet denn nun genau tickt. Dabei hilft uns Get-Help („Hol‘ Hilfe“) weiter. Im Gegensatz zur restlichen Hilfe unter Windows, die von Version zu Version schlechter wird, ist die Hilfe hier in der PowerShell, das Beste was ich je von einem Computer an Erklärung angeboten bekam. Diese Hilfe ist sogar so klasse, dass dieses Buch auf eine Befehlsreferenz komplett verzichten kann. All das bringt die PowerShell schon von Haus aus mit. Warum also ein Buch neben dran legen, wenn Sie schon alles „always at your fingertips“ haben? Leider gilt das nicht mehr für Version 3.0. Heute muss ja alles Cloud und Online sein. So auch die PowerShell Hilfe. Das meiste, hier beschriebene wird also unter Version 3.0 zumindest in der Deutschen Fassung so zunächst einmal nicht funktionieren. Für die Besonderheiten in der Version 3.0 lesen Sie bitte den Abschnitt Hilfe im Kapitel PowerShell 3.0.

Um Hilfe zu einem Cmdlet zu erhalten können Sie hier nicht einfach /? oder –help hinter das jeweilige Cmdlet schreiben, sondern hier schreiben Sie das Cmdlet Get-Help vor das Cmdlet zu dem Sie gerne eine Erklärung hätten. Beispielsweise so:

Get-Help Get-Process

Die Ausgabe gliedert sich für jedes Cmdlet gleichermaßen in 6 Abschnitte:

Name gibt einfach den Namen des Cmdlets wieder, für das Sie eine Erklärung haben möchten. Wenn Sie hinter Get-Help einen Alias (mehr dazu im nächsten Kapitel) angegeben haben, sehen Sie hier in der Beschreibung aber den Namen des Cmdlets auf den der Alias zeigt. Dies ist auch eine Möglichkeit herauszubekommen, welches Cmdlet von einem Alias verwendet wird.

Übersicht beschreibt in einem knappen Satz was dieses Cmdlet für Sie machen kann. Wenn das nicht Ihren Vorstellungen entspricht, schauen Sie doch noch einmal bei den anderen Cmdlets mit Get-Help rein, die Ihnen das Get-Command zu Ihrer Suche angezeigt hat.

Syntax listet auf wie der Befehl korrekt mit zusätzlichen Informationen versorgt wird. Im nächsten Abschnitt kommt gleich eine detaillierte Erklärung dazu.

Beschreibung liefert ähnlich wie Übersicht eine Information was ich mit dem Cmdlet machen kann, nur detaillierter als in der Übersicht.

Verwandte Links listet Cmdlets auf, die mit diesem verwandt sind. Also Cmdlets mit ähnlichen Funktionen. Sollte dieses hier von der Übersicht oder Beschreibung her Ihren Bedürfnissen nicht gerecht werden, lesen Sie doch einmal die Hilfe zu diesem Cmdlets. Eines wird schon passen. Darüber hinaus finden Sie weiterführende Dokumentation teilweise auch noch in Form von Hyper-Links die Sie Ihrem Webbrowser übergeben können für weiterführende Recherchen.

Hinweise ist ein Standardabschnitt der unter jedem Cmdlet so ziemlich den gleichen Inhalt hat und kann daher ignoriert werden. Zumindest, wenn Sie weiter im Buch gelesen haben ;-).

Im Großen und Ganzen ist damit schon fast alles gesagt. Schauen wir uns aber die Syntax noch etwas genauer an.

Im Falle unseres Beispiels mit Get-Process stehen da gleich 3 Zeilen. Fangen wir einmal mit der 1. Zeile an. Zuerst kommt natürlich der Name des Cmdlet, also Get-Process. Darauf folgt [[-Name] <String[]>]. Die eckigen Klammern sollen uns klar machen, dass die enthaltene Angabe optional ist. Sie müssen dies also nicht schreiben, damit der Befehl irgendetwas macht. Geben Sie z.B. einfach nur Get-Process ein bekommen Sie eine Liste mit allen Prozessen. Dann sollte auch klar sein, warum bei diesem Cmdlet alle weiteren Parameter auch in eckigen Klammern stehen. Sie müssen nämlich gar keine Parameter eintippen.

Wenn Sie nun die äußeren eckigen Klammer wegnehmen bleibt noch [-Name] <String[]>. Hier fällt auf, dass –Name noch einmal eckig geklammert ist, String jedoch in <> eingeschlossen ist. Das soll Sie darauf hinweisen, dass auch der Parameter -Name komplett entfallen kann und Sie einfach einen Prozessnamen oder einen Teil davon übergeben können z.B. so:

Get-Process Power*

Auch hier sind offensichtlich wieder Platzhalterzeichen (das *) zulässig. Der Befehl liefert Ihnen dann nur noch Ihren PowerShell Prozess, statt der kompletten Prozessliste. So können Sie also ganz schnell und einfach nach einem bestimmten Prozessnamen suchen. Aber Vorsicht mit den Platzhaltern! Wenn Sie nun auch PowerPoint gestartet hätten, wäre bei diesem Namensfilter auch der PowerPoint Prozess mit in der ausgegebenen Liste.

Wenn wir die 3 Syntax Zeilen miteinander vergleichen stellen wir fest, dass in den unteren beiden Varianten –Id und –InputObject nicht eckig geklammert sind. Das bedeutet, wenn Sie mit Get-Process nicht nach dem Namen des Prozesses gehen möchten, sondern lieber die Prozessnummer angeben wollen, dann müssen Sie den Schalter –Id unbedingt (keine eckigen Klammern!) gefolgt von der Prozessnummer angeben. Tun wir das nicht, dann würde PowerShell in der Spalte Name nach der Nummer suchen, wie man im folgenden Screenshot sieht:

Grundsätzlich wäre die PowerShell in der Lage anhand des übergebenen Datentyps die Zuordnung für die jeweilige Syntax zu treffen. In diesem Fall funktioniert es aber nicht und die Hilfe weißt uns ja auch darauf hin.

Apropos Datentyp… bei den 3 Varianten sehen wir jeweils beim ersten Parameter <String[]>, <Int32[]> und <Process[]>. String soll uns klar machen, dass hier ein Text erwartet wird, Int32 deutet auf eine Zahl hin und Process auf ein Objektyp vom Typ Process. Mehr über Datentypen erfahren Sie im Kapitel Objekt-Typen identifizieren. Von daher soll das an dieser Stelle erst einmal nur ein Hinweis sein, dass es zwischen Zahlen und Texten einen Unterschied gibt. Die eckigen Klammern ohne Inhalt hinter dem Datentyp sollen Ihnen an dieser Stelle sagen, dass Sie mit Komma getrennt auch gerne mehrere Angaben machen können. So würde

Get-Process Power*,expl*

sowohl den PowerShell Prozess, als auch Ihren Windows-Explorer Prozess liefern, der Ihren Desktop bereitstellt.

Dann sind Ihnen sicher auch die Schalter aufgefallen denen keine Angabe zum Datentyp folgt. Hier sollten Sie aber besser einmal

Get-Help Get-ChildItem

ausführen. Hier sehen Sie als datentyplosen Parameter –Recurse. Er sorgt bei Get-ChildItem für das von dir bekannte /s oder von ls das –r. Sprich entweder will ich nur im angegebenen Verzeichnis suchen, oder auch in Unterverzeichnissen. Fehlt der Parameter –Recurse wird nur im angegebenen Verzeichnis gesucht. Wird –Recurse angegeben sucht Get-ChildItem auch in Unterverzeichnissen.

Die Parameter Namen müssen nicht vollständig eingetippt werden, sondern nur so weit bis diese eindeutig sind. Als Beispiel soll uns hier –Recurse von Get-Childitem dienen. Alle diese Schreibvarianten sind zulässig:

Get-Childitem –Recurse
Get-Childitem –recurse
Get-Childitem –Recurs
Get-Childitem –recur
Get-Childitem –Rec
Get-Childitem –re
Get-Childitem –R
ls -r

Gerade die letzte Variante lässt uns nun wieder Linux Kompatibilität vermuten, aber es ist einfach nur ein “dummer Zufall”. Mehr zur Beziehung zwischen ls und Get-ChildItem verdeutlicht das nachfolgende Kapitel über Alias Cmdlets.

Ganz am Ende der Parameterliste finden Sie immer noch einen Eintrag Common Parameters. Diese Common Parameters sind Schalter, die für alle Cmdlets gleichermaßen gelten. Wir werden an verschiedenen Stellen des Buches auf ein paar interessante CommonParameters eingehen. Wenn Sie allerdings jetzt schon einmal sehen möchten was es alles für CommonParameters gibt tippen Sie Get-Help about_CommonParameters. Zugegeben eine etwas seltsame Syntax. Das ist aber Absicht, denn es soll sich von der normalen Cmdlet Erläuterung abheben. Wenn Sie nur Get-Help about eintippen, bekommen Sie eine Auflistung über sämtlichen „Aboutfiles“. Die Aboutfiles erklären alles, was nicht mit einem bestimmten Cmdlet zu tun hat, wie beispielsweise die CommonParameters, die ja für alle Cmdlets gleicher Maßen gelten, aber auch wie man Variablen oder Arrays zurechtkommt, wie die PowerShell rechnet, wie man Werte miteinander vergleicht, wie man Skripte erstellt u.v.a.m. Dazu müssen Sie lediglich Get-Help about_ und nach dem Tiefstrich den Begriff den Sie aus der Liste von Get-Help about (ohne Tiefstrich) entnehmen.

Auch das Get-Help Cmdlet hat Parameter. Hier die wichtigsten:

Mit –Example bekommen Sie Beispiele angezeigt inkl. Erklärung was das Beispiel genau macht.
Mit -Detailed bekommen Sie zu den Parametern noch eine prima Erklärung.
Mit –Full bekommen Sie das volle Programm. Also Parameter Erklärung (noch etwas mehr Info als bei –Detailed) und die Beispiele.

Weiterhin ist es möglich mit Platzhalter-Zeichen die komplette Hilfe zu durchsuchen, z.B.:

Get-Help *WSMAN*

Zeigt Ihnen alle Hilfequellen in denen die Bezeichnung WSMAN vorkommt an.

Wenn Sie noch mehr über Get-Help wissen möchten, können Sie auch gerne Get-Help Get-Help ausprobieren.

Auch bei Get-Help gibt es Aliasnamen, wie im folgenden Kapitel beschrieben, allerdings ist hier eine kleine Besonderheit zu beachten. Help ist kein Alias auf Get-Help, sondern eine Funktion! Auf den ersten Blick liefern Help und Get-Help die gleichen Ergebnisse. Das Get-Help Cmdlet läßt den Hilfetext am Bildschirm durchfluschten. Die Help Funktion hingegen macht nach jeder Bildschirmseite eine Pause und wartet auf das Drücken der Leertaste bevor die nächste Bildschirmseite angezeigt wird. Sollten Sie vorzeitig die seitenweise Darstellung verlassen wollen können Sie die Hilfe mit Strg+C oder einfach nur Q abbrechen. Auf die Funktion Help gibt es allerdings einen Alias und zwar man. Daher führen man und Help eine Seitenweise Darstellung durch, während das eigentliche Cmdlet Get-Help den Hilfeinhalt am Stück ausgibt.

2.2.5   Alias Cmdlets

Ein Alias ist ein alternativer, 2. oder 3. Name für Cmdlet. Da Cmdlets wie im vorangegangenen Kapitel dargestellt meist etwas längere Tipparbeit erfordern, haben Sie die Möglichkeit Aliase einzusetzen und damit die Cmdlets abzukürzen. Wenn Ihnen das Wort Alias nichts sagt, können Sie dieses Wort mit Platzhalter, Synonym, Pseudonym, Akronym oder Verknüpfungen auf ein Cmdlet gleichsetzen. Suchen Sie sich den Begriff raus, den Sie am ehesten damit in Verbindung bringen. Natürlich gibt es Bedeutungsunterschiede zwischen den Worten, aber wir wollen hier ja keinen Deutschunterricht machen, sondern PowerShell vermitteln.

2.2.5.1   Alias Definitionen abfragen

Mit Hilfe von

Get-Alias

können Sie sich eine Liste der bereits definierten Aliase anzeigen lassen.

Auch hier gibt es natürlich wieder die Möglichkeit Parameter anzugeben. Mit

Get-Alias –Definition Get-ChildItem

Bekommen Sie alle Aliase angezeigt, die auf Get-Childitem deuten. Es ist also zunächst einmal egal, ob Sie Get-ChildItem, dir, gci oder ls schreiben. Alle listen Ihnen den aktuellen Verzeichnisinhalt auf. Probieren Sie das ruhig einmal aus.

Das ist übrigens auch der Grund warum bei dir *.txt /s, im Kapitel Kompatibilität, in der Fehlermeldung Get-ChildItem angezeigt wurde. Da dir einfach nur auf Get-ChildItem verweist, wurde entsprechend nicht der DOS-Befehl dir ausgeführt, sondern das PowerShell Cmdlet Get-ChildItem.

Get-Alias dir

also ohne den Schalter –Definition macht die Zuordnung genau umgekehrt. Statt zu einem Cmdlet alle Aliase anzuzeigen, wird ausgegeben welches Cmdlet hinter einem Alias steckt. Noch kürzer könnten Sie einfach alias dir schreiben, oder für die Linux Freaks alias ls. Für Sie selbst auf der Kommandozeile ist das ok. Aber in Skripten an denen vielleicht sogar gemeinsam mit Kollegen arbeitet ein absolutes „no-go“. Dort sollten Sie schön brav Get-Alias –Name dir schreiben. Das sorgt für Klarheit und gut lesbaren Code. Mehr zu guten Skripting im Kapitel über Skripte im Abschnitt Gutes Skripting.

2.2.5.2   Alias Definitionen erstellen, ändern

Mit

Set-Alias Alternative Original

oder genauer

Set-Alias –Name Alternative –Value Original

können Sie selbst einen solchen Alias erstellen. Eine witzige, wenn auch wenig praktikable Möglichkeit wäre zu allen Cmdlets eine Bayerische, Hessische oder Schwäbische Version zu erstellen. Das können Sie an dieser Stelle ja gerne einmal ausprobieren. Z. B. Get-ChildItem auf Hessisch:

Set-Alias Zeisch-e-ma Get-ChildItem

oder

Set-Alias Guck-e-ma-do Get-ChildItem

Wer von Linux zu uns stößt hat nun gleich im Sinn „..“ auf „cd ..“ zuzuweisen. Das geht hier aber mit Aliasen nicht! Dafür gibt es Funktionen. Das Problem liegt dabei nicht daran, dass cd selbst ein Alias auf das Cmdlet Set-Location ist, sondern daran, dass ein Parameter in Form von .. übergeben wurde. Sie können mit Aliasen also nur weitere Namen für Cmdlets festlegen, aber keine Parameter damit verknüpfen.

2.2.5.3   Alias Definitionen ex- und importieren

Alias Definitionen gelten nur so lange bis Sie die aktuelle Konsole in der Sie die Aliase definiert haben wieder schließen. Wenn Sie Ihre eigenen Abkürzungen gerne schon zu Beginn Ihrer Arbeit an der Konsole zur Verfügung hätten stehen Ihnen zwei Möglichkeiten offen.

1.       Können Sie die definierten Aliase in eine Datei exportieren. Um die aktuellen Alias Definitionen zu exportieren tippen Sie einfach:
Export-Alias aliasfile.csv
Um diese wieder zu bekommen, sprich zu importieren
Import-Alias aliasfile.csv
Wenn Sie mögen können Sie natürlich vor den Dateinamen auch noch einen Pfad (z. B. Export-Alias c:\powershell\aliasfile.csv) schreiben, falls die Aliasdatei nicht im aktuellen Verzeichnis liegen soll. Als Dateinamenerweiterung habe ich csv gewählt, weil es sich hierbei um eine simple Textdatei handelt, in welche die Zuordnungen einfach durch Komma getrennt hinein geschrieben werden. Das bedeutet, dass Sie die Datei auch mit Notepad oder einem Tabellenkalkulationsprogramm bearbeiten können. Die Endung muss nicht csv lauten. Genauso gut können Sie z.B. auch txt oder auch gar keine Erweiterung angeben.

2.       Können Sie Ihre Alias Definitionen im Profile Skript hinterlegen. Mehr dazu im Kapitel Profil Skripte.

Wer im vorigen Kapitel gut aufgepasst hat, wird sich fragen warum ich hier nicht das Cmdlet New-Alias verwendet habe. Sie haben Recht! Um neue Aliase anzulegen würde man eigentlich das Cmdlet New-Alias verwenden. Mit Set-Alias klappt das allerdings genauso gut. Der kleine aber feine Unterschied liegt darin, dass mit New-Alias wirklich nur noch nicht definierte Aliase angelegt werden können. Existiert schon eine Definition würde New-Alias einen Fehler melden, während Set-Alias die bestehende Zuordnung einfach abändert. Set-Alias ist also flexibler, setzt aber voraus, dass ich weiß was tue. Ein versehentliches Überschreiben ist bei Set-Alias gut möglich, erlaubt mir aber auch bestehende Vorgaben zu überschreiben. Aliase die vom System vorgegeben sind, werfen allerdings auch bei einem einfachen Set-Alias erst einmal einen Fehler. Um selbst Systemaliase wie ls oder dir zu überschreiben, müssten Sie noch den Parameter –option Allscope dranhängen. Das komplette Kommando würde dann so aussehen:

Set-Alias dir get-process –option Allscope

Nach diesem Befehl zeigt der Alias dir nicht mehr auf Get-ChildItem, sondern auf Get-Process! Ob das sinnvoll ist lassen wir einmal dahingestellt, aber um Kollegen zu ärgern und Spaß zu haben taugt es allemal.

2.2.6   Navigation

Vielleicht haben Sie sich ja auch schon einmal gefragt, warum Get-Childitem so seltsam heißt. Warum nennt sich das nicht einfach Get-Directory? Oder der Alias von cd nennt sich auch nicht Change-Directory, sondern Set-Location. Nun, ich glaube das wird am besten deutlich, wenn Sie einmal folgendes eintippen:

Set-Location HKLM:

Get-ChildItem

und nun

cd Software

ls

Ja, genau! Sie stehen gerade mitten im HKEY_Local_Machine der Registry und können hier mit den ganz normalen Befehlen, die Sie vom Dateisystem her kennen navigieren.

Gehen Sie doch einmal in den Hauptschlüssel Current_User, da können Sie auch gleich ein bisschen was anstellen:

cd HKCU:

ls

mkdir Abgefahren

ls

regedit

Jetzt ist der Regedit (ein grafisches Werkzeug zur Bearbeitung der Registrierdatenbank) geöffnet. Schauen Sie doch einmal in den Current_User Schlüssel, ob es da einen Unterschlüssel namens Abgefahren gibt. Schließen Sie Regedit wieder und löschen Sie auch den Schlüssel Abgefahren wieder über die PowerShell.

Windows-like:

del Abgefahren

oder Linux-like:

rm Abgefahren

Prüfen Sie, ob der Key auch wirklich entfernt wurde.

Windows-like:

dir

oder Linux-like:

ls

Ihnen ist das mit der Registry nicht ganz geheuer und Sie möchten gerne lieber wieder zurück zu Laufwerk C:? Kein Problem:

cd c:

Der ganze Spuck basiert auf sogenannten PowerShell Drives, oder kurz PSDrive.Da gibt es natürlich auch gleich ein paar Cmdlets dazu. Um zu erfahren wo Sie überall herumspazieren können fragen Sie dieses Cmdlet:

Get-PSDrive

Das bedeutet Sie können, abgesehen von den Festplattenlaufwerken, z.B. auch in die Aliase oder die Variablen hineinspazieren. Da wir die Aliase schon kennen probieren wir es damit gleich einmal aus:

cd alias:

ls

Vielleicht haben Sie aus dem vorangegangenen Abschnitt noch ein paar Aliase hier herumstehen, die Sie nicht mehr brauchen? Wenn nein, legen Sie sich doch einen „Guck-e-ma-do“ Alias, wie im vorangegangenen Abschnitt beschrieben an. Im vorhergehenden Abschnitt haben Sie vielleicht auch bemerkt, dass es kein Cmdlet wie Remove-Alias zum löschen von Aliasen gibt. Nun, jetzt stehen Sie schon hier „in“ den Aliasen, also probieren Sie doch gleich auch einmal ganz frech:

Windows-like:

del Guck-e-ma-do

oder Linux-like:

rm Guck-e-ma-do

Klappt, oder? Wenn nein, prüfen Sie ob die Namen richtig geschrieben sind und ob Sie zuvor einen Alias mit dem entsprechenden Namen erstellt haben. Vor allem müssen Sie aber versuchen den Aliasnamen löschen und nicht das Cmdlet auf den der Alias zeigt!

Wie Sie sehen müssen die PSDrives nicht wie normale Laufwerke aus einem einzelnen Buchstaben bestehen. Der ein oder andere kennt vielleicht das Subst Kommando vom DOS her. Wenn nein, ist auch nicht weiter schlimm. Mit New-PSDrive können wir „Abkürzungen“ anlegen. Pfadangaben zu Netzlaufwerken können manchmal zu lang werden, aber auch lokal kann man sich Abkürzungen basteln. Sie können sich z.B. für das C:\Windows\System32-Verzeichnis eine Abkürzung namens System32 basteln:

New-PSDrive –name System32 –psprovider filesystem –root c:\windows\system32

Der Schalter –name gibt an, wie Ihr Laufwerk angesprochen werden soll (allerdings ohne den Doppelpunkt!). –PSProvider gibt an, welcher „Anbieter“ den Zugriff bereitstellt. In diesem Falle also das Dateisystem. Mit Get-PSDrive sehen Sie in der Spalte Provider welche weiteren Anbieter Ihnen zur Verfügung stehen. Natürlich dürfen Sie sich auch mit Get-Help informieren, was man mit PSDrives so alles anstellen kann. Zum Schluss haben wir noch den Schalter –root, der den Einsprungpunkt Ihres Laufwerks definiert.

Ab sofort können Sie direkt in das Verzeichnis C:\Windows\System32 hinein wechseln in dem Sie einfach tippen:

cd system32:

Wenn Sie die PowerShell Konsole schließen und neu starten ist das genau wie bei den Alias-Definitionen wieder vergessen. Wenn Sie das gerne dauerhaft haben möchten, müssten Sie die Anweisungen in ein Profile-Skript schreiben, wie Kapitel mit Skripten arbeiten im Abschnitt Profil Skripte beschrieben steht.

2.2.7   Das EVA-Prinzip und seine Anwendung

EVA steht für eines der Grundprinzipien in der elektronischen Datenverarbeitung (EDV) oder wie man moderner Weise sagt Informationstechnologie (IT):
Eingabe
Verarbeitung
Ausgabe

In der Regel wollen wir, dass der Computer für uns etwas erledigt. Deshalb muss er natürlich eine Anweisung erhalten, was zu tun ist. Über Eingaben können Sie dem Benutzer eines Skriptes die Möglichkeit geben mit den Skripten, die wir später erstellen werden, etwas mitzuteilen. Gehen wir einmal von einer einfachen Plus Rechenaufgabe aus, bei der wir zwei Zahlen zusammenzählen möchten. Die Masterfrage ist zunächst natürlich welche beiden Zahlen wir addieren möchten. Anstatt diese Zahlen fest ins Skript einzubauen, können Sie diese Zahlen vom Benutzer entgegennehmen. Sie wollen also, dass der Benutzer eine Eingabe macht, welche beiden Zahlen addiert werden sollen und fordern den Benutzer auf nacheinander 2 Zahlen einzugeben. PowerShell Cmdlets die solche Aktionen für Sie durchführen sind Eingabebefehle. Nachdem Sie die Zahlen vom Benutzer erhalten haben, sollen diese zusammengerechnet werden. Dazu benötigen Sie dann Verarbeitungsbefehle. Zum Schluss wollen Sie natürlich, stolz wie Oskar, Ihr Ergebnis präsentieren. Dafür haben Sie dann die Ausgabebefehle, um dem Benutzer Ihr Ergebnis anzuzeigen. Daher schauen wir uns an dieser Stelle ein paar der wichtigsten Cmdlets dafür näher an.

2.2.8   Pipelineverarbeitung und Umleitung von Ein- und Ausgaben

Pipelineverarbeitung kann jede nicht vollkommen antiquierte Shell. Die Pipelineverarbeitung der PowerShell zeichnet sich aber ganz besonders durch Ihre Objektorientierung aus (dazu später mehr in Kapitel Objektorientierung). An dieser Stelle sollen nur die Grundlagen der Ein- und Ausgabeoperationen verdeutlicht werden. Für erfahrenen Skripter von DOS oder bash denen die Bedeutung dieser Zeichen |, <, >, >>, 2>, 2>>, 2>&1 klar ist, kann der Abschnitt Pipelineverarbeitung mit Ausnahme der Eingabeumleitung übersprungen werden.

2.2.8.1   Eingabeumleitung

Das < Zeichen für die Umleitung der Eingabe ist bei der PowerShell nicht zulässig, aber für Powershell Cmdlets auch nicht nötig. Hier haben wir andere effektivere Mechanismen, die Sie im Laufe des Buches noch kennenlernen werden.

2.2.8.2   Die Umleitung von Standardausgaben zur Standardeingabe

Wenn Sie die PowerShell geöffnet haben ist die Standardeingabe die Tastatur und die Standardausgabe der Bildschirm. Sie tippen etwas über die Tastatur ein und PowerShell antwortet entsprechend auf dem Bildschirm.

Get-Command und Get-Help kennen Sie ja bereits. Lassen Sie die beiden doch zusammen spielen!

Wenn Sie Get-Command –Verb Read (über die Standardeingabe = Tastatur) eingeben, kommt dabei nur ein einziges Cmdlets heraus: Read-Host. PowerShell präsentiert Ihnen den Fund am Bildschirm, der Standardausgabe. Nun würden Sie normaler Weise Get-Help Read-Host (wieder über die Standardeingabe) eintippen um mehr über dieses Cmdlet heraus zu finden. Die Hilfe würde natürlich auch wieder auf die Standardausgabe (den Bildschirm) geleitet werden.  Das können Sie auch abkürzen, in dem Sie die Ausgabe vom Get-Command direkt zur Eingabe von Get-Help leiten. Durch den senkrechten Strich, die sogenannte Pipe (geht mit der AltGr-Taste und der Taste mit den <> Pfeilen drauf in Kombination) wird die Ausgabe des ersten Befehls zur Eingabe des danach folgenden.

Get-Command –verb read | Get-Help

Diese Technik können Sie abgesehen von Ausgabecmdlets, die selbst die Umleitung durchführen überall und beliebig oft hintereinander einsetzen.

2.2.8.3   Ausgaben in Dateien umleiten

Wenn Sie den Text vom Bildschirm lieber in einer Datei hätten, können Sie die Ausgabe vom Standardausgabegerät dem Bildschirm umleiten in eine Datei Ihrer Wahl.

Get-Childitem C:\Windows > ~\Verzeichnisinhalt.txt

Wenn Sie diesen Befehl ausgeführt haben rührt sich auf dem Bildschirm gar nichts. Die Ausgabe des Bildschirms wurde umgeleitet in Ihr Basisverzeichnis und dort in die Datei Verzeichnisinhalt.txt. Oh, Sie wissen nicht wo Ihr Basisverzeichnis ist? Kein Problem! Tippen Sie cd ~ ein und schon stehen Sie in Ihrem Basisverzeichnis. Dort sollten Sie nun durch eintippen des Cmdlet Get-Childitem die Datei Verzeichnisinhalt.txt sehen. Dieses Verzeichnis sollten Sie sich merken. Wie es heißt, sehen Sie am Eingabeprompt, also da wo PS C:…> steht. Öffnen Sie nun den Explorer und navigieren Sie in das Verzeichnis und doppelklicken Sie die Datei Verzeichnisinhalt.txt. Sehen Sie nun, wo Ihre Ausgabe gelandet ist? Schließen Sie die Datei (bzw. Ihr Notepad) wieder.

2.2.8.4   Fehlermeldungen in Dateien umleiten

Wenn Sie nun einmal absichtlich einen Fehler produzieren indem Sie z.B. ein Verzeichnis anzeigen lassen, dass es nicht gibt:

Get-ChildItem C:\XYZ123

dann taucht eine in Rot dargestellt Fehlermeldung auf dem Bildschirm auf. Probieren Sie nun einmal:

Get-ChildItem C:\XYZ123 > ~\Verzeichnisinhalt.txt

Öffnen Sie noch einmal die Datei Verzeichnisinhalt. Und?! Ups…nix mehr drin. Hier wurde nun ein Fehler produziert. D.h. der Befehl ist fehlgeschlagen und hat somit keine erfolgreiche Ausgabe (nichts!) produziert. Also hat er „Nichts“ in die Datei Verzeichnisinhalt.txt umgeleitet und somit deren vorherigen Inhalt gelöscht.

Möchten Sie Fehlermeldungen in Dateien festhalten, müssen Sie den Fehlerausgabekanal bemühen. Der Kanal für Fehlermeldungen hat die Nummer 2. und der Kanal für die Standardausgabe (für erfolgreiche Befehle) ist Nummer 1. Wenn Sie wie bisher keine weiteren Angaben machen ist immer Kanal Nr. 1 (Erfolg) gemeint. Nun schalten wir einmal auf Kanal 2 um (da steht genau dasselbe wie oben, nur in der Mitte habe ich eine 2 hineingemogelt ;-):

Get-ChildItem C:\XYZ123 2> ~\Verzeichnisinhalt.txt

Am Bildschirm ist nun wieder nichts passiert. Wenn Sie nun allerdings wieder die Datei öffnen, ist die Fehlermeldung darin zu lesen. Das Rot von der Schrift in der PowerShell Meldung geht dabei verloren, da Textdateien keine Farben und auch sonst keine Formatierungen (fett, unterstrichen, etc…) unterstützen. Bitte schließen Sie die Datei wieder.

2.2.8.5   Ausgaben und Fehler in dieselbe Datei leiten

Jetzt wollen wir aber auf jeden Fall die Ausgabe der PowerShell in der Datei haben, egal ob es schief gegangen ist, oder nicht. Dazu müssen wir beide Kanäle (Fehler + Erfolg) in die Datei umleiten. Da kommt man so leicht nicht drauf wie das zu schreiben ist:

Get-ChildItem C:\Windows 2>&1 > ~\Verzeichnisinhalt.txt

Sie leiten 2 & (und) 1 in die Standardausgabe (den Bildschirm) um. Und von da aus weiter in die Datei. Wenn Sie die Datei öffnen, sehen Sie den Verzeichnisinhalt (die vorherige Fehlermeldung wurde wieder überschrieben). Schließen Sie bitte wieder die Datei und geben Sie nun

Get-ChildItem C:\XYZ123 2>&1 > ~\Verzeichnisinhalt.txt

ein. Öffnen Sie die Datei und Sie sehen nun wieder die Fehlermeldung, die nun wiederum den Verzeichnisinhalt ersetzt hat. Mit der Angabe 2>&1 > werden also sowohl die erfolgreichen Ausgaben von Befehlen in die Datei geleitet, als auch auftretende Fehlermeldungen.

Nur 2>&1 (wie es in der PowerShell Hilfe steht) hat nur den Effekt, dass Fehler- und Erfolgsmeldung auf dem Bildschirm landen.

2.2.8.6   Meldungen an eine Datei anhängen

Stellen Sie sich vor Sie möchten gerne eine Fehlerprotokolldatei, nennen wir Sie Error.log, benutzen um Fehler zu dokumentieren. Bisher haben die Umleitungen den aktuellen Dateiinhalt aber immer überschrieben. Bei einer Protokolldatei sollen aber natürlich alle Fehler, der Reihe nach, aufgelistet werden.

Das ist ganz einfach, egal ob Fehler oder nicht, wenn Sie an eine Datei noch etwas dranhängen möchten, anstatt den aktuellen Inhalt zu überschreiben, nehmen Sie einfach 2 von den Umleitungspfeilen. Für erfolgreiche Befehle >>, für Fehler 2>> und für beide 2>>1&.

Also machen Sie zunächst einmal einen ersten Fehler für die Error.log:

Get-ChildItem C:\XYZ123 2> ~\Error.log

Damit wurde die Datei erstellt und eine erste Zeile hineingeschrieben. Dann wiederholen Sie noch einmal den Befehl, aber ergänzen noch > Zeichen:

Get-ChildItem C:\XYZ123 2>> ~\Error.log

Wenn Sie die Datei Error.log nun mit notepad öffnen werden Sie 2 Fehlermeldungen sehen.

2.2.8.7   Ausgabe in Dateien und auf dem Bildschirm

Vielleicht möchten Sie auch gerne auf dem Bildschirm sehen was los ist und das auch zusätzlich noch in eine Datei schreiben. Den Linux Leuten fällt dazu nichts Besseres ein als einen Tee zu trinken, den PowerShell Skriptern auch und die DOS-Leute haben nicht einmal den Funken einer Idee ;-).

In der PowerShell haben Sie ein Tee-Object Cmdlet, dass Sie aber gerne beim Alias tee nennen dürfen.

ls c:/ | tee ~\Verzeichnisinhalt.txt

Da fühlen sich die Linuxianer doch gleich wie zu Hause ;-). Die Windows-Leute dürfen es natürlich auch so schreiben:

dir c:\ | tee ~\Verzeichnisinhalt.txt

Und die PowerShell Hardliner (syntaktisch 100% PowerShell):

Get-ChildItem -Path “C:\” | Tee-Object –FilePath “~\Verzeichnisinhalt.txt”

Egal für welche der drei Varianten Sie sich entscheiden, dass Ergebnis ist dasselbe. Die Ausgabe des Inhalts von Laufwerk C:\ erscheint auf dem Bildschirm und wird auch gleich in die Datei Verzeichnisinhalt.txt in Ihrem Basisverzeichnis geschrieben. Wenn Sie mögen können Sie mit notepad wieder in die Datei Verzeichnisinhalt.txt hineinschauen.

2.2.8.8   Mehrere Befehle nacheinander ausführen, ohne dass die Ausgabe umgeleitet wird

Wenn Sie mehrere Cmdlets nacheinander ausführen möchten, ohne dass die Ausgabe des vorangegangenen zur Eingabe des nächsten wird. Mit Hilfe eines Semikolons als Trenner wird jeder Befehl für sich ausgeführt und stellt seine Informationen am Bildschirm dar ohne zwischendurch zum Prompt zurück zu kehren.

cd c:\; dir; cd windows; dir

Wechselt zunächst ins Wurzelverzeichnis von Laufwerk c:; zeigt den Inhalt von Laufwerk c: an; wechselt dann ins Windows-Verzeichnis und zeigt direkt nach dem ersten Verzeichnis, das zweite (Windows) an. Diese Technik ist besonders bei mehreren, Zeit intensiven Befehlen sehr zu empfehlen.

2.2.9   Eingabe Befehle und Variablen kennenlernen und verstehen

Eingabe bedeutet immer, dass wir von irgendwo Informationen abholen. In diesem Kapitel erfahren Sie wie Sie Informationen bekommen. Im ersten Abschnitt befassen wir uns mit der wichtigsten Schnittstelle Mensch/Computer. Danach werden einige bedeutsame Cmdlets näher erklärt und schließlich ein allgemeiner Überblick über alle zur Verfügung stehenden Eingabebefehle gegeben.

2.2.9.1   Benutzereingaben entgegennehmen und in Variablen ablegen

In diesem Abschnitt befassen wir uns mit Cmdlets, die uns ermöglichen vom Benutzer Eingaben entgegen zu nehmen.

Die primitivste aber zugleich auch recht flexible Variante ist das Cmdlet Read-Host. Tippen Sie einfach einmal Read-Host ein und bestätigen Sie mit der Enter-Taste. Daraufhin passiert augenscheinlich erst einmal gar nichts. Wenn Sie nun anfangen einen Text einzutippen und diesen auch wieder mit der Enter-Taste bestätigen, plappert Ihnen die PowerShell einfach nach, wie ein Papagei.

Die Eingabe die Sie mit Read-Host gemacht haben muss natürlich irgendwo hin. Die sogenannte Standardausgabe für die PowerShell ist wie bei anderen Shells auch der Bildschirm. Wenn ein Befehl etwas zu sagen hat tut er dies, wenn er nicht anderweitig dazu angewiesen wird, immer auf dem Bildschirm. Daher also der Papagei Effekt.

Wenn Sie später die Eingaben weiter verarbeiten wollen, müssen Sie diese irgendwo unterbringen wo Sie sie wiederfinden. Daher an dieser Stelle eine kleine Einführung in Variablen:

Eine Variable ist eine Möglichkeit Informationen (ganz gleich welcher Art) irgendwo abzulegen, wo Sie diese wiederfinden können. Variablen können frei wählbare Text basierte Bezeichner sein. Wichtig ist nur, dass ein $-Zeichen vorne dran steht. Der Einfachheit halber nehmen wir hier einfach einmal ein a. Wenn Sie statt einfach nur Read-Host, nun lieber

$a=Read-Host

eingeben und mit Enter bestätigen, ist zunächst einmal kein Unterschied festzustellen. Wenn Sie nun aber Ihren Text eintippen und diesen auch wieder mit Enter bestätigen fällt auf, dass der Papagei ausgeflogen ist. Zumindest hat Ihnen niemand nachgeplappert. Der Text, den Sie soeben eingetippt haben ist nun in $a (Ihre erste Variable) dank der Zuweisung mit dem =-Zeichen. Ihre nächste Frage ist nun unweigerlich: „Wie komme ich denn wieder an den Inhalt?“. Es ist wieder einmal viel einfacher als Sie glauben. Tippen Sie einfach nur $a ein und bestätigen Sie wieder mit Enter. Simpel, oder? Wollen Sie noch einmal? Nur zu…

Mit Read-Host können Sie auch Nachrichten an den Benutzer herausgeben, was Sie überhaupt von Ihm wollen. Tippen Sie dazu

$a=Read-Host “Wie ist Ihr Name“

und bestätigen Sie mit Enter. Nun weiß der Benutzer gleich was Sie von ihm wollen und er wird (hoffentlich) auch seinen Namen eingeben. Wenn Sie nun einfach wieder $a tippen und mit Enter bestätigen, sollte der eingegebene Name wiedergegeben werden. Der vorherige Inhalt von $a wurde also einfach überschrieben. Der Wert aus dem vorangegangenen Beispiel ist damit verloren.

Auch Kennwörter können Sie damit entgegennehmen. Dazu brauchen wird nur den Schalter ‑AsSecureString mit anzugeben.

$a=Read-Host „Her mit dem Kennwort“ –AsSecureString

Das ist so geheim, dass nicht nur die Eingabe mit Sternchenplatzhalterzeichen versehen wird, sondern auch wenn Sie nun versuchen $a auszulesen kommt nur die Rückmeldung System.Security.SecureString - der Verweis auf ein geschütztes .NET-Objekt.

2.2.9.2   Grundlagen der Variablenverwaltung

Welchen Zweck Variablen erfüllen sollte durch den vorangegangenen Abschnitt klar sein. Sie können also in Variablen Informationen hinterlegen. Die Zuweisung kann denkbar einfach funktionieren:

$a=“Inhalt“

Ebenso wie die Abfrage des Inhalts:

$a

Den Inhalt einer Variablen zu ändern funktioniert genauso wie das Zuweisen:

$a=“anderer Inhalt“

$a

Eine Auflistung aller im Moment verwendeten PowerShell Variablen finden Sie mithilfe des Cmdlets Get-Variable oder kurz über den Alias darauf:

gv

In der Spalte Name steht die Bezeichnung der Variablen und unter Value der aktuelle Inhalt. Irgendwo in dieser Liste müsste also auch in der Spalte mit der Überschrift Name der Buchstabe a (von $a) auftauchen und unterhalb von Inhalt entsprechend das was Sie zugewiesen haben: anderer Inhalt.

Selbstverständlich können Sie auch den Inhalt einer Variablen löschen, indem Sie einfach angeben:

$a=““

Damit haben Sie den Inhalt der Variablen $a gelöscht, doch nicht die Variable selbst, wie ein erneutes Get-Variable beweist. Nun steht in der Spalte Name immer noch das a, aber in der Spalte Value ist nichts mehr zu sehen. Wenn Sie eine Variable komplett löschen und somit den reservierten Speicherplatz freigeben möchten, wie das ein ordentlicher Programmierer/Skripter tut, dann können Sie das Remove-Variable, oder kurz dem Alias darauf rv, tun:

rv a

Achtung! Hier bitte kein $-Zeichen vor die Variablenbezeichnung schreiben. Wenn Sie nun erneut Get-Variable bzw. gv ausführen, stellen Sie fest, dass a nicht mehr in der Spalte Name auftaucht und somit der Speicherplatz, den die Variable im RAM belegt hat freigegeben wurde. Natürlich müssen Sie nicht immer erst den Inhalt löschen, bevor Sie die Variable entfernen, sondern können Remove-Variable auch direkt auf Variablen, die noch einen Inhalt haben, anwenden.

Weitere Informationen zu Variablentypen erhalten Sie im Kapitel Objektorientierung und dort im Abschnitt Objekt-Typen identifizieren. Ein paar Tricks bei der Zuweisung von Werten zu Variablen finden Sie im Kapitel Quoting im Abschnitt Verwenden von Rechen- und Zuweisungs-Operatoren.

2.2.9.3   Benutzeranmeldeinformationen entgegennehmen

Wenn wir gerade solche Angaben wie Benutzername und Kennwort benötigen um z.B. etwas als ein anderer Benutzer durchzuführen hilft uns ein anderes Cmdlet noch besser weiter. Achtung:

$a=Get-Credential

Cool, oder? Ihr erstes grafisches Eingabefenster und Sie haben nicht einmal großartig etwas programmieren müssen.

Das nachfolgende Beispiel wird im Kapitel Remotezugriff genauer erläutert und soll hier nur die Möglichkeiten klar machen. Abtippen wird an dieser Stelle noch nicht funktionieren.

Viele Cmdlets haben einen Schalter –Credential. Sie könnten z.B. zu einem anderen Computer eine Verbindung aufbauen indem Sie zunächst einmal $a=Get-Credential eingeben und danach Enter-PSSession Computername –Credential $a. Oder Sie kombinieren gleich beide Befehle miteinander:

Enter-PSSession Computername –Credential (Get-Credential)

Die runde Klammer an dieser Stelle bewirkt, dass der Teil in den Klammern zuerst ausgeführt wird und dessen Ergebnis an dieser Stelle eingesetzt wird. Das funktioniert also so ähnlich wie in der Mathematik.

2.2.9.4   Weitere besonders interessante Cmdlets für die Eingabe

Get-Alias und Get-Childitem dürfte bereits aus dem Kapitel über Aliase bekannt sein. Ebenso Get-Command und Get-Help aus den ebenfalls vorangegangenen Abschnitten.

2.2.9.4.1    Informationen über Wiederherstellungspunkte

Get-ComputerRestorePoint listet Ihnen Wiederherstellungspunkte des Systems auf. Wenn Sie die PowerShell Konsole nicht als Administrator ausführen bekommen Sie eine Fehlermeldung. Um die Konsole als Administrator zu starten, können Sie entweder einen Rechtsklick auf das PowerShell Icon machen und „Als Administrator ausführen“ auswählen, oder ab Vista aufwärts können Sie auch den Begriff powershell ins Suchfeld des Startmenüs eingeben und mit Strg+Shift+Enter bestätigen. Wiederherstellungspunkte werden teils automatisiert vor dem Einspielen von Updates erstellt. Mit dem zusätzlichen Schalter –LastStatus sehen Sie ob der letzte Wiederherstellungsversuch erfolgreich war. Damit man die Wiederherstellungspunkte nutzen kann muss der Computerschutz aktiviert sein und der Dienst Volumenschattenkopie laufen. Am einfachsten aktivieren Sie diese Funktion mittels eines Rechtsklick auf Computer im Startmenü. In den daraufhin erscheinenden Systemeigenschaften finden Sie links oben den Punkt Computerschutz. Dort können Sie dann für die einzelnen Laufwerke den Computerschutz einschalten und somit auch den Dienst für die Volumenschattenkopien aktivieren.

2.2.9.4.2    Dateiinhalte auslesen

Mit Get-Content können Sie u. a. den Inhalt von Textdateien, also zum Beispiel auch Log-Dateien, auslesen. Erstellen Sie dazu zunächst einen Ordner namens PowerShell auf Ihrem Laufwerk C:. Nun einfach mit notepad eine kleine Textdatei mit beliebigem Inhalt auf Laufwerk c: in Ihrem Ordner unter dem Namen Textdatei.log abspeichern. Um den Inhalt von der PowerShell auszulesen geben Sie gc C:\PowerShell\Textdatei.log ein. gc ist ein Alias auf Get-Content – das tippt sich schneller! Die Linux-Fans dürfen auch gerne cat benutzen. Dies ist ein weiterer Alias auf Get-Content. So Spielchen wie tac machen wir hier unter Windows aber nicht ;-). Wenn Sie unbedingt wollen, können Sie sich gerne später einen tac Befehl basteln.

2.2.9.4.3    Ländereinstellungen abfragen

Mittels Get-Culture können Sie sich über die Spracheinstellungen auf Ihrem System informieren. Get-UICulture bezieht sich auf die Benutzeroberfläche.

2.2.9.4.4    Datum und Uhrzeit abfragen

Get-Date klärt Sie über Datum und Uhrzeit auf.  Wenn Sie nur das Datum haben möchten können Sie den Schalter –DisplayHint Date eingeben oder um nur die Uhrzeit zu erfahren ‑DisplayHint Time.

2.2.9.4.5    Ereignisanzeige auslesen

Mit Get-EventLog Logname können Sie sich Informationen aus der Ereignisanzeige zusammenstellen lassen. Für Logname geben Sie System, Application oder Security ein um die entsprechenden Berichte anzeigen zu lassen. Mit Get-Eventlog –List können Sie sich anzeigen lassen, wie die Lognamen heißen, die Sie abrufen können. Das Sicherheitslog können Sie sich auch wieder nur mit administrativen Berechtigungen anzeigen lassen. Die anderen beiden können Sie aber auch als normaler Benutzer abfragen. Mittels Get-EventLog System ‑Newest 10 bekommen Sie nur die 10 aktuellsten Einträge aus dem Systemlog.  Mit Get-EventLog System –InstanceID 24 bekommen Sie nur die Ereignisse angezeigt, mit der Ereignis-ID 24 (dazu sollten Sie natürlich ein Ereignis mit der Nummer im Systemlog stehen haben. Schauen Sie ggf. in die Ereignisanzeige und probieren Sie mal eine der ersten Nummern, die dort stehen). Mit Hilfe des Schalters –EntryType können Sie sich nur Fehler des Typs Fehler (=“Error"), Informationen (= "Information"), Warnungen (="Warning"), Überwachungsfehler (="FailureAudit") oder Überwachungserfolge (= "SuccessAudit") anzeigen lassen. Durch Get-EventLog System ‑Message "Installation*" können Sie sich Meldungen anzeigen lassen, die mit „Installation“ im Meldungstext beginnen.

Selbstverständlich können Sie auch verschiedene Schalter kombinieren. Get-EventLog System ‑EntryType „Error“ –newest 10 listet Ihnen beispielsweise die letzten 10 Fehler aus dem Systemlog.

Natürlich sind noch viel mehr Varianten mit weiteren Schaltern möglich. Aber damit haben Sie schon die wichtigsten Möglichkeiten beisammen. Nur eine interessante Variante kommt noch im Bereich Verarbeitung, wenn klar ist wie man mit Zeiten arbeitet.

2.2.9.4.6    Befehlshistorie ausgeben

Get-History zeigt Ihnen die von Ihnen bereits eingegebenen Befehle nach Zeit geordnet an.

2.2.9.4.7    Windows Updates prüfen

Get-HotFix listet Ihnen die installierten Patches bzw. Windows-Updates auf.

2.2.9.4.8    Zufallszahlen generieren

Get-Random liefert Zufallszahlen. Mit den Schaltern –Min und –Max können Sie den Wertebereich eingrenzen. Möchten Sie also eine Zufallszahl zwischen 50 und 100 haben geben Sie ein Get-Random –Min 50 –Max 100 und schon sehen Sie ein 64 auf dem Bildschirm. Nein?! Dann haben Sie etwas falsch gemacht. Wiederholen Sie den Befehl so lange bis auch bei Ihnen 64 rauskommt – Spaß ;-)!

2.2.9.4.9    Prozesse anzeigen lassen

Get-Process macht Ihnen eine Auflistung aller laufenden Prozesse. Wenn Sie nur wissen möchten ob ein PowerShell-Prozess läuft, können Sie auch Get-Process PowerShell eintippen. Damit werden nur noch die laufenden PowerShell-Prozesse angezeigt. Wenn Sie PowerShell und PowerPoint Prozesse sehen möchten können Sie auch gerne wieder mir Platzhalterzeichen arbeiten:

Get-Process Power*

Die Linux-Fans werden den definierten Alias sehr zu schätzen wissen. Geben Sie doch einfach einmal nur ps ein und drücken Enter.

2.2.9.4.10             System Dienste auswerten

Get-Service liefert Ihnen eine Liste aller Dienste und ob diese gerade laufen, oder nicht. Mit Get-Service RPCSS -RequiredServices können Sie sich die Dienste anzeigen lassen, die für die Ausführung des RPC-Dienstes nötig sind. Wenn Sie statt -RequiredServices  den Schalter –DependentServices angeben, sehen Sie die umgekehrte Richtung, sprich welche Dienste ihrerseits den RPC Dienst benötigen.

2.2.9.4.11             Liste mit Cmdlet Verben anzeigen

Get-Verb ist eigentlich kein Cmdlet, sondern eine Funktion. Wie Sie bereits wissen bauen sich die Cmdlets aus einem Verb und einem Hauptwort auf. Get-Verb listet Ihnen alle möglichen Verben der Cmdlets auf. Falls Sie mal mit Start-Irgendwas und Stop-Irgendwas nicht weiter kommen, ist diese Funktion ganz nett, denn vielleicht heißt es ja z. B. Enable-Irgendwas oder Disable-Irgendwas. Die Funktion bringt Sie vielleicht manchmal auf neue Ideen wo oder wie Sie weiter nach einem bestimmten Cmdlet suchen können.

2.2.9.5   Vollständige Übersicht aller Eingabe Cmdlets

Wenn Sie Eingaben haben möchten stehen Ihnen natürlich noch weitaus mehr Cmdlets zur Verfügung. Vom Bauchgefühl her müssten Sie da eigentlich schon fast selbst darauf kommen. Sie wollen etwas haben. Also wie wäre es mit einem Get-Command –Verb Get? Schon haben Sie eine Übersicht über alle Cmdlets, die Ihnen Informationen verschaffen können und somit Eingabebefehle darstellen. Mit Get-Help können Sie sich dann weitere Informationen verschaffen, wie die jeweiligen Cmdlets funktionieren. Lesen Sie ggf. noch einmal in den Abschnitten Anatomie des PowerShell Cmdlets und Hilfefunktionen der PowerShell nach wie das geht.

2.2.10                     Kontrollfragen

Wie springen Sie zum Zeilenanfang in der PowerShell Konsole?

Wie löschen Sie alle Zeichen von der aktuellen Position bis zum Zeilenende?

Wie kann man einen Text in die Zwischenablage kopieren?

Wie kann man einen Text aus der Zwischenablage einfügen?

Wie ist der grundlegende Aufbau eines PowerShell Cmdlet?

Welches Cmdlet beendet einen Dienst?

Welches Cmdlet zeigt die Prozessliste an?

Welches Cmdlet zeigt Ihnen alle zur Verfügung stehenden Cmdlets an?

Wie finden Sie heraus, welche Cmdlets zur Informationsbeschaffung dienen?

Wie erfahren Sie, mit welchen Cmdlets Sie Prozesse verwalten können?

Wie bekommen Sie eine Auflistung aller Verben, die Sie in Cmdlets verwenden können?

Was müssen Sie eingeben um ein Cmdlet wie z.B. Start-Process vollständig erklärt zu bekommen?

Wie bekommen Sie heraus, ob ein Befehl wie z.B. spps ein Alias ist und falls es ein Alias ist auf welches Cmdlet es deutet?

Wie erfahren Sie, ob es für ein Cmdlet wie z.B. Get-Childitem bereits einen Alias gibt?

Sie möchten gerne selbst einen Alias erstellen. Welches Cmdlet verwenden Sie dazu?

Können Sie einen Alias names ... erstellen, um zwei Verzeichnisebenen nach oben zu gelangen?

Wie lange gilt eine Ihrer eigenen Alias-Definitionen?

Wie können Sie Ihre Alias-Definitionen für die spätere Verwendung konservieren?

Wie kommen Sie von der PowerShell aus in die Registrierdatenbank?

Wie löschen Sie einen Alias?

Wie können Sie die Ausgabe mithilfe eines Umleitungsoperators in eine Datei schreiben?

Wie protokollieren Sie am besten Fehler in einer Textdatei?

Wie können Sie mehrere Befehle in einer Zeile hintereinander absetzen?

Wie können Sie ein Passwort (ohne Benutzername!) sicher entgegennehmen und für die spätere Verwendung zwischenparken?

Wie können Sie eine Variable komplett (nicht nur den Inhalt) löschen?

Wie können Sie den Inhalt einer Datei auslesen?

Wie können Sie Datum und/oder Uhrzeit in Erfahrung bringen?

Wie erzeugen Sie eine Zufallszahl?

Link zu den Lösungen für Eingabebefehle

2.2.11                     Die Grundlagen der Verarbeitung

Im Abschnitt Verarbeitung widmen wir uns den grundlegenden Möglichkeiten der Verarbeitung von Daten.

Wie wär’s mit Lottozahlen? 7 aus 49 bedeutet so viel wie, dass 7 zufällige Zahlen zwischen 1 und 49 gezogen werden.

1..49 | get-random –count 7

Cool, oder? 1..49 erstellt eine Zahlenreihe von 1 bis eben 49. Wenn Sie mögen, tippen Sie doch einfach einmal nur 1..49 ein. Durch die Pipe (der senkrechte Strich) wird die Ausgabe von unserer ersten Anweisung 1..49 zur Eingabe des nächsten Befehls, also Get-Random –count 7. Der nimmt nun aus den 49 Zahlen per Zufall 7 Stück heraus. Das bedeutet, dass unser Eingabebefehl Get-Random nun zu einem Verarbeitungsbefehl mutiert ist. Er hat Informationen erhalten und diese dadurch verarbeitet, dass er zufällig 7 aus diesen 49 Zahlen heraus gedeutet hat. Man kann auch sagen, dass die Informationen in diesem Fall gefiltert wurden. Da die Ausgabe standardmäßig auf dem Bildschirm erfolgt ist damit unsere „EVA“ schon wieder ein Stück weiter. Schon haben wir die Lottozahlen der nächsten Ziehung. Das Buch zu lesen hat sich also schon gelohnt, wenn Sie kommenden Samstag 6 Richtige haben ;-).

2.2.11.1                Rechnen mit der PowerShell

Grundsätzlich funktioniert das hier genauso wie mit einem Taschenrechner. Sie können also z.B. 4+5 eintippen und Enter drücken. Die Ausgabe auf dem Bildschirm sollte 9 sein. Bei 7-5 sollte 2, bei 2*3 sollte 6 und bei 12/3 sollte 4 herauskommen. Schon haben wir die 4 Grundrechenarten durch. War nicht so schwer, oder?

Das Ganze klappt natürlich auch mit Variablen! Anstatt nun jede Variable mit Read-Host abzuholen (klappt so einfach sowieso nicht, dazu später mehr), können wir diese auch direkt zuweisen. Tippen Sie:

$a=14
$b=7
$a/$b

$b*2

$c=$a-$b*5

$c

Haben Sie dieses Ergebnis zum Schluß in $c erwartet? Huhu…Punkt geht vor Strichrechnung…Sie erinnern sich 2. Klasse Grundschule?

Wenn Sie möchten können Sie auch gerne, wie in der 5. Klasse, Klammer einsetzen. Damit können Sie festlegen in welcher Reihenfolge gerechnet werden soll.

$c=($a-$b)*5

So, passt es jetzt ;-)?

Kommazahlen gehen natürlich auch, aber bitte daran denken, das Skript- und Programmiersprachen grundsätzlich immer im internationalen Zahlenformat arbeiten.  Also bitte kein Dezimalkomma, sondern einen Dezimalpunkt verwenden. Hier ein kleines Beispiel:

2.5+1.2

Das Ergebnis sollte 3.7 sein. Wenn Sie bei einem Get-Culture unter Displayname Deutsch stehen sehen ist die Ausgabe etwas verwunderlich. Dann kommt nämlich eine 3,7 als Ausgabe und kein 3.7 als Rückgabewert. Aber darum brauchen Sie sich keine Gedanken zu machen. Nur bei der Eingabe müssen Sie aufpassen!

Wie Sie wissen sind 50000 Byte nicht 50 KByte, wegen des Umrechnungsfaktors 1024! Drei Möglichkeiten stehen Ihnen offen, um genauer zu werden. Entweder Sie rechnen mit dem Taschenrechner aus wie viel Byte 50000KB sind, oder Sie geben die Rechnung in runden Klammern in die Verarbeitungskette ein. Statt 50000 würden Sie schreiben: (50*1024). Ich glaube aber, dass Ihnen die 3. Möglichkeit am besten gefällt und daher im Gedächtnis hängen bleiben wird. Schreiben Sie doch einfach:

50KB

Erlaubt ist alles bis zum Petabyte, das Exabyte nicht mehr. Also: KB, MB, GB, TB, PB

Für Wurzelziehen, Sinus, Cosinus, Pi oder andere fortgeschrittene Mathematische Operationen müssen wir uns des .NET bedienen. Mehr dazu im Kapitel über .NET-Objekte.

2.2.11.2                Einfache Textoperationen

Auch mit Texten können Sie „rechnen“. Texte müssen generell in Anführungszeichen gesetzt werden, damit man Sie von Zahlen unterscheiden kann. „123“ ist demnach ein Text, während 123 (ohne die Gänsefüßchen) tatsächlich eine Zahl ist.

$a=“3“
$b=“4“
$a+$b

$a*$b

Da kommen schon merkwürdige Ergebnisse heraus. Das wollen Sie doch nicht! Also hinter die Ohren schreiben!!! Mehr zu der Geschichte mit den Anführungszeichen im Kapitel Quoting.

Auf der anderen Seite lassen sich aber auf diese Art und Weise Texte sehr einfach verbinden:

$a=“Melinda“
$b=“ + „
$c=“Martin“
$a+$b+$c

Fertig ist der Liebesbeweis an meine Frau. J

Jetzt muss ich das nur noch schnell per E-Mail verschicken. Wie das geht erfahren Sie im Buchteil mit den Praxisbeispielen.

2.2.11.3                In Texten suchen

Das Cmdlet Select-String arbeitet ähnlich dem grep Befehl von Linux oder dem findstr von der DOS-Kommandozeile.

Zunächst brauchen wir einen Text. Vielleicht haben Sie ja noch die Textdatei  aus dem Abschnitt Dateiinhalte auslesen. Wenn nicht, legen Sie sich bitte noch einmal so eine Datei wie in diesem Abschnitt beschrieben an. Die Datei sollte nun mehrere Zeilen enthalten. In eine der Zeilen schreiben Sie bitte das Wort Abracadabra. Geben Sie nun folgenden Befehl ein:

Select-String –Path “C:\PowerShell\Textdatei.log” –Pattern “Abracadabra”

Dieser fischt aus der Textdatei.log die Zeile mit unserem Zauberwort inkl. der Zeilennummer und des Dateinamens heraus. Bei dem Schalter -Pattern dürfen Sie gerne sog. reguläre Ausdrücke verwenden. Wie das geht und was man alles Tolles damit anstellen kann, erfahren Sie im Kapitel Vergleichs-Operatoren und reguläre Ausdrücke.

2.2.11.4                Uhrzeit umstellen

Mit Set-Date -Adjust -1:15:5 -DisplayHint Time können Sie die Uhrzeit um 1 Stunde 15 Minuten und 5 Sekunden zurückstellen, falls Sie die PowerShell als Administrator gestartet haben. So einfach mit Zeiten umzugehen wie in der PowerShell geht in keiner anderen mir bekannten Skript- oder Programmiersprache. Den ersten Befehl fanden Sie vielleicht nicht ganz so einfach, aber wie wäre es damit?

Set-Date ((Get-Date)+ (New-TimeSpan –Days 3))

Rollen wir das Ganze von Hinten auf. Mit dem Cmdlet New-TimeSpan können Sie Zeitspannen anlegen und brauchen sich dabei weder um Schaltjahre noch sonstige schrägen Zeitberechnungen zu kümmern. In diesem Fall hätte ich gerne 3 Tage. Sekunden in Minuten umrechnen, Minuten in Stunden, Stunden in Tage…wie geht so etwas überhaupt ;-)? Das Get-Date liefert uns die aktuelle Zeit. Das + zwischen beiden Klammern sorgt dafür, dass auf die aktuelle Zeit 3 Tage draufgelegt werden. Das Set-Date außen herum stellt schließlich die Zeit darauf ein, was innerhalb der Klammern zusammengerechnet wurde.

Wenn Sie Zeit nicht dazu addieren oder subtrahieren möchten, sondern absolut einstellen, geht das natürlich auch:

Set-Date (Get-Date -Day 30 -Month 3 -Year 2012)

2.2.11.5                Ereignisanzeige in einem festgelegten Zeitraum auslesen

Das bereits im Abschnitt Eingabe kennengelernte Cmdlet Get-Eventlog verfügt auch über die Möglichkeit einen bestimmten Zeitraum auszulesen. Wenn Sie beispielsweise wissen möchten, welche Fehler sich von vor 3 bis vor 5 Tagen (z.B. am Wochenende) zugetragen haben, würde folgende Verarbeitung hilfreich sein:

$von= (get-date)-(new-timespan -days 5)
$bis= (get-date)-(new-timespan -days 3)
Get-EventLog System -EntryType "Error" –After $von –Before $bis

2.2.11.6                Daten filtern

Sie wollen ja bestimmt nicht immer in einer wahren Datenflut ertrinken. Das Cmdlet Get-Process liefert Ihnen, wie Sie bereits wissen, viele Informationen zu Prozessen. Aber vielleicht wollen Sie gar nicht immer alle Informationen, sondern es interessiert Sie lediglich der Name eines Prozesses, seine ID und vielleicht noch die CPU-Last. Dabei hilft Ihnen das 3. heilige Cmdlet Select-Object. Mit dem Select-Object Cmdlet oder dem Alias Select können Sie das auf die folgende Art und Weise zusammenstutzen:

ps | select Name,Id,CPU

Nach select geben Sie einfach durch Komma getrennt die Spaltenüberschriften an, die das Cmdlet normaler Weise (ohne select) in der ersten Zeile der Ausgabe wiedergibt. Auch hier habe ich ein bisschen gemogelt. Denn eigentlich ist die Spaltenüberschrift ProcessName und nicht einfach nur Name und bei CPU steht auch etwas Anderes in der ersten Zeile. Bei ProcessName handelt es sich um einen Eigenschaftenalias und bei CPU ist die Überschrift nicht wirklich die Eigenschaft. Ich habe absichtlich dieses Cmdlet gewählt, damit Sie gleich eine der unrühmlichen Ausnahmen kennen lernen. Denn normaler Weise können Sie davon ausgehen, dass das was in der Überschrift steht auch bei select eingesetzt werden kann. Wenn es einmal nicht auf Anhieb klappt, versuchen Sie es zunächst einfach auf das Notwendigste zu reduzieren. Später, im Kapitel über Objektorientierung, verrate ich Ihnen dann, wie Sie herausbekommen wie die Eigenschaften wirklich heißen.

Darüber hinaus können Sie select auch dazu verwenden nur die ersten 10 Ergebnisse anzuzeigen:

ps | select –first 10

oder nur die letzten 5:

ps | select –last 5

2.2.11.7                Maximal- ,Minimal-, Durchschnittswerte berechnen und Summen bilden.

Mit dem Measure-Object Cmdlet lassen sich all diese Dinge ganz einfach verarbeiten. Nehmen wir doch unser vorangegangenes Beispiel und lassen und lassen uns diese Werte von den CPU Zeiten bilden.

ps | select Name,Id,CPU | Measure-Object –Property CPU ‑Min ‑Max ‑Sum –Average

-Property gibt die zu vermessende Eigenschaft an und die weiteren Schalter was wir berechnen möchten. Die Ausgabe enthält allerdings immer alle Eigenschaften, auch wenn diese nicht berechnet wurden. Haben Sie eine Idee, wie Sie z. B. nur noch den Durchschnitt in der Ausgabe haben? Einfach noch einen select Average hinterher:

ps | select Name,Id,CPU | Measure-Object –Property CPU –Average | select Average

Natürlich können Sie auch gleich mehrere Eigenschaften vermessen:

ps | select Name,Id,CPU | Measure-Object –Property CPU,Id –Min –Max –Sum –Average

Einfach durch ein Komma getrennt noch die Eigenschaft Id angeben und schon wird die mit berechnet.

2.2.11.8                Daten sortieren

Bleiben wir doch noch ein bisschen bei den Prozessen.

ps | select Name,Id,CPU,VM

Die Befehlskette listet uns den Namen des Prozesses, die ProzessID, die CPU-Last und den verbrauchten Speicher auf. Die Ausgabe wird dabei alphabetisch, nach Namen sortiert angezeigt. Die Reihenfolge der Eigenschaften hinter select spielt für die Sortierung keine Rolle. Es wird immer nach der „Haupt“-Eigenschaft sortiert. Wenn Sie aber lieber nach der ProzessID Ihre Liste aufbauen möchten verwenden Sie das Sort-Object Cmdlet oder kürzer den Alias sort:

ps | select Name,Id,CPU,VM | sort Id

Vielleicht hätten Sie die Ausgabe lieber Absteigen, statt aufsteigend sortiert?

ps | select Name,Id,CPU,VM | sort Id -Descending

Wenn Sie noch einmal ohne sort Alias die Liste abrufen fallen Ihnen sicher die vielen svchost Prozesse auf. Vielleicht hätten Sie es ja doch gerne nach Prozessname, aber wenn der Name gleich ist, in 2. Instanz sortiert nach Speicherverbrauch?

ps | select Name,Id,CPU,VM | sort Name,VM

2.2.11.9                Daten gruppieren

Mit dem Cmdlet Group-Object können wir die Ausgabe nach Eigenschaften gruppieren bzw. zusammenfassen lassen. Der Alias dazu nennt sich group.

Wenn wir uns mit Get-Service die Liste der Systemdienste anschauen fällt auf, dass ein Teil den Status Running (=läuft) und ein anderer den Status Stopped (=beendet) hat.

Wie wäre es mit einer Ausgabe, welche die Dienste nach Status gruppiert?

Get-Service | Group-Object –Property Status

oder mal schauen wie viele Prozesse es mit identischem Namen gibt:

ps | group name

2.2.12                     Ausgabe Befehle kennenlernen und verstehen

Hier werden wir Sie Möglichkeiten kennen lernen Ihre Ergebnisse dem Benutzer zu präsentieren. Wie Sie bereits wissen kommen alle Rückmeldungen standardmäßig auf dem Bildschirm heraus. Diese Ausgabe können wir aber noch etwas anpassen. Grundsätzlich sollten Ausgabebefehle immer am Schluss einer Verarbeitungskette stehen.

2.2.12.1                Ausgaben am Bildschirm

Eine Ausgabe am Bildschirm benötigt keine speziellen Cmdlets, da standardmäßig die Ausgabe sowieso auf dem Bildschirm erfolgt. Im einfachsten Fall kann man also einfach einen Text oder eine Zahl in Anführungszeichen setzen und schon erfolgt nach drücken der Enter-Taste die Ausgabe auf dem Bildschirm.

“Das ist ein Test“

2.2.12.1.1             Daten vor der Ausgabe anpassen

Die Ausgabe kann aber auch noch vor der Ausgabe angepasst werden. Wer aus C oder anderen Programmiersprachen mit printf bzw. dem –f Operator vertraut ist, kann diesen Abschnitt überspringen und gleich bei Ausgaben in Listenform weiterlesen.

Der –f Operator kann die Bildschirmausgabe modifizieren:

“An dieser Stelle steht der 1. Wert: {0} und an dieser Stelle der 2.: {1}. Hier haben wir noch einmal den 1.Wert: {0}“ –f “Eins“, “Zwei“
(Bitte am Stück eintippen und nur 1 x zum Schluß mit Enter bestätigen. Siehe Abbildung:)

Hier wird mit sogenannten positionalen Parametern gearbeitet. Das bedeutet, dass das was hinter ‑f an erster Stelle steht an die Positionen eingefügt wird, wo {0} steht und das, was nach einem Komma an zweiter Stelle steht, wird bei allen Positionen eingefügt, wo {1} steht. Das kann beliebigt erweitert werden, wobei {0}=erster Wert, {1}=zweiter Wert, {2}=dritter Wert usw..

Im ersten Moment mögen Sie sich fragen, warum Sie statt {0} und {1} nicht gleich die Texte, die hinter –f stehen einfügen. Am folgenden Vergleich wird es etwas deutlicher:

$ErsterWertDenIchVergebe=“Eins“

$ZweiterWertDenIchVergebe=“Zwei“

“An dieser Stelle steht der 1. Wert: {0} und an dieser Stelle der 2.: {1}. Hier haben wir noch einmal den 1.Wert:{0}“ –f $ErsterWertDenIchVergebe, $ZweiterWertDenIchVergebe
(Die Variablenzuweisung der ersten beiden Zeilen jeweils direkt mit Enter bestätigen und den Rest bitte wieder am Stück eintippen, wie eben auch)

„An dieser Stelle steht der 1. Wert: $ErsterWertDenIchVergebe und an dieser Stelle der 2.: $ZweiterWertDenIchVergebe. Hier haben wir noch einmal den 1.Wert: $ErsterWertDenIchVergebe“
(und das auch wieder am Stück mit nur 1 x Enter)

Sie sehen, wenn Sie lange und viele Variablennamen haben, ist die Schreibweise mit –f durchaus übersichtlicher.

Der –f Operator hat aber noch weitaus mehr zu bieten! Zum Beispiel die Darstellung in einem bestimmten Zahlenformat:

$wert=27

“Der Wert: {0} wurde nicht verändert.“ –f $wert

“Der Wert: {0:x} wird in Hexadezimal ausgegeben.“ –f $wert

“Der Wert: {0:x8} wird in Hexadezimal und 8-stellig ausgegeben.“ –f $wert

“Der Wert: {0:c} wird als Währung ausgegeben.“ –f $wert

“Der Wert: {0:f} wird mit 2 Nachkommastellen ausgegeben.“ –f $wert

“Der Wert: {0:f1} wird mit einer Nachkommastellen ausgegeben.“ –f $wert

“Der Wert: {0:f4} wird mit 2 Nachkommastellen ausgegeben.“ –f $wert

“Der Wert: {0:0,000} wird mit Tausendertrennzeichen erzwungen.“ –f $wert

“Der Wert: {0:0,000} wird mit 3 Nachkommastellen erzwungen.“ –f $wert

“Der Wert: {0:#.000} wird mit 3 Stellen vor dem Komma erzwungen. Bei 4 Stellen vor dem Komma, wird optional ein Tausendertrennzeichen eingefügt.“ –f $wert

“Der Wert: {0:0,0##} wird mit 1 Nachkommastellen erzwungen. Die 2 und 3 Nachkommastellt wird nur angezeigt, wenn die 2 und 3 Nachkommastelle im Wert vorhanden ist.“ –f $wert

Auch wenn Sie nicht benötigten Platz mit Leerstellen auffüllen möchten, können Sie –f entsprechende Anweisungen z. B. für 10 Zeichen (10 bzw. -10) geben:

$wert=“Text“

“Der String: {0,10} wird dabei rechtsbündig ausgerichtet.“ –f $wert

“Der String: {0,-10} wird dabei linksbündig ausgerichtet.“ –f $wert

$wert=get-date

“Und es gibt sehr viele Möglichkeiten Zeiten darzustellen {0:yyyy.MMM.dd:HH.mm.ss}“ –f $wert

Sie dürfen auch alles miteinander kombinieren:

$wert=27

“Hier {0,-10:f7} eingefügt“ –f $wert

Eine noch detaillierte Übersicht über die Formatierungsmöglichkeiten des –f Operators, finden Sie im Anhang unter –f Format-Operatoren.

2.2.12.1.2             Ausgaben in Listenform

Wie die Ausgabe auf dem Bildschirm erfolgt, ist standardmäßig davon abhängig wie viele Eigenschaften ausgegeben werden. Wenn Sie Beispielsweise Get-Host aufrufen wird die Ausgabe als Liste dargestellt. Bei Get-ChildItem hingegen als Tabelle. Hat die Ausgabe mehr als 5 Eigenschaften erfolgt die Ausgabe als Liste, bei wenigeren als Tabelle. Mit den nachfolgenden Befehlen können Sie die Ausgabe an Ihre Bedürfnisse anpassen.

Wenn Sie eine Ausgabe in Listenform erzwingen möchten, übergeben Sie die Ausgabe mittels einer Pipe (der senkrechte Strich) an das Cmdlet Format-List, oder kurz als Alias fl.

Get-ChildItem | Format-List
ls | fl    # (Kurzform mittels Alias)

Wenn Sie möchten können Sie die Bemerkung inkl. dem #-Zeichen gerne einmal mitabtippen. Dabei werden Sie gleich feststellen, dass ein sogenanntes Hashmark (auch Doppelkreuz, Gartenzaun oder Fliegenklatsche genannt) als Kommentarzeichen dient. Ein # bedeutet für die PowerShell so viel wie: „Vorsicht der Admin hat mal wieder geistige Ergüsse. Bloß nicht den Rest der Zeile interpretieren. Das ist irgend so ein Menschenkram.“

2.2.12.1.3             Ausgaben in Tabellenform

Wenn Sie eine Ausgabe in Tabellenform erzwingen möchten, übergeben Sie die Ausgabe mittels einer Pipe an das Cmdlet Format-Table, oder kurz als Alias ft.

Get-Host | Format-Table

Bei Eingabe des Verarbeitungskette ps | select Name,Id,CPU ist Ihnen bestimmt aufgefallen, wie auseinandergerissen die Werte dargestellt wurden. Obwohl es bereits als Tabelle ausgegeben wurde können wir es trotzdem noch an Format-Table übergeben und zwar mit dem Schalter –AutoSize oder einfach kurz –auto:

ps | select Name,Id,CPU | ft -auto

Sieht schon besser aus, gell? Das ist in etwa so, wie wenn Sie bei Excel einen Doppelklick zwischen die Spaltenköpfe machen. Die einzelnen Spalten werden nur noch so breit wiedergegeben, wie notwendig um den längsten Text aus der jeweiligen Spalte darstellen zu können.

2.2.12.1.4             Nur das Nötigste am Bildschirm darstellen

ls c:\Windows gibt eine ganz schön lange unübersichtliche Ausgabe mit den ganzen Eigenschaften. Meistens braucht man aber nur den Dateinamen. Auch hier hilft uns ein Format Cmdlet weiter. Probieren Sie:

ls c:\windows | fw

Der Alias fw deutet auf das Cmdlet Format-Wide und stellt nur die wichtigste Eigenschaft über mehrere Spalten dar. In diesem Fall den Dateinamen. Mit dem zusätzlichen Schalter –Property können Sie auch gerne die darzustellende Eigenschaft (die Spaltenüberschrift aus der normalen Ausgabe) vorgeben. Mit –Column können Sie die Anzahl der Spalten vorgeben, oder verwenden Sie wie bei Format-Table den Schalter –AutoSize, um die Spalten automatisch so breit wie nötig zu machen.

2.2.12.1.5             Bildschirmausgabe in Farbe

Mit Write-Warning „Ihre Warnung“ können Sie einen Text in gelber Schrift mit schwarzem Hintergrund ausgeben. Vor Ihrer Nachricht an den Benutzer wird zusätzlich der Text “WARNUNG: “ eingebaut.

Mit Write-Host können Sie verschiedene Formatierungen an der Ausgabe vornehmen. Der Parameter –NoNewLine bewirkt, dass der sonst automatische Zeilenumbruch nach der Ausgabe entfällt.

write-host "Eingabeprompt folgt direkt nach diesem Text " –nonewline

Der Schalter –ForegroundColor legt die Schriftfarbe fest und –BackgroundColor die Hintergrundfarbe.

write-host "Achtung!" -fo black -ba red

Dieser Befehl zeigt den Text „Achtung!“ in schwarzer Schrift auf rotem Hintergrund an. Nur noch einmal zur Erinnerung: Sie können die Schalter soweit abkürzen, solange diese eindeutig sind. Sie müssen also -Foregroundcolor nicht jedes Mal ausschreiben –fo reicht aus. Welche Farben das Cmdlet versteht finden Sie mittels Get-Help Write-Host heraus.

2.2.12.1.6             Weitere Cmdlets für die Bildschirmausgabe

Out-Host, Out-Default und Write-Output geben die Daten ebenfalls auf dem Bildschirm aus.

Mit Out-Host können Sie keine spektakulären Effekte erzielen, allerdings verfügt es über den Schalter –Paging. Das sorgt dafür, dass die Daten, bei mehr als einer darzustellenden Bildschirmseite, nicht einfach durchflutschen, sondern nach jeder Bildschirmseite ein Stop eingelegt wird. Den können Sie mit der Leertaste bildschirmweise oder mit der Enter-Taste zeilenweise fortsetzen. Mit Strg+C brechen Sie die Ausgabe ab.

ls c:\windows | Out-Host –Paging

Alternativ können Sie den Alias oh verwenden:

ls c:\windows | oh -Paging

oder, wie Sie es vielleicht von anderen Shells her gewohnt sind, auch einfach mit der Funktion more arbeiten:

ls c:\windows | more

Out-Host wird uns in Funktionen noch mehr gute Dienste leisten. Mehr dazu erfahren Sie im Bereich Skripting im Abschnitt über Funktionen.

Out-Default ist vollkommen sinnlos, denn es macht eigentlich gar nichts. Es leitet die Ausgabe der Verarbeitungskette auf den Bildschirm um. Ähm…was war doch gleich, das Standardausgabegerät, der Bildschirm?

ls c:\windows | Out-Default

ist also identisch mit:

ls c:\windows

Mit Write-Output verhält es sich ähnlich, wie mit Out-Default.

2.2.12.2                Ausgaben in einer Datei

2.2.12.2.1             Textdateien anlegen, überschreiben und erweitern

Eigentlich hat das Cmdlet den falschen Namen. Wenn wir etwas Neues wie eine Datei anlegen und hineinschreiben wollen, sollte das Cmdlet eigentlich New-Content heißen, stattdessen nennt es sich aber Set-Content. Wenn man’s weiß ist das ja auch kein Beinbruch und früher oder später wären Sie sicher auch von selbst darauf gekommen.

Set-Content Text.txt “Meine erste eigens erstelle Textdatei”

Damit schreiben Sie den Text in Anführungszeichen in die Datei Text.txt. Sollten Sie mit dem Inhalt unzufrieden sein, können Sie den Befehl einfach erneut mit einem anderen Inhalt absetzen wodurch der aktuelle Inhalt ersetzt wird. Unter diesem Gesichtspunkt ist die Bezeichnung Set-Content doch nicht so falsch, denn Sie würden in diesem Fall den bestehenden Inhalt verändern. Auch verarbeitete Daten können Sie damit in Textdateien schreiben. Entweder direkt z.B. den Inhalt von Laufwerk C: :

Set-Content Text.txt (Get-Childitem c:\)

oder aber auch indirekt mit Hilfe von Variablen:

$Zeit=(Get-Date)+(New-TimeSpan –Hours 2)
$Benutzer=Read-Host –Prompt “Was sagen Sie dazu”
Set-Content Datei.txt $Zeit,"Immer müssen Benutzer Ihren Senf dazu geben: $Benutzer"

Das Komma zwischen $Zeit und dem Text mit den Anführungszeichen bewirkt einen Zeilenumbruch.

Das Cmdlet Out-File kann ebenfalls in Dateien schreiben. Da sich die Parameter bei Out-File etwas anders gestalten als bei Set-Content müssen Sie den Text entweder über eine Pipe an Out-File übergeben, oder aber den Schalter –InputObject verwenden:

Out-File Text.txt –InputObject “Andere Methode um in Dateien zu schreiben“

Wenn Sie mit gc Text.txt in die Datei hineinschauen, stellen Sie fest, dass der alte Inhalt auch hier wieder überschrieben wurde. Out-File kann jedoch etwas, das Set-Content nicht kann. Es verfügt über den Schalter –Append. Damit können wir unseren Text an bestehende Dateien anhängen, ohne den alten Inhalt zu löschen.

Out-File Text.txt –InputObject „…und noch eine 2. Zeile“ -Append

Prüfen Sie mit gc Text.txt das erwartete Ergebnis.

2.2.12.2.2             Ausgabe in CSV-Dateien und einlesen von CSV-Dateien

Wenn Sie z. B. einen Verzeichnisinhalt gerne in Excel weiterverarbeiten möchten, können Sie die Ausgabe auch in eine CSV-Datei schreiben. CSV steht für „Comma separated Value“ also zu Deutsch: Die Ausgabe wird in eine Textdatei geschrieben wobei die einzelnen Eigenschaften durch ein Komma getrennt werden. Fast jedes Programm ist in der Lage so eine Datei als Tabelle zu importieren. Mit PowerShell ist das auch wieder denkbar einfach:

ls c:\windows | Export-CSV Tabelle.csv

Was in der PowerShell so einfach ist, wird im Anwendungsprogramm Excel 2010 zu einer echten Herausforderung! Bei allen mir bekannten Tabellenkalkulationsprogrammen geht man in den Datei Öffnen Dialog und wählt als Dateitypfilter *.csv aus. Doppelklickt auf die Datei und schon hat man es Tabelle im Programm und kann es weiterverarbeiten. In alten Excel-Versionen ging das auch noch so einfach. Leider muss Microsoft mit komischen Ribbons und anderen seltsamen Erfindungen uns das Leben „vereinfachen“. In Excel 2010 müssen Sie erst in der Menüleiste auf Daten klicken und dann im Menüband auf Aus Text.

Wenn Sie beim Export lieber ein Semikolon, oder sonstige andere Zeichen statt dem Komma als Trennzeichen zwischen den Eigenschaften verwenden möchten, können Sie das mit dem Schalter ‑Delimiter gerne tun.

ls c:\windows | Export-CSV –Delimiter “;“ Tabelle.csv

3 x dürfen Sie raten, wie Sie Tabellen-Daten die im CSV-Format vorliegen in die PowerShell bekommen. Da kommen Sie bestimmt leichter drauf, als in Excel 2010. ;-)

Import-CSV Tabelle.csv

Sie möchten es gerne weiter verarbeiten?

$tabelle= Import-CSV Tabelle.csv
$tabelle | select Name,Length

Die erste Zeile holt sich den Inhalt aus der CSV-Datei in die Variable $tabelle. In der 2. Zeile wird der Inhalt von $tabelle an den select Alias übergeben: Der wiederum filtert nur den Namen und die Dateilänge heraus.

2.2.12.2.3             Ausgabe in XML-Dateien und einlesen von XML-Dateien

Das XML-Format wird ebenfalls unterstützt. Die Cmdlets hierfür nennen sich Export-CliXML und Import-CliXML. Diese funktionieren im Prinzip genauso wie die im vorangegangenen Abschnitt erklärten Cmdlets für CSV-Dateien. In der Datei sind dann allerdings noch mehr Informationen zu den exportierten Daten vorhanden. Nicht nur die Daten an sich sind enthalten, sondern auch die Datentypen der jeweiligen Information. Sie können gerne einmal mit dem Alias gc in die mit den Export-Cmdlets erstellten Dateien hineinschauen und die XML mit den CSV Dateien vergleichen. Wenn Ihnen das zu schnell durchflutscht können Sie die Ausgabe auch gerne, wie aus anderen Shells bekannt an die Funktion more pipen:

Import-CliXML XMLDatei.xml | more

Noch deutlicher wird der Vergleich aber, wenn Sie sich das Verzeichnis mit den beiden unterschiedlichen Dateiformaten auflisten lassen und sich die Dateigröße anschauen. Bekommen Sie keinen Schock ;-). Bevor Sie Daten in ein XML-Dokument schreiben, sollten Sie also gut überlegen, ob Sie nicht vorher mit select überflüssigen Ballast herausfiltern.

2.2.12.3                Daten auf dem Drucker ausgeben

Mit Out-Printer können Sie Ihre Daten auch auf einem Drucker ausgeben.

ls c:\windows | Out-Printer

Diese Befehlskette gibt den Inhalt des Windows Verzeichnisses auf dem Standarddrucker, statt auf dem Bildschirm aus. Wenn Sie mehrere Drucker angeschlossen haben und einen bestimmten ansteuern möchten, geben Sie nach Out-Printer einfach noch den Namen des Druckers an, so wie er in der Systemsteuerung unter Drucker angezeigt wird.

2.2.12.4                Datenausgabe unterdrücken

Wenn Sie keine Ausgabe haben möchten, können Sie diese mittels Out-Null einfach verwerfen. Dies entspricht auf der DOS-Eingabeaufforderung >nul und unter Linux | /dev/null. Hier wird es für Sie noch nicht allzu viel Sinn ergeben, aber Sie werden später im Verlauf des Buches noch öfter Gelegenheit haben den tieferen Sinn erfahren.

2.2.13                     Quoting Regeln

Quoting Regeln sind Regeln, die festlegen wie die PowerShell Benutzereingaben interpretiert. Einen Bruchteil davon haben Sie bereits kennengelernt.

2.2.13.1                Regel Nr. 1

Zahlen werden niemals in Anführungszeichen gesetzt – Texte immer!

2.2.13.2                Regel Nr. 2

Inhalte von Anführungszeichen werden interpretiert, Inhalte von Hochkommata nicht!

Nun gibt es auf Ihrer Tastatur aber viele unterschiedliche Formen von dem was man landläufig unter Anführungszeichen, oder auch liebevoll Gänsefüßchen genannt, versteht. Da gibt es die normalen, doppelten Anführungszeichen , das einfach Hochkomma , und den sogenannten Backtick `.

Wenn Sie einen Text wiedergeben möchten, haben Sie bereits erfahren, dass wir hier in der Powershell keinen speziellen Befehl dafür benötigen. Echo, Print oder so etwas in der Art können wir uns hier sparen.

Wenn wir einen Text wiedergeben möchten, können wir einfach den Text in doppelte Anführungszeichen setzen und schon wird es nach einem Druck auf die Enter-Taste am Bildschirm ausgegeben. 2 x hintereinander das einfache Hochkomma eintippen, gilt nicht als doppeltes Anführungszeichen!

“Mein auszugebender Text“

Variablen kennen Sie auch bereits. Erstellen Sie sich eine Variable mit Ihrem Namen als Inhalt. Beispielsweise so:

$MeinName=“Martin Lehmann“

Nun können wir Text und Variablen mischen:

“Ich heiße $MeinName“

Dass Sie Texte “addieren“ oder besser formuliert verketten können, haben Sie auch bereits gelernt.

“Ich heiße “+$MeinName

Was davon direkt Text und was in einer Variable hinterlegt ist, spielt dabei offensichtlich keine Rolle. In seltenen Fällen kommt dabei allerdings dieser Text heraus: Ich heiße +MartinLehmann

Da haben wir ein Plus zu viel. Wenn das einmal der Fall sein sollte schreiben Sie die Variable einfach direkt hinter die Anführungszeichen, ohne das Pluszeichen:

“Ich heiße “$MeinName

An dieser Stelle dürfte dies allerdings zu einer Fehlermeldung führen. Wie heißt es doch so schön? Ausnahmen bestätigen die Regel! Im Normalfall sollten Sie daher immer mit dem Pluszeichen arbeiten.

In einigen Situationen wollen Sie aber gar nicht den Inhalt der Variablen wiedergeben, sondern den Namen der Variablen selbst. Das kann es schwierig werden, wie das nachfolgende Beispiel demonstrieren soll:

An beiden Stellen wird der Inhalt der Variablen eingesetzt.

Um das zu unterbinden können wir das einfache Hochkomma verwenden. Die Taste für das Hochkomma finden Sie links neben der Enter-Taste. Ja genau, die Taste mit dem Hashmark #, allerdings bei gedrückter Großschreibtaste. Probieren Sie einmal folgendes:

Hmm…sieht anders aus. Es ist aber auch dämlich, wenn beide Variablen angezeigt werden. Das wollten Sie ja auch nicht. Ein Text in Hochkomma bewirkt also, dass Variablen nicht expandiert (der Inhalt ausgelesen wird) werden, sondern einfach als Text dargestellt. Das gilt nicht nur bei Variablen, sondern auch bei allen anderen Sonderzeichen auf die wir gleich noch kommen.

Aber vielleicht können Sie auf den Rechentrick zurückgreifen?

Ha, ha…ausgetrickst! Sie sehen also mit etwas Überlegung kommt man meistens doch an sein Ziel, wenn man unterschiedliche Techniken einfach kombiniert.

2.2.13.3                Regel Nr. 3

Der Backtick ` kann bei Inhalten von Anführungszeichen, ein einzelnes nachfolgendes Sonderzeichen zu normalem Text umwandeln, aber vor einem normalen Buchstaben kann dieser Buchstabe eine Sonderfunktion bekommen.

Möchten Sie nur für ein einzelnes Symbol wie das $ Zeichen, der PowerShell erklären, dass Sie es nicht als Sonderzeichen behandeln soll, können Sie das mit einem Escape-Zeichen tun. Escape zu Deutsch heißt Flucht. Ab und zu findet man daher auch den eingedeutschten Begriff Fluchtzeichen. Das Escape-Zeichen, um der PowerShell zu sagen schau mal gerade weg, oder mache eine Pause ist der Backtick: `. Den Backtick bekommen Sie mit der Taste links neben der Rücklöschtaste, wenn Sie dabei die Großschreibtaste gedrückt halten und danach noch irgendeine Taste drücken. Zugegeben, das ist etwas seltsam, aber das liegt nicht an der PowerShell, sondern das ist allgemein so. Der Backtick hat gleich 3 Funktionen.

Zum einen können wir damit unser Problem lösen:

Dabei wir nur beim ersten $ Zeichen durch den Backtick „weggeschaut“ haben, wird somit die erste Stelle nicht als Variable, sondern als darzustellender Text interpretiert. Da beim zweiten $ Zeichen kein ` davor steht, wird hier wie gewohnt der Inhalt der Variablen ausgegeben. Achtung! Der gesamte Text muss dabei in doppelten Anführungszeichen stehen. Wäre ein Hochkomma um unseren Text, würde er alle darin enthaltenen Zeichen als Text anzeigen. Also sowohl die $ Zeichen, als auch den Backtick `.

Zum anderen kann er aber auch normale Buchstaben zu Sonderfunktionen umwandeln:

`t erzeugt also einen Tabstop und `n eine neue Zeile. Was es sonst noch so für Escape-Zeichen gibt erfahren Sie, wenn Sie help about_escape_characters in die PowerShell eintippen.

2.2.13.4                Regel Nr. 4

Der Backtick kann als Zeichen für einen Zeilenumbruch während der Befehlseingabe verwendet werden.

Zum Dritten können Sie damit einen Zeilenumbruch während der Befehlseingabe durchführen. Nach dem Backtick können Sie Enter drücken und kommen damit in die nächste Zeile, ohne den Befehl abzusetzen. Jetzt können Sie in der zweiten Zeile einfach weiter tippen. Wenn Sie Ihren „Roman“ dann als Befehlskette absetzen möchten, tippen Sie zum Schluss 2 x Enter. Beim ersten Mal geht er wieder in die nächste Zeile und beim zweiten schließlich wird die Befehlskette ausgeführt. Wenn Sie die Befehlseingabe abbrechen möchten, weil Sie sich vertippt haben, können Sie das mit Strg+C tun.

Get-ChildItem `
>> C:\Windows
>>

An dieser Stelle muss ich gleich einmal mit einem Gerücht aufräumen. Viele behaupten, das geht auch mit =, |, {, [ oder (. Nun, das geht schon…aber das sind keine Zeichen um eine neue Zeile auf zu machen, sondern die PowerShell ist so clever zu merken, wenn Benutzer Fehler machen!

PowerShell Denkprozess ;-): Wenn jemand ein=tippt, dann will er sicher etwas zuordnen. Wenn der Benutzer jetzt einfach Enter drückt, dann gebe ich ihm in der zweiten Zeile die Gelegenheit es fertig zu stellen. Bevor der Typ vorm Bildschirm wieder seinen Ausraster bekommt und wild auf die Tastatur einprügelt.

Probieren Sie es einmal aus:

$a=
>> “Hutzli Butzli“
>>

Wäre das = Zeichen tatsächlich für den Zeilenumbruch, dann müsste man es in der zweiten Zeile eigentlich noch einmal eintippen. Das ist aber nicht der Fall, dann gäbe es einen Fehler. Die PowerShell ist schon recht clever und kann einige fehlerhafte Eingaben ausbaden, aber Doppelfehler sind zu viel. Der Fehler ist meist irgendwo vor dem Bildschirm. Sie schlagen doch keine armen, wehrlosen Computer? ;-)

2.2.14                     Verwenden von Rechen- und Zuweisungs-Operatoren

Operatoren kennen Sie z. B. aus der Mathematik das + Zeichen. Mit diesem Zeichen weiß jeder was mit den Zahlen links und rechts davon zu tun ist. Auch die Bedeutung eines=Zeichens kennt wohl jeder. In der PowerShell funktioniert das aber alles ein bisschen anders.

Die Grundrechenarten haben Sie bereits im Abschnitt Verarbeitung des vorangegangenen Kapitels kennen gelernt. Hier nun ein paar weitere Möglichkeiten für Berechnungen:

$a=5
$a=$a+3

Zunächst wird die Zahl 5 in die Variable $a gelegt. In der zweiten Zeile sagen wir der PowerShell erneut was in $a stehen soll und greifen dabei auf den zuvor zugewiesenen Wert (5) zurück. Im $a rechts vom=Zeichen, steht erst einmal noch der Wert 5 aus der ersten Zeile. Jetzt wird zu diesem alten Wert 3 dazu addiert und in der Variablen links vom=Zeichen abgelegt. Das auch Links $a steht ist dabei nicht weiter problematisch. Wenn Sie sich nun den Inhalt von $a anzeigen lassen, werden Sie feststellen, dass 5+3 gerechnet wurde, also 8 herauskommt.

Das Ganze kann auch, wie z.B. in der Programmiersprache C üblich, abgekürzt werden:

$a-=4

Hört sich an, als ob Joda von Krieg der Sterne an der Formulierung beteiligt war: „PowerShell Du lernen musst und die Muschel der Macht wird sein mit Dir, mein junger Padawan“. Eben hatten wir noch eine 8 in $a hinterlegt. Die Anweisung hat nun 4 von der 8 abgezogen. Jetzt sollte der Wert demnach 4 betragen, wenn Sie sich $a anzeigen lassen.

$a+=5 legt 5 drauf und $a wird 9, $a/=3 teil durch 3 und $a wird zu 3, bei $a*=2 ergibt folglich 6 in $a.

Wenn mehrere Variablen den gleichen Inhalt haben sollen, können Sie die Zuweisung auch in einem Rutsch vornehmen:

$a=$b=$c=15

Sowohl $a, $b als auch $c beinhalten nun die Zahl 15.

Wenn Sie nur 1 drauf legen möchten, oder wegnehmen können Sie ganz auf die Schnelle folgende Schreibweise verwenden:

$a--
$b++

Nach diesen beiden Zeilen enthält $a 14 und $b 16, während $c immer noch 15 ist.

Noch eine interessante Zuweisungsmöglichkeit finden Sie in dieser Form:

$a, $b, $c, $d=17, 4, 47, 11

$a enthält jetzt 17, $b 4, $c 47 und $d die 11. Natürlich können Sie noch mehr Zuweisungen machen, dazu müssen Sie einfach die Werte und Variablen weiterhin mit Komma getrennt auflisten. Die Position (nach Kommatrennung betrachtet) gibt dabei an welche Variable welchen Wert zugeordnet bekommt. $a steht ganz links, also an Position 1 vor dem=Zeichen und 17 steht ebenfalls ganz links, aber nach dem=Zeichen. $c steht somit an Position 3 und daher wird Ihr die 3. Position nach dem=Zeichen zugewiesen, also 47.

2.2.15                     Kontrollfragen

Was kommt dabei raus, wenn Sie 2+“5“ eintippen?

Was kommt dabei raus, wenn Sie „2“+5 eintippen?

Was kommt dabei heraus, wenn Sie 2+“Fünf“ eintippen?

Mit welchen Cmdlet können Sie eine Textdatei nach einem Stichwort durchsuchen?

Mit welchem Cmdlet können Sie das Datum einstellen?

Was ist der Unterschied zwischen dem Cmdlets Select-String und Select-Object?

Auf welches Cmdlet verweist der Alias Select?

Mit welchem Cmdlet können Sie einen Durchschnittswert berechnen lassen?

Wie können Sie eine bestimmte Spalte absteigend sortieren lassen?

Mit welchem Cmdlet können Sie gleiche Daten zusammenfassen lassen?

Wie können Sie eine Zahl in Hexadezimal darstellen?

Mit welchen Cmdlets können Sie eine Ausgabe in Listen- bzw. in Tabellenform erzwingen?

Wie können Sie am Bildschirm farbige Texte darstellen?

Welches Cmdlet erlaubt Ihnen in eine Datei zu schreiben?

Wie können Sie eine tabellarische Ausgabe des Bildschirms in Excel weiter verarbeiten?

Wie können Sie Ausgaben auf den Drucker schicken?

Wie können Sie Ausgaben komplett unterdrücken?

Was ist der Unterschied zwischen den Zeichen " ' `?

Wie kann man eine Zählvariable $x am einfachsten um den Wert 1 erhöhen?

Link zu den Lösungen für Ausgabebefehle

2.3     Objektorientierung

In diesem Kapitel lernen Sie die Objekt orientierte Programmierung kennen. Die PowerShell selbst arbeitet komplett Objekt orientiert, da Sie auf dem .NET-Framework aufbaut. Allerdings finden wir auch Schnittstellen zu anderen Objekt orientierten Modellen die sich fast nahtlos in die PowerShell Konsole integrieren. Auch der WMI-Zugriff und das COM-Modell werden hier erläutert.

2.3.1   Theoretische Einführung in objektorientierte Programmierung

Sie werden vielleicht Angst vor diesem Abschnitt haben, aber ich kann Ihnen versichern die PowerShell und ich werden Ihnen im Handumdrehen erklärt haben was es damit auf sich hat und dass es gar nicht so schwer ist, wie die C und Java Programmierer immer tun. Eigentlich sind Sie sogar schon mittendrin und es hat Ihnen nur noch keiner gesagt. Denn in der PowerShell gilt: Alles ist Objekt! Die Cmdlets, die Sie bis jetzt eingetippt haben sind alle Objekte und sogar die zuvor besprochenen Variablen…alles Objekte. Also eigentlich sind Sie jetzt schon ein Programmierer der Objektorientierung verwendet, Sie wussten es nur noch nicht. Aber hier werden Sie nun gleich die ganze Macht erleben, die in unserer Muschel steckt und den ersten Schlüssel zur Macht erhalten.

Am einfachsten fangen wir mit dem richtigen Leben an. Was würden Sie denn im richtigen Leben als Objekt bezeichnen? Ein Auto, einen Tisch, einen PC, einen Menschen, einen Hund usw… Da liegt es nahe zu behaupten: Alles was ich anfassen kann ist ein Objekt.

Wenn wir nun ein Objekt beschreiben wollen, wird uns auffallen, dass wir das meist mit irgendwelchen Eigenschaften tun. Die Farbe eines Autos ist z.B. Rot, Grün oder Gelb. Menschen haben eine bestimmte Augen- oder Hautfarbe und im Idealfall eine Temperatur von 37 °C. Auch die PowerShell-Objekte haben Eigenschaften. Im Englischen werden zwei Begriffe dafür verwendet. In der PowerShell werden Sie häufig die Bezeichnung Property finden. In einem Cmdlet haben wir das sogar schon eingesetzt. Auch die Spaltenüberschrift der Ausgabe ist die Bezeichnung der Eigenschaft (Property, in der Mehrzahl: Properties). Gelegentlich stoßen Sie auch auf den Begriff Attribut. Zurück zu unseren Auto Beispiel:

Ein Auto hat die Farbe Rot
entspricht in PowerShell in etwa:
Die Datei hat den Namen Explorer.exe
oder Syntaktisch:
Das Objekt hat die Eigenschaftenbezeichnung Eigenschaft.

Auch mit den Fähigkeiten wird oft ein Objekt beschrieben. Autos können fahren, Menschen können laufen, Flugzeuge und Vögel können fliegen. In der PowerShell sprechen wir aber nicht von Fähigkeiten, sondern von einer Methode. Ab und zu taucht auch der Begriff Funktion auf, der stellt in der PowerShell aber ein bisschen was anderes dar. Halten wir fest: Methoden eines Objektes können etwas machen! Eigenschaften werden immer durch Methoden verändert. Oft bekommen Sie davon aber herzlich wenig mit. Erinnern Sie sich noch, als Sie geschrieben haben $a=5? Wenn ich diesen so einfachen Vorgang Objekt orientiert beschreiben wollte, könnte ich noch ein Kapitel dran hängen ;-).

Ein Objekt besteht also grundsätzlich aus Eigenschaften und Methoden.

Sind Sie immer noch der Meinung, dass man Objekte anfassen können muss? Was ist mit der Luft? Die hat z.B. einen Geruch, oder eine Temperatur als Eigenschaft. Wenn der Wind weht, könnte man von einer Methode der Luft sprechen. Mit dieser kleinen Analogie will ich Sie nur einmal dazu anhalten, etwas über den Tellerrand hinaus zu blicken. Denn in der Programmierung und im Skripting werden Sie immer wieder feststellen, dass Ihr Standardmuster nicht passt und Sie einfach einmal nachdenken müssen, wie es sonst noch klappen könnte.

Ein Objekt kann sich aber auch aus anderen Objekten zusammensetzen. Zurück zum Auto: Ein Auto ist ein Objekt! Was ist dann ein Rad davon? Auch ein Objekt! Das Objekt Auto setzt sich also aus 4 Rädern, einer Karosserie, einem Motor usw…zusammen. Das Rad besteht wiederum aus Felge und Reifen. Der Reifen besteht aus…

Sie sehen wie vielfältig sich Objekte zusammensetzen können.

Was macht eigentlich einen Menschen aus? Da muss es doch irgendeinen Bauplan geben, der beschreibt wie ein Objekt der Art Mensch auszusehen hat. Das ist noch ein Begriff der im Zusammenhang mit Objektorientierung erklärt werden muss: die Objektklasse. Die Objektklasse sagt aus wie ein Objekt auszusehen hat. Beim Objekt Mensch z.B. wären 4 Beine doch eher unpassend für einen Hund hingegen wunderbar. Die Objektklasse Mensch würde beispielsweise festlegen, dass sich ein Mensch aus folgenden untergeordneten Objektklassen wie 2 Beinen, 2 Armen, 1 Kopf und 1 Torso, der alles miteinander verbindet, zusammensetzt. Ach ja, und eine Eigenschaft Name wäre auch nicht schlecht. Aus dieser Vorlage (der Objektklasse) kann ich nun einen bestimmten Menschen erstellen, z.B. den Martin Lehmann. O.k., davon gibt es viele, aber durch die vielen Einstellmöglichkeiten an den Eigenschaften, der Objektklasse und deren Unterklassen gibt es eine einzigartige Mischung die dann zu einem Buchautor führt. Wird ein Objekt aus einer Objektklasse erstellt nennt sich das instanziieren. Wenn Sie einmal den Begriff Instanz lesen ist damit das Objekt (z.B.: Explorer.exe) das aus einer Objektklasse (z.B.: Datei) generiert wurde gemeint. Die Objektklasse gibt weiterhin die Einstellmöglichkeiten der Eigenschaften und die Methoden vor. Die Objektklasse Kopf enthält wiederum zwei Augen. Die Augenfarbe beim Menschen kann Grau, Grün, Blau oder Braun sein und es kann zwinkern. Das ist von der Objektklasse Auge so vorgegeben. Die Eigenschaft Lila der Augenfarbe ist in der Objektklasse menschliches Auge leider nicht vorgesehen. Hey, kann da mal einer ein Update schreiben? Ich will lila - das wäre cool! Weiter hinten im Kapitel werden wir ein bisschen Genforschung betreiben, sprich in der PowerShell können wir uns einfach lila Augen basteln!

Ich fasse noch einmal kurz zusammen: Die Objektklasse gibt die Methoden und Eigenschaften von Objekten vor. Jedes Objekt ist die Instanz (Ableitung) aus einer Objektklasse. Bei der Instanziierung werden den Objekten die Eigenschaften zugewiesen und Methoden aus der Objektklasse „vererbt“.

Auf eine Objekt-Eigenschaft greifen Sie einfach in der .NET-Schreibweise zu:

Objekt.Eigenschaftenbezeichnung

oder

Objekt.Unterobjekt.Eigenschaftenbezeichnung

Denken Sie an das Beispiel Auto, könnte das so aussehen:

VW.Golf.Karosserie.Farbe

Wenn Sie die Eigenschaft Farbe ändern möchten geht das in den meisten Fällen ganz einfach:

VW.Golf.Karosserie.Farbe=“Rot“

Auf eine Methode greifen Sie in der folgenden Schreibweise zurück:

Objekt.Methode()

oder

Objekt.Methode(zu übergebender Wert)

oder

Objekt.Unterobjekt.Methode(zu, übergebende, Werte)

Aufrufen einer Methode am Beispiel Auto:

VW.Golf.MotorStarten()

Am Beispiel Mensch, um den Zeigefinger der linken Hand zu 60% zu krümmen:

MartinLehmannAusBruchkoebel.LinkerArm.Hand.Zeigefinger.Kruemmern(60)

2.3.2   Objekt-Eigenschaften in der Praxis

Wenn Sie ein Cmdlet ausführen macht das etwas für Sie. Bei den meisten Cmdlets bekommen Sie nach der Ausführung etwas auf der Standardausgabe (dem Bildschirm) angezeigt. Sie bekommen zwar Text angezeigt, aber nur weil das für uns Menschen optisch in Text besser zu verstehen ist. Das Cmdlet an sich generiert ein Objekt und durch die Standardausgabe wird es als Text auf dem Bildschirm dargestellt.

Wenn Sie Get-ChildItem ausführen, wird Ihnen das aktuelle Verzeichnis auf dem Bildschirm dargestellt. Das was Sie da sehen ist in etwa zu vergleichen, wie wenn Sie jemand auf der anderen Straßenseite laufen sehen. Stellen Sie sich eine vierspurige Straße in beide Richtungen vor. Können Sie die Augenfarbe der Person sicher erkennen? Also ich mit meinen 43 Jahren nicht mehr, obwohl ich laut Augenarzt immer noch keine Brille brauche. Wenn Sie nun die Straßenseite wechseln, können Sie sich die Person genauer anschauen. Und wenn Sie hübsch ist, Ihr eine lange Zeit in die Augen schauen. Nach einer Minute des Starrens sind Sie bestimmt in der Lage die Augenfarbe zu bestimmen. Es sei denn die Person hatte so hübsche Augen, dass Sie auch gar nicht mehr wissen wie Sie selbst heißen ;-). Genau das können Sie in der PowerShell auch machen – die Straßenseite wechseln und sich das Objekt einmal aus der Nähe betrachten. Die Cmdlets zeigen immer nur einen Bruchteil der Informationen an, die es eigentlich zu bieten hat. Der Befehl um die Straßenseite zu wechseln nennt sich Get-Member oder der kürzere Alias gm. Get-Member ist das 4. heilige Cmdlet. Wenn Sie nun die Ausgabe (egal von was, Variable, Cmdlet, Funktion, WMI oder COM) mittels einer Pipe an gm umleiten können wir uns das Objekt der Begierde ganz genau ansehen:

Get-Childitem c:\windows| Get-Member
oder kurz:
ls c:\windows | gm
Vielleicht möchten Sie auch lieber noch einmal an more pipen:
ls c:\windows | gm | more

Da sich im Windows-Verzeichnis sowohl Dateien, als auch Unterverzeichnisse befinden, sehen wir 2 Objektklassenbeschreibungen und zwar von den Objektklassen System.IO.DirectoryInfo (Verzeichnisse) und System.IO.FileInfo (Dateien). Welche Objektklassen es sind steht also immer in den Zwischenüberschriften hinter TypeName:.

Auf die Zwischenüberschrift folgt jeweils eine Tabelle mit den Methoden und Eigenschaften (Properties). Das .NET-Framework selbst kennt nur Methoden und Eigenschaften. Die PowerShell macht sich die sogenannte .NET-Erweiterbarkeit zunutze um bei den Eigenschaften noch einmal verschiedene Varianten der Eigenschaften (z.B. ScriptProperty oder NoteProperty) zu implementieren. Im Moment reicht es für Sie erst einmal aus zu wissen, dass überall wo das Wort Property drin vorkommt, es sich um eine Eigenschaft handelt. Bei Methoden ist das ähnlich, aber in diesem Beispiel haben wir nur die .NET-Methoden (ohne Erweiterung).

Beim einfachen ls bekommen Sie als Spaltenüberschrift LastWriteTime angezeigt. Schauen Sie sich die Klasse FileInfo einmal näher an. Da finden Sie in der ersten Spalte ziemlich weit unten auch die LastWriteTime. Aber das steht nicht nur die LastWriteTime, sondern auch die LastAccessTime und wenn Sie wollen auch in UTC-Zeit, statt Ortszeit. Noch einmal ein Stückchen hoch finden Sie auch eine CreationTime. Wenn Sie nun alle drei Zeiten von einer Datei wissen möchten, können Sie sich das auch anzeigen lassen. Mit select haben Sie ja schon gearbeitet. Sie erinnern sich, wie Sie damit die Eigenschaften beschnitten haben? Wo Sie abschneiden können, dürfen Sie auch drauf legen! Wenn Sie den Namen und die 3 Zeiten (ohne UTC) haben möchten geben Sie einfach ein:

ls c:\ | select Name,CreationTime,LastWriteTime,LastAccessTime

Da dieselben Eigenschaften auch bei Verzeichnissen verwendet werden, wird es für die Verzeichnisse gleichermaßen bereitgestellt. Das dies nun aus 2 unterschiedlichen Objektklassen stammt ist der PowerShell egal. Oh, wie ich diese Shell liebe (ich kenne auch JAVA und C :-P). Ja, das geht auch in den anderen Sprachen, aber was müssen Sie dafür tun (Angefangen bei Klassendeklaration…)?

Alles wo in der mittleren Spalte bei gm das Wort Property auftaucht, dürfen Sie sich gerne mit select abholen und anzeigen lassen. Da eine Methode nun einmal keine Eigenschaft ist, geht das mit den Methoden nicht.

Im Abschnitt Daten filtern im vorigen Kapitel, hatte ich Ihnen beim ps Alias geschrieben, dass ich Ihnen hier verrate wie Sie herausbekommen, wie die Eigenschaften wirklich heißen. Nun, machen Sie doch einmal ein ps | gm und schon wissen Sie die echten Namen der Eigenschaften. Hier sehen Sie jetzt z.B. auch ganz oben die AliasEigenschaft Name, die auf die tatsächliche Eigenschaft ProcessName deutet. VM ist ebenfalls eine AliasEigenschaft. Da finden Sie sicher selbst heraus, wie die eigentliche Eigenschaft heißt. Ja, auch über Prozesse lässt sich hier eine Menge mehr herausfinden, als unter DOS und das auf super einfache Art und Weise.

Sie können gm am Ende einer jeden Verarbeitungskette einsetzen, um herauszufinden welches Universum sich hinter dem Offensichtlichen verbirgt.

Select kann noch einen weiteren Trick. Anstatt sich die Objektklassen anzuschauen, können Sie sich auch alle Eigenschaften mit allen Werten liefern lassen. Das hilft besonders, wenn Sie sich unter einer Eigenschaftenbezeichnung nicht vorstellen können. Statt nun jede einzeln anzugeben können Sie einfach mit dem * Platzhalterzeichen „Alle“ angeben.

ls c:\ | select *

Ein Haufen Info, was? Sie können die Informationsflut eindämmen, indem Sie nur vom ersten, oder letzten Objekt alle Eigenschaften auslesen.

ls c:\ | select * -first 1

Aber Vorsicht! Wenn unterschiedliche Objektklassen über die Pipe kommen, wie bei einem Verzeichnisinhalt könnte im ersten Objekt als Klasse ein Verzeichnis hinterlegt sein und beim letzten Objekt eine Datei. Gut, hier sind die Unterschiede nicht so groß, aber wenn Sie mehrere gänzlich unterschiedliche Objektklassen über die Pipe bekommen, kann das ohne gm ganz schön verwirren. Wenn Sie wissen möchten welche Objektklassen generiert werden, z.B. von ls können Sie das einmal ausprobieren:

ls c:\ | gm | select typename | unique –asstring

Sie können auch direkt auf Eigenschaften in der .NET-Schreibweise zugreifen. Wenn Sie z.B. wissen möchten welcher Wochentag heute ist, führen Get-Date | gm aus. Dort sehen Sie die Eigenschaft DayOfWeek. Anstatt nun lange mit Select zu hantieren klammern Sie das Get-Date einfach ein, damit wird das Datumsobjekt generiert. Wenn Sie das so eintippen ist erst einmal kein Unterschied zwischen mit oder ohne Klammer sichtbar. Allerdings dürfen Sie, wenn Sie es klammern, einfach .Eigenschaft hinten dran schreiben. In unserem konkreten Fall also:

(Get-Date).DayOfWeek

Eine „allgegenwärtige“, aber Get-Member gegenüber unsichtbare Eigenschaft ist Count. Sollte Count einmal nicht funktionieren, probieren Sie es stattdessen einfach mit Length. Einer von beiden geht immer! Wenn Sie ein Get-ChildItem abschicken, bekommen wir mehrere Dateien und Verzeichnisse zu sehen. Aber wie viele Objekte sind das eigentlich insgesamt? Genau das verrät uns diese gut versteckte Eigenschaft.

(ls).count

Jetzt wollen Sie auch noch wissen, wie viele Dateien und wie viele Verzeichnisse? Hmmm…mal überlegen. Wenn Sie von ls | gm die Ausgabe genauer untersuchen, stellen Sie fest, dass sich die Eigenschaft PSIsContainer  von Dateien und Verzeichnissen in der Spalte Definition unterscheiden. Für Verzeichnisse steht dort True und für Dateien False. True steht für Wahr und False für Falsch. Die Eigenschaft PSIsContainer ist wahr, wenn es ein Container ist (=Verzeichnis, denn es kann ja etwas beinhalten) und falsch, wenn es keiner ist, also eine Datei. Wie wäre es mit einer Gruppierung nach dieser Eigenschaft?

ls c:\windows | group psiscontainer

Schon sehen Sie in der ersten Spalte, wie viele Verzeichnisse und wie viele Dateien im Windows-Verzeichnis liegen. In der 2. Spalte steht dann True für Verzeichnisse bzw. False für Dateien. Natürlich werden Sie noch viel elegantere Möglichkeiten kennenlernen.

2.3.3   Daten Objekt orientiert filtern

Was uns jetzt noch fehlt, ist eine SQL-ähnliche Abfrage um Daten zeilenweise zu filtern. Gegen das, was ich Ihnen nun zeige ist ein grep oder Select-String langweilig.

Wechseln Sie ins Windows-Verzeichnis, da haben Sie eine schöne Spielwiese. Wenn Sie den Inhalt anzeigen lassen, kommt da einiges auf den Bildschirm. Wenn wir nur die Dateien mit der Dateinamenerweiterung exe angezeigt haben möchten, können Sie das schon direkt über Get‑Childitem mit dem Parameter –Filter tun:

ls –filter *.exe

oder ganz frech (die PowerShell wird’s schon richten):

ls *.exe

Mit –Filter zu arbeiten ist die empfohlene Vorgehensweise, da Filterfunktionen, wenn Sie vom Cmdlet vorgesehen sind, schneller arbeiten, als die nachfolgend vorgestellte Möglichkeit. Aber zum einen hat nicht jedes Cmdlet Schalter, wie –Filter, -Include oder –Exclude und zum anderen sind diese Parameter nicht so mächtig, wie das Where-Object.

Auf das Where-Object Cmdlet gibt es gleich 2 Aliase: where und das ?. Sie dürfen natürlich einsetzten was Sie möchten, ich werde hier der Kürze und Übersichtlichkeit halber aber die kürzeste Form wählen. Die geschweifte Klammer auf, finden Sie auf der Taste 7 und die dazugehörige Klammer zu, auf der Taste 0. Um diese zu bekommen müssen Sie die Tasten zusammen mit der AltGr-Taste (rechts neben der Leertaste) drücken. Den Tiefstrich erhalten Sie mit der Großschreibtaste und der Minus-Taste links neben der rechten Großschreibtaste.

ls | ? {$_.Name –like “*.exe“}

Das macht nun genau dasselbe wie ls *.exe, nur wesentlich langsamer. Wie viel langsamer werden Sie gleich sehen, doch jetzt muss ich Sie erst einmal überzeugen, wie toll der „Umstand“ sein kann.  Das ?-Zeichen ist der Alias auf Where-Object. Der von Ihnen definierte Filter muss in geschweifte Klammern geschrieben werden.  $_ ist eine spezielle Variable(eine sogenannte automatische Variable) in der PowerShell. Sie enthält den Inhalt der Pipeline. Ja, richtig! Alle Objekte, die übergeben wurden. Da sowohl Dateien, als auch Verzeichnisse den Namen in der Eigenschaft Name speichern, können Sie durch $_.Name den Name des jeweiligen übergebenen Objekts durch den Vergleichsoperator –like mit einem Muster, wie *.exe, vergleichen. Da *.exe ein Text ist, muss dieser natürlich wieder in Anführungszeichen stehen. Heraus kommen demnach auch nur wieder Dateien (und Verzeichnisse!) deren Name auf .exe endet. Da die wenigsten Verzeichnisse mit .exe aufhören, geschweigen denn überhaupt einen Punkt enthalten, werden hier wohl auch nur Dateien erscheinen.

Was ist nun so toll an Where-Object? Nun, Sie haben doch mit Get-Member gesehen wie viele Eigenschaften so ein Objekt enthält. Mit Where-Object können Sie nicht nur nach dem Dateinamen filtern, wie es die Parameter von Get-ChildItem tun, sondern nach jeder beliebigen Eigenschaft.

Mit dem Where-Object haben Sie das 5. heilige Cmdlet kennengelernt und somit die 5 heiligen Cmdlets zusammen. Fassen wir die 5 heiligen Cmdlets noch einmal zusammen:

1.       Get-Command

2.       Get-Help

3.       Select-Object

4.       Get-Member

5.       Where-Object

Wenn Sie sich auf diese 5 Cmdlets besinnen, sind Sie in der Lage alle Aufgabenstellungen mit PowerShell zu bewältigen!

2.3.4   Messen wie viel Zeit eine Verarbeitungskette in Anspruch nimmt

So und nun die üble Nachricht, die Sache mit der Geschwindigkeit. Dazu können wir das Measure-Command einsetzen (bitte nicht mir Measure-Object verwechseln). Measure-Command misst die Verarbeitungszeit von dem was Sie hinter Measure-Object in geschweiften Klammern hineinschreiben. Die leere Hülle würde so aussehen:

Measure-Command {}

Messen wir einmal nach!

Measure-Command {ls *.exe}

Merken Sie sich bitte die Millisekunden und dann führen Sie das hier aus:

Measure-Command {ls | ? {$_.Name -like "*.exe"}}

2.3.5   Objekte miteinander vergleichen

Nun vergleichen Sie einmal die Millisekunden. Where-Object ist in diesem Fall also rund um den Faktor 5-10 langsamer! 5-10, warum so ungenau? Nun das hängt wohl damit zusammen, wie die PowerShell gerade so drauf ist und Sie ist diesbezüglich sehr launenhaft. Mit dem Cmdlet Compare‑Object können wir auch Objekte bzw. deren Eigenschaften miteinander vergleichen.  Die Syntax sieht wie folgt aus:

Compare-Object (Objekt A) (Objekt B) –Property Eigenschaft

 Tippen Sie einmal folgendes ein:

compare-object (measure-command {ls *.exe}) (measure-command {ls | `
?
{$_.name -like "*.exe"}}) –property milliseconds

Objekt A ist dabei die Variante mit dem Schalter –Filter und Objekt B die Version mit dem Where‑Object. Als zu vergleichende Eigenschaft haben Sie die Milliseconds angegeben.

Wenn Sie nun die Taste mit dem Pfeil nach oben auf Ihrer Tastatur drücken, kommt der eben eingegebene Befehl erneut. Mit Enter setzen Sie ihn erneut ab. Machen Sie das doch ein paar Mal und sehen Sie wie launisch unsere Machtmuschel ist.

Get-Process, kurz ps,  z.B. hat keinen Parameter –Filter, oder –Include. Hier kann uns also nur das Where-Object weiterhelfen.

ps | ? {$_.name –like “powershell“}

listet Ihnen alle PowerShell-Prozesse auf. Interessanter ist vielleicht eher der svchost Prozess.

Probieren Sie doch einmal, alle svchost Prozesse anzeigen zu lassen. Die Lösung finden Sie im Anhang unter Übungen.

Übungsaufgabe: svchost Prozesse auflisten

Geben Sie doch einmal folgendes ein und versuchen Sie herauszufinden, was da passiert ist:

ps | ? {$_.name –eq “powershell“} | spps

Übungsaufgabe: spps

2.3.6   Vergleichs-Operatoren und reguläre Ausdrücke

Nein, reguläre und vulgäre Ausdrücke sind nicht das Gleiche. Reguläre Ausdrücke werden in der Regel zum Filtern von Ausgaben verwendet. Bestimmt kennen Sie von anderen Shells so etwas wie dir *.exe oder ls *.sh. Reguläre Ausdrücke können so etwas auch, nur sind sie viel mächtiger. Doch zunächst zu den Vergleichs-Operatoren.

Aus anderen Programmiersprachen kennen Sie vielleicht die folgenden Anweisungen:

=, ==, <, >, >=, usw.

In der PowerShell können Sie diese nicht verwenden, doch wir haben ähnliche Möglichkeiten, die zwar mehr Tipparbeit erfordern, aber auch vielfältiger sind.

2.3.6.1   Vergleichs-Operatoren für Zahlen

Wenn Sie wissen möchten, ob etwas größer ist als etwas anders können Sie auf –gt (greater than=größer als) oder –lt (less than=kleiner als) zurückgreifen.

Nur Zeile 2 und Zeile 6 ergeben True, weil alle anderen Aussagen nun einmal falsch sind. Wenn Sie größer gleich oder kleiner gleich auswerten möchten müssen Sie auf –ge (greater or equal) bzw. ‑le (less or equal) zurückgreifen.

Hier ist nur Zeile 3 und Zeile 5 False, da in Zeile 3: 1 nicht größer, aber auch nicht gleich 2 ist und in Zeile 5: 2 nicht weniger und auch nicht gleich 1 ist.

Wenn Sie wissen möchten über etwas ungleich ist können Sie das mit –ne (not equal=nicht gleich) abfragen.

Hier bekommen wir in Zeile 2 False, weil 1 und 1 gleich sind. Wir haben gefragt, ob die Linke und Rechte Seite vom Vergleichsoperator unterschiedlich sind. Nein, das sind sie nicht, also: False.

Dem = oder == zum exakten Vergleichen kommt der Operator –eq wie equals gleich. Sie können Operatoren auch direkt ausprobieren z.B.:

Die erste Zeile liefert True, weil 1 nun einmal genauso viel wie 1 ist und die zweite Zeile sagt False, weil 1 eben nicht 2 ist. –eq eignet sich weniger zum Vergleichen von Texten, da eine genaue Übereinstimmung gegeben sein muss. Denn:

1 –eq 1.0001

ist nun einmal nicht exakt gleich, sondern nur annähernd, also kommt hier auch False raus. D.h. wenn wir Texte vergleichen müssen die ebenfalls identisch sein:

…und nicht nur so ähnlich. Zumindest bei –eq.

2.3.6.2   Vergleichs-Operatoren für Text

Wenn wir Texte mit Platzhalterzeichen wie ? oder * vergleichen möchten müssen wir zu –like greifen, so wie im vorangegangenen Abschnitt mit dem Where-Object. Damit wissen Sie auch gleich wo Sie u. a. die Vergleichs-Operatoren einsetzen können.

Nur bei der 3. Zeile kommt False raus, weil ABC nicht mit B beginnt. Bei den Zahlenvergleichen gab es –eq und –ne. Das haben wir auch bei –like. Das Gegenteil dazu ist: –notlike.

Alle Vergleichs-Operatoren können also noch angepasst werden. Ihnen stehen für die Anpassung not, c und i zur Verfügung. Diese können Sie zwischen das und den eigentlichen Vergleich setzen, wie eben beim Beispiel –like und -notlike. not dreht den Vergleich logisch herum. c macht das Ganze Case-Sensitiv, sprich es wird beim Vergleich auf Groß-/Kleinschreibung geachtet. i ist sinnlos, weil es das Gegenteil von c ist, sprich Ignore-Case-Sensitivity. Da standardmäßig sowieso nicht nach Groß-/Kleinschreibung geschaut wird, werden Sie wohl nie ein i extra reinschreiben.

ls c:\windows | ? {$_.name –clike „W*“}

Gibt nur die Dateien aus dem Windows-Verzeichnis aus, die mit einem großen W beginnen, aber nicht mehr diejenigen, welche mit einem kleinen w beginnen. Die folgende Abbildung zeigt zuerst den Vergleichsoperator –like und danach den Operator –clike im Einsatz:

2.3.6.3   Reguläre Ausdrücke verwenden um mächtige Textfilter zu erstellen

Die mächtigste Waffe beim Textvergleich sind wie gesagt die regulären Ausdrücke.  Für diesen Vergleichsoperator verwenden Sie –match, wenn Sie alle Übereinstimmungen haben möchten und –notmatch, für alles, was nicht passt. Das ^ Zeichen bekommen Sie mit der Taste links neben der Taste für die Zahl 1 (im Alphanumerischen Bereich der Tastatur, nicht im Zahlenblock). Das ist allerdings genauso seltsam, wie beim Backtick. Erst müssen Sie die ^ Taste drücken und dann noch eine weitere (egal welche) hinterher, damit das ^ Zeichen erscheint.

ls c:\windows | ? {$_.Name –match „^w.*\.exe$”}

Den grundsätzlichen Aufbau des Where-Objects habe ich im vorangegangenen Abschnitt erläutert, daher will ich hier nur auf den Bereich zwischen den Anführungszeichen eingehen. Das ^ Zeichen sagt aus, dass das Zeichen, welches danach kommt am Anfang stehen muss. Der Dateiname muss also mit einem w beginnen, weil das w auf das ^ Zeichen folgt. Das $ Zeichen am Schluss sagt genau das Gegenteil aus, sprich: Die Zeichen die vor dem $ Zeichen sind müssen am Ende stehen. Daher muss der Dateiname auf .exe enden. Der Punkt entspricht dem Fragezeichen unter DOS. Er ist also ein Platzhalter für irdendein einzelnes Zeichen (Symbol, Buchstabe, Zahl…ganz egal). Das auf den Punkt folgende Sternchen ist ein sogenannter Wiederholungsoperator und sagt aus, dass das Zeichen, welches vorne dran steht, 0 mal, 1 mal oder mehrfach vorkommen kann. Der Witz an der Sache ist der, dass der Punkt ja selbst ein Platzhalter für ein Zeichen ist. Durch die Kombination .* haben wir das, was wir von DOS her als den * Platzhalter kennen. Was soll nun das \ Zeichen? Nun gerade haben Sie gelesen, dass der Punkt ein Platzhalterzeichen ist und somit eine besondere Funktion innehat. Das \ Zeichen sorgt dafür, das der Punkt seiner Sonderfunktion beraubt wird und nunmehr nur noch als ein ganz normales Zeichen, sprich ein . interpretiert wird. Ohne das \ Zeichen könnte die Datei mit „2exe“, „aexe“, !exe“ oder sonst wie, Hauptsache mit 4 Zeichen wovon die letzten 3 „exe“ sind, enden.

Die eckigen Klammern für das nächste Beispiel bekommen Sie mit AltGR+8, bzw. AltGR+9.

ls c:\windows\system32 | ? {$_.Name –match „^[wrt].*\.(exe|com)$”}

Das Beispiel ähnelt dem vorigen. Allerdings haben wir das w mit einem r und einem t in eckigen Klammern und es geht ins System32-Verzeichnis, da im Windows-Verzeichnis keine *.com-Dateien liegen. Die eckigen Klammern bedeuten, dass alle eingeklammerten Zeichen an dieser Position stehen können, aber kein anderes. Dadurch, dass das ^ Zeichen vor der eckigen Klammer steht, bedeutet dies, dass der Dateiname mit w, r oder t beginnen muss. Die runde Klammer mit der Pipe funktioniert ähnlich. Aber hier bezieht es sich nicht auf ein einzelnes Zeichen, sondern auf eine Zeichenkette. Das Pipe-Symbol dient hier als „Oder“. Da nach der runden Klammer ein $ Zeichen steht, muss die Datei also auf exe oder com enden. Im ersten Moment mag man den Eindruck haben, dass keine *.com-Dateien dabei sind, aber schauen Sie einmal genau hin. Eine win.com sollte da auch mit dabei sein. Damit haben Sie bereits die wichtigsten Sonderzeichen der regulären Ausdrücke kennengelernt, mit denen man schon verdammt viel anstellen kann.

Zeit für eine knifflige Übungsaufgabe! Listen Sie alle Dateien aus dem System32-Verzeichnis auf, die an der 2. Stelle ein w oder ein t stehen haben und die Dateiendung exe oder com.

Übungsaufgabe: RegEx

Für die Freaks sei die Lektüre help about_Regular_Expressions und help about_Comparison_Operators empfohlen, wo Sie eine sehr gute Erklärung für noch weitere reguläre Ausdrücke und Vergleichs-Operatoren finden.

2.3.7   Logische Verkettung von Vergleichs-Operatoren mit Logikoperatoren

Um z.B. die Where-Object Abfragen noch detaillierter zu gestalten haben Sie die Möglichkeit sogenannte Logische Operatoren einzusetzen. Damit erhalten Sie die Möglichkeit die einzelnen Vergleiche mit „und“, „oder“ bzw. „ist nicht“ zu verknüpfen.

2.3.7.1   Die Not Verknüpfung und boolsche Variablen

$true und $false sind fest definierte Variablen der PowerShell (so wie $_), die den Wert Wahr bzw. Falsch enthalten. Diese sollen hier nur verdeutlichen, wie man einen Wahrheitswert außer mit –not noch negieren (logisch umdrehen) kann. Mit den nachfolgenden Zeilen wird dargestellt, wie man anhand der Eigenschaft PSIsContainer, auf eine weitere Weise Dateien von Verzeichnissen unterscheiden kann.

Zeigt Ihnen alle Verzeichnisse (psiscontainer=True, ja es ist ein Behälter) auf Laufwerk c:.

Zeigt Ihnen alle Dateien (psiscontainer=False, nein es ist kein Behälter) im aktuellen Verzeichnis. Viele Wege führen nach Rom! Sicher fallen Ihnen noch ein paar mehr Varianten ein, die auch zum Ziel führen.

2.3.7.2   Die And Verknüpfung (und)

Wenn Sie alle Dateien aus dem Windows-Verzeichnis sehen möchten, die min. 50000 Byte groß sind und die Dateinamenerweiterung exe haben geben Sie folgendes ein:

$_.length fragt die Dateigröße in Byte ab. Demzufolge müssen wir vergleichen, ob diese größer als 50000 Byte sind. Achtung! Hier geht es um eine Zahl, also keine Anführungszeichen. Dann kommt die logische UND-Verknüpfung mit –and. Daraufhin folgt die bereits bekannte Abfrage der Namenseigenschaft die geprüft wird, ob sie auf .exe endet.

2.3.7.3   Die Or Verknüpfung (oder)

Nun lassen Sie sich doch einmal alle Dateien anzeigen, die größer als 50 KB sind oder die Dateinnamenerweiterung exe haben.

Das ist also dasselbe Spiel wie eben bei –and, welches einfach durch –or ersetzt wird.

Alles recht logisch, oder? Dann testen Sie doch einmal Ihr logisches Denken und Ihr Erinnerungsvermögen. Listen Sie doch einmal alle Verzeichnisse und log-Dateien aus dem Windows-Verzeichniss, deren letzter Schreibzugriff nach dem 20.11.2010 liegt.

Übungsaufgabe: Where-Object

Weitere Informationen zu logischen Operatoren finden Sie in:
help about_Logical_Operators.

2.3.8   Objektmethoden in der Praxis

Beim Aufruf von Get-Member sind Ihnen sicher bereits die Methoden (in der Spalte mit der Überschrift Membertype)  aufgefallen. Nun wollen wir die Objekte einmal etwas machen lassen.

2.3.8.1   Dateien ver- und enschlüsseln

Bei den Dateien gibt es u. a. die Methode Encrypt und Decrypt. Diese beiden Methoden dienen zum ver- und entschlüsseln von Dateien mittels EFS. Erstellen Sie sich ein Verzeichnis mit dem Namen Test auf Laufwerk c:, falls noch nicht geschehen. Wechseln Sie auf der PowerShell in das Verzeichnis und erstellen Sie mit set-content c:\test\opfer.txt “Opfer“ eine Datei. Prüfen Sie ob bei

ls | ? {$_.name –eq „opfer.txt“}

wirklich nur diese eine Datei dabei herauskommt. Durch

ls | ? {$_.name –eq „opfer.txt“} | gm

stellen Sie fest, dass es hier eine Methode Decrypt und eine Methode Encrypt gibt. Sie haben doch Ihr Laufwerk C: mit NTFS formatiert ;-)? Bei FAT würde das natürlich nicht klappen, da EFS-Verschlüsselung ein Feature von NTFS ist. Aber heutzutage hat wohl niemand mehr ein Laufwerk C:, das mit FAT formatiert ist. Sie erinnern sich noch an die Information aus der Theorie, dass man Methoden immer mit einer runden Klammer am Schluss aufruft? Um die Methode Encrypt anwenden zu können gibt es zwei Möglichkeiten. Bei der ersten arbeiten wir mit einer Variablen:

$Datei=ls | ? {$_.name –eq „opfer.txt“}

Jetzt ist unsere Datei in der Variablen hinterlegt. Das können Sie prüfen, indem Sie einfach einmal $Datei eintippen und bestätigen. Da kommt genau dasselbe heraus, wie mit der kompletten Verarbeitungskette. Geben Sie nun einmal

$Datei | select Attributes

ein. Damit sehen Sie, dass Sie nichts sehen ;-). Zumindest steht hier kein Attribut Encrypted. Im Explorer erkennen Sie verschlüsselte Dateien in der Regel daran, dass diese grün statt schwarz angezeigt werden, falls Sie dort schauen möchten. Dazu  muss allerdings im Explorer unter Extras/Ordneroptionen/Ansicht ein Häkchen bei Verschlüsselte oder komprimierte NTFS-Dateien in anderer Farbe anzeigen gesetzt sein. Auf modernen Systemen ist das standardmäßig gesetzt. Um nun die Methode anzuwenden geben Sie einfach

$Datei.encrypt()

ein. Nun noch ein

ls | select Name,Attributes

hinterher und Sie sehen, dass Ihre Datei nun verschlüsselt ist, da Sie das Attribut verschlüsselt trägt. Gerne dürfen Sie das auch im Explorer prüfen.

Zum Entschlüsseln werden wir den direkten Zugriff wählen, ohne den Umweg über die Variable. Dazu müssen Sie die Verarbeitungskette in runde Klammern setzen, damit das Objekt generiert wird und danach können Sie ähnlich wie bei der Variante mit der Variablen die Methode hinten dran schreiben:

(ls | ? {$_.name –eq "opfer.txt"}).decrypt()

Ob das funktioniert hat können Sie wieder mit ls | select Name,Attributes prüfen. Die Schlaumeier unter meinen Lesern werden sich bestimmt denken, dass das auch mit
$Datei | select Name,Attributes
funktionieren müsste.  U. U. werden Sie aber unterschiedliche Ergebnisse zwischen direktem und dem Zugriff über eine Variable feststellen. Sie haben sich das Dateiobjekt zu Beginn in $Datei geholt und dann den Code, der durch die Methode Encrypt aufgerufen wird ausgeführt. Damit wurde die Datei verschlüsselt. Doch wer hat die Objekt-Eigenschaften, aktualisiert? Richtig, niemand! D. h. in $Datei.Attributes steht immer noch, dass die Datei entschlüsselt vorliegt, obwohl bei einer direkten Abfrage auf die Datei nun steht, dass sie verschlüsselt ist. Das bekommen Sie aber schnell wieder synchron in dem Sie einfach die Methode $Datei.refresh() aufrufen. Dadurch werden in diesem Fall die Objekt-Eigenschaften aktualisiert. Nicht an jedem Objekt gibt es die Methode Refresh. Zum einen weil das an manchen Objekten gar nicht nötig ist und zum anderen weil ein anderer Methodenprogrammierer u. U. die Bezeichnung Update sinnvoller findet. Ein bisschen muss man schon mitdenken, aber man findet eigentlich immer recht schnell nach was man sucht.

Eine leichte Aufgabe: Löschen Sie die Datei Opfer.txt mithilfe einer Methode, nicht über das Cmdlet Remove-Item oder deren Aliase!

Übungsaufgabe: Datei löschen Methode

2.3.8.2   Objekt-Typen identifizieren

In der PowerShell gilt der Spruch: Alles ist ein Objekt! Sie erinnern sich vielleicht noch an die Möglichkeit die Versionsnummer der PowerShell abzufragen. Da gab es 2 Möglichkeiten: Die Variable $host und das Cmdlet Get-Host. Schauen wir uns die doch einmal genauer an. Mit Get-Member werden Sie herausfinden, dass die gelieferten Ansichten für beide Kommandos identisch sind. In beiden finden Sie daher die Methode GetType(). Diese Methode werden Sie nicht nur hier finden, sondern GetType ist an jedem .NET-Objekt vorhanden und kann daher auf jedes Objekt angewendet werden. Vergleichen Sie doch einmal die Ausgaben der folgenden beiden Befehle:

$host.gettype()

und

(Get-Host).GetType()

Na?! Identisch! Beide liefern also das Objekt InternalHost in der Spalte Name, welches auf die Objektklasse oder den sogenannten BaseType System.Management.Automation.Host.PSHost zurückzuführen ist.

Von nun an bleibe ich bei $host der kürzeren Schreibweise wegen, aber was nun kommt gilt natürlich genauso für das Cmdlet Get-Host.

Beim Get-Member auf $host haben Sie in der letzten Zeile sicher die Eigenschaft Version bemerkt. In der 3. Spalte mit der Überschrift Definition finden Sie Angaben auf welche Objektklassen die Methoden und Eigenschaften zurückzuführen sind. Bei der Version steht hier System.Version. Wenn Sie $host.Version aufrufen, bekommen Sie wiederum 4 Spalten. Das bedeutet also, das die Eigenschaft Version eigentlich gar keine Eigenschaft ist, sondern selbst auch wieder ein Objekt! Rufen Sie nun $host.Version | gm auf, sehen Sie das z.B. Major wiederum als Eigenschaft, basierend auf der Objektklasse System.Int32, gelistet ist. Int32=32 Bit Integer = eine Zahl zwischen -2147483648 und +2147483648. Es kann also noch ein paar weitere Versionen der PowerShell geben. Ach ja und die –Zahlen waren die Beta-Phase ;-).

2.3.8.2.1    Datentypen für ganze Zahlen

Zunächst einmal eine kurze Übersicht zu den Wertebereichen der Zahlen. Die kleinste Speichereinheit ist das Byte und kostet am wenigsten Speicher. Sie können Variablen Speichertypen zuweisen, indem Sie in eckigen Klammern die Bezeichnung voranstellen.

[Byte] $a=5

Wenn Sie wissen möchten, ob es funktioniert hat, nutzen Sie wieder die Methode GetType.

$a.gettype()

In der Spalte Name taucht nun Byte auf. 1 Byte=8 Bit, also binär 8 x 0 oder 1. Mit 8 x 0 oder 1 gibt es 256 Kombinationsmöglichkeiten. Das bedeutet, dass die höchste mögliche Zahl dezimal 255 ist, denn wir haben ja auch noch eine 0. Probieren Sie folgendes:

$a=0

$a=255

$a=256

$a=-1

Ergebnis:

Bei den letzten beiden gibt es Gemecker von der PowerShell, weil Sie der Variablen $a explizit den Datentyp Byte zugewiesen haben. Versuchen Sie dann außerhalb des Speicherbereiches zuzugreifen, bekommen Sie eine auf den Deckel ;-).

Bislang haben wir einfach nur Variablen Zahlen oder Texte zugewiesen, ohne uns über den Datentyp Gedanken zu machen. Dank PowerShell müssen wir das auch nicht. PowerShell nimmt bei Zahlen automatisch Int32. Das mag Ihnen verschwenderisch vorkommen, denn wie Sie wahrscheinlich schon vermuten, werden hier 32 Bit für die Zahl benötigt, auch wenn Sie lediglich eine 5 hineinlegen. 32 Bit=4 Byte=4 x 8 Bit. Also vier Mal so viel wie für ein Byte. Bei heutiger Speicherausstattung von PCs ist das aber zu verschmerzen. Schneller oder langsamer wird Ihr PC dadurch auch nicht. Bei Zahlen außerhalb des 32-Bit Integerbereiches (-2147483648 bis +2147483648), also beispielsweise bei einer Zuweisung: $a=3000000000, gibt es keinen Fehler, sondern PowerShell nimmt automatisch den nächstgrößeren Datentyp Int64. Der reicht dann von -9223372036854775808 bis +9223372036854775808 und sollte wohl für einfache administrative Tätigkeiten ausreichen. Sie wollen hier doch hoffentlich keine Spaceshuttleflugbahnen berechnen? Da würde ich Ihnen dann für Quick & Dirty zu Java raten, das ist leichter als in C und nicht wirklich langsamer. Die PowerShell ist für solche Berechnungen sowieso zu langsam.

Ärger bekommen Sie nur, wenn Sie explizit eine Variable als Int32 definiert haben und Ihr einen größeren Wert zuweisen möchten. Ansonsten konvertiert PowerShell automatisch das Int32-Objekt in ein Int64. Der Beweis:

Bis hier hin, alles wunderbar, aber jetzt:

Den letzten gettype können Sie sich sparen, denn es gab ja bei der Zuweisung von 2147483648 bereits Ärger.

2.3.8.2.2    Datentypen für Kommazahlen

Die bisher vorgestellten Zahlen unterstützen nur ganze Zahlen. Für Kommazahlen haben wir die Datentypen Float oder Single und Double. Standardmäßig wird Double verwendet, da der Wertebereich von -1,79769313486232E+308 bis +1,79769313486232E+308 wohl für die meisten Aufgaben ausreicht. Float und Single sind identisch und für Speichergeizkragen geeignet, da der Wertebereich hier „nur“ -3,402823E+38 bis +3,402823E+38 reicht und dementsprechend weniger Speicher benötigt wird.

2.3.8.2.3    Datentypen für Wahrheitswerte

Wenn Sie ein $true.gettype() ausführen sind Sie eigentlich schon im Bilde. Der Typ ist Boolean und kann natürlich ebenso von Ihnen selbst bei der Variablenerstellung definiert werden. Die Variable kann dann wie ein An- u. Ausschalter verwendet werden. Entweder enthält Sie $true, oder $false. Sie können auch Zahlen benutzen: 0=false und 1=true

2.3.8.2.4    Datentypen für Zeiten

Auch die Zeit hat einen eigenen Datentyp:

$zeit=get-date

$zeit.gettype()

Liefert in der Spalte Name DateTime zurück. Dies ist immer ein kombinierter Wert aus Datum und Uhrzeit, da die Zeit absolut geführt wird.

2.3.8.2.5    Datentypen für Texte

Es gibt nur einen Datentyp für Texte und das ist String.

$eingabe=read-host „Tippen Sie einen Text“

$eingabe.gettype()

Damit kommen wir gleich zu einer wichtigen Erkenntnis! Sie haben hier den Datentype String nicht explizit zugewiesen. Das bedeutet, Sie können nun ohne Bauchschmerz einfach behaupten:

$eingabe=5

$eingabe.gettype()

Sehen Sie, alles gut! $eingabe wurde automatisch zum Datentyp Int32 konvertiert. Jetzt ist $eingabe also eine Zahl. Doch passen Sie einmal auf und geben Sie bitte eine Zahl ein:

$eingabe=read-host "Tippen Sie eine Zahl"

$eingabe.gettype()

Sehen Sie genau das ist der Grund, warum es manchmal sinnvoll ist einen Datentyp explizit zuzuweisen und warum Sie sich mit der Möglichkeit auskennen sollten.

[int32] $eingabe=read-host "Tippen Sie eine Zahl"

$eingabe.gettype()

Denn es können auch andere interessante Dinge passieren, wenn man über Datentypen nicht Bescheid weiß. Starten Sie für das nachfolgende Beispiel am besten eine frische Shell, sonst tauchen noch andere Merkwürdigkeiten auf:

$a=3

$a.gettype()

$b=“2“

$b.gettype()

$c=$a+$b

$c

$c.gettype()

$c=$b+$a

$c

$c.gettype()

Bis hierhin ist das Beispiel noch selbsterklärend, aber jetzt wird es ominös:

$d=”Acht”

$d+$a

$a+$d

Sehr faszinierend! Bei $d+$a hat die PowerShell festgestellt, dass der erste Operand $d ein String ist und hat kurzerhand $a ebenfalls in einen String konvertiert und einfach verkettet. Bei $a+$d ist der erste Operand $a aber eine Zahl. Also versucht PowerShell den zweiten Operand passend zu machen indem sie versucht den String $d in eine Zahl zu konvertieren. PowerShell ist schlau, aber nicht so schlau, dass er aus einem Text „Acht“ auch eine Zahl 8 machen kann. Das Thema hatten Sie schon, nur hier haben Sie es genauer unter die Lupe genommen.

Im Anhang finden Sie eine Übersicht aller Datentypen.

2.3.8.3   Textverarbeitung mittels Objektmethoden

Vielleicht haben Sie auch schon einmal einen Get-Member auf eine Variable des Typs String gemacht. Bestimmt sind Ihnen dabei die vielen Methoden aufgefallen. Diesen sehr wertvollen Methoden zur Textverarbeitung wollen wir hier nun auf den Grund gehen. Fangen wir ganz gemütlich an:

$autor="Martin Lehmann"

Erstellt einen einfachen String, wie Sie das bestimmt schon mehrfach getan haben.

Mittels der Methode CompareTo können Sie prüfen ob ein bestimmter Text in Ihrer Variablen vorkommt.

$autor.compareto("Martin Lehmann")

Liefert eine 0 bei exakter Übereinstimmung.

$autor.compareto("Martin")

Liefert eine 1 bei teilweiser Übereinstimmung.

$autor.compareto("Xaver")

Liefert eine -1 wenn es gar nicht passt.

Anstatt dem Text mit den Anführungszeichen können Sie natürlich auch mit dem Inhalt anderer Variablen vergleichen. Beispielsweise so:

$IhrName=“Ihr Name“

$autor.compareto($IhrName)

Wenn hier eine 0 rauskommt, schreiben Sie mir doch einmal eine E-Mail und sagen „Hallo, ich habe Ihr Buch gelesen und könnte somit auch als der Autor durchgehen.“. ;-)

Wenn Sie einen Wahrheitswert bei einem Vergleich zurück haben möchten können Sie auch auf die Methode Contains zurückgreifen:

$autor.contains("tin")

Liefert Ihnen $true als Rückgabewert.

$autor.contains("ni")

Liefert Ihnen $false als Rückgabewert.

Wenn Sie wissen möchten ob der zu vergleichende Text an Anfang, oder am Ende vorkommt, können Sie die Methoden StartsWith und Endswith einsetzen:

$autor.startswith("Lehmann")

Liefert $false als Wahrheitswert.

$autor.startswith("Martin")

Liefert $true als Wahrheitswert.

$autor.endswith("Lehmann")

Liefert $true als Wahrheitswert.

$autor.endswith("Martin")

Liefert $false als Wahrheitswert.

Die Position eines Textabschnittes innerhalb eines Strings ermitteln können Sie mit IndexOf:

$autor.indexof("Lehmann")

Nur wenn der komplette Vergleichstext gefunden wird, gibt es die Position vom ersten übereinstimmenden Zeichen, also das L von Lehmann zurück. Da PowerShell in diesem Fall (und auch in den meisten anderen) bei 0 zu zählen beginnt, wird eine 7 als Ergebnis zurück geliefert.

$autor.indexof("a")

Liefert eine 1 zurück. Das a von Martin, aber nicht mehr das in Lehmann.

Wenn Sie lieber von hinten nach einem Zeichen oder Text suchen möchten benutzen Sie LastIndexOf:

$autor.lastindexof("Lehmann")

Liefert wieder die 7 (das wird also nach wie vor von vorne gezählt). Die Position des L von Lehmann wird zurückgegeben, durchläuft aber bei der Suche den String von rechts nach links.

$autor.lastindexof("a")

Liefert entsprechend die 11. Das a von Lehmann, aber nicht mehr das in Martin, weil bei LastIndexOf von hinten gesucht wird.

Wenn Sie einzelne Zeichen suchen, die Reihenfolge gesuchter Zeichen aber keine Rolle spielt, verwenden Sie die Methode IndexOfAny.

$autor.indexofany("Lr")

Liefert eine 2 zurück, da das r von Martin noch vor dem L von Lehmann gefunden wird.

Texte zusammenkleben geht einfach über das + Zeichen. Vielleicht möchten Sie aber auch einen Text an einer bestimmten Position in einen anderen einfügen. Das klappt prima mit der Methode Insert:

$autor.insert(0,"Autor ")

Fügt das Wort Autor am Anfang („nulltes“ Zeichen) ein. Gut, dass hätte man auch mit "Autor "+$autor regeln können. Aber das hier ist ein klassischer Fall für Insert:

$autor.insert(6,"des Buches ist ")

Damit haben wir den Text mitten rein gepflanzt. Allerdings beachten Sie bitte, dass die Standardausgabe der Bildschirm ist! Wir haben mit der Methode Insert nicht den Inhalt der Variablen verändert. Wenn Sie das möchten, müssten Sie schreiben (heben Sie sich das Testen aber bitte für den Schluss des Abschnitts auf, damit wir uns hier die Variable nicht „verbiegen“):

$autor=$autor.insert(6,"des Buches ist ")

Wenn Sie die Zeichen einmal untereinander, statt nebeneinander haben möchten verwenden Sie die Methode GetEnumerator:

$autor.getenumerator()

Mit Leerzeichen auffüllen können Sie mit den Methoden PadLeft und PadRight:

$autor.padleft(30)

Reserviert 30 Zeichen in die der Inhalt rechtsbündig eingefügt wird. Hier wird also auf der linken Seite mit Leerzeichen aufgefüllt, daher der Name.

$autor.padright(30)+“Endtest“

Reserviert 30 Zeichen in die der Inhalt linksbündig eingefügt wird. Damit man das auch mitbekommt, habe ich noch das +“Endtest“ angehängt.

Das Gegenteil von PadLeft und PadRight finden Sie in den Trim Methoden:

$autor.trim()

Entfernt alle links und rechts stehenden Leerzeichen. In diesem Beispiel passiert daher nichts, das Leerzeichen in der Mitte bleibt erhalten.

($autor.padleft(30)).trimstart()

Entfernt alle Leerzeichen auf der linken Seite. Erst haben Sie mit PadLeft Leerzeichen hinzugefügt, diese dann aber wieder mit TrimStart entfernt.

$autor.trimend()

Würde alle Leerzeichen auf der rechten Seite entfernen, wenn da welche stehen würden. ;-)

Wo man etwas reinsteckt, kann man auch etwas rausholen. Hierfür können Sie auf die Methode Remove zurückgreifen:

$autor.remove(6,1)

Entfernt 1 Zeichen an der 6. Position. Also in dem Fall das Leerzeichen.

Das Gegenteil davon finden Sie in der Methode SubString. Remove lieferte Ihnen das zurück was übrig bleibt, während SubString das Ausgeschnittene zurück liefert:

$autor.substring(7,3)

Schneidet ab Position 7, 3 Zeichen aus und gibt diese zurück. In diesem Falle also Leh.

Wenn Sie nur ein einzelnes Zeichen extrahieren möchten greifen Sie auf die Methode Char zurück:

$autor.chars(5)

Liefert das Zeichen der 5. Position, also das n von Martin.

Texte ersetzen können Sie mit der Methode Replace:

$autor.replace("Lehmann","Nachname")

Tauscht das Wort Lehmann gegen das Wort Nachname.

Um Texte durch bestimmte Zeichen zu trennen, wie z.B. einer Tabelle können Sie auf die Methode Split zurückgreifen:

$autor.split(" ")

Trennt den Inhalt an den Positionen von Leerzeichen.

Die einfachen Dinge des Lebens:

$autor.tolower()

Macht aus allen enthaltenen Großbuchstaben Kleinbuchstaben.

$autor.toupper()

Macht aus allen enthaltenen Kleinbuchstaben Großbuchstaben.

Hier noch ein Screenshot der zu erwartenden Ergebnisse:

Jetzt haben Sie alle Methoden der Stringverarbeitung kennengelernt.

2.3.8.4   Besondere Variablentypen: Array

Ein gutes Verständnis von Variablen haben Sie nun bereits. Jetzt wollen wir das noch etwas vertiefen mit mehrdimensionalen Variablen. Also Variablen, die nicht nur einen einzelnen Wert sondern mehrere enthalten können.

Auch mehrdimensionale Variablen, sogenannte Arrays können Sie explizit zuweisen mit dem Begriff [Array]. Auch hier ist das aber nicht unbedingt notwendig, da die PowerShell in der Regel erkennt was tun ist. Um mehrere Werte zuzuweisen gehen Sie wie folgt vor:

$array=“Eins“,“Zwei“,“Drei“

Der Zugriff und die damit verbundene Ausgabe auf dem Bildschirm kann so aussehen:

$array

In einem Array wird automatisch eine Indexnummer geführt. Der erste Wert erhält die Indexnummer 0, der zweite die 1 usw... Um auf einen bestimmten Wert aus dem Array zuzugreifen, können Sie diese Indexnummer einfach in eckigen Klammern hinten dran schreiben:

$array[0]

Dies liefert den Text Eins, oder

$array[2]

liefert natürlich den Text Drei zurück.

Wenn Sie einen leeren Array erstellen möchten geht dies auch mit:

$leer=@()

Um zu überprüfen, ob es wirklich ein Array ist, können wir auch hier wieder die GetType Methode anwenden:

$leer.gettype()

Spannend daran ist, dann es eigentlich gar keinen Array gibt. Zumindest steht nicht wie bei den anderen Datentypen, der Type in der Spalte Name, sondern nur der Verweis auf die Objektklasse unter BaseType bringt uns schließlich zu dem Schluss, dass es ein Array sein muss. Der Objekt-Typ ist hier tatsächlich Objekt. Das rührt daher, dass ein Array unterschiedliche Objekt-Typen in sich aufnehmen kann. Wir können also kreuz und quer, Texte, Zeiten, Zahlen und andere Objekte einfach in einem Array zusammen mixen. Glauben Sie nicht? Dann machen Sie doch einmal das hier:

$array+=2

$array+=get-date

$array

$array[4]

Damit haben Sie auch gleich die Möglichkeit gesehen einen Array zu erweitern. Aber woher wissen Sie nun, dass wir hier wirklich eine Zahl bzw. eine Zeit hinzugefügt haben? GetType weiß Rat:

$array[3].gettype()

$array[4].gettype()

$array[2] hatten wir mit dem Text Drei gefüttert. Durch $array+=2 haben wir $array um ein $array[3] vom Datentyp Int32 erweitert und die Zahl 2 hineingeschrieben. Daher erhalten wir mit $array[3].gettype den Datentyp dieses einen Eintrags. Ähnlich verhält es sich mit der Zeitangabe. Da jetzt $array[3] bereits existiert (da steht die Zahl 2 drin), wird ein neuer Eintrag unter $array[4] angelegt. Weil er eine Zeit aufnehmen muss wird natürlich automatisch der Datentyp DateTime verwendet.

Das gemeine am Array ist, dass wir hier kein Get-Member sinnvoll nutzen können. Aber natürlich können wir das einmal ausprobieren:

$array | gm

Statt dem Array selbst, hat Get-Member die „Mitglieder“ ausgewertet. Vielleicht erinnert Sie das jetzt an einen ls | gm auf ein Verzeichnis, dass sowohl Dateien, als auch Unterordner enthält. Genau! Auch hier bekommen wir einen Array mit allen Verzeichnissen und Dateien geliefert. Das bedeutet Sie können die Technik mit dem Index, auch auf das anwenden was über die Pipe kommt, oder auch direkt:

(ls c:\Windows)[3]

Gibt das 4. Element (die PowerShell fängt bei 0 das Zählen an!) aus dem Windows-Verzeichnis zurück.

(ls c:\Windows)[-3]

Macht das Ganze von unten. Aaaaber! Gemerkt? Was habe ich eben noch so großspurig von mir gegeben? Die PowerShell fängt bei 0 an zu zählen. Ätsch! Nicht, wenn wir das Pferd von hinten aufsatteln, da fängt Sie bei -1 an, dann -2 usw… Da haben wir eine der Ausnahmen die wieder einmal die Regel bestätigt.

Was in die eine Richtung klappt geht natürlich auch in die andere:

$array | select –last 2

Gibt uns die letzten beiden Objekte des Arrays zurück.

$array | select –first 2

Gibt uns die ersten beiden Objekte des Arrays zurück.

Von bis Angaben? Geht auch:

$array[2..3]

oder

(ls c:\windows)[2..3]

2.3.8.4.1    Elemente aus einem Array löschen

Jetzt wollen wir wieder etwas aus dem Array entfernen, aber wie ohne Methoden? Trick 17!

$array=$array[0..1]+$array[3..4]

Wir definieren $array einfach neu und erzählen Ihm, dass nur die Bestandteile des alten Arrays von 0 bis 1 und von 3-4 enthalten sein sollen. Position 2 haben wir damit herausgeschnitten. J

Das hätte auch funktioniert:

$array[0,1,3,4]

Aber das leider nicht:

$array[0..1,3..4]

Sie können also nur die von bis Schreibweise mit den Pünktchen werden, oder die einzelnen Objekte in einer durch Komma getrennten Liste. Beide Verfahren kombinieren geht über einen kleinen Trick:

$array[0..1+3..4]

Also statt des , einfach ein + verwenden.

Wie viele Objekte ein Array enthält bekommen Sie wieder mit der Eigenschaft Count heraus:

$array.count

2.3.8.4.2    Multidimensionale Arrays

Mehrdimensionale Arrays brauchen Sie in der PowerShell eigentlich nicht. Wenn Ihnen sowieso schon der Kopf raucht, trinken Sie einen Kaffee und widmen Sie sich danach dem nächsten Abschnitt über Hash und Splatting. Sie könnten stattdessen einfach einen Array in einen Array stecken. Der Vollständigkeit halber sei es aber erwähnt. Da so etwas in der PowerShell nicht vorgesehen ist, wir aber wissen, dass auch der normale Array ein Objekt ist, können wir einfach ein Objekt selbst anlegen (mehr Details dazu im Abschnitt .NET-Objekte):

$multi=New-Object 'object[,]' 10,10

Damit haben Sie ein zweidimensionales Datengrab, ähnlich einer Tabelle angelegt. Jetzt können Sie z.B. ein 1 x 1 aufbauen:

Achtung! Auch hier startet die PowerShell wieder bei 0 zu zählen. Das bedeutet in der Positionsangabe der Variable $multi können Sie Zahlen von 0-9 angeben, da Sie 10 Felder (0,1,2,3,4,5,6,7,8,9=10 Ziffern,Felder,Positionen,Indexnummern) durch die Zuweisung 10,10 vorgenommen haben.

Ohne Schleifen, die wir uns später im Kapitel Mit Skripten arbeiten, ist das allerdings recht mühselig:

$multi[5,6]=30

$multi[8,9]=8*9

$multi

$multi[5,6]

$multi[8,9]

5 x 6 ist 30. 8 x 9 kann ich nicht im Kopf, deshalb habe ich es die PowerShell ausrechnen lassen ;-). $multi gibt das komplette Datenfeld zurück. Mit $multi[5,6] haben Sie dann explizit auf das Datenfeld zugegriffen und den Wert 30 ausgelesen. Wie gesagt, ohne Schleifen wäre es sehr mühselig, nun das gesamte Datenfeld von Hand zu bestücken.

Kommt mehr als ein Objekt über die Pipeline zum nächsten Cmdlet passiert dies auch in Form eines Arrays, da die einzelnen Objekte in ein Array verpackt werden. Starten Sie ein notepad und tippen Sie danach in der PowerShell (nicht im notepad):

(ps | ? {$_.name -like "notepad*"}).gettype()

Hier haben wir es also mit einem Prozess-Objekt zu tun. Starten Sie bitte noch ein zweites notepad und wiederholen Sie den Befehl. Ist es immer noch ein Prozess? Nein! Weil Sie nun zwei notepads haben, werden die in einen Array verpackt.

Probieren Sie nun einmal das hier, sofern Sie nicht noch ein weiteres notepad offen haben, in dem noch etwas wichtiges drin steht:

ps | ? {$_.name -like "notepad*"} | kill

Schwupps! Alle notepad gekillt, weil kill ein Alias auf das Cmdlet Stop-Process ist. Aber wissen Sie welcher Aufwand dahinter steckt? Schauen Sie sich das doch einmal etwas näher an!

Öffnen Sie bitte noch einmal ein notepad. Schauen Sie einmal welche Methoden es gibt:

ps | ? {$_.name -like "notepad*"} | gm

Da gibt e seine Methode kill. Bitte nicht verwechseln mit dem Alias kill! Der Alias hat so direkt nichts der Methode zu tun. Probieren Sie die Methode doch einmal aus:

(ps | ? {$_.name -like "notepad*"}).kill()

Das sollte geklappt haben. Jetzt starten Sie doch bitte mindestens zwei notepads und führen Sie noch einmal ein Get-Member durch und prüfen Sie, ob die Methode kill immer noch da ist. Obwohl Sie nun einen Array haben, zeigt Get-Member die Methoden und Eigenschaften der Array Mitglieder, nicht des Arrays. Versuchen Sie nun bitte noch einmal die notepads zu killen:

(ps | ? {$_.name -like "notepad*"}).kill()

Hmm…da gab es wohl eine Fehlermeldung, aber wieso? Ganz einfach! Am Array haben wir keine Methode kill, sondern nur an den Objekten im Array. Das Cmdlet Stop-Process bzw. der Alias kill packen uns das Array automatisch aus. Wenn Sie aber die .NET-Methoden anwenden möchten müssen Sie sich selbst um das Auspacken kümmern. Nehmen Sie also wenn möglich immer die Cmdlets, statt den .NET-Methoden. Erstens ist es einfacher und zweitens weniger Tipparbeit. Allerdings sollten Sie die Möglichkeit kennen, falls Sie gewisse Dinge nur über das .NET erledigen können, weil es dazu kein Cmdlets gibt.

Schleifen werden normaler Weise im Zusammenhang mit Skripting erklärt, doch wir benötigen Sie in diesem Falle auch schon bei der normalen Kommandoeingabe. Die Schleifenform die an dieser Stelle für uns interessant ist nennt sich ForEach-Object, zu Deutsch „für jedes Objekt“. Der Kürze halber empfehle ich ein Alias foreach zu benutzen:

ps | ? {$_.name -like "notepad*"} | foreach {$_.kill()}

Bis zum foreach sollte alles klar sein. Darauf folgt innerhalb geschweifter Klammern was mit jedem einzelnen Objekt, das über die Pipe kommt geschehen soll. Das interessante an der Geschichte ist, dass wir den Pipelineinhalt (alle Objekte) normaler Weise in $_ haben. Durch das foreach aussen herum wird aber nun $_ zum jeweiligen Prozess. Auf diese einzelnen Prozesse können Sie dann auch die Methode kill anwenden. Das was in der gescheiften Klammer steht passiert also mehrmals hintereinander. Erst wird NotepadNr.1 gekillt, dann NotepadNr.2 usw. bis es keine notepad mehr gibt und alle notepad Prozesse ausgerottet sind.

Ähnlich wäre das mit einer Liste von Dateien in einem Verzeichnis, auf die Sie die Methode Delete anwenden möchten.

2.3.8.4.3    Arrays miteinander vergleichen

Leider kann die PowerShell das genausowenig wie einzelne Elemente im Array löschen. Für die Lösung zu dem Problem sind Sie an dieser Stelle aber wahrscheinlich noch nicht fit genug. Für diejenigen die es sich vielleicht doch schon zutrauen, können die Lösung im Praxisbeispiel: Arrays miteinander vergleichen  im Kapitel mit Skripten arbeiten nachlesen.

Schauen Sie doch einmal was aus den letzten beiden Abschnitten hängen geblieben ist. Sie kennen Methoden des Datentyps String und Sie wissen wie ein Datentyp Array funktioniert. Bringen Sie nun beides zusammen. Erstellen Sie eine Variable mit der Bezeichnung $VollerName mit Ihrem Vor- und Nachnamen als Inhalt. Z.B.:

$VollerName=“Lieschen Müller“

Nun hätte ich gerne in einer Variablen $Vorname den Vorname und in $Nachname den Nachnamen. Aber nicht einfach zuweisen, sondern aus $VollerName automatisiert extrahieren.

Übungsaufgabe: String

2.3.8.5   Besondere Variablentypen: Hash, Splatting und Unterausdrücke

Eine Hash Variable hat nichts mit Verschlüsselung zu tun! Ein Hash ist ähnlich eines Arrays, nur dass Sie den Index benennen können, statt ihn automatisch durchnummerieren zu lassen.

2.3.8.5.1    Hash

Einen leeren Hash können Sie auch ähnlich eines leeren Arrays anlegen:

$hash=@{}

Der Unterschied liegt in den geschweiften, statt runder Klammern. Wenn Sie gleich einen Inhalt zuweisen möchten kann das so aussehen:

$hash=@{„Eins“=1;“Drei“=3;“Zwei“=2}

Der Index ist hier ganz einfach ein Text und der zugeordnete Inhalt sind Zahlen, die dem Indextext entsprechen.

Als erstes schauen wir uns einmal den Datentyp an:

$hash.gettype()

Hier steht der Datentyp nun wieder in der Spalte mit der Überschrift Name, wie bei den anderen Datentypen auch. Der Array bildet hier also die Ausnahme. In vielen anderen Büchern zum Thema PowerShell finden Sie oft den gut gemeinten Rat, den Datentyp so in Erfahrung zu bringen:

$hash.gettype().fullname

Wegen der Ausnahme beim Array möchte ich Ihnen aber eher empfehlen, nur gettype() einzusetzen.

Einen weiteren Eintrag hinzufügen ist ähnlich wie bei einem Array:

$hash+=@{“Vier“=4}

oder noch einfacher:

$hash.Fuenf=5

Auf alle Elemente des Hash zuzugreifen ist wieder genauso wie beim Array:

$hash

Möchten Sie nun aber die Zahl 2 haben geht das einfach so:

$hash.Zwei

Bestimmt haben Sie sich schon gefragt, ob ich nun nicht einmal mehr zählen kann, wenn ich schon beim Array nicht 8x9 im Kopf rechnen kann, da ich den Hash nicht der Reihe nach aufgebaut habe. Das war Absicht! Denn eine Hash Variable kann einen schon zur Verzweiflung treiben. Insbesondere, wenn man versucht Sie zu sortieren.

$hash | sort

Sortiert nämlich gar nichts! Weder den Index, noch die eingetragenen Werte. Ah, Sie sind ja schlau und haben ein tolles Buch gelesen. Ohne den Schalter -Property sortiert er ja nach allem, nur nicht nach der Eigenschaft, die Sie eigentlich sortiert haben möchten. Nun gut, dann probieren wir einmal unser Glück mit –Property und den Spaltenüberschriften:

$hash | sort -Property Name

$hash | sort -Property Value

Hmmm…da zuckt sich wieder nichts. Was machen jetzt die ganz schlauen? Get-Member, gucken wie die Eigenschaft wirklich heißt.

$hash | sort -Property Keys

$hash | sort -Property Values

Ärgerlich! Sind die Keys wirklich der Index?

$hash | select Keys

Scheint so! Aber er will Sie wirklich ärgern! Die Lösung liegt in der Methode GetEnumerator versteckt (da soll einer drauf kommen!):

$hash.getenumerator() | sort –Property Name

bzw.

$hash.getenumerator() | sort –Property Value

Wenn Sie noch einmal ein Get-Member auf $hash durchführen, können Sie dort wo sich in der 3. Spalte Definition und in der Zeile GetEnumerator trifft lesen: System.Collection.IDictionaryEnumerator (Sehr frei ins Deutsche übersetzt: Wörterbuchauflister)

Im Gegensatz zum Array hat der Hash eine Methode um Elemente wieder zu entfernen, wie Sie vielleicht gerade auch beim Get-Member gesehen haben:

$hash.remove("Vier")

Wirft den Index mit der Beschriftung „Vier“ mit samt seinem Inhalt weg.

Auch beim Hash gibt es natürlich wieder eine Eigenschaft Count. Aber ich glaube so etwas und was die Eigenschaft macht, brauche ich Ihnen an dieser Stelle gar nicht mehr auf die Nase binden. ;-)

2.3.8.5.2    Splatting

Machen wir lieber etwas, dass Sie noch nicht kennen, das Splatting! Keine Angst – keine Horrorfilme. Unter Splatting versteht man die Technik, die Parameter für ein Cmdlet in einem Hash zu speichern und bei Bedarf zu übergeben. So können Sie sich z.B. eine Hashvariable namens $exe in dieser Form anlegen:

$exe=@{"Path"=".";"Filter"="*.exe"}

Der Hashvariablen werden also 2 Zuordnungen erstellt. Zum einen, dass Path=. ist und das Filter=*.exe ist. Wenn Sie nun z.B. wissen möchten, ob im aktuellen Verzeichnis exe-Dateien vorhanden sind, können Sie die Hashvariable an Get-Childitem bzw. den Alias ls splatten:

ls @exe

Wichtig dabei ist, dass die Hashvariable nicht mit $exe, sondern mit @exe übergeben wird. Dadurch werden die Index-Benennungen zu Parametern und die Wertezuweisungen zu den Angaben für die Parameter. Das ist im Moment noch nicht so spektakulär, aber wenn Sie einmal immer wiederkehrend eine lange Parameterkette eintippen müssen, werden Sie sich hoffentlich an diese Technik erinnern. Abgesehen davon können Sie die Splatvariablen in einem Profile-Skript hinterlegen, so dass Sie diese jedes Mal wenn Sie eine neue Konsole starten, verfügbar haben. Wie das geht erfahren Sie im Kapitel Scripting und dort im Abschnitt Profil Skripte. Apropos Skript. In einem Script können Sie damit die Parameter natürlich beliebig zusammen bauen bevor Sie diese an ein Cmdlet übergeben.

2.3.8.5.3    Unterausdrücke

Unterausdruck werden ab PowerShell 2.0 verwendet, um Probleme beim Zugriff auf Hash und Arraywerte zu vermeiden.

Ab PowerShell 2.0 kommt es bei Arrays und Hashwerten vor, dass diese in auszugebenden Texten nicht wie erwartet interpretiert werden. Daher müssen Sie ab PowerShell 2.0 gelegentlich mal zu der aus Linux bekannten Kapselungsmethode $($Variable…) greifen. Die offizielle Bezeichnung dafür ist Subexpression bzw. Unterausdruck. Falls Sie die Konsole inzwischen geschlossen hatten, öffnen Sie diese bitte erneut und erstellen Sie wie im vorangegangenen Abschnitt Splatting beschrieben einen Hash in $exe. Versuchen Sie die Eigenschaft eines Hashs auf den Bildschirm zu bekommen:

“Der Wert der Eigenschaft Filter ist: $exe.filter.“

Das war nicht ganz das erwartete Ergebnis, oder? Also versuchen Sie es mit der $() Kapselung:

“Der Wert von `$exe ist: $($exe.Filter).“

2.3.8.6   Konstruierte Variablenbezeichner

Wenn Sie einen Variablenbezeichner bzw. Variablennamen selbst aus Variablen oder Textteilen zusammensetzen möchten, können Sie das mit Hilfe des Cmdlets New-Variable wie folgt tun:

$a="Eins"

$b="Zwei"

New-Variable -Name ("Text"+$a+$b) -Value 10

Dies erstellt eine Variable mit dem Namen $TextEinsZwei und belegt diese mit dem Wert 10.

$TextEinsZwei

Gibt den Inhalt der soeben dynamisch zusammen gesetzten Variable zurück.

So weit, so gut, aber wie lese ich die Variable nun wiederum dynamisch aus? Das ist etwas umständlicher, aber mittels Get-Variable Cmdlet möglich:

(Get-Variable -Name ("Text"+$a+$b)).Value

Vielleicht interessiert es Sie wann Ihr Rechner gestartet wurde? Das können Sie in der Ereignisanzeige herausfinden indem Sie im Systemlog nach der EventID 12 suchen. Von der PowerShell aus müssten Sie dann immer eingeben:

Get-Eventlog –Logname System –EventID 12 | Select TimeWritten

Schicker ware das doch mit einem Spallting in Form von:

Get-Eventlog @Boot | Select TimeWritten

Bauen Sie die Splatting Hash Variable @Boot für den Befehl zusammen.

Übungsaufgabe: Splatting Hash

2.3.8.7   Umgebungsvariablen

Umgebungsvariablen sind keine PowerShell-Variablen! Sie können allerdings von der PowerShell aus auf die Umgebungsvariablen zugreifen. Die Umgebungsvariablen haben ein eigenes PSDrive:

Env:

Um sich alle Umgebungsvariablen anzeigen zu lassen geben Sie:

ls env:

ein. Möchten Sie auf den Inhalt einer bestimmten Umgebungsvariable zugreifen geht das einfach am Beispiel der Path Variable mit:

$Env:Path

Dies hat allerdings nur eine Änderung innerhalb der aktuellen PowerShell-Konsole zur Folge! Wenn Sie die Konsole schließen und eine neue starten ist der Spuk vorbei. Genauso wenig hat dies Auswirkungen auf das System selbst. Sie können also nicht kaputt machen ;-). Wie Sie darüber hinaus dauerhaft Änderungen an Umgebungsvariablen durchführen können, lesen im Buchteil Praxisbeispiele im Kapitel über die Registry im Abschnitt Umgebungsvariablen dauerhaft ändern.

2.3.9   .NET-Objekte

Wie bereits beschrieben ist die PowerShell selbst eine komplette .NET-Entwicklung. Alles was wir hier tun wird über das .NET-Framework zur Verfügung gestellt. Die Cmdlets vereinfachen uns den Zugriff auf das .NET indem Sie uns mit Ihrer einfachen Verb-Noun Form schon fast das Wort aus dem Mund nehmen. Allerdings hat das .NET-Framework unendlich viel mehr zu bieten, als nur ein paar Cmdlets. In diesem Abschnitt bekommen Sie den zweiten Schlüssel zur Macht indem ich Ihnen den direkten Zugriff auf das .NET-Framework eröffne.

2.3.9.1   Zugriff auf .NET-Klassen und Objekte

Erinnern Sie sich noch an die Abfrage, welche PowerShell Version auf dem Computer installiert ist? Zwei Möglichkeiten, entweder $host (eine vordefinierte Variable), oder aber Get-Host. Weil $host weniger Tipparbeit ist, wollen wir an dieser Stelle bei der Variablen bleiben. Geben Sie zur Erinnerung, doch bitte noch einmal $host ein. Dieses Mal schauen Sie sich bitte die Eigenschaft UI einmal näher an. $host.UI enthält nur eine einzelne Eigenschaft RawUI. $host.UI.RawUI wiederum ist schon etwas spannender. Mit ForeGroundcolor und BackgroundColor, aber natürlich auch den weiteren Eigenschaften. Sie stellen fest, dass die aktuelle ForegroundColor Eigenschaft DarkYellow ist. Mit einem Get-Member erfahren Sie in der Spalte Definition zu der Eigenschaft ForegroundColor, dass es einen sogenannten Getter und einen Setter gibt {get;set;}. Ein Get sagt aus, dass wir die Informationen auslesen können. Ein Set sagt aus, dass wir es auch einstellen können. Das es mit dem Set trotzdem nicht immer klappt, auch wenn es hinten dran steht ist Schade, aber teilweise auch offensichtlich. So gibt es Beispielsweise auch Objekte die den eingebauten RAM-Speicher angeben; mit einem Setter! Das wäre doch einmal was, wenn man das einfach einstellen könnte. Aber zurück zu unserer Farbe. Die können Sie, dank des Setters, tatsächlich auch einstellen:

$host.ui.rawui.foregroundcolor=“Red“

Schon ist unsere Schriftfarbe Rot. Wenn Sie wieder weiß haben möchten, können Sie entweder „White“ zuweisen, oder das Microsoft-Weiß: „DarkYellow“ ;-).

Nun wollen Sie bestimmt wissen, welche Farben Sie noch zuweisen können. Eine Möglichkeit wäre ausprobieren. Besser ist allerdings, wenn Sie .NET fragen. Machen Sie noch einmal einen Get‑Member auf $host.ui.rawui. Unter Definition sehen Sie bei der ForegroundColor auf welche .NET-Klasse die Farbe zurückzuführen ist. Richtig: System.ConsoleColor! Mithilfe des .NET-Objekts System.Enum können wir uns eine Auflistung geben lassen, welche Werte an ForegroundColor bzw. System.ConsoleColor übergeben werden können. Wenn Sie direkt ein .NET-Objekt ansprechen möchten müssen Sie es in eckige Klammern setzen.

[System.Enum]

Das ist nun nicht weiter spannend, aber wenigstens haben wir keine Fehlermeldung erhalten. Auch auf .NET-Objekte können Sie den Get-Member anwenden:

[System.Enum] | gm

Da kommt eine ganze Latte an Methoden und Eigenschaften, die uns aber alle nicht interessieren. Denn an dieser Stelle geht es nicht um eine Methode einer Objektinstanz, sondern um eine Methode der Objektklasse. Ja, auch Objektklassen und nicht nur die Objekte selbst können Methoden haben. Z.B. Methoden um aus der Objektklasse ein Objekt zu erstellen. Bei Methoden die an Objektklassen direkt ausgeführt werden können (ohne ein Objekt daraus abzuleiten) spricht man von statischen (fest zugeordneten) Methoden. Um die statischen Methoden zu sehen, müssen Sie Ihr Get-Member etwas aufpimpen mithilfe des Schalter –Static.

[System.Enum] | gm -static

Das sind weniger, aber hier ist genau das dabei, was Sie brauchen, nämlich die Methode GetNames. Um direkt am .NET die Methode aufzurufen, brauchen Sie auch eine etwas andere Syntax, als bisher:

[System.Enum]::GetNames(„System.ConsoleColor“)

Schwupps, schon haben wir alle zulässigen Farbwerte. So, was ist passiert? Sie haben nun direkt auf die .NET-Klasse System.Enum zugegriffen und dort die statische Methode GetNames aufgerufen. Der Methode haben Sie die Information System.ConsoleColor (die .NET-Klasse, die Sie hinter der ForegroundColor Eigenschaft in der Spalte Definition gefunden haben) übergeben. Die Aufgabe von [System.Enum]::GetNames() ist es herauszufinden, welche Werte von einer Eigenschaft angenommen werden. Das hat Sie in diesem Fall ganz vortrefflich gelöst, nur funktioniert dies leider nicht bei jeder x-beliebigen Eigenschaft. Warum?

$a=5

$a.gettype().fullname

[System.Enum]::GetNames(“System.Int32”)

Klar…welche Werte kann denn ein Int32 so haben? Wollen Sie das wirklich alles auflisten? Das klappt also in der Regel nur, wenn eine Eigenschaft nur ein paar wenige Einstellmöglichkeiten hat und zudem von Programmierer eine sogenannte Enumeration angelegt wurde. Aber vielleicht können Sie direkt Int32 fragen, um den gültigen Wertebereich zu ermitteln? Sie erinnern sich wie man eine Variable explizit als Int32 definiert? Ja, richtig mit [Int32]! Warum probieren Sie hier nicht einmal Get-Member mit dem Schalter –Static?

[int32] | gm –static

Aha! Da gibt’s eine Min- und eine MaxValue Eigenschaft. Abfragen? Bitte schön:

[int32]::MaxValue

[int32]::MinValue

2.3.9.2   Komplette .NET-Dokumentation

Sollten Sie einmal mit diesen Tricks nicht weiterkommen und mehr Informationen brauchen, googeln Sie doch einfach einmal nach der .NET-Klasse und klicken Sie den ersten besten Link an, der zu msdn.microsoft.com führt. Probieren Sie das doch gleich einmal mit System.Math aus. Ja, googeln Sie bitte System.Math und klicken Sie den MSDN-Link.

Hier finden Sie nun alle Methoden und Eigenschaft der Klasse System.Math erklärt. Hier ist eigentlich das komplette .NET-Framework dokumentiert. Durch die Suchmaschine sind wir aber gleich auf der Seite die uns interessiert gelandet.

Die Math Bibliothek ist nicht für Drogensüchtige, sondern hier können wir erweiterte Mathematische Funktionen nutzen, wie z.B.:

[Math]::Pi

für die Zahl Pi, oder

[Math]::Sqrt(9)

zum Wurzel ziehen. Die Überschrift Methods (z.B. Sqrt) dürfte klar sein, doch mit Fields sind in dem Fall Eigenschaften (z.B. Pi) gemeint.

2.3.9.3   Bestehende .NET-Objekte erweitern und eigene erstellen

Zeit für etwas Gentechnik und lila Augenfarbe! Hier werden Sie lernen Ihre Objekte um Ihre selbst gebastelten Eigenschaften zu erweitern und sogar eigene Objekte zu erstellen.

2.3.9.3.1    Alternative Namen (Aliase) für Objekt-Eigenschaften hinzufügen

Wenn Sie sich die Ausgabe von Get-Process | Get-Member anschauen stellen Sie fest, dass es eine AliasProperty gibt die statt der eigentlichen Eigenschaft ProcessName auch die Bezeichnung Name zulässt. Sie erinnern sich bestimmt auch noch an die Eigenschaft PSIsContainer bei Dateien und Verzeichnissen, die angibt, ob es sich um einen Ordner oder um eine Datei handelt. Dann wollen wir doch einmal eine AliasProperty an die Objekte eines Verzeichnisinhalts dran kleben:

$Inhalt=ls c:\windows

$Inhalt=$Inhalt | Add-Member -Membertype aliasproperty `
-Name Ordner -Value PSIsContainer –PassThru

$Inhalt | gm

ls c:\windows | gm

Zunächst holen Sie sich in der ersten Zeile den Verzeichnisinhalt des Windows-Verzeichnisses ab und hinterlegen es in der Variablen $Inhalt. Keine Angst! Die Veränderungen, die Sie hier durchführen passieren nur in Ihres Objekts, der Variablen $Inhalt. Das Dateisystem, oder selbst das normale Get-Childitem Cmdlet bzw. der Alias ls bleibt dabei unangetastet. In der zweiten Zeile fügen Sie den Objekten aus der ArrayVariable $Inhalt mithilfe des Add-Member Cmdlet ein neues „Mitglied“ hinzu. -MemberType gibt dabei vor, dass es sich um eine AliasProperty handelt. Mit dem Schalter –Name vergeben wir die alternative Bezeichnung und mit –Value die Eigenschaft auf die sich der alternative Name bezieht. Da Add-Member normaler Weise keine Ausgabe erzeugt, Sie aber die Veränderung wieder $Inhalt zuweisen möchten, steht am Schluss noch der Schalter ‑PassThru. Der sorgt bei Cmdlets die keine Ausgabe erstellen dafür, dass Sie das doch tun. Die 3. Zeile beweist mittels Get-Member, dass Sie eine AliasEigenschaft hinzugefügt haben und Zeile 4, dass diese Eigenschaft beim normalen Cmdlet nicht übernommen wurde. Das geht natürlich auch, aber dazu sollten wir uns erst einmal über Skripting (nächstes Kapitel) unterhalten und dann können wir uns die dauerhafte Erweiterung von .NET-Objekten anschauen.

Um nun auf Ihre erste, selbst gebastelte Eigenschaft zuzugreifen, können Sie z.B. mit select darauf zugreifen:

$Inhalt | select name,ordner | ft -auto

Holt Ihnen die Eigenschaften Name und Ordner aus dem Inhalt und damit es ein bisschen schicker aussieht ein Format-Table hinterher. Ihre AliasEigenschaft verhält sich gegenüber allen Befehlen so, als wäre Sie schon immer da gewesen. Sie können Sie also genauso gut einsetzen, wie die Aliaseigenschaft Name aus dem Get-Process Cmdlet.

2.3.9.3.2    Objekte um selbst erstellte Eigenschaften erweitern und Ballast entfernen

Untersuchen Sie doch einmal das Get-Service Cmdlet mit Get-Member. Finden Sie eine Eigenschaft die eine Zeitangabe macht? Nein, warum auch? Normaler Weise will man den aktuellen Stand der Dienste damit abfragen und eine Historie wird nicht geführt. Vielleicht möchten Sie aber dennoch gerne wissen, wann ein Dienst gelaufen ist und wann nicht. Wäre doch toll, wenn Sie das Get-Service und das Get-Date Cmdlet irgendwie mixen könnten. Willkommen im Genlabor!

Vom Get-Service interessieren uns an dieser Stelle nur der Name und der Status.

$TimeService=Get-Service | select Name, Status

Ein Get-Member auf $TimeService offenbart Ihnen, dass nur noch die Eigenschaften Name und Status in Ihrer ArrayVariable $TimeService enthalten sind. Das Verfahren ist ähnlich der AliasProperty, nur hier verweisen Sie nicht einfach auf eine andere Eigenschaft im selben Objekt, sondern schieben ein ganzes Zeit Objekt hinein.

$TimeService=$TimeService | Add-Member -Membertype `
noteproperty ‑Name Zeit -Value (Get-Date) –PassThru

$TimeService |gm

$TimeService

In dieser schlanken Form können Sie das nun sehr gut exportieren. Es hat aber den Nachteil, dass Sie auch die Methoden entfernt haben, sodass Sie auf einen der gelisteten Services kein Start oder Stop Befehl mehr senden können. Wenn Sie es nicht exportieren möchten, aber die Methoden behalten, dann lassen Sie bei der Definition von $TimeService einfach die Pipe mit dem select weg, denn der select schneidet nicht nur die nicht erwähnten Eigenschaften, sondern auch die Methoden raus.

Falls Ihnen der Aufwand in diesem Fall zu hoch erscheint, können Sie solche angepassten Listen auch auf andere Weise mit dem select Alias direkt zusammenstellen:

$TimeService=Get-Service | select `
Name,Status,@{Label=“Zeit“;Expression={Get-Date}}

Bis zu Get-Service | select sollte soweit alles klar sein. Name, Status übernimmt aus dem Get-Service Cmdlet nur die beiden Eigenschaften Name und Status. Dann folgt noch ein Komma und danach die 3. Eigenschaft. Aber, was ist das? In dem immer gleichen Konstrukt @{Label=;Expression={}} können Sie selbst direkt weitere Eigenschaften einfügen. Nach dem=hinter Label legen Sie dabei die Spaltenüberschrift, bzw. die neue Eigenschaftenbezeichnung fest. Alternativ können Sie statt Label= auch Name= schreiben, falls Ihnen das besser gefällt. Bei Expression dürfen Sie in die geschweiften Klammern reinschreiben was Sie möchten, Hauptsache die PowerShell könnte das auch so auf der normalen Kommandozeile interpretieren. Anstatt des Get‑Date Cmdlets, kann hier auch eine komplette Befehlskette, oder einfach nur “Lemmi was here“ stehen. Bei Befehlsketten dran denken: Soll die Ausgabe des vorhergehenden Cmdlet zur Eingabe des nächsten werden ist die Pipe zu verwenden, sollen aber mehrere Befehle hintereinander ausgeführt werden, deren Eingaben nicht verkettet werden sollen ist das Semikolon zu verwenden. PowerShell erleichtert uns das Anpassen von Objekten also wieder ungemein.

2.3.9.3.3    Externe Programme ausführen und Ihr erstes eigenes Objekt

Da wir das Skripting noch nicht besprochen haben wird Ihr erstes eigenes Objekt zunächst einmal nur Eigenschaften enthalten und keine Methoden. Das Objekt soll den Namen Ihres PCs und die IPv4-Adresse enthalten. Um den Namen des PCs herauszubekommen bedienen Sie sich des externen Programmes hostname, dass Sie sicherlich von anderen Shells her kennen. Um ein externes Programm wie hostname aufzurufen bedarf es keinerlei Verrenkungen, sondern wir können es einfach direkt eintippen und mit Enter bestätigen, wie auch die ganz normalen PowerShell Cmdlets:

hostname

Schon wissen Sie wie Ihr Rechner heißt. Wenn Sie nun aber externe Programme mit internen oder anderen externen Kommandos kombinieren möchten müssen Sie sich allerdings bewusst werden, was da im Hintergrund abläuft:

Hinweis: Weil ich IPv6 aktiviert habe kommt in der Rückmeldung ::1 statt 127.0.0.1.

Hier versuchen Sie ein externes Programm ping in Kombination mit noch einem externen Programm hostname zu verwenden. Die erste Variante schlägt fehl, aber die zweite funktioniert. Warum? In der ersten Variante interpretiert die PowerShell hostname nicht als externes Kommando, sondern als Rechnername den wir anpingen möchten. Um nun erst das externe Programm hostname auszuführen und den Namen der Arbeitsstation zu erhalten, müssen Sie es in runde Klammern setzen. Die Bash-Fans kennen das in der Form: $(Befehl der zuerst ausgeführt werden soll), oder: ` Befehl der zuerst ausgeführt werden soll`. Dadurch wird uns der Name zur Arbeitsstation geliefert und an ping übergeben. Dann versucht ping die Arbeitsstation mit dem tatsächlichen Namen und nicht eine Station mit dem Namen hostname zu erreichen.

Externe Kommandos haben allerdings den Nachteil gegenüber PowerShell Cmdlets ziemlich dumm zu sein, da sie nicht Objekt orientiert arbeiten, sondern einfach nur Texte ausspucken. Bei einem einzelnen Wert, wie ihn hostname liefert ist das nicht weiter tragisch. Allerdings bei ping oder IPConfig sind wir wieder in der Shell-Steinzeit angelangt, bei der wir mit wilden Textoperationen die Informationen herausschneiden müssen :-P.

Die PowerShell hat zum Glück das Cmdlet Test-Connection als ping Ersatz, das Ihnen die gewünschte Objektorientierung zur Verfügung stellt. Durch das externe Programm hostname bekommen Sie den Namen und durch Test-Connection bekommen Sie die IP-Adresse zum Namen. Allerdings müssen Sie da noch ein wenig filtern, da Test-Connection Ihnen viel mehr Infos liefert, als Sie hier haben möchten.

Auch hier sorgen Sie mit der runden Klammer um hostname, dass zunächst der Name des eigenen Rechners abgeholt wird. Der Schalter –count 1 sorgt dafür, dass nur ein einzelnes ICMP Paket verschickt wird. Das select schließlich sorgt dafür, dass wir nur die IPv4-Adresse erhalten.

Jetzt haben Sie Kenntniss über die Voraussetzungen wie Sie sich die beiden Informationen für Ihr Objekt beschaffen. Jetzt geht’s ans Objekt basteln.

Mit dem Befehl New-Object PSCustomObject können Sie eigene Objekte erstellen. Auch hier müssen Sie natürlich einen Ablageort bereitstellen:

$EigenesObjekt=New-Object PSCustomObject

Das war’s eigentlich schon! Ihr erstes Objekt ist geboren. Ab sofort können Sie Get-Member darauf ansetzen:

$EigenesObjekt | gm

Hier sind nun lediglich ein paar Standardmethoden zu sehen, die jedes .NET-Objekt bei der Geburt mit auf den Weg bekommt. Z.B. das schon bekannte GetType(), oder ToString(). ToString will ich an dieser Stelle auch gleich erklären, da wir es gerade von dummen externen Programmen hatten. Wenn Sie von der PowerShell aus externe Programme mit Objekten füttern, kommen die damit nicht klar. Die fressen nur Gras (also Text), kein Haute Cuisine (Objekte). Genau das macht die Methode ToString() für Sie. Die komplette Ausgabe wird von Objekten in Text umgewandelt, bevor es irgendwo weiter geleitet wird. Achten Sie darauf die Kühe auf der Weide nicht mit ganzen Heuballen zu erschlagen, sprich kürzen Sie die Objektinformationen auf die Eigenschaften zusammen, die auch als Text vom externen Programm verstanden werden.

Um nun unsere beiden Eigenschaften dran zu basteln, können Sie wieder auf das im vorangegangenen Abschnitt erklärte Add-Member Cmdlet zurückgreifen. Hier finden Sie eine weitere in diesem Fall etwas kürzere Möglichkeit:

Add-Member –InputObject $EigenesObjekt -MemberType `
NoteProperty ‑Name Name -Value (hostname)

Hier pipen Sie nicht, sondern geben über den Schalter –InputObjekt an, welches Objekt Sie erweitern möchten. Der Rest sollte aus dem vorangegangenen Abschnitt bereits klar sein.

Auch um von der IP-Adresse nur den String (den Text) zu bekommen und nicht den gesamten Hash mit der Bezeichnung, können Sie auf die Methode ToString zurückgreifen:

$ip=(Test-Connection (hostname) -Count 1).ipv4address.tostring()

Dran kleben:

Add-Member –InputObject $EigenesObjekt -MemberType `
NoteProperty ‑Name IP -Value $ip

Fertig! Wenn Sie mögen, können Sie nun auf die Eigenschaften zugreifen und sich natürlich auch mit Get-Member Ihr Werk anschauen.

Dieses Objekt ist im Moment allerdings recht starr. Ändert sich der Name des Computers, dann bleibt der Name des Computers in Ihrem Objekt erhalten. Es bleibt so immer der Wert im Objekt von dem Zeitpunkt, wo das Objekt um die jeweilige Eigenschaft erweitert wurde.

Um immer den aktuellen Rechnernamen im Objekt zu führen können wir eine ScriptProperty einsetzen. Funktioniert fast genauso:

Add-Member –InputObject $EigenesObjekt –MemberType `
ScriptProperty ‑Name DynamicName -value {(hostname)}

Die Unterschiede sind gelb hervorgehoben. Also zum einen ändert sich der Membertype und zum anderen muss der auszuführende Code wie für einen Skriptblock üblich in geschweifte Klammern eingefasst werden. In diesem Beispiel könnten sogar die runden Klammern entfallen. Bei dieser Form wird also jedes Mal, wenn die Eigenschaft abgefragt wird das Skript in den geschweiften Klammern ausgeführt.

Noteproperties können auch jeder Zeit „verunglimpft“ werden. Sie können nun ja über:

$EigenesObjekt.IP

direkt die IP abrufen. Sie können jetzt allerdings auch hergehen und so etwas machen:

$EigenesObjekt.IP=“Das wird schwer zu pingen“

Das entspricht nun natürlich keiner „üblichen“ IP-Adresse mehr.

ScriptProperties hingegen können Sie so einfach nicht klein bekommen. Probieren Sie doch einmal:

$EigenesObjekt.DynamicName=“Hutzli Butzli“

Es sollte die Fehlermeldung:

Der Set-Accessor für die DynamicName-Eigenschaft ist nicht verfügbar.

erscheinen. Eine ScriptProperty kann also so einfach nicht zerstört werden.

2.3.10                     Kontrollfragen

Wie können Sie herausfinden der wie vielte Tag des Jahres gerade ist?

Wieviele Prozesse laufen gerade auf Ihrem System?

In welcher Variable finden Sie den Pipelineinhalt?

Was ist der Unterschied zwischen Measure-Command und Measure-Object?

Wie filtern Sie aus dem Windows-Verzeichnis alle *.exe Dateien, die größer sind als 100KB?

Was ist der Unterschied zwischen den Vergleichs-Operatoren –eq, -like und –match?

Welche Datentypen (Arten von Variablen) kennen Sie?

Was ist der Unterschied zwischen einem Array und einem Hash?

Wie nennt sich das Vorgehen um durch einen Hash Parameter an ein Cmdlet zu geben?

Wenn Sie einen Array oder Hash in einem Text wiedergeben möchten, worauf sollten Sie achten?

Wie können Sie auf Umgebungsvariablen zugreifen?

Wo finden Siedie Dokumentation zum .NET-Framework?

Wie ist die grundlegende Syntax um auf ein .NET-Objekt zuzugreifen?

Mit welchem Cmdlet können Sie .NET-Objekte erweitern?

Einige Cmdlets erzeugen keine Ausgabe. Mit welchem Parameter können diese Cmdlets überreden doch eine Ausgabe zu erstellen?

Wie nennt sich das Cmdlet, um neue Objekte zu erstellen?

Was müssten Sie eingeben um die installierten Updates des Betriebssystems mit im Objekt zu haben?

Link zu den Lösungen für Objektorientierung

2.3.11                     WMI Objekte

Die WMI-Schnittstelle eröffnet uns ein weiteres objektorientiertes Framework ähnlich dem von .NET, aber etwas älter, was nicht heißen soll, dass es nicht mehr gepflegt wird. Windows Management Instrumentation (WMI) ist die Microsoft Version des WBEM-Standards. WBEM steht für Web based Enterprise Management. D.h. Sie haben es hier mit einem Hersteller übergreifenden Standard zu tun bei dem weit über 200 Firmen, wie z. B. auch Cisco, DELL, Novell, Oracle und HP mitmachen. Das bedeutet wir können also von WMI aus z.B. auch einen Cisco Switch verwalten. Auch das gute alte NT 4.0 könnten Sie über WMI ansprechen, ohne dass dort PowerShell oder .NET installiert sein muss. In diesem Kapitel gibt es den dritten Schlüssel zur Macht, der den Weg auch zu anderen Systemen ebnet.

2.3.11.1                Theorie

WMI nutzt RPC für die Kommunikation über das Netzwerk. Möchten Sie also auf einen entfernten Rechner per WMI Zugreifen müssen Sie dafür sorgen, dass eventuell vorhandene Firewalls den Port 135 über TCP geöffnet haben und die Kommunikation durch lassen.

WMI gliedert sich in verschiedene Namespaces (Namensräume). In diesen Namespaces befinden sich bereits die Objekte. Der oberste Einsprung Punkt in WMI auf einem PC nennt sich root. Darunter befinden sich Bereiche die von PC zu PC unterschiedlich sein können. Die angebotenen Namespaces sind abhängig vom jeweiligen Betriebssystem, sowie der installierten Anwendungen. Für reine Windowssysteme ist dies jedoch 2.rangig, da sich das Meiste sowieso unter dem Standard Namespace \\.\Root\CimV2 abspielt. Für die Neugierigen unter Ihnen: Mit dem Punkt der noch vor Root steht, ist der lokale Rechner gemeint. Wenn Sie auf einen anderen Rechner zugreifen möchten brauchen Sie nur den Punkt gegen Namen des gewünschten Remoterechners auszutauschen. Mit PowerShell geht das aber alles viel eleganter, wie Sie gleich lesen werden. Apropos Zugriff auf andere Rechner. Wie steht es eigentlich mit der Sicherheit? Wird sich an dieser Stelle der ein oder andere fragen. Die Sicherheit bzw. die Berechtigungen können Sie mittels WmiMgmt.msc eingestellen:

In der Regel brauchen Sie sich hierüber aber keine Gedanken machen. Die Sicherheitseinstellungen von WMI sind von Haus aus als sicher einzustufen. Benutzer können WMI-Informationen auslesen, während Administratoren auch Methoden ausführen können. Ein Reboot des PCs z. B. kann also standardmäßig  nur von einem Administrator durchgeführt werden.

2.3.11.2                WMI-Namensräume und Objektklassen auflisten

Eine Liste der verfügbaren Namensräume erhalten Sie mit:

Get-WmiObject -Namespace root -Class "__namespace" | ft name

oder kurz mit Alias und gekürzten Schalternamen:

gwmi –name root “__namespace” | ft name

Eine vollständige Liste der Namespaces inklusive der Unterklassen ergeben sich mit:

gwmi -Namespace root -List -Recurse | Select -unique __namespace

Anhand der aufgelisteten Namen können Sie bestimmen, ob der jeweilige Namensraum vielleicht für Sie interessante Informationen und Methoden bereithält. Dies ist jedoch nur dann wichtig, falls Sie nicht im Standardnamensraum \root\CimV2 fündig wurden. Die Angabe zum Namespace kann komplett entfallen, wenn Sie auf den Standardnamensraum zugreifen. Um die Objektklassen eines Namespace auflisten zu lassen können Sie einfach:

gwmi –name root\Microsoft –list

eingeben. Falls Sie sich die Liste der Objektklassen des Standardnamespace root\CimV2 anschauen möchten, darf die Angabe des Namespace komplett entfallen:

gwmi –list

Ich glaube jetzt wissen Sie die Bedeutung der Worte: „Hier spielt die Musik!“. Wenn möglich gehen Sie doch einmal an einen anderen PC und geben dort wie hier ein:

(gwmi –list).count

Damit haben Sie die verfügbaren Objektklassen nur (!) im Namespace \root\CimV2 gezählt. Und ich möchte fast wetten, dass auf beiden PCs, selbst wenn Sie zur selben Firmenumgebung gehören, trotzdem eine unterschiedliche Anzahl von WMI-Objektklassen auftaucht. Wenn Sie keine Lust haben zu dem anderen PC hinzulaufen, dann verwenden Sie doch einfach den Schalter –ComputerName oder kurz –computer. Unter PowerShell 1.0 hatten nur recht wenige Cmdlets diesen Schalter, aber von Version zu Version werden es mehr, sodass Sie die meisten Cmdlets einfach unter Angabe dieses Schalters auf einem anderen PC ausführen lassen können. Beispiel:

gwmi –list –computer RemoteRechnerName

Die Liste ist, wie Sie gesehen haben ziemlich lang. Aber wir können einen Suchfilter einsetzen. Die meisten Objektklassen die unter Windows-Betriebssystemen Daten liefern beginnen mit Win32_. Wenn Sie also Objektklassen finden möchten, die etwas mit Festplatten zu tun haben können Sie z.B. eingeben:

gwmi -list win32_*disk*

Schon kommt nur noch eine Liste mit Objektklassen, die etwas mit Disk zu tun haben. Alles was mit Software zu tun hat z.B.:

gwmi -list win32_*software*

2.3.11.3                WMI-Objekte abfragen

Bei den Festplatten werden Sie schon beim Eintrag Win32_LogicalDisk interessante Informationen vorfinden:

gwmi win32_logicaldisk

Hier finden Sie nun mehrere Objektinstanzen (Laufwerk C:, D:, usw.) der Objektklasse Win32_LogicalDisk. Da WMI ebenfalls Objekt orientiert arbeitet, können Sie alle Cmdlets wie gewohnt auch auf die WMI-Objekte anwenden. Wollen Sie sich das Objekt Laufwerk C: etwas genauer anschauen, empfiehlt es sich zunächst einmal, dass Ganze drum herum zu entfernen:

gwmi win32_logicaldisk | ? {$_.DeviceID –eq „C:“}

Auch hier sehen wir zunächst einmal nur ein paar Eckdaten, wie Größe des Datenträgers und freier Speicher. Mit select * wird das gleich viel interessanter:

gwmi win32_logicaldisk | ? {$_.DeviceID –eq „C:“} | select *

Unter anderem erfahren wir hier z.B. in der Eigenschaft Filesystem (die Sie ohne select * nicht gesehen haben), ob das Dateisystem NTFS, FAT oder was auch immer ist. Gerade bei WMI-Objekten ist select * sehr zu empfehlen, um nicht wie bei Get-Member nur die Bezeichnungen der Eigenschaften zu sehen, sondern auch gleich was drin steht, da hier die Beschriftungen manchmal in etwas in die Irre führen (z.B. Name, VolumeName, SystemName … ganz schön viele Namen!). Wenn man dann gleich sieht was sich hinter der jeweiligen Bezeichnung verbirgt, ist man meisten schon ein ganzes Stückchen schlauer und stochert nicht planlos im Dunkeln.

Um die Methoden zu sehen führt aber nach wie vor kein Weg an Get-Member vorbei:

gwmi win32_logicaldisk | gm

Um nur die Methoden ohne die Eigenschaften anzuzeigen, können Sie bei Get-Member auch den Schalter –Membertype Methods angeben, oder gekürzt:

gwmi win32_logicaldisk | gm -m methods

Hier finden Sie nun ein chkdsk, aber kein defrag. Ein defrag gibt es doch bestimmt auch irgendwo?! Nun, wenn wir mit dem Suchbegriff disk nicht weiterkommen, dann vielleicht mit volume?

gwmi –list win32_*volume*

Aha! Und dann:

gwmi win32_volume | gm -m methods

In WMI gibt es fast alles, was das Herz begehrt, Sie müssen nur etwas kreativ beim Suchen sein und dürfen nicht gleich aufgeben.

2.3.11.4                WMI-Objekte benutzen

Schauen Sie doch zunächst einmal, ob Ihre Festplatte überhaupt ein Defrag nötig hat. Zunächst schauen wir erst einmal bei win32_volume in die Eigenschaften:

gwmi win32_volume | select *

Dabei stellen Sie fest, dass es hier im Gegensatz zu Win32_LogicalDisk gar keine DeviceID Eigenschaft gibt, nach der Sie Ihre Auswahl auf Laufwerk C: beschränken können! Hier können Sie aber nach der DriveLetter oder Name Eigenschaft filtern. Sie sehen also noch einmal, wie wichtig es ist sich erst einmal Beschreibungen mit Inhalt ausgeben zu lassen und nicht voller Frust, weil DeviceID nicht funktioniert, irgendwann einfach genervt aufgeben. Keine Vermutungen anstellen – rein schauen!

(gwmi win32_volume | ? {$_.Driveletter -eq "C:"}).defraganalysis()

In der ersten runden Klammer holen wir uns die Objektinstanz zu Laufwerk C:, der WMI-Objektklasse Win32_Volume. Auf dieses Objekt (Laufwerk C:) führen wir dann die Methode defraganalysis() aus und finden in der Eigenschaft DefragRecommended einen Wahrheitswert True oder False. Zu Deutsch: Defragmentieren von Laufwerk C: empfohlen: Ja(=True)/Nein(=False).

Wenn Sie hier ein True bekommen, möchten Sie vielleicht auch gerne gleich defragmentieren. Dazu schauen Sie sich aber bitte erst noch einmal kurz die Definition der Methode an:

gwmi win32_volume | ? {$_.Driveletter -eq "C:"} | gm -m methods defrag

Da steht nämlich ganz am Schluss hinter Defrag in der runden Klammer ein wichtiger Hinweis. Die Methode erwartet hier die Angabe des Datentyps System.Boolean (also ein $true oder $false). Das ist zwar auf den ersten Blick ein bisschen dämlich, da Sie die Methode defrag aufrufen können, um Ihr dann mit $false zu sagen: Schlaf weiter! Aber die Defraganalyse liefert Ihnen ebenfalls einen Wahrheitswert. Sie könnten also gleich beide ineinander verschachteln, sodass wenn die Analyse sagt, dass ein Defrag empfohlen wird, gleich auch das Defrag ausgeführt wird. Z.B. so:

$Laufwerk=(gwmi win32_volume | ? {$_.Driveletter -eq "C:"})

$Laufwerk.defrag($Laufwerk.defraganalysis())

Manche Dinge, wie z.B. ein Reboot erfordern besondere Privilegien. Die Befehlskette:

(gwmi win32_operatingsystem).reboot()

bringt Ihnen die Fehlermeldung:

Ausnahme beim Aufrufen von "Reboot": "Recht wurde aufgehoben. "

In solchen Fällen sollten Sie zu dem Schalter –EnableAllPrivileges greifen. Falls Sie auf Ihrem PC noch irgendetwas sichern/speichern möchten, wäre jetzt ein guter Zeitpunkt! Denn der nächste Befehl macht tatsächlich, was er vermuten lässt:

(gwmi win32_operatingsystem -EnableAllPrivileges).reboot()

Denken Sie mal an den Schalter –ComputerName. Da kann man auch so etwas schreiben:

(gwmi win32_operatingsystem –EnableAllPrivileges –Computer Rechner1,Rechner2,Rechner3,usw).reboot()

Sie müssen dazu auf den Zielsystemen nur zur Gruppe der lokalen Administratoren gehören. Handelt es sich um ein modernes Betriebssystem, dass Sie durchstarten möchten, können Sie natürlich auch ganz frech die Cmdlets Restart-Computer bzw. Stop-Computer verwenden ;-). Das eben gelistete Beispiel würde aber sogar bei einem alten Windows NT 4.0 klappen.

WMI-Eigenschaften lassen sich leider nicht so einfach einstellen:

(gwmi win32_logicaldisk -enableallprivileges| ? {$_.DeviceID -eq `
"C:"}).VolumeName="System"

Dazu müssen Sie auf Set-WmiInstance oder den Alias swmi zurückgreifen. Da dieses Cmdlet aber erst ab PowerShell 2.0 zur Verfügung steht lesen Sie dies bitte im 2. Buchteil Unterschiede zwischen PowerShell 1.0, 2.0 und 3.0 und dort im Kapitel PowerShell 2.0 im Abschnitt Allgemeine zusätzliche Befehle der Version 2.0 – WMI Eigenschaften einstellen nach.

2.3.11.5                WMI-Zugriff ohne Active-Directory

Achtung! Auch bei WMI ist es erforderlich, dem System auf das man zugreifen möchte über die WSMan-Konfiguration mittels TrustedHosts wie unter Remoting in einer Arbeitsgruppe beschrieben zu vertrauen, auch wenn Sie gar kein WinRM nutzen.

2.3.12                     Kontrollfragen

Welcher Port muss für WMI-Abfragen auf anderen Rechnern geöffnet sein?

Können WMI-Abfragen auch auf NT 4.0 Systeme durchgeführt werden?

Wie nennt sich der für Windows Systeme wichtigste Namespace?

Wie können Sie die sichere Konfiguration von WMI überprüfen?

Wie erfahren Sie welche Namespaces auf einem System zur Verfügung stehen?

Wie erfahren Sie auf welche Objektklassen Sie in einem Namespace zugreifen können?

Link zu den Lösungen für WMI Objekte

2.3.13                     COM Objekte

Die COM-Schnittstelle ist auch älter als .NET und wird ebenfalls noch rege eingesetzt, z.B. bei den Microsoft-Office Produkten. Hier erhalten Sie u. a. die Möglichkeit Excel-Dokumente in Excel zu öffnen oder zu bearbeiten, oder auch einen Browser zu starten, der gleich eine vorgegebene Website ansurft. Dies ist der vierte und letzte Schlüssel zur Macht. Hiernach steht Ihnen die komplette Anwendungsprogrammierung offen, mit der Sie jede beliebige Aufgabenstellung meistern können. Das einzige was uns C Programmierer noch voraushaben sind Treiberprogrammierung und etwas Verarbeitungsgeschwindigkeit.

2.3.13.1                COM-Objektklassen auflisten

Ähnlich wie bei WMI hängt das vom installierten Betriebssystem und den Anwendungen ab. Ist kein Excel auf dem Rechner installiert, können Sie natürlich auch kein Excel starten. Das Hauptproblem bei COM-Objekten dürfte sein, in Erfahrung zu bringen, welche Objektklassen auf einem PC zur Verfügung stehen, es hierfür leider keinen Befehl wie Get-WmiObject –List gibt. Auf der MSDN Website (http://msdn.microsoft.com/en-us/library/aa911706.aspx - leider in Englisch) von Microsoft findet man allerdings einen kleinen Hinweis, wie man aus COM-Objektklassen Objektinstanzen erstellt. Alle Programme werden in der Registry unter HKEY_Classes_Root\CLSID mit einer eindeutigen Nummer eingetragen. Bei einigen dieser Schlüssel gibt es für die COM‑Objektklassen einen nicht mehr zwingend eindeutigen, aber sehr viel sprechenderen Unterschlüssel, als diese „wilde“ CLSID. Das sind die ProgIDs. Zum Glück werden die und nicht die CLSID für die Objektinstanziierung verwendet. Mithilfe des Regedits danach zu suchen kann, schon etwas eklig werden. Lassen Sie das doch die PowerShell für Sie machen:

ls REGISTRY::HKEY_CLASSES_ROOT\CLSID -include PROGID -recurse `
| foreach {$_.GetValue("")}

Dies erstellt eine Auflistung aller COM-Objektklassen. Auf 64-Bit-System empfiehlt sich zusätzlich auch nochmal hier nachzuschauen:

ls REGISTRY::HKEY_CLASSES_ROOT\Wow6432Node\CLSID\ -include PROGID -recurse | foreach {$_.GetValue("")}

Das ist ähnlich wie bei Get-WmiObject –List eine recht lange Liste. Auch das Zusammenstellen kostet unsere PowerShell etwas Gehirnschmalz (braucht Zeit). Wie wäre es, wenn uns das einfach in eine Variable packen?

$COMObjekte= ls REGISTRY::HKEY_CLASSES_ROOT\CLSID `
-include PROGID -recurse | foreach {$_.GetValue("")}

Jetzt können Sie darin schön schnell nach interessanten Dingen suchen:

$COMObjekte | Select-String Explorer

$COMObjekte | Select-String Excel

$COMObjekte | Select-String Outlook

$COMObjekte | Select-String Paint

$COMObjekte | Select-String Voice

Ihrer Phantasie und Kreativität sind natürlich keine Grenzen gesetzt, nach was Sie suchen. Einfach nur den letzten Begriff austauschen und Sie werden schon etwas finden. Wenn auf Anhieb nichts dabei ist, probieren Sie ähnliche Begriffe.

2.3.13.2                COM-Objekte benutzen

Zunächst müssen Sie sich ein Objekt aus der Objektklasse erstellen. Bei den Beispielen zum Auflisten haben Sie bei dem Suchwort Explorer sicher auch den InternetExplorer gesehen. Machen wir uns von der Objektklasse InternetExplorer doch zunächst einmal ein Kopie zum benutzen (Objekt instanziieren):

$IE=New-Object –com InternetExplorer.Application.1

Das funktioniert also fast genauso wie bei .NET-Objekten, nur mit dem Schalter –com gefolgt von der Klassenbezeichnung. Nun haben Sie einen Internet Explorer gestartet. Wie, Sie glauben mir nicht? Würde ich auch nicht! ;-) Aber schauen Sie doch mal im Taskmanager unter dem Reiter Prozesse. Sehen Sie, dort so etwas wie iexplore.exe? Schon, aber Sie glauben mir immer noch nicht. Hmm, na gut. Schauen Sie sich doch einmal Ihr „tolles Gespenst“ mit Get-Member an:

$IE | gm

Da gibt es eine Eigenschaft Visible (welche fast alle Anwendungen der grafischen Oberfläche haben).

$IE | select Visible

Sichtbar = Falsch! Dann probieren Sie doch einmal ganz frech:

$IE.visible=$true

Wow! Wenn Sie jetzt in den Taskmanager schauen, stellen Sie fest, dass der Prozess nun auch als Anwendung geführt wird. Sie haben gerade den Internet Explorer gestartet und sichtbar gemacht. Wenn Sie wollen können Sie ihn auch wieder unsichtbar machen (z.B. wenn Sie erst in aller Ruhe alles vorbereiten wollen, bevor Sie dem Benutzer das Ergebnis anzeigen). Wie? Na kommen Sie, dass können Sie nun schon selbst!

Surfen wir doch einmal eine bestimmte Website mithilfe einer Methode an:

$IE.Navigate(„http://www.martinlehmann.de“)

Sie brauchen wieder eine Beschreibung, was man mit den Methoden und Properties so anstellen kann? Googeln Sie nach InternetExplorer.Application.1 und in diesem Fall (weil eine Microsoft-Anwendung) finden Sie die Beschreibung wieder beim erst besten Link zu MSDN.

Setzen Sie dem Spuck ein Ende mit:

$IE.quit()

Was ist eigentlich SAPI.SpVoice.1 aus der Liste vorhin? Schauen Sie doch einmal, oder besser: Hören Sie doch einmal! Ich hoffe Sie haben eine Soundkarte eingebaut und die Lautsprecher eingeschaltet, wenn nein, entweder mit dem nächsten Kapitel Mit Skripten arbeiten weiter, oder: Soundkarte rein, Treiber installieren und Lautsprecher an.

$Voice=New-Object –com SAPI.SpVoice.1

$Voice.speak(“Good morning my dear”)

$Voice | gm

Die liebe Anna kann leider nur Englisch gut aussprechen und ist die einzige standardmäßig installierte Stimme. Wenn Sie aber z.B. Microsoft MAPS, die Navigationssoftware installiert haben, stehen Ihnen auch Deutsche Sprecher zur Verfügung, die Sie dann mit der Eigenschaft Voice ändern können. Viel Spaß beim erkunden!

2.3.14                     Kontrollfragen

Wie können Sie sich die auf Ihrem System verfügbaren COM-Objektklassen auflisten lassen (Sie dürfen nachschauen ;-)?

Wie instanziieren Sie aus einer COM-Objektklasse ein Objekt?

Wenn Sie COM-Anwendungen auf der grafischen Oberfläche starten, aber die Anwendung nicht zu sehen ist, woran könnte das liegen?

Link zu den Lösungen für COM Objekte

2.4     Mit Skripten arbeiten

Bislang haben wir Befehle immer direkt an der Konsole eingetippt. An der einen oder anderen Stelle haben Sie sich sicher schon gewünscht das Ganze lieber in einer Datei niederzuschreiben und diese ablaufen zu lassen damit, wenn man sich einmal vertippt hat, nicht noch einmal alles abtippen muss. Des Weiteren will man ja nicht immer wieder denselben Sermon abtippen, wenn man etwas auf mehreren Rechnern machen muss oder zu verschiedenen Zeiten. Dem Umstand möchte ich mit diesem Kapitel über Skripte abhelfen.

2.4.1   Grundlagen zur Skriptausführung

Aus Sicherheitsgründen ist die Ausführung von Skripten in der PowerShell nicht möglich. Da ist wohl noch das Brandmal vom Loveletter (einem VBS-Virus) drauf. Wenn das Skripting abgeschaltet ist, können u.U. auch einige Module (Befehlserweiterungen) nicht geladen werden. Mehr zu Modulen finden Sie im Buchteil Unterschiede zwischen PowerShell Versionen 1.0, 2.0 und 3.0 im Kapitel PowerShell 2.0 und dort im Abschnitt Module in Version 2.0. Bei Windows Server 2012 R2 ist Microsoft mutiger geworden und erlaubt standarmäßig die Ausführung von Skripten die auf der lokalen Festplatte liegen (Execution Policy = RemoteSigned).

PowerShell Skripte sind ganz normale Textdateien mit der Dateinamenerweiterung ps1. PowerShell Skripte können Sie daher mit einem ganz normalen Texteditor wie notepad schreiben. Für gehobenere Ansprüche können Sie auf das Integrated Scripting Evironment (ISE) zurückgreifen. Dies enthält einen Debugger und auch Syntax-Highlighting, allerdings keine Kommandovervollständigung in Version 2.0. In Version 1.0 fehlt das Tool komplett, in Version 2.0 muss es ggf. über die Featureverwaltung im Servermanager hinzugefügt werden und in Version 3.0 haben Sie sogar Kommandovervollständigung. Diejenigen, die noch unter Version 2.0 oder 1.0 arbeiten müssen, können auf das kostenlose Tool von Quest namens PowerGUI zurückgreifen. Das hat schon seit eh und je diese Features an Bord und noch vieles andere mehr, wie z.B. die Anzeige von Variableninhalten.

In den folgenden beiden Unterabschnitten erfahren Sie zunächst die technischen Hintergründe der Skriptausführung.

2.4.1.1   Ausführungsrichtlinie für Skripting abfragen und aktivieren

Um herauszufinden ob PowerShell Skripting auf dem System aktiviert ist, können Sie ein Cmdlet bemühen:

Get-ExecutionPolicy

Dies liefert Ihnen wahrscheinlich Restricted (zu Deutsch: eingeschränkt – bedeutet: Skripte werden nicht ausgeführt) als Ergebnis. Das ist die Standardeinstellung bis einschließlich Windows 8.1. Ausnahme ist Windows Server 2012 R2. Auf Windows Server 2012 R2 ist die Standardeinstellung RemoteSigned.

Wo es ein Get- gibt ist ein Set- oft nicht weit. Mittels

Set-ExecutionPolicy unrestricted

können Sie die Ausführung jeglicher Skripte zulassen.

Alternativ könnte man noch RemoteSigned oder AllSigned zuweisen. RemoteSigned bedeutet, dass Skripte, die sich auf der Festplatte des lokalen Rechners befinden ohne Umschweife ausgeführt werden können, währen Skripte die „Remote“ abgespeichert sind eine digitale Signatur benötigen, um ausgeführt zu werden. Remote bedeutet in diesem Falle Skripte die im Internet, auf einem Fileserver, aber auch auf einer DVD liegen. Bei AllSigned müssten auch die auf der lokalen Festplatte liegen digital signiert sein.

Auch per ActiveDirectory Gruppenrichtlinien (GPO) können Sie die Ausführungsrichtlinie für Computer ebenso wie für Benutzer festlegen. Die Einstellungen finden Sie unter Administrative Vorlagen\Windows-Komponenten\Windows PowerShell\Skriptausführung aktivieren.

Es gibt allerdings noch wesentlich mehr Möglichkeiten, wie ein Blick mit

Get-Help Set-ExecutionPolicy

verrät.

Um ein Skript digital zu signieren benötigen Sie ein digitales Zertifikat, das extra für den Zweck der Codesignatur ausgestellt wurde. Ein Zertifikat mit dem Zweck E-Mail zu verschlüsseln ist hier nutzlos. Das bedeutet weiterhin, dass Sie einen Zertifikatsserver benötigen, der Ihnen dieses Zertifikat ausstellt und dem muss Ihr PC auch noch das Vertrauen aussprechen. Vielleicht schreibe ich dazu auch einmal einen 1000-Seiten Wälzer, aber machen Sie sich nicht zu viel Hoffnung. Ein IT-Fachbuch, ist schließlich kein Harry Potter, der sich millionenfach verkauft. Das hier schreibe ich auch nicht um Geld zu verdienen, sondern weil ich Spaß am Thema PowerShell habe. Wenn Sie tatsächlich ein Zertifikat zur Codesignatur Ihr Eigen nennen, können Sie mit den folgenden beiden Zeilen eines Ihrer PowerShell Skripte signieren:

$cert=Get-PfxCertificate C:\Zertifikatsdatei.pfx

Set-AuthenticodeSignature -Filepath C:\IhrPowerShellSkript.ps1 `
-Cert $cert

Die erste Zeile holt sich das Zertifikat mit dem Privaten Schlüssel zur Code Signatur direkt aus der Zertifikatsdatei von Laufwerk C:\ in die PowerShell Variable $cert. Die zweite Zeile nutzt durch den Schalter –Cert das Zertifikat aus Ihrer Variablen $cert um Ihr PowerShell Skript IhrPowerShellSkript.ps1 auf Laufwerk C:\ digital zu unterschrieben, dass es von Ihnen stammt.

Ich schätze einmal, wenn Sie den nächsten Abschnitt gelesen haben, schlagen Sie sich die Idee sowieso gleich wieder aus dem Kopf.

2.4.1.2   Skriptschutz umgehen

Nun, ich will hier nicht zu viele Angaben machen, sondern nur wie Sie sich selbst helfen können. Aber wenn Sie 2 Ecken weiterdenken, werden Sie ein ernsthaftes Problem entdecken.

Auf Ihrem eigenen Rechner können Sie jederzeit mit Set-ExecutionPolicy Cmdlet den Skriptschutz aus- und wieder einschalten. Aber was, wenn Tante Frieda anruft und Sie Ihr mal eben schnell ein PowerShell Skript schicken möchten, dass die Probleme auf dem Rechner von Tante Frieda beseitigt? Tante Frieda schreibt mit Adler-Suchsystem und braucht auch sonst ziemlich lange, um irgendwas an Ihrem PC zu machen. Ganz zu schweigen davon, wenn Sie etwas von Ihr wissen möchten, wie z.B. ob Sie ein 32, oder 64 Bit Office-Paket einsetzt. Es gibt sogar Gerüchte, dass Sie, als Sie noch berufstätig war einen Helpdesk Mitarbeiter in den Freitod getrieben hat ;-). Wäre doch nett, wenn Sie irgendwie auf Tante Friedas Computer irgendwie den Skriptschutz umgehen könnten:

powershell –executionpolicy unrestricted –file `
C:\IhrPowerShellSkript.ps1

Dadurch wird ein PowerShell Prozess mit Ihrem Skript unter Umgehung der Ausführungsrichtlinie des jeweiligen PCs gestartet. Sollte das irgendwann einmal nicht mehr funktionieren, können Sie zu einem fiesen Trick greifen:

Type C:\IhrPowershellSkript.ps1 | powershell –

Mit dem DOS-Befehl Type können Sie den Inhalt einer Textdatei (in dem Fall Ihr PowerShellSkript) auslesen. Mit der Pipe wir die Ausgabe an das zu startende Programm (powershell.exe) weitergeleitet. Das Minus hinter powershell bewirkt, dass der PowerShell Prozess seine Eingaben von der Standardeingabe liest. Das ist dann ungefähr so, als ob Sie Ihr Skript gerade selbst in die PowerShell Konsole eintippen würden. Da soll es Leute geben, die sich tatsächlich die Mühe machen PowerShell Skripte digital zu signieren. !-)

Leider habe ich, da ich diese Zeilen schreibe, noch keine Möglichkeit (z.B. GPO zur Ausführungsrichtlinie oder AppLocker) gefunden, etwas gegen böswillige Menschen zu tun, die in der Lage sind 2 Ecken weiter zu denken.

Für die nachfolgenden Beispiele sollten Sie die Skriptausführung zulassen. Ob Sie nun gleich Unrestricted oder RemoteSigned zuweisen überlasse ich Ihrem scharfen Verstand.

2.4.1.3   Ausführungsrichtlinienbei PowerShell Core

Die Standard Ausführungsrichtlinien unter PowerShell Core 6.0 ist RemoteSigned. Auf Windows Systemen kann dies mittles der Datei powershell.config.json unter im Verzeichnis $PSHome angepasst werden. Hierzu ändern Sie Microsoft.PowerShell:ExecutionPolicy z.B. auf Restricted:

{"Microsoft.PowerShell:ExecutionPolicy":"Restricted"}

Unter Linux wird das „Feature“ nicht unterstützt. 3 mal dürfen Sie raten warum man es dort erst gar nicht implementiert hat.

2.4.2   Gutes Skripting

Ein paar warme Worte vorneweg! Alles was Sie an der Kommandozeile eintippen, können Sie auch im Skript verwenden. Doch das sollten Sie nicht! An der Kommandozeile ist es toll, wenn man Aliase nutzt, um schnell zu einem Ergebnis zu kommen. Es ist auch toll Schalter abzukürzen, oder ganz weg zu lassen. Im Moment haben Sie einen bestimmten Alias voll drauf, weil Sie laufend damit arbeiten. Aber ist das in einem Jahr auch noch so? Wenn Sie nach einem Jahr wieder in ein Skript reinschauen, möchten Sie vielleicht ganz gerne auf Anhieb erkennen, was das Skript macht. Wenn Sie einen Alias nach dem anderen verwenden und die Schalter weglassen, war Ihnen vor einem Jahr noch sonnenklar was da passiert ist, aber nun nicht mehr. Sie müssen sich in Ihr eigenes Skript erst wieder einarbeiten. Noch schlimmer, wenn Sie im Team arbeiten. Stellen Sie sich vor, Sie haben da so einen PowerShell Guru als Kollegen und der schreibt nur so was:

gps | ? {$_.name –like „pow*“} | spps

Dann gehe ich einmal stark davon aus, dass er Angst um seinen Arbeitsplatz hat und sich unersetzlich machen möchte. Teamwork hingegen geht so:

Get-Process | Where-Object {$_.name –eq „PowerShell“} | Stop-Process

Beide Zeilen machen genau dasselbe, nur sieht jeder mit PowerShell Grundkenntnissen sofort, was im zweiten Beispiel passiert. Die zweite Zeile ist also vorbildliches Skripting. Zugegeben: ein Where‑Object schreibe ich auch immer als ?, aber ansonsten gebe ich mir schon Mühe es auch für andere leserlich zu verfassen.

$a=read-host „Geben Sie Ihren Namen ein“

Welchen Namen haben wir den nun vom Benutzer bekommen? Den Vor-, oder den Nachnamen? Sein Sie mit Ihren Anweisungen an den Benutzer möglichst präzise, denn die Qualität Ihre Frage bestimmt die Qualität der Antwort, die Sie vom Benutzer erhalten werden. Und wer zum Kuckuck, soll nach einem Jahr noch wissen, dass $a einen Vornamen enthält? Besser wäre folgende Version:

$Vorname=Read-Host –Prompt „Geben Sie bitte Ihren Vornamen ein“

Sie sehen sofort an dem Namen der Variable, was da wohl drin steckt. Vor lauter a,e,i,o,u und x,y,z als Variablenbezeichnung blickt später keiner mehr in einem großen Skript durch. Allerdings sollten Sie es auch nicht übertreiben! Zum einen müssen Sie den Variablenbezeichner jedes Mal wieder eintippen und zum anderen können Sie Ihren Computer manchmal ganz schön verwirren, wenn Sie z.B. Umlaute, wie ö,ä,ü oder Tiefstriche oder sonstige wilde Phantasien einbauen. 26 Buchstaben hat unser Alphabet – das sollte reichen um einen klaren Namen für eine Variable zu finden! Das sollten Sie sich generell auch für alle anderen Benennungen (z.B. Freigaben oder Servernamen) angewöhnen. Auch die Anweisung an den Benutzer ist klar formuliert. Das Zauberwort bitte ist in kurzen Anweisungen auch gut, sollte aber nicht nerven. Groß- u. Kleinschreibung zur Hervorhebung einzelner Worte ist nett, aber nicht so wichtig, wie auf Aliase zu verzichten und Parameter auszuschreiben.

Schreiben Sie reichlich Kommentare, so wie im nachfolgenden Abschnitt beschrieben.

Das sind Tipps, um Arbeitszeit zu sparen. Ob das Skript nun 3 oder 7 Sekunden benötigt kann Ihnen eigentlich egal sein. Arbeitszeit ist teurer, als Rechenzeit! Manchmal sind Skripte jedoch Zeit kritisch, dann will ich nicht sagen: „Vergessen Sie all das hier.“, aber lesen Sie auch den Abschnitt über Skripttuning.

2.4.3   Kommentare

Hier finden Sie Informationen wie man in PowerShell Skriptzeilen kommentiert.

Je mehr Sie kommentieren, umso leichter wird es Ihnen später fallen Änderungen vorzunehmen. Ohne Kommentare hat man ein Skript oft schneller neu geschrieben, statt sich wieder einzuarbeiten. Absolutes Minimum sollte sein, dass Sie die Hauptabschnitte und Funktionen Ihres Skriptes kommentieren. Grundsätzlich kann man sagen: Je mehr, desto besser!

Sie können einen Kommentar durch ein führendes # Zeichen in eine ganze Zeile schreiben:

# Mein erstes PowerShell Skript

oder auch nach einem Befehl, oder eine Befehlskette:

ls # Ist ein Alias auf Get-Childitem

ls | select name,length # Holt den Verzeichnisinhalt ab und zeigt davon den Namen und die Größe an

Ein Kommentar wird also generell durch das # Zeichen eingeleitet. Alles was in dieser Zeile danach folgt wird von der PowerShell nicht mehr interpretiert.

2.4.4   Einfache Parameterübergabe an Skripte

Um ein Skript mit Informationen zu versorgen, kann man einem Skript beim Start gleich einige Dinge mitteilen. Wie das funktioniert, wird nachfolgend erläutert.

Schreiben Sie mit einem Editor Ihrer Wahl (z.B. Notepad oder ISE=Integrated Scripting Environment) folgende Zeilen (die Nummern dienen der Orientierung was zu einer Zeile gehört und sollten nicht mit eingegeben werden!):

1   "Ihrem Skript wurde als erster Wert: $($args[0]) übergeben und ist vom Typ $($args[0].gettype())."

2   "Ihrem Skript wurde als zweiter Wert: $($args[1]) übergeben und ist vom Typ $($args[1].gettype())."

3   "Ihrem Skript wurde als dritter Wert: $($args[2]) übergeben und ist vom Typ $($args[2].gettype() | select BaseType)."

4   "Insgesamt hat Ihr Skript diese Werte erhalten:"

5   $args

6   '$args ist vom Typ: '+($args.gettype() | select BaseType

Nachdem Sie das Skript unter der Bezeichnung Parameter, mit der Dateinamenerweiterung ps1 (spezielle PowerShell Skripteditoren wie ISE und PowerGui machen das automatisch, aber nicht notepad) abgespeichert haben, können Sie das Skript entsprechend aufrufen:

./Parameter.ps1 ABC 123 (ls)

Wenn Sie in dem Verzeichnis stehen indem das Skript liegt, müssen Sie ebenfalls wieder aus Sicherheitsgründen das Skript mit einem einleitenden ./ aufrufen. Sie können gerne die Anfangsbuchstaben des Skripts einsetzen und die Tab-Taste drücken. Dadurch wird nicht nur der Name automatisch vervollständigt, sondern auch gleich das ./ automatisch vorangestellt. Wenn das Skript nicht im aktuellen Verzeichnis liegt, müssen Sie den Pfad (z.B.: C:\IhrVerzeichnis\Parameter.ps1 ABC 123 (ls)) davor schreiben.

Die Variable $args ist wiederum eine besondere Variable, die automatisch die übergebenen Parameter enthält. $args ist immer ein Array, auch wenn nur ein einzelner Wert übergeben wird. Demzufolge haben Sie also in $args[0], den ersten übergebenen Wert, in $args[1], den zweiten, etc…Greifen Sie auf $args ohne eckige Klammer zu, bekommen Sie alle Werte auf einen Schlag. Lesen Sie ggf. noch einmal im Kapitel Objektorientierung den Abschnitt über Besondere Variablentypen: Array.

2.4.5   Im Skript den Skriptpfad herausfinden

Wenn Sie zu einem Skript, weitere Dateien benötigen, z.B. .ico Dateien um Icons anzuzeigen und diese im Verzeichnis des Skriptes selbst ablegen, stellt sich oft die Frage, wo denn ein Benutzer der das Skript und die zugehörigen Dateien überhaupt abgelegt hat. Wenn er das Skript nun auch noch von einer anderen Position aus startet, statt dem Verzeichnis, in dem das Skript liegt, werden Sie schnell feststellen, dass ein Aufruf mit .\DateiImVerzeichnisDesSkripts.txt nicht funktioniert.

Um in einem Skript zu ermitteln, wo das Script überhaupt liegt, können Sie die Systemvariable $MyInvocation abfragen, oder um genau zu sein:
$MyInvocation.MyCommand.Definition

bzw.

$MyInvocation.MyCommand.Path

Ab PowerShell 3.0 geht es noch einfacher:
$PSScriptRoot

Häufig findet man im Internet den Hinweis auf $MyInvocation.InvocationName. Dies liefert aber unter Umständen auch einfach nur einen Punkt, wenn man das Skript aus dem aktuellen Verzeichnis heraus startet.

2.4.6   Kontrollfragen

Welche Dateinamenerweiterung muss ein PowerShell Skript haben?

Ein Skript auf Ihrem Rechner läßt sich nicht starten. Woran liegt das? Wie können Sie das prüfen? Und wie können Sie das Problem beheben?

Sie möchten gerne ein Skript auf einem anderen Rechner ausführen. Wie können Sie sicherstellen, dass das Skript dort läuft, egal welche Ausführungsrichtlinie eingestellt ist?

Wie können Sie in einem Skript einen Kommentar schreiben?

In welcher Variable können Sie in einem Skript übergebene Parameter auslesen und von welchem Typ ist die Variable?

Wie greifen Sie auf das 2. übergebene Element dieser Variable zu?

Link zu den Lösungen für Skripting: Einführung

2.4.7   Schleifenfunktionen

Im Kapitel über Schleifen finden Sie Skript Anweisungen, wie bestimmte Dinge mehrfach durchgeführt werden können, ohne dass Sie den Code dazu mehrmals eintippen müssen.

2.4.7.1   Foreach

Mit ForEach haben Sie ja bereits im Kapitel Objektmethoden in der Praxis und dort im Abschnitt Besondere Variablentypen: Array Bekanntschaft gemacht. Dort haben wir ForEach direkt in einer Pipeline verwendet. ForEach kann aber auch anders:

1   $ObjektArray=Get-ChildItem C:\Test

2   ForEach ($Objekt in $ObjektArray) {

3    Remove-Item $Objekt.Fullname

4   }

Speichern Sie dieses Skript als ps1 Datei ab. Bevor Sie es laufen lassen, sollten Sie allerdings den Inhalt des Ordners Test auf Laufwerk C: an eine andere Stelle kopieren, falls Sie dort noch wichtige Sachen drin haben, die Sie gerne behalten möchten! Das Skript löscht den Inhalt des Test-Ordners.

In der ersten Zeile wird der Verzeichnisinhalt des Verzeichnisses C:\Test in der Array-Variablen $ObjektArray hinterlegt. In Zeile zwei startet die ForEach Anweisung mit dem öffnen der geschweiften Klammer und zieht sich bis zum Ende des Skripts in Zeile 4. In der 3. Zeile wird schließlich die jeweilige Datei gelöscht. Der Übersichtlichkeit halber rückt man den Inhalt der Schleife entweder mit einem Leerzeichen oder einem TabStop etwas ein. Dieser Bereich wird auch Schleifenkörper oder Schleifenrumpf genannt. Der grüne Bereich ist der sogenannte Schleifenkopf. Hier legen Sie fest, aus welchem Array die einzelnen Objekte generiert werden sollen und wie die einzelnen Objekte innerhalb des Rumpfes als Variable angesprochen werden können. Die Variable für das einzelne Objekt und die Arrayvariable werden dabei durch das Zauberwörtchen in getrennt. Um die Datei zu löschen, müssen Sie die Eigenschaft Fullname des Dateiobjekts verwenden, welche den Pfad und den Namen zur Datei enthält. Ein einfaches Remove-Item auf das Objekt selbst funktioniert an dieser Stelle leider nicht. Die Grundlegende Syntax einer ForEach Anweisung sieht so aus:

1   ForEach (Umwandlung eines ObjektArrays in einzelne Objekte) {

2    Anweisung, was mit den Objekten passieren soll

3    Falls Sie mehrere Anweisungen zu schreiben haben, können Sie diese gerne einfach untereinander wegschreiben.

4    So lange bis die geschweifte Klammer geschlossen wird, wird all das wiederholt bis kein Objekt mehr übrig ist.

5   }

In der runden Klammer steht im Skript, dass die einzelnen Dateien und Verzeichnisse aus der Array-Variablen innerhalb der geschweiften Klammern in die Variable $Objekt gelegt werden. Der Code innerhalb der geschweiften Klammern wird mehrfach hintereinander ausgeführt. Um genau zu sein, für jede Datei und jedes Verzeichnis innerhalb von C:\Test genau einmal. In der Variablen $Objekt ist dann die jeweilige Datei bzw. das Verzeichnis hinterlegt und wird durch den Aufruf von Remove‑Item $Objekt entsprechend gelöscht.

Der Übersichtlichkeit halber rückt man die Anweisungen in der geschweiften Klammer etwas ein. Sie können aber auch alles in eine Zeile quetschen:

ForEach ($Objekt in $ObjektArray) {Remove-Item $Objekt.Fullname}

Selbstverständlich können Sie auch innerhalb eines Skriptes die Pipelinevariante mit $_ verwenden:

Get-ChildItem C:\Test | ForEach {Remove-Item $_.Fullname}

In diesem Fall kann der Schleifenkopf komplett entfallen, da hier die Variable $_ die als Array über die Pipe kommt innerhalb des Schleifenrumpfes das jeweilige einzelne Objekt enthält. Für die nächsten Beispiele erspare ich uns immer wieder zu lesen/schreiben, dass Sie die Zeilen in einer Datei speichern sollen, um diese dann auszuführen.

2.4.7.2   ForEach-Object

Am Anfang des Buches habe ich einmal erwähnt, dass es egal ist, ob Sie ForEach oder ForEach‑Object verwenden. Nun, das stimmt nicht ganz. Es gibt einen kleinen, aber feinen Unterschied.

Bei ForEach wird der komplette Schleifen Inhalt für alle Durchläufe am Stück kompiliert und dann ausgeführt. Das führt dazu, dass es in der Regel schneller als Foreach-Object abgearbeitet wird, allerdings jeder Durchlauf einzeln im RAM gehalten wird und so entsprechend mehr Speicher kostet.

Bei ForEach-Object wird jeder Durchlauf einzeln kompiliert, ausgeführt und bei jedem weiteren Objekt beginnt der Spaß von vorne. Dadurch ist es langsamer als ForEach, braucht aber weniger RAM. Ein weiterer Vorteil ist, die „Streaming“-Fähigkeit. Übergibt man also Beispielsweise:

ls | foreach-object {$_.length/1024}

kann von ls die erste Datei direkt über die Pipe übergeben werden und ForEach-Object beginnt sofort mit der Berechnung, während ls dann zur selben Zeit die zweite Datei über die Pipe liefert usw.

Bei

ls | foreach {$_.length/1024}

muss ls zunächst die komplette Liste aller Dateien zusammenstellen und diese dann auf einen Rutsch über die Pipeline übergeben, erst dann kann ForEach mit seiner Arbeit beginnen.

Wenn Sie bei einem Ihrer Skripte einmal Speicherprobleme haben, weil Sie etliche millionen Objekte an ein ein ForEach (statt ForEach-Object) pipen, sollten Sie mal überlegen, ob Sie nicht lieber etwas mehr Tipparbeit leisten.

2.4.7.3   For

Mit ForEach können Sie genau so viele Durchläufe stattfinden lassen, wie Sie Objekte haben. Wenn Sie aber einmal eine bestimmte Anzahl Durchläufe vorgeben möchten ist ForEach keine gute Wahl. Hier eignet sich die For-Schleife wesentlich besser Die Syntax würde so aussehen:

For (Variable mit Anfangswert; So lange diese Bedingung erfüllt ist mache einen weiteren Durchlauf; Zähle den Anfangswert bei jedem Durchlauf um den hier genannten Betrag hoch) {

 Anweisungen wie in der ForEach Schleife, was jedes Mal zu tun ist.

}

Und als Praxisbeispiel:

For ($i=1; $i -le 10; $i++) {

 Write-Host $i

}

Hier wird der Anfangswert für die Zählvariable $i auf 1 gesetzt. Solange $i weniger oder genauso viel wie 10 ist, führe die Anweisungen in der geschweiften Klammer noch einmal aus. Nach jedem Durchlauf zähle $i um 1 hoch. $i fängt demzufolge also bei 1 an zu zählen und schreibt den Inhalt von $i durch die Anweisung Write-Host auf den Bildschirm. Der Zauber ist vorbei, wenn $i nicht mehr kleiner oder gleich 10 ist. Erreicht $i also den Wert 11 wird die Schleife nicht mehr durchlaufen und mit dem Rest des Skriptes (so den einer folgt) fortgefahren.

Das hätten wir auch einfacher haben können:

1..10

hätte den gleichen Effekt gehabt. Allerdings hätten Sie so keine komplexen Anweisungen damit ausführen können, wie Sie es in der For-Schleife machen können. Um 1.5 könnten Sie auch nicht erhöhen bei jedem Durchlauf. Bei der For-Schleife könnten Sie allerdings statt $i++ wie im Beispiel auch $i=$i+1.5 schreiben.

2.4.7.4   While

Die While-Schleife prüft noch vor dem ersten Durchlauf, ob die Bedingung erfüllt ist. Die grundlegende Syntax sieht so aus:

While (so lange diese Bedingung wahr ist durchlaufe die Schleife in den geschweiften Klammern) {

 Hier stehen die wieder die Anweisung was bei jedem Durchlauf zu tun ist.

 Zusätzlich sollte hier auch gezählt werden, da in den runden Klammern keine Zählmöglichkeit gegeben ist.

}

Um nun wieder von 1 bis 10 zu zählen könnte das so aussehen:

$i=1

While ($i –le 10) {

 Write-Host $i

 $i++

}

Die erste Zeile erstellt die Variable $i und weißt Ihr den Wert 1 zu. In den runden Klammern wird geprüft ob $i weniger oder gleich 10 ist. So lange das der Fall ist werden die Anweisungen in den geschweiften Klammern ausgeführt. In Zeile 3 wird der Inhalt von $i auf den Bildschirm gebracht. Danach, in Zeile 4, wird $i um 1 erhöht. Nach der Zeile 4 Beträgt der Wert von $i also 2. Zeile 5 beendet den Abschnitt der zu wiederholenden Anweisungen womit wir wieder am Anfang der Schleife in Zeile 2 stehen und erneut geprüft wird, ob $i immer noch weniger oder gleich 10 ist. Da $i nun 2 ist wird der Schleifenrumpf (der Teil mit den geschweiften Klammern) noch einmal wiederholt. Das Spielchen geht immer so weiter bis $i als Wert 10 auf dem Bildschirm kommt. Danach wird $i auf 11 hochgezählt und wieder zum Schleifenkopf (die Anweisung in der runden Klammer) gesprungen. Dort wird festgestellt, dass $i nun nicht mehr kleiner oder gleich 10 ist. Daher wird nun der Schleifenrumpf nicht mehr durchlaufen und im restlichen Skript weiter gemacht. Da nach der Schleife nichts mehr folgt ist das Skript damit beendet.

2.4.7.5   Do While

Wenn Sie eine Schleife mindestens 1 mal durchlaufen möchten, können Sie zu dieser Form greifen, da hier die Überprüfung der Bedingung erst am Ende der Schleife erfolgt. Hier steht also Ausnahmsweise der Schleifenkopf am Ende, nach dem Schleifenkörper. Mit dem Schlüsselwort Do wird die Schleife eingeleitet:

Do {

 Gleiches Spiel wie bei While

} While (so lange diese Bedingung wahr ist, durchlaufe die Schleife in den geschweiften Klammern)

Zählen Sie wieder bis 10:

$i=1

do {

 Write-Host $i

 $i++

} While ($i –le 10)

Um den Unterschied zu verdeutlichen hier noch 2 Beispiele, zunächst ohne Do am Anfang:

$i=11

While ($i –le 10) {

 Write-Host $i

 $i++

}

Und mit Do:

$i=11

do {

 Write-Host $i

 $i++

} While ($i –le 10)

Sie sehen, obwohl die Bedingung der Schleife von Anfang an beim 2. Beispiel nicht erfüllt war wurde die Schleife einmal durchlaufen.

2.4.7.6   Do Until

Until ist ähnlich der While-Schleife nur anders herum. Sprich die Until-Schleife wird so lange durchlaufen, bis die Bedingung erfüllt ist.

Do {

 Gleiches Spiel wie bei While

} Until (so lange diese Bedingung nicht wahr ist, durchlaufe die Schleife in den geschweiften Klammern)

Auch hier wieder das praktische Beispiel um von 1 bis 10 zu zählen:

$i=1

do {

 Write-Host $i

 $i++

} until ($i –gt 10)

Until-Schleifen müssen immer mindestens einmal durchlaufen werden, da es von Until keine Form ohne einleitendes Do gibt, spricht Sie dürfen die Schleife nicht mit Until einleiten.

2.4.7.7   Verschachteln von Schleifen

Schleifen können ineinander geschachtelt werden, wie dieses 1x1 Beispiel zeigt:

For ($x=1;$x –le 5;$x++) {

 For ($y=1;$y –le 5;$y++) {

  Write-Host ”$x x $y=“($x*$y)

 }

}

In der inneren Schleife wird $y von 1 bis 5 gezählt. In der äußeren Schleife steht $x zunächst auf 1. Ist die innere Schleife mit $y einmal komplett durchlaufen, zählt die äußere Schleife $x um eins hoch und startet die innere Schleife erneut, sprich $y wird erneut von 1 bis 5 durchgezählt. Das Spielchen geht so weiter bis $x der äußeren Schleife auf 6 hochzählen würde und somit die Bedingung nicht mehr erfüllt. Das Write-Host im Kern der beiden Schleifen zeigt zunächst die Werte von $x und $y an und dann noch den berechneten Wert aus der Multiplikation von $x mit $y.

2.4.8   Kontrollfragen

Sie möchten gerne von 50 bis 5 in Abständen von 0,5 herunterzählen. Welche Schleifenform ist dafür am besten geeignet? Erstellen Sie die Schleife und lassen Sie die Werte zur Überprüfung am Bildschirm anzeigen.

In einem Temp-Verzeichnis auf Ihrer Festplatte befindet sich eine unbestimmte Anzahl von Dateien. Sie möchten diese gerne löschen. Welche Schleifenform verwenden Sie und wie würde diese Schleife aussehen?

Wie können Sie eine Endlosschleife erzeugen?

Was ist der Unterschied zwischen der while und der do-while Schleife?

Link zu den Lösungen für Skripting: Schleifen

2.4.9   Bedingte Ausführung

Im Bereich des bedingten Ausführens werden Sie Anweisungen kennenlernen, die Ihnen ermöglichen Programmabschnitte nur unter bestimmten Voraussetzungen durchlaufen zu lassen. Also ganz einfach ausgedrückt: Wenn die Ampel grün ist fahre über die Kreuzung und wenn sie rot ist halte an.

2.4.9.1   If, Else und Elseif (Wenn-Dann-Sonst - Abfragen)

Mit If (zu Deutsch: Wenn) können Sie einen Programmabschnitt nur dann durchlaufen, wenn die formulierte Bedingung wahr ist. Am praktischen Beispiel:

$Ampel=“Grün

If ($Ampel –eq “Grün“) {

 Write-Host “Die Ampel ist grün.“ –Foregroundcolor green

}

If ($Ampel –eq ”Gelb“) {

 Write-Host ”Die Ampel ist gelb.“ –Foregroundcolor yellow

}

If ($Ampel –eq ”Rot“) {

 Write-Host ”Die Ampel ist rot.“ –Foregroundcolor red

}

Wie Sie sehen, wurde nur der Text in grün angezeigt. Der gelbe und rote Text aber nicht. In der runden Klammer wird abgefragt, ob die Bedingung wahr ist. Ähnlich wie bei den zuvor beschriebenen Schleifen wird der Inhalt der geschweiften Klammern nur dann ausgeführt, wenn die Bedingung in den runden Klammern wahr ist. Da Sie der Variablen $Ampel in der ersten Zeile den Wert grün zugewiesen haben, stimmt nur die Bedingung des ersten If Vergleichs. Wenn Sie mögen ändern Sie doch einmal in der ersten Zeile die Zuweisung z.B. auf das Wort Gelb und lassen Sie Ihr Skript erneut ablaufen.

Etwas eleganter und kürzer würde das Ganze mit Else und ElseIf aussehen:

$Ampel=“Rot

If ($Ampel –eq „Grün“) {

 Write-Host „Die Ampel ist grün.“ –Foregroundcolor green

} ElseIf ($Ampel –eq „Gelb“) {

 Write-Host „Die Ampel ist gelb.“ –Foregroundcolor yellow

} Else {

 Write-Host „Die Ampel ist rot.“ –Foregroundcolor red

}

Hier wird unser Text nun in Rot ausgegeben. Die erste If Bedingung $Ampel=“Grün“ ist nicht erfüllt, da Sie dieses Mal in der ersten Zeile die Zuweisung Rot getroffen haben. Also entfällt schon einmal der grüne Text. Else zu Deutsch heißt andernfalls. Da bei Gelb hierauf direkt ein If folgt bedeutet das so viel wie „Wenn die Ampel schon nicht grün ist, dann prüfe einmal ob sie vielleicht gelb ist“. Da Sie aber auch nicht Gelb ist, wird auch der gelbe Text nicht angezeigt. Da Verkehrsampeln nun einmal nur drei Farben haben und sie weder grün noch gelb ist, muss sie wohl folglich rot sein. Das abschließende Else bewirkt also in allen anderen Fällen: Zeige an, dass die Ampel rot ist.

Nun ja, die Ampel könnte vielleicht auch kaputt sein, oder einen Stromausfall haben, also gar keine Farbe.

Übungsaufgabe: Überlegen Sie doch einmal wie sie das mit PowerShell formulieren könnten. Die Lösungen finden Sie im Anhang Lösungen im Abschnitt Skripte. Auch wenn Sie meinen die Lösung zu kennen, schauen Sie doch einmal rein, da ist noch ein anderer Tipp versteckt.

2.4.9.2   Verschachteln von Bedingungen

Auch Bedingungen können geschachtelt werden.

For ($x=1;$x –le 2;$x++) {

 For ($y=1;$y –le 2;$y++) {

  If ($x –eq 1) {

   Write-Host “X ist Eins!

   If ($y –eq 1) {

    Write-Host “Y ist Eins!

   }

  }

 }

}

Böses Beispiel! Da muss man ja denken ;-). Ganz außen herum haben Sie zwei verschachtelte Schleifen. Die innere zählt $y von 1 auf 2 hoch und die äußere $x von 1 auf 2. Das ergibt also 2 x 2=4 Durchläufe des inneren Schleifenrumpfes mit folgenden Werten:

1.       Runde: $x=1, $y=1

2.       Runde: $x=1, $y=2

3.       Runde: $x=2, $y=1

4.       Runde: $x=2, $y=2

In der ersten Runde stoßen Sie innerhalb der $y Schleife auf eine If-Abfrage, deren Anweisung wird nur ausgeführt, wenn $x=1 ist. Da dies der Fall ist, wird auf den Bildschirm „X ist Eins!“ ausgegeben und weiterhin gefragt, ob $y auch 1 ist. Da dies in Runde 1 der Fall ist, wird auch die Anweisung nach der $y-Abfrage ausgeführt und somit „Y ist Eins!“ ebenfalls angezeigt.

In Runde 2 ist $x immer noch 1, daher wird abermals „X ist Eins!“ angezeigt. $y ist aber 2. Folglich ist die zweit If-Abfrage nicht mehr wahr und somit entfällt der Text „Y ist Eins!“.

In Runde 3 ist $x zu 2 geworden. Das bedeutet, dass die erste If-Bedingung bereits nicht erfüllt ist. Daher entfällt die Ausgabe „X ist Eins!“. Zur Abfrage ob $y=1 ist kommt es nicht, da diese Abfrage innerhalb der geschweiften Klammer der ersten If-Abfrage steht und folglich nur dann überhaupt abgefragt wird, wenn $x=1. Obwohl also $y=1 wird dieser Text hier nicht ausgegeben, weil $x nicht 1 ist.

In der 4. Runde ergibt sich dieselbe Lage wie in Runde 3, da auch hier bereits die erste Anforderung $x=1 nicht erfüllt ist.

Übungsaufgabe: 1 x 1

Versuchen Sie doch einmal ein 1x1 auf dem Bildschirm auszugeben, das ungefährt so aussieht:

Die Lösung finden Sie im Anhang Lösungen und dort im Abschnitt Skripte. Das Beispiel beinhaltet auch wie man einen 2 dimensionalen Array anlegt.

2.4.9.3   Switch

Switch eignet sich hervorragend zu Auswertung einer Menüauswahl.

1   $Eingabe=Read-Host –Prompt “Wählen Sie 1 für ABC, 2 für DEF und die 3 für XYZ“

2   Switch ($Eingabe) {

3    1 {“ABC“}

4    2 {“DEF“}

5    3 {“XYZ“}

6    default {“Bitte geben Sie nur eine Zahl zwischen 1-3 an.“}

7   }

Die erste Zeile holt sich eine Benutzereingabe in die Variable $Eingabe. In der 2. Zeile folgt die Switch Anweisung. Die Switch Anweisung verzweigt hier anhand des in runden Klammern angegebenen Inhalts der Variablen $Eingabe. Von wo bis wo die Switch Anweisung geht markiert die fett gedruckte geschweifte Klammer. In der 3. Zeile steht zunächst welchen Inhalt die unter Switch angegebene Variable haben muss um den nachfolgenden Code in den nachfolgenden geschweiften Klammern auszuführen. Bei 1 wird also ABC angezeigt, bei 2 DEF und bei 3 XYZ. Falls der Benutzer bei der Eingabe geschummelt hat und nicht macht, was Sie Ihm sagen, können Sie mit default alle anderen Eingaben abfangen, die zu keinem der vorigen Abfragen passen.

Bei Switch sind mit dem Schalter –Wildcard auch Platzhalterzeichen erlaubt:

$Person=“Autor: Martin Lehmann“

Switch –Wildcard ($Person) {

 “Bäcker*“ {“Bäcker“}

 “Autor*“ {“Autor“}

 “Arzt*“ {“Arzt“}

 “*Martin*“ {“Martin“}

 “*Markus*“ {“Markus“}

 “*Lehmann“ {“Lehmann“}

 “*Meyer“ {“Meyer“}

}

Dabei werden alle in der Switch Anweisung zutreffenden Bedingungen ausgeführt. Bäcker steht nicht am Anfang der Variable $Person und somit wird die Anweisung in der folgenden geschweiften Klammer nicht ausgeführt. Allerdings beginnt $Person mit den Buchstaben Autor und daher wird die hiernach folgende Anweisung das Wort Autor auf den Bildschirm zu schreiben ausgeführt. Arzt passt auch nicht und wird daher auch nicht ausgeführt. Der Name Martin kommt allerdings wieder in der Variablen vor. Daher wird auch hier wieder die Anweisung ausgeführt. Markus ist nicht Bestandteil der Variable, also keine Anweisung. $Person endet mit Lehmann und es wird auch wieder Lehmann auf den Bildschirm geschrieben. Meyer=Fehlanzeige, deshalb auch hier wieder keine Anweisung in aus der nachfolgend geschweiften Klammer.

2.4.9.4   Schleifen- und Programmsteuerung

Manchmal möchte man eine Schleife, die Verarbeitung einer Bedingung, oder ein komplette Skript gerne vorzeitig beenden. Dafür gibt es verschiedene Befehle.

2.4.9.4.1    Endlosschleifen

Eine Endlosschleife, können Sie ganz einfach dadurch herstellen, indem Sie eine Bedingung für das Schleifenende formulieren, die immer bzw. nie zutrifft. Je nachdem ob Sie mit While oder Until arbeiten. Solche Schleifen, oder auch wenn Ihr Skript hängen bleibt, können Sie manuell mit Strg+C abbrechen.

While (1 –eq 1) {

 “Autsch! Drücken Sie Strg+C um das Skript zu beenden.“

}

1 wird wohl für immer und ewig 1 bleiben. Also wird der Schleifenrumpf in den geschweiften Klammern immer wieder aufs Neue ausgeführt.

Alternativ wird wahr auch immer wahr bleiben:

While ($true) {

 “Autsch! Drücken Sie Strg+C um das Skript zu beenden.“

}

2.4.9.4.2    Schleifen vorzeitig abbrechen

Mit der Break Anweisung können Sie eine Schleife vorzeitig beenden.

1   “Skriptstart“

2   While (1 –eq 1) {

3    $a=Read-Host -Prompt “Ich nerve so lange, bis Sie endlich eine 1 eintippen“

4    If ($a –eq „1“) {break} Else {“Sie haben $a eingegeben. Also noch mal!“}

5   }

6   “Skriptende – geschafft!“

Hier haben Sie eine Endlosschleife wie im vorigen Abschnitt. Im Schleifenrumpf wird mittels eine If-Anweisung geprüft, ob der Benutzer endlich eine 1 getippt hat. Ist das der Fall wird die Anweisung Break ausgeführt und somit die Schleife verlassen und die letzte Zeile des Skripts angezeigt. Ist das nicht der Fall wird dem Benutzer durch die Else Anweisung erklärt, was er falsch gemacht hat.

Oder als Beispiel mit einer Zählschleife:

“Skriptstart”

$a=0

Do {

 Write-Host $a

 if ($a -eq 5) {break}

 $a++

} While ($a -lt 10)

“Skriptende!”

Obwohl Ihre Schleife laut der While-Abfrage am Schluß bis 9 zählt, ist das Skript schon bei der Zahl 5, wegen der Break-Anweisung, in der letzten Zeile angelangt.

Wenn Sie eine Schleife nicht vorzeitig verlassen möchten, sondern noch einmal von vorne starten ohne den Rest der Schleife zu durchlaufen können Sie das mit der Anweisung Continue erreichen. Bei diesem Beispiel bitte kurz nach dem Start gleich Strg+C drücken.

“Skriptstart”

$a=0

Do {

 Write-Host $a

 if ($a -eq 5) {continue}

 $a++

} While ($a -lt 10)

“Skriptende!”

Kurz habe ich gesagt…was haben Sie denn für eine Reaktionszeit? ;-)

Sobald $a 5 ist kommt es zur Anweisung Continue. Das verursacht den Sprung zum Anfang der Schleife. Dummer Weise kommt es erst nach dieser Abfrage zum hochzählen der Variable $a und somit zu einem erneuten endlosen Schleifendurchlauf.

2.4.9.4.3    Skript beenden

Um ein Skript komplett zu verlassen, statt nur ein Konstrukt (Schleife, Bedingung, etc…), können Sie die Anweisung Exit verwenden.

“Skriptstart”

$a=0

Do {

 Write-Host $a

 if ($a -eq 5) {exit}

 $a++

} While ($a -lt 10)

“Skriptende!”

Den Unterschied zwischen Exit und Break verdeutlicht der Vergleich zwischen diesem Skript und dem Zählskript mit der Break-Anweisung. Die Bildschirmausgabe ist fast identisch. Allerdings fehlt bei dem Skript mit Exit, die Ausgabe aus der letzten Zeile “Skriptende!“, da die Anweisung Exit das Skript und sogar die aktuelle PowerShell Konsole komplett verlässt, während Break nur aus der aktuellen Schleife aussteigt.

Die Exit Anweisung kann auch Statuscodes an das aufrufende Programm senden.

Exit 0                                     bedeutet normaler Weise: Alles in Ordnung
Exit andereZahl            bedeutet das etwas schief gelaufen ist. Die Nummern sind dabei nicht
                                                     vorgegeben. Das aufrufende Programm muss halt etwas mit diesen
                                                     Nummern anfangen können.

2.4.10                     Funktionen

Im Abschnitt über Funktionen lernen Sie PowerShell Cmdlets selbst zu schreiben. D. h. Sie können ganze Programmabschnitte mit einem einfachen „selbstgestrickten“ Befehl ausführen lassen und auch diesen Befehlen noch etwas mitteilen oder sich auch von Befehlen etwas zurückgeben lassen.

2.4.10.1                Einfache Subroutinen

Mit einfachen Subroutinen können Immer wiederkehrende Aufgaben von beliebigen Stellen im Skript aus mit einem einfachen Befehl aufrufen. Bei Schleifen macht man auch immer wiederkehrende Aufgaben, aber an einer bestimmten Stelle im Skript. Wenn Sie z.B. die Mehrwertsteuer für einen Nettobetrag ausrechnen möchten, wollen Sie das vielleicht an mehreren Stellen des Skriptes machen. Ohne Funktionen müssten Sie an Ort und Stelle immer wieder erneut den gesamten Code für die Berechnung schreiben. Bei der Mehrwertsteuer ist das sicher nicht so dramatisch, aber wenn die Berechnungen komplexer werden und mehrere Zeilen Code ausfüllen wäre das schon ganz schön nervig. Um den Einstieg noch weiter zu vereinfachen, werden wir zunächst einfach nur Text anzeigen.

Funktionen müssen immer am Anfang des Skriptes stehen, damit PowerShell sie kennenlernt. Das beudetet aber nicht, dass das was da drin steht auch am Anfang passiert, wie dieses einfach Beispiel zum Einstieg verdeutlichen soll:

“Anfang des Skripts mit der Funktionsdefinition“

Function MwSt {

 “Hier würden Sie die Mehrwert Steuer berechnen.“

}

“Ende der Funktionsdefinition und Start des Hauptprogrammes.“

“Erster Sprung zur Mehrwert Steuer Berechnung.“

MwSt

“Dann machen Sie ein paar andere Dinge.“

“Jetzt brauchen Sie noch einmal die Mehrwert Steuer.“

MwSt

“Ende des Skripts“

Durch das Zauberwort Function eingeleitet können Sie einen Namen für Ihr kleines Unterprogramm vergeben. In diesem Falle heißt es MwSt. Was die Funktion tun soll, steht in den nachfolgenden geschweiften Klammern. An der Stelle im Programm wo die Funktion beschrieben steht wird sie aber nicht ausgeführt, sondern nur bekannt gemacht. Jedes Mal wenn Sie den Programmcode der Funktion verwenden möchten müssen Sie einfach nur deren Namen aufrufen. Im Beispielskript wird 2 Mal der Name MwSt genannt und demzufolge auch 2 Mal ausgeführt.

2.4.10.2                Variablen in Funktionen verwenden

Hier soll Ihr Hauptprogramm mit Ihrer Funktion kommunizieren, sprich Werte austauschen. Das dies manchmal ziemlich verzwickt sein kann liegt meistens daran, dass man die Hintergründe nicht kennt. Dem will ich hier abhelfen.

2.4.10.2.1             Grundlagen zur Parameterübergabe an Funktionen und Rückgabewerte von Funktionen

Bleiben wir bei der Mehrwert Steuer. Dieses Mal lassen Sie Ihre Funktion aber auch tatsächlich rechnen:

Function MwSt ($Eingabe) {

 Return $Eingabe*0.19

}

[Double]$Abfrage=Read-Host –Prompt „Geben Sie eine Zahl ein“

"Netto: $Abfrage"

"Mehrwertsteuer: "+(MwSt $Abfrage)

"Brutto: "+($Abfrage+(MwSt $Abfrage))

Die ersten drei Zeilen definieren wieder die Funktion namens MwSt. Dazu gleich weitere Infos. Schauen wir uns aber erst einmal das weitere Skript an. Mit Read-Host holen Sie sich eine Zahl vom Benutzer. Damit es auch wirklich eine Zahl ist, wird die Variable $Abfrage mit [Double] explizit als Kommazahl definiert, da Read-Host selbst bei Zahleneingabe standardmäßig immer einen String-Datentyp übergibt. Nach dieser Eingabe haben Sie in $Abfrage also eine Zahl und somit zeigt die nächste Zeile einfach mit „Netto: “ eingeleitet noch einmal an, was der Benutzer eingetippt hat. Die vorletzte Zeile wird nun interessant. „Mehrwertsteuer: “ wird als Text ausgegeben – kein Hexenwerk. + fügt eine weitere Ausgabe hinzu. Damit Ihre MwSt-Funktion nicht als darzustellender Text oder Parameter fehlinterpretiert wird, müssen Sie an dieser Stelle den Aufruf Ihrer Funktion in runde Klammern setzen. Das Spannende daran ist, dass Ihrem Funktionsaufruf noch die Angabe der Variable $Abfrage folgt. Der in $Abfrage enthaltene Wert wird damit an die Funktion MwSt übermittelt. Jetzt kommen wir zur Funktion. Vor der geschweiften Klammer auf steht in runden Klammern wieder eine Variable mit dem Namen $Eingabe. Der Wert aus der Variablen $Abfrage, der an die Funktion MwSt gesendet wurde wird damit in der Variable $Eingabe entgegen genommen. Innerhalb der Funktion haben Sie also den Nettobetrag in der Variablen $Eingabe. In der zweiten Zeile wird die Mehrwertsteuer ausgerechnet durch $Eingabe*0.19. Das Return davor sorgt dafür, dass das Ergebnis der Berechnung nicht auf dem Bildschirm erfolgt, sondern zum Rückgabewert der Funktion wird. Zusätzlich sorgt die Return Anweisung auch dafür, die Funktion an dieser Stelle zu verlassen. Der berechnete Wert wird also zurück zum Skriptteil geschickt, der die Funktion aufgerufen hat. Zurück im Hauptprogramm wird der Rückgabewert aus der Funktion übernommen. Da wird hier einen Text + einen weiteren Text verketten, jedoch die berechnete Mehrwertsteuer in Form einer Zahl vorliegen haben, baut unsere clevere PowerShell automatisch den numerischen berechneten Mehrwertsteuer Wert in einen Text um, damit er mit dem vorderen Text verknüpft auf den Bildschirm gebracht werden kann. In der letzten Zeile läuft es ähnlich. Hier wird ebenfalls wieder die Funktion aufgerufen und der Nettowert aus $Abfrage ermittelt. Die Funktion liefert den fertig berechneten Wert zurück. Zu der zurückgeliefert Mehrwert Steuer wird noch einmal der Nettobetrag addiert. Beides zusammen konvertiert uns die PowerShell automatisch in einen Text, um ihn mit dem ersten Text „Brutto: “ zu verknüpfen und auf dem Bildschirm darzustellen.

OK, ich habe etwas geflunkert. Die Return Anweisung ist „Schmuck am Nachthemd“. Was in anderen Sprachen unerlässlich ist, kann in der PowerShell entfallen, da alles was Sie innerhalb einer Funktion ausgeben nicht auf dem Bildschirm ausgegeben wird, sondern zum Rückgabewert der Funktion wird. Wenn Sie so wollen, ist die Standardausgabe innerhalb einer Funktion von Bildschirm zum aufrufenden Skriptbefehl verbogen worden. Das nachfolgende Beispiel soll das verdeutlichen:

# Funktion

Function Calc ($x,$y) {

 $x=$x+10

 $y=$y*20

 $x,$y

}

# Hauptprogramm

$l=10;$m=3

$a=Calc $l $m

$a[0]

$a[1]

$f,$g=Calc $l $m

$f

$g

Die Funktion calc erwartet 2 Werte. Der erste wird in $x innerhalb der Funktion hinterlegt und der 2. Wert in $y. calc erhöht $x um den Wert 10 und $y wird mit 20 multipliziert. Nach den zwei Berechnungszeile erfolgt die Rückgabe der 2 Werte $x und $y an den aufrufenden Skriptteil. Das Hauptprogramm definiert und belegt zu Beginn die 2 Variablen $l und $m. $a nimmt die von calc berechneten Werte entgegen. $a ist nur eine Variable, aber calc liefert 2 Werte zurück. Macht nix! PowerShell ist schlau und merkt, dass das wieder einmal nicht passt und definiert $a automatisch als Array, der die zwei integer Werte aufnehmen kann. Einfach nur cool! Auf die beiden Werte kann dann Array typisch getrennt zugegriffen werden: $a[0] und $a[1]. Wem das etwas zu seltsam anmutet, versteht vielleicht die Zuweisung mit $f,$g=calc $l $m etwas besser. Wie Sie sicher gemerkt haben wird innerhalb der Funktion $x,$y nicht auf dem Bildschirm geschrieben!

Fügen Sie innerhalb der Funktion zwischen der Zeile $x,$y und } noch eine zusätzliche Zeile ein:

 $x,$y

 “Hutzli Butzli“

}

Untersuchen Sie anschließend einmal die Variablen $a, $f und $g. $a ist ein Array mit 3 Elementen. $f ist ein Integer und $g ist jetzt auch ein Array. Das $x aus calc wird in $f hinterlegt. $y kommt in $g[0] und der Text “Hutzli Butzli“ landet in $g[1].

Nun schreiben Sie nach “Hutzli Butzli“ doch noch einmal eine Pipe und Out-Host, also so:

 “Hutzli Butzli“ | Out-Host

Starten Sie das Skript. Standardausgabe ist ja innerhalb der Funktion die aufrufende Skriptstelle. Durch das Pipen an Out-Host verbiegen Sie die Ausgabe wieder zurück auf den Bildschirm. Das bedeutet $x und $y sind weiterhin Rückgabewerte der Funktion, während der Text “Hutzli Butzli“ innerhalb der Funktion auf den Bildschirm schreibt. Es gibt also doch noch einen Einsatzzweck für das Out-Host Cmdlet. ;-)

Wenn Sie innerhalb einer Funktion eine Ausgabe unterdrücken möchten, z.B. den Rückgabewert eines .NET Aufrufs, dann finden Sie sehr häufig diese Schreibweise:

 [void] “Hutzli Butzli“

Damit wird die Ausgabe des Textes “Hutzli Butzli“ komplett unterdrückt. Es wird also weder zum Rückgabewert der Funktion, noch wird es auf dem Bildschirm ausgegeben, sondern landet im „Nirwana“. Alternativ wären auch diese beiden Varianten noch praktikabel:

 “Hutzli Butzli“ | Out-Null

oder

 “Hutzli Butzli“ > $null

2.4.10.2.2             Unterschied zwischen Return, Break und Exit

Alle drei sind zum Aussteigen gedacht. Break dient zum Ausstieg aus Schleifen, Return zum Ausstieg aus Funktionen und Exit zum Ausstieg aus Skripten bzw. der aktuellen Konsole. Zunächst einmal zur Return Anweisung ein kleines Beispiel:

Function Call {

 Return "Eins"

 Return "Zwei"

}

Call

Der Text Zwei wird von der Funktion call nicht mehr ausgegeben, da die Return Anweisung vor “Eins“ bereitsden Ausstieg aus der Funktion herbeiführt.

Probieren Sie auch diese beiden Skripte einmal nacheinander (nicht in eine Datei schreiben!) aus:

# Skript 1

Function Call {

 "Eins"

 Exit

}

Call

"Zwei"

 

# Skript 2

Function Call {

 "Eins"

 Return

}

Call

"Zwei"

Dies macht wohl eindeutig den Unterschied zwischen Exit und Return klar.

So, und nun zu den ganz kuriosen Begebenheiten:

Foreach ($i in 1..5) {$i; If ($i -eq 3){Return}}

1..5 | Foreach {$_; If ($_ -eq 3){Return}}

1..5 | Foreach {$_; If ($_ -eq 3){Break}}

Foreach ist eine Zählschleife. Break ist das Statement um Schleifenkonstrukte abzubrechen – nicht Return! Das kann zwar gut gehen, wie die erste Zeile beweist, aber auch ganz schön in die Hose gehen, wie die zweite Zeile zeigt. Wie man Schleifen korrekt verlässt steht in der dritten Zeile.

2.4.10.2.3             Alternative Möglichkeit zur Parameterübergabe

Sie haben im vorigen Abschnitt diese Schreibweise für Funktionen kennengelernt:

Function Name (1. Wert, 2. Wert, usw.) {Was mit den Werten zu tun ist. -> Rückgabe}

Das ist auch ganz in Ordnung. Allerdings, kann die Zeile bei vielen Werten recht lang und damit unübersichtlich werden. Alternativ steht Ihnen noch diese Schreibweise zur Verfügung:

Function Name {

 Param (

  $VariableFürWert 1,

  $VariableFürWert 2,

  usw.

 )

 Was mit den Werten zu tun ist. -> Rückgabewert.

}

Statt die Parameter also vor der geweiften Klammer in runde Klammern gefasst zu übergeben, wird erst die geschweifte Klammer geöffnet und danach folgen die Parameter wieder in runde Klammern eingefasst. Bei dieser Schreibweise müssen die runden Klammern aber mit dem Begriff Param als sogenannter Parameterblock kenntlich gemacht werden.

2.4.10.2.4             Scopes: Gültigkeitsbereiche von Variablen und Funktionen und deren Vererbung

In diesem Abschnitt lernen Sie etwas über die Gültigkeitsbereiche von Variablen und Funktionen. Dies ist eine wichtige Voraussetzung, um später eigene PowerShell Cmdlets zu schreiben.

Erstellen Sie zunächst ein einfaches Skript:

$Inhalt

$Inhalt=“Skript“

$Inhalt

Speichern Sie das Skript ab. Öffnen Sie eine frische PowerShell Konsole und geben Sie ein:

$Inhalt=“Konsole“

$Inhalt

Wie erwartet ist in der Variablen $Inhalt das Wort Konsole enthalten. Starten Sie nun Ihr eben geschriebenes Skript. Das hat zunächst einmal den Wert von $Inhalt wiedergegeben. Haben Sie $Inhalt zuvor in Ihrem Skript einen Wert zugewiesen? Nein! Es hat die Variable $Inhalt von der Konsole geerbt. Dann haben Sie in Zeile zwei Ihres Skriptes der Variablen $Inhalt einen neuen Wert zugewiesen und in Zeile drei zu Testzwecken ausgeben lassen. Dabei stellte sich heraus, dass nun der Wert Skript in der Variablen $Inhalt steht. Ist das so? Geben Sie doch nun, nur noch einmal zu Sicherheit $Inhalt auf der Konsole ein. Überrascht? Ja, ich auch ;-).

Wenn von der Konsole aus ein Skript gestartet wird erbt das Skript alle Variablen von der Konsole. Änderungen daran bleiben jedoch im Skript und gehen nicht zurück auf die Konsole. Diese Grafik soll das verdeutlichen:

Wenn Sie auf der Konsole $a und $b mit dem Wert Konsole belegen und dann ein Skript starten, wird automatisch quasi eine Kopie der Variablen unter demselben Namen angelegt. Um das zu verdeutlichen sind die Skriptvariablen hellgrau und die Konsolenvariablen weis dargestellt. Alles was man an den Variablen dann innerhalb des Skriptes ändert passiert an der Kopie, nicht am Original. Wird dann das Skript beendet hat man wieder den Originalzustand, wie vor den Skriptaufruf. Ähnlich ist das mit Funktionen innerhalb eines Skripts. Auch hier erbt die Funktion die Variablen von der Konsole, bzw. des Skriptes. Auch hier wird automatisch wird eine Kopie unter demselben Namen erzeugt. Diese Kopie ist etwas dunkler dargestellt. Alle Änderungen bleiben somit innerhalb der Funktion. Sobald die Funktion beendet ist, gelten wieder die Werte aus dem Skript und ist auch das Skript zu Ende, wieder die Werte der Konsole.

Auch aus Funktionen kann man andere Funktionen aufrufen. Das hat natürlich wieder zur Folge, dass die aufrufende Funktion Ihre Variablen an die Unterfunktion vererbt, aber Änderungen nicht wieder an die aufrufende Funktion zurückgegeben werden. Wie Sie Werte an aufrufende Bereiche zurückgeben können haben Sie im vorangegangenen Abschnitt lesen können. Es gibt allerdings eine Möglichkeit direkt Variablen in einem bestimmten Bereich zu ändern.

Möchten Sie aus einem Skript heraus eine globale Variable (eine Variable von der PowerShell Konsole) ändern, können Sie das indem Sie zwischen dem $ Zeichen und dem Namen der Variablen den Scope-Bezeichner Global einsetzen. Um dies zu verdeutlichen ändern Sie Ihr kleines Skript von eben auf folgenden Inhalt:

$Inhalt

$Inhalt=“Skript“

$Inhalt

$Global:Inhalt=“Mit Scope Bezeichner“

$Inhalt

Mit dem Scope-Bezeichner Global in der vorletzten Zeile wurde die globale Variable $Inhalt auf der Konsole verändert, nicht die Variable $Inhalt im Skript. Die letzte Zeile enthielt immer noch den Wert Skript. Wenn Sie nun aber auf der Konsole $Inhalt ausgeben lassen werden Sie feststellen, dass nun Mit Scope Bezeichner der Inhalt ist.

Ähnlich ist das innerhalb von Funktionen. Auch hier können Sie gezielt auf den Bereich der Konsole, oder des Skriptes, statt auf den Bereich der Funktion zugreifen. Auf den globalen Bereich funktioniert das wieder genauso wie im vorangegangenen Beispiel. Um auf den Skriptbereich zuzugreifen verwenden Sie statt Global das Wort Skript.

Abgesehen davon, können Sie auch relativ zur eigenen Position, auf andere Bereiche mithilfe einer Nummerierung zugreifen. Mit $0:Variablenbezeichner greifen Sie explizit auf den aktuellen Bereich zu. Mit $1:Variablenbezeichner auf den darüber liegenden. Mit $2:Variablenbezeichner noch einen darüber, usw…

Stellen Sie sich vor, Sie haben ein Skript geschrieben und befinden sich in einer Funktion, die von einer anderen Funktion aufgerufen wurde. Dann würde Sie mit $1:Variablenbezeichner auf die Variablen der aufrufenden Funktion zurückgreifen und mit $2:Variablenbezeichner auf die Skriptvariablen und mit $3:Variablenbezeichner letztendlich auf die PowerShell-Konsole.

Ähnlich wie mit den Variablen verhält es sich mit Funktion, wie im nächsten Abschnitt beschrieben.

2.4.10.2.5             Eigene Cmdlets und Funktionen erstellen

Wenn Sie eine Funktion schreiben, ist diese Funktion zunächst einmal nur innerhalb Ihres Skriptes bekannt.

Unter SuSE-Linux gibt es ein Kommando namens ll, was letztendlich ein Alias auf ls –l darstellt und Dateien mit all Ihren Informationen wiedergibt. In der PowerShell können wir aber keine Aliase mit Parametern versehen, sondern einfach nur von einem Begriff auf ein Cmdlet deuten. Für zusätzliche Parameter müssen wir Funktionen einsetzen. Schreiben Sie folgendes kleines Skript und speichern Sie es ab:

Function ll {

 ls | select *

}

ll

Wenn Sie es starten listet es Ihnen schon die Dateien aus dem aktuellen Verzeichnis auf. Aber was passiert, wenn Sie ll auf der Konsole tippen? Eine Fehlermeldung wird angezeigt, da die Funktion ll nur innerhalb des Skriptes bekannt ist. Ändern Sie die erste Zeile des Skripts wie folgt ab:

Function Global:ll {

Löschen Sie noch die letzte Zeile und speichern Sie es erneut ab. Starten Sie Ihr Skript. Hmm…funktioniert nicht mehr?! Zumindest scheint das Skript nichts gemacht zu haben. Aber nun tippen Sie doch einmal auf der Konsole den „Befehl“ ll ein. Soeben haben Sie Ihren ersten eigenen PowerShell-Befehl geschrieben. J

Durch die Angabe des Bereichs Global wurde Ihre Funktion auf der Konsole bekannt gemacht. Wenn Sie die Konsole schließen und neu starten ist Ihr Befehl allerdings wieder vergessen. Kein Problem! Wenn Sie ihn wieder haben möchten, starten Sie einfach noch einmal Ihr Skript. Ihr Skript ist natürlich nicht auf eine einzelne Funktion beschränkt. Sie können dort gerne mehrere globale Funktionen einfach hintereinander weg schreiben. Lassen Sie dann das Skript ablaufen, werden alle als Global deklarierten Funktionen als Befehle in Ihre Konsole eingebaut. Wenn Sie keine Lust haben jedes Mal das Skript von Hand zu starten, können Sie entweder den Aufruf Ihres Skriptes in die Profildateien hinterlegen, oder die globalen Funktionen direkt in die Profile-Skripte hineinschreiben. Mehr zu Profil Skripten erfahren Sie im Abschnitt über Profil Skripte.

Weitere Informationen und Beispiele über Scopes finden Sie in der PowerShell-Hilfe:

Help about_Scopes

2.4.10.2.6             Parameter by Value

Über die Pipeline können, wie schon mehrfach erwähnt, Objekte übergeben werden. Hier schauen wir uns nun genauer an was dabei passiert.

Kommen Objekte über die Pipeline können diese Objekte ganz unterschiedlicher Art (String, Int, Active-Directory User, u.v.a.m.) sein.

Wenn Sie sich nun einmal die vollständige Hilfe von Start-Service (help sasv -full) anzeigen lassen, schauen Sie sich den Syntax Abschnitt einmal genauer an. Dort gibt es 3 Varianten den Befehl aufzurufen:

-Name
-Displayname
-InputObject

Dies sind die ersten Schalter, die nach dem Bezeichner des Cmdlet folgen. Name und Displayname sind beide vom Datentyp String, während InputObject vom Typ ServiceController ist.

Führen Sie beispielsweise folgende Befehlskette aus:

Get-Service winrm | Start-Service

Was glauben Sie wohl welche Variante hat PowerShell benutzt? Get-Service winrm hat sich ein Object vom Typ ServiceController des Dienstes winrm geholt. Es wird also ein ServiceController Object über die Pipe übergeben, an das Cmdlet Start-Service. Weil der übergebene Value ein ServiceController Object ist, wird also die 3. Variate mit –InputObject ausgeführt.

Warum das wichtig ist? Nun, in diesem Fall unterscheiden sich die restlichen Parameter nicht, aber schauen Sie sich z.B. einmal die Hilfe von Get-WMIObject an. Da werden Sie feststellen, dass dort teilweise ganz unterschiedliche Schalter verwendet werden können.

2.4.10.2.7             Parameter by Name

PowerShell versucht zuerst immer eine Zuordnung der Parameter by Value. Nur wenn dies nicht passt wird die zweite Variante des Zuordnens mittels des Names probiert. Beispiel:

Get-Service | Get-Process 2> $null

Nun, Get-Process, hat so rein gar nichts mit Services zu tun. Wenn Sie mit Get-Help Get-Process -full mal schauen, werden Sie sehen, dass keiner der Parameter von Get-Process ein Objekt vom Typ ServiceController annimmt (was durch Get-Service geliefert wird). Daher wird hier die Zuordnung des ServiceController Objekts mittels Parameter by Value nicht funktionieren. Dafür gibt es aber jede Menge Parameter vom Typ String. Bei den String Parametern ist nun die Frage, an welchen der Parameter die ServiceController Objekte nun zugewiesen werden sollen? Die gelieferten Get-Service Objekte besitzen eine Eigenschaft Name. Get-Process hat einen Parameter mit der Bezeichnung -Name. Da dies nun eindeutig zugewiesen werden kann, erfolg hier die Übergabe der Parameter by Name. Die Eigenschaft des Objekts muss also mit dem Parameter des nachfolgenden Cmdlets übereinstimmen und das nachfolgende Cmdlet muss auch die Zuordnung (in der Hilfe mit dem Schalter –full zu erkennen) über die Pipeline in dieser Form annehmen:

Das oben angegebene Beispiel zeigt Ihnen dann alle „Nicht“-Windows-Dienste an. Haben Sie keine, sondern nur Windows-Dienste, erfolgt einfach keine Ausgabe. Da die Windows-Dienste-Namen nicht mit den Prozessnamen übereinstimmen, liefert Get-Process für die nicht als Prozess gelisteten Namen Fehlermeldungen, die aber durch die Umleitung 2> $null unterdrückt werden. Auf den meisten PCs findet sich aber min. 1 zusätzlicher Dienst.

2.4.10.2.8             Parameterübergabe im Detail

Wenn Sie im Team programmieren, ist es gut zu wissen, was sich die Kollegen bei bestimmten Funktionen die Sie geschrieben haben, dachten. Oder auch wenn Sie als PowerShell Guru für normal sterbliche Administratoren und Anwender PowerShell Befehle schreiben, soll man gleich erkennen wie Ihre Kommandos aufgebaut sind und funktionieren. Abgesehen davon ist es auch schön, wenn die PowerShell falsche Eingaben von sich aus gleich ablehnt und Sie sich nicht selbst um fehlerhafte Ausführung Ihrer Kommandos kümmern müssen. Je genauer Sie Ihre Parameter deklarieren, umso mehr Ärger nimmt Ihnen die PowerShell durch Standardfehlermeldungen ab.

Die PowerShell in der Version 1.0 hat keinen Befehl um Webseiten einzulesen, dass .NET-Framework schon. In Version 2.0 gibt es zwar mit dem Modul BitsTransfer ganz nette Cmdlets für Webtransfers, aber dazu muss auch Server seitig Bits (Bits=Backgroud Intelligent Transfer Service) unterstützt werden. Hier soll auch einfach nur verdeutlicht werden, wie Sie ordentlich Parameter übergeben und dafür bauen Sie sich doch am besten ein Cmdlet um Webseiten in der PowerShell einzulesen.

Nach kurzer Internetrecherche dürften Sie auf die .NET-Klasse System.Net.Webclient gestoßen sein. Davon sollten Sie sich zunächst einmal ein Objekt bauen:

$Webclient=New-Object System.Net.WebClient

Ein Get-Member verrät uns, dass es hier u. a. eine Methode DownloadString gibt, welche eine klassische Webpfadangabe (URI=Uniform Ressource Indicator oftmals auch unter der Bezeichnung URL=Uniform Ressource Locator) in der Form: http://www.irgendwo.im.internet.de erwartet. Holen Sie sich doch zum Testen einfach einmal meine Internetseite, oder irgendeine Ihrer Wahl ab:

$Webclient.DownloadString(“http://www.martinlehmann.de/wp“)

Gar nicht so schwer, wenn man es erst einmal gefunden hat ;-). Jetzt wollen wir daraus einmal einen PowerShell Cmdlet namens Get-Website basteln:

Function Global:Get-Website {

 Param(

  [String]$Website

 )

 $Webclient=New-Object System.Net.WebClient

 $Webclient.DownloadString($Website)

}

Speichern Sie den Code als Skript ab und starten Sie das Skript. Danach tippen Sie:

Get-Website http://www.martinlehmann.de/wp

Vielleicht haben Sie gemerkt, dass Ihr eigenes Cmdlet bereits voll in die PowerShell integriert ist, wenn Sie versucht haben mit Tab-Vervollständigung zu arbeiten. Get-w + Tab-Taste vervollständigt nämlich schon Ihr Cmdlet und Sie brauchen nur noch die Website einzugeben, die Sie abholen möchten. Aber das ist noch nicht alles! Geben Sie doch einmal Help Get-Website ein. Dann stellen Sie fest, dass schon eine rudimentäre Hilfe zu Ihrem Cmdlet angegeben wird. Man erhält die Information, dass es einen Schalter –Website gibt und dieser einen String erwartet. Das liegt an Ihrer Parameter Definition. Der Name der Variablen wird automatisch zum Schalter und weil Sie die Variable gleich als String deklariert haben, wird auch diese Information in der Hilfe angezeigt. Geben Sie doch einmal nur Get-Website ohne Parameter ein. Das führt zu einer unschönen Fehlermeldung. Es gibt verschiedene Möglichkeiten diesem Problem zu begegnen.

Erstens: Sie weisen einen Standardwert zu, der verwendet wird, wenn kein Parameter übergeben wird. Dazu tauschen Sie einfach im Parameterblock, die Zeile mit der Variablen wie folgt aus:

  [String]$Website=“http://www.martinlehmann.de/wp“

Speichern Sie das Skript und starten Sie es erneut.

Wird nun nur das Cmdlet eingetippt, wird eben immer meine Website aufgerufen. Gibt man jedoch eine Internetseite vor, hat dies Vorrang vor dem was im Parameterblock zugewiesen wird. Auf diese Weise lassen sich also Standardwerte zuweisen.

Zweitens: Fordern Sie den Benutzer auf eine Eingabe zu machen. Auch hier tauschen Sie bitte einfach wieder die Variablendefinition im Parameterblock aus:

  [String]$Website=(Read-Host -Prompt "Bitte geben Sie eine Internetadresse ein")

Speichern Sie die Änderung, führen Sie das Skript erneut aus und versuchen Sie Ihr Cmdlet ohne Angabe der URI zu starten. Wenn Sie mögen können Sie dem Benutzer auch bei Beispiel mit angeben.

…und zum Dritten mit folgendem Parameterblock:

Param(

  [Parameter(Mandatory=$true)]

  [String]$Website

 )

Durch die zusätzliche Parameter-Beschreibung Mandatory=$true weiß die PowerShell, dass die nachfolgende Variable eingegeben werden muss. Fehlt die Angabe, fordert PowerShell mit einen Standardtext zur Eingabe des fehlenden Parameters auf. Das ist nicht so Informativ, wie die Sache mit Read-Host, dafür aber sprachneutral.

Für den [Parameter()] Abschnitt können noch mehr Angaben gemacht werden, wie z.B.:

 [Parameter(ValueFromPipeline=$true,Position=0,HelpMessage=“HilfeInfo“)]

ValueFromPipeline sagt aus, ob Eingaben von einer Pipeline an diese Variable übergeben werden können. In unserem Beispiel also z.B. so:

“http://www.martinlehmann.de/wp“ | Get-Website

Position ist bei nur einem einzelnen Parameter nicht wirklich spannend, aber bei mehreren Parametern können Sie angeben in welcher Reihenfolge die Angaben an die jeweiligen Variablen geschickt werden.

HelpMessage kann einen zusätzlichen Infotext anzeigen, wenn der Benutzer den Parameter nicht angegeben hat. Wenn der Parameter eingegeben werden muss (Mandatory=$true) erscheint folgende Meldung in der PowerShell:

Cmdlet Get-Website an der Befehlspipelineposition 1
Geben Sie Werte für die folgenden Parameter an:
(Geben Sie !? ein, um Hilfe zu erhalten.)

Wenn der Benutzer dann !? und Enter tippt, bekommt er den Text aus HelpMessage angezeigt.

Wie Sie wissen, müssen Parameter nur so weit eingetippt werden bis sie eindeutig sind. Vielleicht möchten Sie aber auch einen speziellen Kurznamen für einen Schalter definieren. Wenn Sie z.B. einen Schalter Namens –Benutzername haben, könnten Sie eine Kurzform wie –BN einbauen:

Param(

    [Parameter(Mandatory=$true)]

    [Alias("BN")]

    [String]

    $BenutzerName

)

Wichtig dabei ist zu erkennen, dass die Aliasdefinition in einer eigenen Zeile steht und nicht in Parameterzeile, wie die vorangegangenen Beispiele. Noch mehr Möglichkeiten für die Parameterdefinition finden Sie in help about_Functions_Advanced_Parameters.

Hier noch eine kurze Beschreibung weiterer Möglichkeiten:

[AllowNull()]

Erlaubt explizit, dass kein Wert übergeben wird.

[ValidateNotNull()]

Verbietet explizit, dass kein Wert übergeben wird.

[AllowEmptyString()]

Läßt explizit die Übergabe einer leeren Zeichenkette zu.

[AllowEmptyCollection()]

Läßt explizit die Übergabe eines Arrays ohne Objekte zu.

[ValidateNotNullOrEmpty()]

Es darf weder eine leere Zeichenkette, noch gar nichts übergeben werden. Es muss also auf jeden Fall irgendein Wert übergeben werden.

[ValidateCount(x,y)]

Legt fest, dass min. x, aber max. y Werte übergeben werden.

[ValidateLength(x,y)]

Legt fest, dass der übergebene Wert min. x, aber max. y Zeichen lang sein darf.

[ValidatePattern(RegulärerAusdruck)]

Mithilfe regulärer Ausdrücke festlegen, was übergeben werden darf.

[ValidateRange(x,y)]

Übergebene Zahlen dürfen nur im Bereich zwischen x und y liegen.

[ValidateSet(x,y,z,…)]

Funktioniert ähnlich wie ein Schalter, nur die Werte x,y,z,usw. werden akzeptiert.

ValidateScript({})]

Erfordert die Übergabe eines Scripts. Nur wenn das Script erfolgreich abläuft, wird die Funktion ausgeführt.

 

2.4.10.2.9             Ergänzen der allgemeinen Schalter (Common Parameters)

Standardmäßig unterstützt Ihr Script keine CommonParameters (also ‑Verbose, ‑Debug, ‑ErrorAction usw.). Um diese ebenfalls bereit zu stellen, können Sie ganz einfach den Begriff [CmdletBinding()] Ihrer Funktion hinzufügen. Die Funktionalität wird automatisch über PowerShell bereitgestellt.

Das können Sie aber ganz einfach implementieren:

 

Function Name {

 [CmdletBinding()]

 Param (

  $VariableFürWert 1,

  $VariableFürWert 2,

  usw.

 )

 Ihr ScriptCode

}

Setzen Sie dann in Ihrem Skript beispielsweise die Zeile:

Write-Verbose "Detail Infos"

…würde den Text "Detail Infos" nur dann ausgeben, wenn die Funktion mit dem Schalter ‑Verbose aufgerufen wird. Ähnlich verhält sich das Cmdlet Write-Debug, wenn die Funktion mit dem Schalter –Debug aufgerufen wird. Allerdings wird das Skript an dieser Stelle auch angehalten und erst der Benutzer gefragt, ob es weiter gehen soll.

Um –Confirm oder –WhatIf zu unterstützen müssen Sie zusätzlich in [CmdletBinding()] SupportsShouldProcess=$true einsetzen:

[CmdletBinding(SupportsShouldProcess=$true)]

Des Weiteren müssen Sie eine Wenn-Dann-Abfragen mit Hilfe der $PSCmdlet Variable in Ihr Script einbauen:

If ($PSCmdlet.ShouldProcess(”Deleting File $DateiDieDurchIhrScriptGelöschtWird”)) {remove $DateiDieDurchIhrScriptGelöschtWird}

Würde das Script mit dem Schalter –WhatIf ausgeführt, wird die Löschanforderung in der geschweiften Klammer nicht ausgeführt, sondern nur der Text:

WhatIf: Ausführen des Vorgangs NameIhrerFunktion für das Ziel "Deleting File DateiDieDurchIhrScriptGelöschtWird".

angezeigt. Bei –Confirm würde der Benutzer gefragt werden, ob die Aktion in den geschweiften Klammern ausgeführt werden soll.

2.4.10.2.10        Praxisbeispiel: Arrays miteinander vergleichen

Wie im Abschnitt Besondere Variablentypen:Array versprochen, wollen wir hier nun dem Problem Arrayvergleich auf den Grund gehen.

Tippen Sie doch einmal:

1 –eq 1

ein. Da sagt die PowerShell True. Bei

2 –eq 1

sagt Sie False. So soll das sein! Dann bauen Sie sich doch einmal kurz folgende 3 Arrays:

$master="Eins","Zwei","Drei"

$vergleich1=$master

$vergleich2="Ein","Zwo","Drei"

$master soll Ihnen hier als Vergleichsvorlage dienen. $vergleich1 bekommt denselben Inhalt wie $master. $vergleich2 stimmt nur im letzten Element mit $master überein, die ersten beiden unterscheiden sich.

Nun versuchen Sie einmal zu vergleichen:

$master -eq $vergleich1

$master -eq $vergleich2

Was sagt die PowerShell dazu? Gar nichts! Lassen Sie uns eine Funktion dafür bauen:

10.        Function Global:Compare-Array {

11.         Param(

12.          [Array]$Array1,

13.          [Array]$Array2

14.         )

15.         If ($Array1.count –eq $Array2.count) {

16.          $Gleich=$true

17.          For ($Cnt=0;$Cnt –lt $Array1.count;$Cnt++) {

18.           If ($Array1[$Cnt] –ne $Array2[$Cnt]) {$Gleich=$false}

19.          }

20.          If ($Gleich) {$true} else {$false}

21.         } else {

22.          $false

23.         }

24.        }

Der Param Block sorgt dafür, dass Sie genau zwei Arrays übergeben bekommen. Danach folgt in Zeile 15 zunächst einmal ein Vergleich der Anzahl der Elemente in den beiden Arrays. Wenn Sie unterschiedlich viele Elemente haben, können Sie schon einmal nicht gleich sein, dementsprechend wird in 22. Zeile nach der else Anweisung einfach nur $false zurückgegeben.  In Zeile 16 gehen wir zunächst einmal davon aus, dass alles passt und setzen die Variable $Gleich auf $true. Dann folgt in Zeile 17 eine Schleife mit einem Zähler über alle Elemente. Bei 3 Elementen wird also innerhalb der For Schleife in der Variablen $Cnt von 0 bis 2 gezählt. In Zeile 18 liegt die Magie. Hier vergleichen Sie das jeweilige Element aus $Array1 mit dem dazugehörigen Element aus $Array2. Wenn das bei einem der Schleifen Durchläufe nicht passt wird die Variable $Gleich auf $false gesetzt. Nachdem alle Schleifendurchläufe abgearbeitet sind und somit alle Elemente miteinander verglichen, testen Sie in Zeile 20, ob $Gleich noch $true ist. Wenn ja, wird $true zurück geliefert, wenn nein muss min. ein Eintrag die Variable $Gleich geändert haben und somit wird $false zurück geliefert.

Compare-Array $master $vergleich1

Liefert wie erwartet True.

Compare-Array $master $vergleich2

Liefert wie erwartet False.

Compare-Array $master "eins","zwei"

Liefert ebenfalls False, weil schon die Anzahl der Elemente nicht stimmt.

Keine Ahnung warum es nicht standardmäßig so ein Cmdlet gibt. Aber so habe ich auch etwas zu berichten ;-). Und es stellt noch eine tolle Zusammenfassung des bisher Erlernten dar. Die Funktion ist bei weitem noch nicht perfekt. So könnten Sie z.B. die Arrays erst einmal sortieren lassen. Oder sobald ein $false beim Vergleich der einzelnen Elemente auftritt, könnten Sie sich den Rest der Schleife sparen und die Schleife vorzeitig abbrechen, was die Funktion auch gleich noch schneller macht. Na, wissen Sie noch wie das geht?

2.4.11                     Profil Skripte

Hier erfahren Sie, wie Sie Ihre PowerShell Konsole dauerhaft anpassen können und auch um eigene Befehle erweitern können.

In ein Profil Skript können Sie all das hineinschreiben, was Sie auch in andere Skripte hineinschreiben, oder direkt an der Shell als Befehl absetzen. Besonders gut geeignet sind bestimmte Variablen- und Aliasdefinition die nicht standardmäßig vorhanden sind, die Sie aber gerne immer in Ihrer Kommandozeile zur Verfügung hätten. Aber nicht nur dass, sondern auch ganze Funktionen (eigene PowerShell Cmdlets) können Sie in einem Profil Skript verewigen.

Ein System weit gültiges Profil Skript muss im Verzeichnis C:\Windows\System32\WindowsPowerShell\v1.0 liegen und den Namen Profile.ps1 tragen.

Dieses Skript wird automatisch jedes Mal ausgeführt, wenn Sie einen PowerShell Prozess starten.

Hinweis: Wenn Sie Funktionen in Profil Skripten verwenden, entfällt die Anweisung Global:, da PowerShell davon ausgeht, dass hier stehende Funktionen auf jeden Fall als Befehl auf der Konsole zur Verfügung stehen sollen. Wenn Sie möchten, dass ein Funktion nicht von der Konsole als Befehl genutzt werden kann müssen Sie statt des Global: wie in normalen Skripten, die Bezeichnung Script: vor den Funktionsnamen schreiben.

2.4.11.1                Ablageort bei verschiedenen Betriebssystem-Versionen

Statt für den gesamten PC ein Profil Skript anzulegen, können Sie das auch gerne für bestimmte Benutzer tun. Also persönliche Profil Skripte sind auch machbar. Wenn Sie mögen können Sie auch beide Varianten kombinieren. Wobei immer zuerst das Systemweite Profil Skript ausgeführt wird und danach das Skript im Benutzerprofil. Sind in beiden widersprüchliche Angaben vorhanden, gelten immer die des Benutzers, da es zeitlich danach ausgeführt wird.

Bei XP und Windows Server 2003 muss das persönliche Profile.ps1 Skript unter:

C:\Dokumente und Einstellungen\Name des Benutzers\Eigene Dateien\WindowsPowerShell

liegen. Bei Vista, Server 2008 und neueren Betriebssystemen unter:

C:\Benutzer\Name des Benutzers\Dokumente\WindowsPowerShell

In der automatischen Variable $Profile sind die Pfade hinterlegt. Mit

$Profile | Select *

können Sie sich alle Pfade anzeigen lassen. Theoretisch könnten Sie die auch verändern, aber davon würde ich abraten. Das führt nur zu Verwirrungen.

Wenn Sie statt Profile.ps1 den Dateinamen Microsoft.PowerShellISE_profile.ps1 verwenden wir das Profil nur in der ISE ausgeführt.

Auf Linux Systemen ist der Pfad für alle Benutzer:

$PsHome\Profile.ps1

und für einen selbst:

$Home\My Documents\PowerShell\profile.ps1

2.4.11.2                .NET-Objekte dauerhaft erweitern

In den vorangegangenen Kapiteln haben Sie viel über Objekte und Skripting gelernt. Wenn Sie gerne dauerhaft Änderungen an .NET-Objekten machen möchten, sodass diese Ihnen beim Start jeder PowerShell zur Verfügung stehen, müssen Sie das etwas umständlich mithilfe der Profile-Skripte in XML-Dateien verpackt durchführen.

2.4.11.2.1             Eigenschaften .NET Objekten dauerhaft hinzufügen

Hatten Sie schon einmal das Problem, dass ein Prozess sich nicht beenden läßt? Nun, wenn Sie einen störrischen Prozess haben, nieten Sie doch die Eltern um. Das macht den Prozess so traurig, dass auch er Selbstmord begeht. Klingt sehr barbarisch, aber funktioniert. ;-)

Dazu muss man die Eltern aber erst einmal ausfindig machen und dabei ist Get-Process leider nicht besonders hilfreich. Doch dass können Sie ändern!

Erstellen Sie eine Datei namens ppid.ps1xml mit folgendem Inhalt:

10.        <Types>

11.         <Type>

12.          <Name>System.Diagnostics.Process</Name>

13.          <Members>

14.           <ScriptProperty>

15.            <Name>ppid</Name>

16.            <GetScriptBlock>

17.             (Get-WmiObject win32_process | Where-Object {$_.processid –eq $this.id} | select parentprocessid).parentprocessid

18.            </GetScriptBlock>

19.           </ScriptProperty>

20.          </Members>

21.         </Type>

22.        </Types>

Wegen der ordentlichen Darstellung beginne ich hier bei 10. zu zählen. Die Nummer, wie üblich, bitte nicht mit abtippen, da Sie nur zur Orientierung dienen sollen. Alle nicht hervorgehobenen Zeilen müssen immer so aussehen. Spannend wird es ab Zeile 12. Hier steht System.Diagnostic.Process. Wo bekommen Sie diese Information her?  Nun, das ist die Objektklasse an der wir rumschrauben wollen. Wenn Sie Get-Process | Get-Member ausführen, sehen Sie ganz oben den TypeName: System.Diagnostic.Process und bei der Gelegenheit können Sie auch gleich mal alle Properties nach PPID absuchen. Gibt’s nicht, richtig?

In Zeile 14 sagen Sie durch ScriptProperty aus, dass Sie hier eine Eigenschaft (keine Methode) hinzufügen möchten.

Mit Zeile 15 legen Sie den Namen für Ihre „selbst gestrickte“ Eigenschaft fest.

Zeile 16 ist spezifisch für Properties GetScriptBlock anzugeben.

In Zeile 17 liegt Ihr PowerShell Skript Code, der mithilfe von WMI die ParentProcessID (den Elternprozess) ausfindig macht. Wie das mit dem WMI grundsätzlich funktioniert, ist im Abschnitt WMI Objekte erklärt. Die Besonderheit hier, liegt in der Variablen $this bzw. $this.id. Wenn Sie Get‑Process ausführen, bekommen Sie einen Array von Prozess-Objekten. Jedes dieser Objekte landet bei dieser XML-Erweiterung zur Laufzeit in der Variablen $this. Das funktioniert also so ähnlich wie $_ mit der Pipe. Wenn Sie noch einmal bei Get-Process | Get-Member in Ausgabe reinschauen, werden Sie feststellen, dass es da eine Eigenschaft namens id gibt. Wenn Sie nach einer Pipe auf die Eigenschaft z.B. bei Verwendung von Where-Object auf diese Eigenschaft zugreifen wollten, würden Sie $_.id schreiben. Ersetzen Sie also hier an dieser Stelle einfach $_.id durch this.id. Damit haben Sie beim späteren Aufruf von Get-Process die Prozess Nummer. Sie suchen hier einfach in allen laufenden Prozessen mittels WMI Abfrage nach dem Prozess mit der Prozessnummer des jeweiligen Objekts. Das WMI-Objekt Win32_Process liefert im Gegensatz zu Get-Process aber nicht nur die id als Eigenschaft, sondern auch den parentprocessid (den Elternprozess). Die picken Sie sich nun mit select aus den ganzen Eigenschaften heraus und hinterlegen das Ergebnis automatisch in der Eigenschaft namens ppid (aus Zeile 15).

Die restlichen Zeilen schließen nur noch die zuvor geöffneten Tags (Markierungen im XML Dokument).

Jetzt müssen Sie der PowerShell nur noch verklickern, dass Sie diese Datei benutzen soll. Das ist nun wieder etwas einfacher:

update-typedata ppid.ps1xml

bzw. syntaktisch, wenn die ppid.ps1xml nicht im aktuellen Verzeichnis liegt:

update-typedata Laufwerk:\Pfad zur Datei\ppid.ps1xml

Wenn Sie nach Eingabe von update-typedata (vorausgesetzt der Befehl wurde ohne Fehlermeldung ausgeführt. Wenn nicht, suchen Sie nach Tippfehlern in Ihrer ppid.ps1xml) nun Get-Process | Get-Member aufrufen werden Sie Ihre neue Eigenschaft finden. Die können Sie nun wie jede andere Eigenschaft auch z.B. mit Select oder Where-Object auswerten.

Haben Sie schon eine Idee, wie Sie das nun permanent hinterlegen? Schreiben Sie die Anweisung update-typedata einfach in eines Ihrer Profile-Skripte! Schon habe Sie bei jeder PowerShell Konsole die Sie öffnen in Ihrem Get-Process eine echt coole neue Eigenschaft.

Durch Komma getrennt können Sie auch mehrere Erweitungsdateien auf einmal angeben, wie z.B. die Datei aus dem nachfolgenden Beispiel mit der Methoden Erweiterung.

2.4.11.2.2             Methode .NET Objekten dauerhaft hinzufügen

Um eine Text basierte Datei z.B. mit notepad zu öffnen ist viel Tipparbeit nötig. Z.B.:

ls TextDatei.txt | foreach {notepad $_.fullname}

Trotzdem es nur eine einzelne Datei ist, welche über die Pipe kommt, müssen Sie, damit es funktioniert, ein foreach drum herum wickeln. Wenn Sie mit Get-Member mal schauen, werden Sie Methoden wie Open, OpenRead oder gar OpenText finden. Nur leider klappen die irgendwie nicht, zumindest nicht mit der gewünschten Anwendung notepad. Wenn Sie nun bestimmt Dinge mit Dateien machen möchten, wie in diesem einfachen Fall „mit Notepad öffnen“, dann wäre es doch schön eine Methode zu haben, die man einfach angibt und schon passiert der Rest wie von Geisterhand.

Dazu legen Sie sich zunächst eine Datei namens openedit.ps1xml mit folgendem Inhalt an:

10.        <Types>

11.         <Type>

12.          <Name>System.IO.FileInfo</Name>

13.          <Members>

14.           <ScriptMethod>

15.            <Name>OpenEdit</Name>

16.            <Script>

17.             if ($this -match "\.(log|txt|dat|reg|csv|ps1|bat|cmd)$"){

18.              Notepad.exe $this.Fullname

19.             } else {

20.              write-host "Sorry, aber das Dateiformat von $this wird nicht unterstützt!`nNur: log, txt, dat, reg, ps1, bat, cmd oder csv" -foregroundcolor red

21.             }

22.            </Script>

23.           </ScriptMethod>     

24.          </Members>

25.         </Type>

26.        </Types>

Der grundlegende Aufbau ist wieder ähnlich wie bei der Ergänzung einer Eigenschaft.

Zeile 12 kommt dieses Mal von der Objektklasse für Dateien. Wissen Sie noch wo Sie die Info herbekommen (schauen Sie ggf. im vorangegangenen Abschnitt nach)?

In Zeile 14 steht nun ScriptMethod statt ScriptProperty und in Zeile 16 einfach nur Script statt GetScriptBlock.

Zeile 17-20 enthält die Funktion, die angewandt werden soll. Hier ist nun eine kleine Fehlerprüfung eingebaut, damit die Zeile 18 nur dann ausgeführt wird, wenn die Datei mit einer der in Zeile 17 angegebenen Dateinamenerweiterungen endet. Damit man die Methode nicht versehentlich auf Binärdateien oder Word-Dokumente anwedet. Das könnte hässlich werden. Zeile 18 startet nun Notepad mit der entsprechenden Datei. Die Eigenschaft Fullname der Datei enthält den kompletten Pfad zur Datei. Falls die Dateinamenerweiterung nicht passt, wird die Fehlermeldung in Zeile 20 ausgegeben.

Zeile 21 schließt die if-else Anweisung und gehört daher noch zu Ihrem ausführbaren PowerShell Code.

Der Rest schließt wieder nur die offenen Tags.

Jetzt noch ein update-typedata openedit.ps1xml und Ihre Dateiobjekte haben eine neue Methode namens Openedit.

Nun können Sie ganz einfach:

(ls TextDatei.txt).openedit()

schreiben und schon öffnet sich die Textdatei.txt mit Notepad.

Dauerhaft? Packen Sie den update-typedata in eines Ihrer Profile-Skripte. Gerne auch zusammen mit der Eigenschaftsänderung z.B. so:

update-typedata ppid.ps1xml,openedit.ps1xml

Selbstverständlich können Sie auch mehrere Erweiterungen in einer einzelnen Datei zusammenfassen.

2.4.11.3                Dot-Sourcing, Skripte in andere Skripte einbinden

Profil Skripte können schnell unübersichtlich werden. Ein weiteres Problem stellt die Aktualisierung dar. Wenn alle PCs der Firma, oder zumindest mehrere Maschinen dieselben Standardvorgaben haben sollen wäre es ziemlich aufwändig auf allen PCs die Profil Skripte zu aktualisieren. Sie haben die Möglichkeit von einem Skript ein anderes Skript aufzurufen. Nachdem Sie den Abschnitt über Scopes durchgearbeitet haben, liegt das Problem aber auf der Hand. Die Funktionen (z.B. eigene PowerShell Cmdlets) von anderen Skripts sind im aufrufenden Skript unbekannt. Hier hilft die Methode des Dot-Sourcings. Hört sich kompliziert an, ist aber ganz einfach. Dazu schreiben Sie lediglich noch einen Punkt und ein Leerzeichen vor den Aufruf des einzubindenden Skripts. Ein ordentlich, aufgeräumtes Profil Skript könnte z.B. so aussehen:

# Mein persönliches Profile-Skript

# Meine Alias-Definitionen

. ./MeineAliase.ps1

# Meine Active-Directory Cmdlets

. ./ADCmdlets.ps1

# Meine WMI-basierten Cmdlets

. ./WMICmdlets.ps1

# Ein paar weitere allgemeine Einstellungen

Um ein zentrales Skript-Repository zu erstellen, könnten Sie auf einem Dateiserver eine Freigabe anlegen und dort Ihre gesamten Skripte hinterlegen. Um nun von PCs aus einfach den zentralen Speicher mit den Skripten einzubauen, brauchen Sie nur eine Zeile in die Profil Skripte Ihrer Clients zu schreiben:

Get-ChildItem \\Servername\Freigabename -Filter *.ps1 -Recurse | Foreach {. $_.FullName}

Somit brauchen Sie nicht mehr alle Stationen von Hand zu aktualisieren. Sobald ein Skript sauber funktioniert, kopieren Sie es in die Freigabe und alle Clients bauen es automatisch ein. Dank des Filters und des Recurse können Sie auch gerne Unterverzeichnisse anlegen, die auch in der Freigabe für Übersicht sorgen. Das nicht jeder dahergelaufene Schreibzugriff auf die Freigabe haben sollte, muss ich wohl nicht extra erwähnen, oder doch?

Übungsaufgabe: Erstellen Sie ein Skript, um die Festplatte nach bestimmten Dateien und deren Inhalt zu durchsuchen. Das Suchskript soll 3 Parameter verarbeiten:

1.       Von welchem Verzeichnis aus soll im Dateisystem gesucht werden (z.B.: c:\Users)?

2.       Suchmuster für den Dateinamen (z.B.: *.ps1).

3.       Suchbegriff nach dem die Dateien durchforstet werden (z.B.: Foreach)

Syntax: Suchskript.ps1 Verzeichnis Dateinamen Inhalt

Einen Lösungsansatz für diese primitive Variante finden Sie im Buchteil Praxisbeispiele im Kapitel Filesystem und dort im Abschnitt Skript um Festplatte nach bestimmten Dateien und deren Inhalt zu durchsuchen.

Zusatzaufgabe 1: Noch besser wäre, wenn das Skript nicht direkt die Aufgabe ausführt, sondern eine Funktion bereit stellt, die wie ein Cmdlet genutzt werden kann.

Zusatzaufgabe 2: Wenn Sie es perfekt machen möchten, rufen Sie das Suchskript durch das Profile-Skript auf, sodass Ihnen auf jeder PowerShell Konsole die Sucherweiterung zur Verfügung steht.

2.4.12                     Skripttuning

Wenn Ihre Skripte größer werden, kann es schon einmal vorkommen, dass der Ablauf zu lange dauert. In diesem Abschnitt geht es darum Ihre Skripte zu beschleunigen und Speicher zu sparen.

2.4.12.1                Wie schnell ist Ihr Skript?

Um festzustellen, ob Ihre Tuningmaßnahmen erfolgreich waren, müssen Sie wissen wie lange Ihr Skript zur Ausführung benötigt hat. Das lässt sich mittels des Cmdlet Measure-Command ganz schnell überprüfen:

Measure-Command {IhrSkript.ps1 oder auch gerne eine Befehlskette}

Da es zu verschiedenen Ausführungszeitpunkten zu unterschiedlichen Messergebnissen kommt, sollten Sie vielleicht mehrere Messungen durchführen und darüber einen Mittelwert bilden:

$Messung1=For ($i=1;$i -le 10;$i++) {Measure-Command {IhrSkript.ps1}}

$Messung1 | Measure-Object –Property TotalMilliseconds –Average

Natürlich können Sie auch über Sekunden oder Minuten den Mittelwert bilden. Von Stunden oder gar Tagen wollen wir hier einmal nicht ausgehen ;-).

2.4.12.2                Alias oder Cmdlet?

Bei meinen eigenen Skripten habe ich festgestellt, dass man nicht grundsätzlich sagen kann, dass die Verwendung eines Alias oder eine Cmdlets schneller abgearbeitet wird. Im Abschnitt über gutes Skripting habe ich geschrieben, dass Sie Cmdlets bevorzugen sollen und reichlich Kommentieren, was Sie da zusammen geschrieben haben. Da Aliase manchmal schneller funktionieren, als Cmdlets muss ich mich in Sachen Verarbeitungsgeschwindigkeit von dieser Aussage leider wieder distanzieren. Probieren Sie Aliase und messen Sie nach! Auch Kommentarzeilen verlangsamen ein Skript spürbar. Eigentlich sollte man meinen dass Kommentarzeilen oder Aliase keinen oder zumindest keinen spürbaren Einfluss auf die Verarbeitungsgeschwindigkeit haben, aber wenn Sie nachmessen, werden Sie erstaunt sein, wie viel das ausmachen kann. Insbesondere wenn das innerhalb von Schleifen geschieht.

2.4.12.3                Foreach vs. ForEach-Object

Siehe Beschreibung ForEach-Object Cmdlet.

2.4.12.4                Where-Object

Where-Object oder kurz ? ist ein Allheilmittel, das immer und überall funktioniert. Vor allem können Sie damit jede X-beliebige Eigenschaft und deren Kombinationen abfragen. Der Nachteil ist allerdings, dass es jedes Mal interpretiert werden muss und das dauert im Vergleich recht lange. Vergleich zu was? Nun, wenn Sie bei einem Cmdlet einen Parameter wie –Filter, -Exclude oder –Include haben, dann sollten Sie der Parameter Variante gegenüber dem Where-Object Cmdlet den Vorzug geben. Um einmal den Unterschied zu verdeutlichen, lassen Sie sich doch einmal alle Textdateien vom Verzeichnis C:\Windows auflisten:

Measure-Command {ls c:\windows -Filter *.txt -Recurse 2> $null}

Measure-Command {ls c:\windows -Recurse 2> $null | ? {$_.name -like "*.txt"}}

Das macht einen ganz schönen Unterschied aus, was? Um Fehlermeldungen für Bereiche auf die der Zugriff verweigert ist zu unterdrücken, wurden diese mittels 2> $null ins Nirwana geschickt.

2.4.12.5                Piping

2.4.12.5.1             Funktionen

Funktionen lassen sich noch etwas beschleunigen. Innerhalb einer Funktion können Sie mit Unterteilungen arbeiten. Werden beispielsweise von einer Pipeline mehrere Werte an Ihre Funktion geliefert, können Sie Ihrer Funktion klar machen, was nur ein einziges Mal zu Beginn der Verarbeitung durchgeführt werden soll. Das schreiben Sie in den Begin {} Abschnitt. Was für jedes Element durchlaufen werden soll schreiben Sie in den Process {} Abschnitt. Was zum Schluss, nach dem letzten Element geschehen soll, schreiben Sie in den End {} Abschnitt:

10  Function NamederFunktion {

11   Begin {

12   Hier könnten Sie z. B. den aktuellen Verzeichnispfad, oder andere Ausgangsbedingungen in eine Variable retten bevor Sie zu wüten anfangen.

13   }

14   Process {

15    Hier findet die eigentlich Verarbeitung statt. Möglicher Weise benutzen Sie cd um Verzeichnisse zu wechseln etc...

16   }

17   End {

18    Wechseln Sie wieder in das ursprüngliche Verzeichnis zurück und geben Sie Variablen frei und führen Sie weitere "Aufräumvorgänge" durch.

19   }

20  }

Als praktisches Beispiel greifen wir einmal auf die Währungskurse der Europäischen Zentralbank zu. Schreiben Sie dazu folgendes langsames Skript:

10  Function Global:Get-Wechselkurs {

11   Param(

12    [Parameter(Mandatory=$true,ValueFromPipeline=$true)]

13    [Double[]]

14    $Betrag,

15    [String]

16    $Waehrung="USD"

17   )

18  # Für jede zu berechnende Zahl den aktuellen Wechselkurs bestimmen

19   $Xml=New-Object xml

20   $Xml.Load('http://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml')

21   $Wechselkurse=$Xml.Envelope.Cube.Cube.Cube

22   $Wert=$Wechelkurse | ? {$_.Currency -eq $Waehrung} | select -expandproperty rate

23  # Betrag ausrechnen

24   $Betrag*$Wert

25  }

Starten Sie das Skript und messen Sie die Zeit, wie lange es dauert für 100 Zahlen den US-Dollar-Wert (mit dem Schalter -Waehrung können Sie auch andere Währungen angeben) zu bestimmen:

Measure-Command {1..100 | Get-Wechselkurs}

Bei der Funktion wird für jede von der Pipeline übergebene Zahl auf das Internet zugegriffen und der aktuelle Wechselkurs abgeholt. Ich hoffe Sie haben nicht gerade eine Mobil oder ISDN Verbindung, dann wird das sehr deutlich. Mit dem folgenden Skript legen Sie fest, dass zu Beginn der Wechselkurs abgeholt wird und dann nur noch die Berechnungen durchgeführt werden:

10  Function Global:Get-Wechselkurs {

11   Param(

12    [Parameter(Mandatory=$true,ValueFromPipeline=$true)]

13    [Double[]]

14    $Betrag,

15    [String]

16    $Waehrung="USD"

17   )

18   Begin {

19  # Einmalig zu Beginn aktuellen Wechselkurs bestimmen

20    $Xml=New-Object xml

21    $Xml.Load('http://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml')

22    $Wechselkurse=$Xml.Envelope.Cube.Cube.Cube

23    $Wert=$Wechelkurse | ? {$_.Currency -like $Waehrung} | select -expandproperty rate

24   }

25   Process {

26  # Betrag ausrechnen

27    $Betrag*$Wert

28   }

29  }

Speichern Sie das Skript und rufen Sie es auf, danach messen Sie zum Vergleich wieder die Verarbeitungszeit:

Measure-Command {1..100 | Get-Wechselkurs}

Der Begin Abschnitt zur Beschaffung des Wechselkurses wird nur ein einziges Mal vor der Berechnung der ersten Zahl ausgeführt. Nur der Process Abschnitt wird für die Zahlen 1 bis 100 durchlaufen. So wie in diesem Beispiel der End Abschnitt entfällt, kann natürlich auch der Begin Abschnitt weggelassen werden.

Achtung ! Das der Process-Abschnitt für jeden Wert automatisch durchlaufen wird klappt nur, wenn die Werte über die Pipeline kommen. Gibt man mehrere Werte hinter dem Schalter –Betrag an, kann das bei mehrzeiligen Process-Blöcken schief gehen. In dem Fall muss man einfach eine Foreach-Schleife (bzw. Foreach-Object – siehe Abschnitt Foreach-Object) in den Process-Block einfügen wie in diesem Beispiel:

10  Function Global:Get-Wechselkurs {

11   Param(

12    [Parameter(Mandatory=$true,ValueFromPipeline=$true)]

13    [Double[]]

14    $Betrag,

15    [String]

16    $Waehrung="USD"

17   )

18   Begin {

19  # Einmalig zu Beginn aktuellen Wechselkurs bestimmen

20    $Xml=New-Object xml

21    $Xml.Load('http://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml')

22    $Wechselkurse=$Xml.Envelope.Cube.Cube.Cube

23    $Wert=$Wechselkurse | ? {$_.Currency -like $Waehrung} | select -expandproperty rate

24   }

25   Process {

26  # Betrag ausrechnen

27    $Betrag | Foreach-Object {

28     $_*$Wert

29    }

30   }

31  }

2.4.12.5.2             ForEach-Object

Ähnlich den Funktionen, kann man auch bei ForEach-Object mit Begin, Process und End arbeiten, die Syntax sieht aber etwas anders aus. Beispiel:

ls | foreach-object -Begin { "Nur am Anfang" } `
-Process { $_ | ? {$_.name -like "*.txt"} } `
-End { "Zum Schluß" }

Leider ist eine Kleinigkeit nicht offensichtlich. Eigentlich sollte im Process Bereich $_ natürlich das jeweilige Objekt enthalten. Das bedeutet, eigentlich sollte es auch ohne den roten Teil funktionieren. Sie können es ja gerne mal ohne das rote ausprobieren, es wird aber den Process Teil einfach komplett ignorieren. Schreiben Sie hingegen im Process Teil einfach nur $_.name (ohne das Where-Object = ?), klappt die Ausgabe des Dateinamens. Das gehört wohl in die Ecke „Mysterien der PowerShell“ ;-).

2.4.12.6                Speicher Probleme eindämmen

Je größer Ihre Skripte werden um so Speicherintensiver können diese werden. Insbesondere wenn viele Variablen deklariert werden.

Zunächst einmal sei hier noch einmal auf die Möglichkeit verwiesen, Variablen Speicher mittels Remove-Variable explizit freizugeben wie im Abschnitt Grundlagen der Variablenverwaltung beschrieben. Dies gibt den Speicher jedoch nicht sofort für andere Anwendungen frei. Eigentlich passsiert dabei am vom Prozess belegten Speicher erst einmal gar nichts. Der Speicher wird für zukünftige Speicheranfragen Ihres Skripts erst einmal vorgehalten um dann intern neu zugeordnet zu werden. Ein „Recycling“ Ihrer Variablen hat einen ähnlichen Effekt. Wenn Sie also zum zählen immer wieder das beliebte $i verwenden, wird auch der Speicher (den das vorangegangene $i verwendet hat) immer wieder automatisch freigegeben bzw. neu belegt. Wenn $i vorher von einem kompletten .NET-Objekt mit 100MB belegt wurde und Sie verwenden anschließend $i als Zähler, wird es automatisch zum 32-Bit Integer. Der restliche nicht mehr benötigte Speicher wird automatisch (für den Prozess – aber nicht das System!) freigegeben. Auch wenn Sie eine Funktion aufrufen, werden beim Beenden der Funktion der Speicher von allen innerhalb der Funktion genutzten Variablen (außer natürlich denen, die Sie durch einen Scope-Bezeichner sowieso außerhalb gesetzt haben) wieder dem Prozess zur Verfügung gestellt.

Sie sehen, dass sich PowerShell eigentlich schon ganz gut um die Speicherverwaltung kümmert. Trotzdem kann es sein, dass Sie Speicher von Ihrem Prozess ganz gerne wieder dem System zurückgeben möchten. Dafür hat .NET auch eine Müllabfuhr, den sogenannten Garbage-Collector (zu Deutsch: Müllsammler). Den können Sie so direkt aufrufen:

[System.GC]::Collect()

Danach werden alle durch Remove-Variable oder sonst freigegebenen Speicherbereiche von Ihrem Prozess an das System zurückgegeben. Das kostet natürlich auch ein paar CPU-Umdrehungen. Daher sollten Sie nicht wild nach jedem Remove-Variable die Müllabfuhr rufen, wenn Sie sowieso gleich wieder neue Variablen deklarieren. Abgesehen davon läuft der Garbage Collector sowieso in mehr oder weniger regelmäßigen Abständen im Hintergrund. Bei Ihnen daheim kommt sicherlich auch in regelmäßigen Abständen die Müllabfuhr um den Hausmüll entsprechend zu entsorgen. Der Sperrmüll wird wahrscheinlich auch 2 x im Jahr geholt. Manchmal benötigen Sie aber auch einmal zusätzlichen Termin, z.B. wenn Sie den Keller entrümpeln. Dann vereinbaren Sie bei der Stadt einen Termin zur Abholung. Genau so sollten Sie auch den manuellen Aufruf vom Garbage Collector einsetzen. Wenn Sie mehr zur Garbage Collection erfahren möchten:

https://msdn.microsoft.com/de-de/library/0xy59wtx(v=vs.110).aspx

2.4.13                     Kontrollfragen

Mit welchem Cmdlet können Sie die Verarbeitungsgeschwindigkeit überprüfen?

Was ist schneller Alias oder Cmdlet?

Wie würden Sie nach einem bestimmten Dateityp (z.B.: *.ps1 oder *.bat) suchen?

Wie können Sie die Verarbeitung von Funktionen beschleunigen?

Link zu den Lösungen für Skripting: Tuning

2.4.14                     Troubleshooting

Troubleshooting ist natürlich ein wichtiger Bestandteil einer Programmierumgebung. Hier werden Sie mit den Möglichkeiten der Fehlerbehandlung in Skripten der Version 1.0 vertraut gemacht. Erweiterte Methoden des Troubleshooting finden im nächsten Buchteil im Kapitel PowerShell 2.0.

Fehlermeldungen sind klasse! Denn so erfahren Sie, was schief gelaufen ist und können das Problem beheben. Gemein ist, wenn keine Fehlermeldungen auftauchen, das Skript aber nicht funktioniert wie gewünscht. Deshalb freuen Sie sich über jede Fehlermeldung!

2.4.14.1                Wie geht es weiter, wenn ein Fehler passiert?

In der automatischen Variablen $ErrorActionPreference ist festgelegt, wie standardmäßig mit Fehlern umgegangen wird. Der vorgegebene Wert Continue ist prima. Das bewirkt das Fehler angezeigt werden, aber wenn es irgendwie machbar ist, läuft ein Skript weiter. Alternativ gibt es noch die Möglichkeiten SilentlyContinue, Inquire oder Stop zuzuweisen. Bei SilentlyContinue wird die Fehlermeldung nicht angezeigt und das Skript versucht weiterzulaufen. Inquire zeigt den Fehler an und fragt den Benutzer ob er das Skript abbrechen oder weiterlaufen lassen möchte. Stop zeigt den Fehler an und bricht das Skript ab.

Ich schreibe hier die ganze Zeit, dass das Skript weiter ausgeführt wird, falls möglich. Da stellt sich natürlich die Frage, wann dies denn möglich ist. Das kommt auf die Schwere des Fehlers an. Daraus ergibt sich, ob es sich um einen Fehler mit oder ohne Abbruch handelt. In erster Linie wichtig ist an dieser Stelle zu erkennen, dass es zwei verschiedene Fehlertypen gibt.

2.4.14.2                Fehler ohne Abbruch

Die meisten Fehler sind Fehler ohne Abbruch des Skripts. Als Beispiel soll uns an dieser Stelle einmal das Auflisten eines nicht existenten Verzeichnisses dienen:

ls c:\Verzeichnisdasesnichtgibt

Über die automatische Variable $? können Sie abfragen, ob der vorangegangene Befehl erfolgreich war. Wenn Sie dies dann direkt nach dem fehlerhaften ls Befehl eintippen, sollte als Rückgabe False erscheinen. Wenn Sie es gleich noch einmal eintippen kommt True, weil die Abfrage, ob der letzte Befehl erfolgreich war funktioniert hat. Sie können also jederzeit mit $? ermitteln ob der vorangegangene Befehl erfolgreich war.

2.4.14.3                Fehler mit Abbruch

Fehler mit Abbruch sind selten. Sie kommen eigentlich nur dann vor, wenn die PowerShell den Faden verloren hat, also nicht einmal mehr ein Cmdlet, eine Variable, eine Funktion oder ein externes Programm finden kann. Also beispielsweise so etwas:

Keine Ahnung was der da wieder zusammen geschrieben hat ;-)

2.4.14.4                Fehler kontrolliert abfangen

Wie bereits erwähnt sind die meisten Fehler nicht kritisch, daher funktioniert auch eine Trap-Anweisung die eigentlich dazu gedacht ist Fehler kontrolliert zu bearbeiten nicht, da diese nur auf kritische Fehler anspringt. Ein kleines Beispiel soll Ihnen auch hier wieder verdeutlichen wo das Problem liegt:

"Beginn des Skripts!"

$Verzeichnis="Fröschegibtshiernicht"

ls $Verzeichnis 2> $null

if (!$?) {"Mist - Verzeichnis gibt's nicht!"}

"Ende des Skripts!"

So können Sie Fehler ohne Abbruch erfolgreich kontrollieren. Falls in Zeile 3 ein Fehler auftritt, wird die Standard Fehlermeldung unterdrückt. Dafür fragt die darauf folgende Zeile die Variable $? ab, ob der vorangegangene Befehl funktioniert hat. Sie sollten die selbst geschriebene Fehlermeldung sehen, sowie die Information, dass die erste und letzte Zeile des Skripts abgearbeitet wurde. Weisen Sie der Variablen $Verzeichnis ein Verzeichnis zu, das tatsächlich existiert (z.B. C:\), dann sehen Sie auch wieder die erste und die letzte Zeile. Dieses Mal aber, statt der selbst gestrickten Fehlermeldung, den Inhalt des Verzeichnisses.

Probieren wir einmal für Fehler eine Falle mit der Trap-Anweisung aufzustellen:

"Begin des Scripts!"

trap {"Achtung";exit}

$Verzeichnis="Fröschegibtshiernicht"

ls $Verzeichnis 2> $null

"Scriptende"

Wir sehen leider nur die erste und die letzte Zeile des Skripts, da der Fehler von Trap nicht erkannt wurde. Schreiben Sie doch einfach einmal vor den ls Alias die Worte: Keine Ahnung.

Keine Ahnung ls $Verzeichnis 2> $null

Wenn Sie nun das Skript erneut starten kommt die erste Zeile, die Falle schnappt zu, zeigt die Fehlermeldung und beendet wegen des 2. Befehls exit in der geschweiften Klammer das Skript. Daher wird die letzte Zeile des Skripts nicht mehr erreicht. Wenn Sie mögen, können Sie das exit auch gerne einal entfernen und dann werden Sie feststellen, dass auch die letzte Zeile noch angezeigt wird. Damit der Trap auch auf kleinere Fehler reagiert, müssen wir aus einem lapidaren Fehler, einen Fehler mit Abbruch machen. Dazu haben Sie zwei Möglichkeiten. Zum einen können Sie die automatische Variable $ErrorActionPreference auf Stop setzen:

$ErrorActionPreference=“Stop

Dadurch wird jeder Fehler zu einem Abbruchfehler. Die andere Möglichkeit besteht in einem Common Parameter namens –ErrorAction oder kurz -EA. Dann können Sie auf einem bestimmten Cmdlet den Schalter –EA Stop anwenden und dann werden Fehler an diesem Cmdlet als Abbruchfehler angesehen.

Auch hier können Sie im Beispiel Skript die Zeile mit ls wie folgt austauschen:

ls $Verzeichnis –ea stop

Haben Sie die Mäusefalle mit Trap erst einmal aufgestellt, gilt diese Anweisung von der Zeile an wo der Trap formuliert wurde bis zum Ende des Skripts. Haben Sie also einen Fehler beseitigt, aber die Trap Anweisung nicht entfernt, gibt es vielleicht später im Skript erneut einen Fehler und es wird wieder die Trap-Anweisung ausgeführt. Dann denken Sie u. U. wieder an das alte Problem und suchen den Fehler an der falschen Stelle. So etwas wie ein „Untrap“ gibt es leider nicht. So etwas in der Art erfüllt aber die Try-Catch-Finally-Anweisung, die es ab PowerShell 2.0 gibt (siehe nächster Buchteil, Kapitel PowerShell 2.0, TroubleShooting in Version 2.0).

2.4.14.5                Fehlerhistorie abfragen

Weiterhin von Interesse ist die Fehlerhistorie. Über aufgetretene Fehler wird in einer automatischen Variable eine Liste geführt. Die Variable ist ein Array und nennt sich $Error. Eine komplette Liste erhalten Sie indem Sie einfach $Error ausgeben. Den aktuellsten Fehler finden Sie in $error[0].

2.4.14.6                Fehler machen

Vielleicht möchten Sie ja selbst auch einmal eine Fehlermeldung generieren und diese an das Aufrufende Progrmm zurück liefern. Dazu bietet Ihnen die PowerShell 3 Möglichkeiten: Throw, Write-Error und Exit.

2.4.14.6.1             Exit

Exit ist nicht wirklich ein PowerShell Befehl, sondern ein Überbleibsel aus Urzeiten, dass in fast allen Progrmmiersprachen gleichermaßen eingesetzt werden kann. Ein einfacher Exit Befehl beendet einfach nur das aktuelle Programm. Allerdings kann man einen sogenannten Exit-Code in Form einer Zahl an das aufrufende Programm beim verlassen senden. Eine 0 bedeutet soviel wie: „Alles OK! Die Beendung des Programmes war so geplant.“. Alle anderen Zahlen lassen auf einen Fehler hindeuten. Welche Zahl, welchem Problem entspricht steht hoffentlich in der Dokumentation des Programmes das den Exit-Code ausgibt, denn dafür gibt es aufgrund der vielschichtigen Fehlermöglichkeiten keine Regeln. Eine 1 kann also in dem einen Programm bedeuten dass auf eine bestimmte Datei nicht schreibend zugegriffen werden kann, bei einem anderen bedeutet eine 1 vielleicht gar keinen Fehler, sondern einfach nur, dass eine Datei ordnungsgemäß vorhanden ist und der Exit-Code die einzige Möglichkeit zur Kommunikation mit der Außenwelt ist (DOS läßt grüßen). Wie man es einsetzt? Z.B.:

Exit 3

2.4.14.6.2             Write-Error

Mit Write-Error produzieren Sie einen Fehler ohne Abbruch. In der einfachsten Form geben Sie einfach nur einen Text an:

Write-Error “Shit happens!“

Damit wird ein Fehler Objekt basierend auf den Standard PowerShell Fehlermeldungen produziert und entsprechend Rot auf dem Bildschirm dargestellt. Falls Sie diese Anweisung in einem Skript einsetzen, wird die Ausführung des restlichen Skripts nach der Bildschirmausgabe fortgesetzt. Die Fehlermeldung wird dabei auch in die Array Variable $Error aufgenommen und kann entsprechend ausgewertet werden. Weitere Details erfahren Sie wie üblich mit Get-Help Write-Error.

2.4.14.6.3             Throw

Mit Throw produzieren Sie einen Fehler mit Abbruch. Im Gegensatz zu Write-Error wird das Skript nach der Anweisung nicht fortgesetzt. Throw stellt kein herkömmliches PowerShell Cmdlet mit etlichen Optionen dar. Auf Throw folgend kann nur noch festgelegt werden, was zurückgegeben werden soll. Beispiele:

Throw “Shit happens!“

oder

Throw (get-process powershell)

Das erste Beispiel liefert einfach nur den Text an das aufrufende Programm, während das 2. Beispiel Informationen zum PowerShell Prozess liefert.

Weitere Informationen liefert Get-Help about_Throw.

Wie bei Write-Error landet der produzierte Fehler in $Error. Gezielt nach dem Text, den Sie in der Fehlermeldung geschrieben haben, können Sie in der Eigenschaft Exception des Fehlerobjekts suchen, z.B. so:

$Error[0].Exception

$Error ist ja ein Array mit allen Fehlern die passiert sind. Den aktuellsten erhalten Sie durch die nachfolgende Angabe von [0] und wie gesagt steckt in der Eigenschaft Exception Ihr Text, falls der Fehler ausgelöst wurde. Eine entsprechende Abfrage könnte so aussehen:

If ($Error[0].Exception –like “Shit”) {“Dumm gelaufen”} else {“Alles gut”}

2.4.14.7                $Error zurücksetzen

Wenn Sie die Fehlerhistorie in der aktuellen Sitzung zurücksetzen (leeren/reseten) möchten, können Sie nicht Clear-Variable Error einsetzen, da dies zu einer weiteren Fehlermeldung führt und Ihre Historie stehen lässt. Das Zauberwort hier ist interessanter Weise: $Error.clear()

2.4.14.8                Debugging

Wie Sie bereits wissen sind Fehler bzw. Fehlermeldungen eine tolle Sache. Aber was, wenn der Fehler vor dem Bildschirm sitzt? Debugging wird genutzt um logische Fehler in Skripten zu entdecken. Die PowerShell führt also Ihre Skripte ohne Fehlermeldung aus, doch am Bildschirm erscheint nicht das gewünschte Resultat. Oft liegt dies an Variablen, die an der entsprechenden Stelle im Skript nicht den gewünschten bzw. erwarteten Inhalt haben. Ich persönlich bevorzuge mein Debugging selbst zu machen indem ich einfach an der problematischen Stelle im Skript den Inhalt von Variablen auf den Bildschirm schreiben lasse. Der Vollständig halber will ich aber die Debuggingmöglichkeiten der PowerShell hier kurz erwähnen.

2.4.14.8.1             Set-PSDebug -Strict

Vom Visual Basic her kennen einige unter meinen Lesern vielleicht die Anweisung Option Explicit. Fast genau dasselbe macht bei uns in der PowerShell die Anweisung: Set‑PSDebug –Strict. Dies hilft bei Tippfehlern in Variablennamen. Geben Sie für eine kleine Kostprobe folgendes ein:

$gibtsnicht

Set-PSDebug –Strict

$gibtsnicht

Das erste $gibtsnicht hat er einfach so geschluckt. Die Variable hat nie einen Wert zugewiesen bekommen. Die PowerShell hat die Variable einfach ignoriert. Nach der Anweisung Set‑PSDebug –Strict haben Sie der PowerShell aber erklärt, dass Sie auf die Verwendung von nicht definierten Variablen mit einer Fehlermeldung hingewiesen werden möchten. Daher hat die PowerShell beim 2. Versuch die Fehlermeldung präsentiert. Das hilft, wenn Sie sich gerne einmal vertippen. Also z.B. ein $j statt ein $i schreiben wie in diesem Beispiel:

For ($i=0;$i –lt 10;$i++) {$j}

Wenn Sie die Konsole seit der Set-PSDebug Anweisung noch nicht geschlossen haben, gilt das natürlich noch und die Schleife wird Ihnen 10 x wegen dem Lapsus $j statt $i in der geschweiften Klammer eine Fehlermeldung ausspucken. Sie haben im Schleifenkopf $i definiert und nicht $j, welches Sie versucht haben in der Schleife auszugeben. Wenn Sie zuvor ein $j angelegt hatten, klappt das natürlich so nicht, dann zeigt er Ihnen den Inhalt von $j 10 x hintereinander an. Sollte das der Fall sein, öffnen Sie einfach eine neue Konsole und probieren Sie es erneut. Das Set-PSDebug gilt wie üblich nur innerhalb des Bereiches in dem die Anweisung erfolgt. Wenn Sie es auf der Konsole eintippen gilt das nur in dieser Konsole und nicht in einer die Sie vielleicht parallel starten. Auch wenn Sie die Anweisung in einem Skript geben, gilt dies nur innerhalb des Skriptes. Ist das Skript beendet und Sie landen wieder an der Eingabeaufforderung, ist in der Eingabeaufforderung das Set-PSDebug –Strict nicht mehr aktiv.

2.4.14.8.2             Set-PSDebug –Trace

Mit dem Schalter –Trace können Sie den Debugging Level festlegen. D.h. wie detailliert die PowerShell den Ablauf Ihres Skriptes anzeigt bzw. verfolgt. Auf den Schalter -Trace folgen Werte zwischen 0 und 2. 0 schaltet die Ablaufverfolgung aus und dies ist die Standardeinstellung. Mit 1 wird Ihnen jede Skriptzeile die gerade durchlaufen wird am Bildschirm angezeit. Durch setzen einer 2 erhalten Sie zusätzliche Informationen, wie z.B. die aktuelle Belegung von Variablen.

2.4.14.8.3             Set-PSDebug –Step

Mit Set-PSDebug –Step gehen Sie schrittweise durch ein Skript. Das bedeutet, Sie müssen jede Zeile abnicken, dass es weiter gehen soll, oder ob Sie das Skript an dieser Stelle abbrechen möchten.

2.4.14.8.4             Set-PSDebug –Off

Set-PSDebug –Off schaltet den gesamten „Budenzauber“ wieder aus.

2.4.14.8.5             Debugger

Sie haben auch die Möglichkeit einen interaktiven Debugger zu aktivieren. Damit können Sie zur Laufzeit von Skripten deren Ablauf beeinflussen. Dazu müssen Sie Breakpoints oder zu Deutsch Haltepunkte setzen. Dazu dient das Cmdlet Set-PSBreakpoint. Ein Haltepunkt kann vor dem Aufruf eines Skripts gesetzt werden, beim Ausführen eines bestimmten Cmdlets oder aber auch bei Verwendung einer Variablen. Um einen Haltepunkt in einem Skript zu setzen geben Sie bevor Sie das Skript starten

Set-PSBreakpoint –Script NamedesSkripts.ps1 –Line 10

ein. Wenn Sie nun das Skript starten, würde ab Zeile 10 der interaktive Debugger aktiviert. Bei

Set-PSBreakpoint –Command Get-Help

Würde jedes Mal, wenn Sie Get-Help aufrufen der interaktive Debugger gestartet und bei

Set-Breakpoint –Variable Variablenbezeichner

wird jedes Mal bei Verwendung der entsprechenden Variable (ohne $ Zeichen angeben) der Debugger gestartet.

Mit dem Schalter –Action gefolgt von Anweisungen in runden Klammern können Sie noch zusätzliche Aktionen erfolgen lassen.

Ist der Debugger gestartet können Sie sich mit eintippen des Buchstaben h eine Hilfe anzeigen lassen, die Ihnen erklärt, was Sie hier tun können. Es stehen Aktionen wie

s – führe den Schritt aus

v – überspringe den nächsten Schritt

o – verlasse die aktuelle Funktion, oder das Konstrukt (z.B. eine Schleife)

c – mache im Skript weiter, ohne Debugger, bis zum nächsten Haltepunkt

q – beende das Skript und den Debugger

zur Verfügung. Wenn Sie einem Skript gleich in mehreren Zeilen Zwischenstopps einlegen möchten, geben Sie einfach hinter –Line mehrere Zeilennummern durch Komma getrennt an.

Mit Get-PSBreakpoint können Sie sich die von Ihnen gesetzten Haltepunkte auflisten lassen.

Durch Disable-PSBreakpoint können Sie einen Haltepunkt vorübergehend deaktivieren, um ihn später mit Enable-PSBreakpoint wieder zu aktivieren. Möchten Sie einen Haltepunkt komplett löschen, verwenden Sie Remove-PSBreakpoint.

Noch mehr Informationen erhalten Sie mit Help about_Debuggers.

2.4.15                     Kontrollfragen

In welcher automatischen Variablen ist festgelegt, was bei einem Fehler geschehen soll?

In welcher automatischen Variablen ist festgelegt, ob das vorangegangene Kommando erfolgreich war?

In welcher automatischen Variablen sind die bisher passierten Fehler notiert und von welchem Typ ist diese Variable?

Sie haben eine Trap-Anweisung in Ihr Skript eingebaut, weil Sie einen Fehler erwarten. Statt Ihren Trap-Code auszuführen, wird aber nur die standard PowerShell Fehlermeldung ausgespuckt. Was ist die wahrscheinlichste Ursache und wie können Sie dafür Sorge tragen, dass Ihr Trap-Code ausgeführt wird?

Was tun Sie damit die PowerShell Sie auf Tippfehler in Variablen aufmerksam macht?

Wie können Sie in einem Skript in der Zeile 5 und 10 den interaktiven Skriptdebugger aktivieren?

Wie verlassen Sie den interaktiven Debugger?

Wie können Sie im interaktiven Debugger die nächste Code-Zeile ausführen lassen?

Wie können Sie eine Code-Zeile im interaktiven Debugger überspringen?

Wie rufen Sie im interaktiven Debugger die Hilfe auf?

Link zu den Lösungen für Skripting: Troubleshooting

3        Unterschiede zwischen PowerShell Version

3.1     PowerShell 1.0

Dieses Kapitel ist recht kurz und beschreibt einige Methoden die zwar unter neueren Versionen der PowerShell auch eingesetzt werden können, dort aber etwas anders gehandhabt oder erweitert werden.

3.1.1   Module in Version 1.0 (Snap-ins)

Hier werden die sogenannten PSSnapins erklärt. Unter PowerShell 1.0 war dies die einzige Möglichkeit den Befehlsumfang der PowerShell durch Dritthersteller zu erweitern. Ein PSSnapin kann z.B. einen Befehlssatz für den Active-Directory-Zugriff enthalten. Dies muss zunächst installiert und dann in die PowerShellumgebung integriert werden. Wie dies geschieht lesen Sie hier am Beispiel der Active-Directory Verwaltungstools von Quest.

Zunächst einmal müssen Sie sich die Erweiterungen von http://www.quest.com/powershell/activeroles-server.aspx herunterladen und installieren. Nach der Installation können Sie mittels Get-PSSnapin –registered herausfinden, welche Erweiterungen auf Ihrem System installiert sind und unter welchem Namen Sie die Erweiterung hinzufügen können. Wenn Sie nun nur Get-PSSnapin ohne Schalter ausführen, stellen Sie fest, dass nur die Erweiterungen gelistet sind, die zu PowerShell standardmäßig dazu gehören. Mittels Add-PSSnapin Quest.ActiveRoles.ADManagement können Sie die neuen Befehle für die Active Directory Verwaltung hinzufügen. Ein erneutes Get-PSSnapin listet nun zusätzlich die Quest Erweiterung als geladen auf. Aber welche neuen Cmdlets stehen Ihnen denn nun zur Verfügung? Um das herauszufinden geben Sie Get‑Command ‑PSSnapin Quest.ActiveRoles.ADManagement ein. Diese Befehle funktionieren, wie die restlichen Cmdlets auch. Sie können diese mit Get-Help entsprechend erforschen. Wenn Sie die Konsole schließen sind die Erweiterungen wieder weg und müssen bei einer neuen Konsole wieder geladen werden. Alternativ können Sie sich die Anweisung auch in Ihr Profil Skript schreiben. Die Quest-Tools haben gegenüber dem in PowerShell Version 2.0 von Microsoft bereits mitgebrachten Modul zur AD-Verwaltung den Vorteil, dass Sie ohne einen Windows Server 2008 R2 Domänencontroller auskommen und somit auch mit einem „alten“ 2003er oder 2008er Domänencontroller ohne Active Directory Management Gateway Service zusammen arbeiten.

3.2     PowerShell 2.0

Hier wird werden die Unterschiede und Vorteile bei der Befehlserweiterung und Troubleshooting im Gegensatz zur Version 1.0 dargestellt, ebenso wie die wichtigsten neu hinzugekommenen Cmdlets.

3.2.1   ISE

Ab PowerShell Version 2.0 wird auch das ISE (Integrated Scripting Environment) mitgeliefert. Dies ist ein Editor mit Debugger speziell für PowerShell Skripte. Anstatt die Debugging-Cmdlet im Skript zu verwenden, können Sie hier z. B. grafisch mit der Maus Ihre Breakpoints setzen. Bei der Arbeit mit Editoren (nicht nur ISE, auch z.B. PowerGUI) ist mir aufgefallen, dass irgendeine Art Zwischenspeicher verwendet wird, der zwischen den einzelnen Aufrufen nicht immer sauber gelehrt wird. So hatte ich schon einige Male das Phänomen, dass Fehler und deren Behebung durch alte Versionen in dem Zwischenspeicher verschleiert werden. Ich empfehle daher die Skripte nicht in Editoren zu testen, sondern zusätzlich zum Editor eine normale PowerShell-Konsole zu starten und von dort aus, die Skripte zu starten. Zum Bearbeiten der Skripte sind die Editoren, dank Syntax Highlighting und anderer netter Spielereien aber sehr gut zu gebrauchen. Anstatt viele Seiten mit Screenshots und seichtem Gewäsch zuzupflastern schauen Sie sich die ISE, doch einfach an. Wer Notepad bedienen kann, kommt auch schnell mit ISE klar. Unter Windows 7 und Server 2008 R2 muss ISE als Funktion (Systemsteuerung – Programme und Funktionen) /Feature (im Servermanager)nachinstalliert werden. Ansonsten können Sie während der Installation angeben, ob Sie ISE mitinstallieren möchten.

3.2.2   Troubleshooting in Version 2.0

Im Gegensatz zur relativ „dummen“ Trap-Anweisung kann Try-Catch-Finally eine ganze Menge mehr. Hauptvorteil ist, dass hier eingegrenzt werden kann von wo bis wo im Skript man mit einem Fehler rechnet. Die Grundlegende Syntax sieht so aus:

Try {

 Probiere den Code bis zur geschweiften Klammer zu

}

Catch {

 Wenn im Try-Abschnitt ein Fehler aufgetaucht ist, mache was hier steht.

}

Finally {

 Dieser Abschnitt ist optional. Wenn er vorhanden ist wird dieser Code auf jeden Fall ausgesführt, egal ob ein Fehler aufgetreten ist oder nicht.

}

Ein einfaches praktisches Beispiel könnte so aussehen:

"Begin des Skripts!"

try {

 von nix eine Ahnung, aber davon jede Menge

}

catch {

 "Achtung"

 $Error[0]

 exit

}

finally {

 "Ganz bestimmt!"

}

"Skriptende"

Die Zeile im Abschnitt Try konnte seltsamer Weise nicht ausgeführt werden ;-). Daher wurde der Code im Abschnitt Catch ausgeführt. Der zeigte den Text Achtung und die eigentliche Fehlermeldung an. Da die Fehlermeldung in weiß und nicht in Rot dargestellt wurde, kam die Fehlermeldung nicht direkt von der PowerShell, sondern wurde aus der Variablen $error gelesen. Interessant ist dabei, dass im Catch Abschnitt auch noch das Kommando exit steht, wodurch das Skript eigentlich beendet wird. Skriptende wird auch nicht mehr angezeigt, aber das was im Finally Abschnitt steht!

Schreiben Sie in der Code-Zeile des Try Abschnitts doch noch einmal den Alias ls vorne dran und setzen Sie den nachfolgenden Text in Gänsefüßchen:

 ls „von nix eine Ahnung, aber davon jede Menge“

Dieses Mal ist die Fehlermeldung in Rot, weil Sie direkt von der PowerShell kommt. Allerdings hat der Catch-Block nicht statt gefunden und das Skriptende wurde erreicht. Auch das Finally wurde natürlich ausgeführt. Wir haben hier einen Fehler der nicht zum Abbruch führt und damit hat Try-Catch-Finally dieselben Probleme wie Trap. Um nun einen Fehler mit Abbruch daraus zu machen schreiben Sie im Try Abschnitt am Ende doch noch –ea stop dahinter:

 ls „von nix eine Ahnung, aber davon jede Menge“ -ea stop

Jetzt hat es wieder funktioniert, da Sie extra angegeben haben, dass Try in dieser Zeile auf den Fehler reagieren soll. Sollen die Try-Anweisungen auf jeden Fehler anspringen, können Sie das genau wie bei Trap, allgemein über die automatische Variable $ErrorActionPreference=“Stop“ festlegen.

3.2.2.1   Fehlermeldungen spezifizieren

Auch Fehler sind .NET-Objekte und zwar von der Klasse System.Management.Automation.ErrorRecord. Das bedeutet man kann diese mit Get-Member und Select auch wieder etwas näher untersuchen. Leider ist die Fehlerklasse des jeweiligen Fehlers nicht sofort aus der normalen Fehlermeldung ersichtlich. Sie erfahren die Fehlerklasse des letzten aufgetretenen Fehlers mithilfe der Fehlerhistorie:

$error[0].Exception.Gettype().Fullname

Wenn Sie nun einen Fehler in der Form:

ls –SchiessMichTot

provozieren sollten Sie nach der o. a. Abfrage auf $Error für den falschen Parameterbezeichner, die folgende Fehlerklasse erhalten:

[System.Management.Automation.ParameterBindingException]

Die Try-Catch-Finally Anweisung kann auch mehrere Catch Abschnitte enthalten, wobei Sie diese mit den Fehlerklassen näher spezifizieren können:

try {ls –SchiessMichTot}

catch [System.Management.Automation.ParameterBindingException] {"Parameter existiert nicht!"}

catch {"Ein unbekannter Fehler ist aufgetreten!"}

finally {$error[0].Exception.Gettype().Fullname}

Der Text aus der ersten Catch Anweisung wird angezeigt, weil dieser genau der vorangestellten Fehlerklasse entspricht. Aus dem Finally-Block erhalten Sie noch einmal die Ausgabe der Fehlerklasse zur Kontrolle.

Wenn Sie im Try Abschnitt das Minuszeichen entfernen versucht ls das Verzeichniss SchiessMichTot zu finden, was es wohl auf den wenigsten Rechnern geben dürfte und somit haben wir wieder eine ItemNotFoundException, die allerdings nicht zu einem Abbruch führen würde und daher springt keiner der Catch-Blöcke an.

Wenn Sie möchten, dass einer der Catch-Blöcke reagiert, müssen Sie wieder die Variable $ErrorActionPreference oder ein –ea stop hinten anfügen:

try {ls SchiessMichTot –ea stop}

Siehe da, ein unbekannter Fehler ist aufgetreten. Im zweiten Catch-Block ist eine Fehlerklasse angegeben und somit verhält sich dieses Catch, wie ein „Catch-All“. Er behandelt also alle Fehler, ausser denen die explizit spezifiziert wurden.

Wenn Sie mögen können Sie auch noch einmal alles aus dem Try-Block entfernen, ausser dem Wörtchen SchiessmichTot. Schauen Sie doch einmal wie die Fehlerbehandlung dann abläuft.

Wenn Sie einen größeren Try -Block angelegt haben und sich nicht sicher sind, woher der Fehler denn nun genau kommt auf den ein Catch anspringt, dann finden Sie weiterführende Informationen in $error[0].InvocationInfo (u.a. die Zeilennummer und den Name des Übeltäters -> Cmdlet).

3.2.3   Kontrollfragen

Wie sieht das grundlegende Konstrukt aus mit dem ab Version 2.0 Fehler behandelt werden können?

Muss jede Feherbehandlung einen Finally-Block haben?

Wie erfahren Sie den .NET-Fehlerklasse, wenn ein Fehler aufgetreten ist?

PowerShell 2.0: Troubleshooting

3.2.4   Allgemeine zusätzliche Befehle der Version 2.0

Hier finden Sie einige Informationen zu grundlegenden Erweiterungen in der Version 2.0.

3.2.4.1   Kommentarzeilen im Skript

Wie Sie bereits aus dem allgemeinen Teil des Buches wissen, können Sie Kommentare durch ein vorangestelltes #-Zeichen einleiten. Ab Version 2.0 haben Sie auch die Möglichkeit mehrere Zeilen in einem Skript als Kommentar-Bereich kenntlich zu machen:

<#

Viele bunte Zeilen

mit Kommentaren,

die nicht extra ein Kommentarzeichen

am Zeilenanfang benötigen

#>

Zwischen <# und #> können mehrere Zeilen als Kommentar direkt hintereinader weg geschrieben werden.

3.2.4.2   Die Ausgabe in einem Fenster auf der grafischen Oberfläche präsentieren

Ab der Version 2.0 steht Ihnen das Cmdlet Out-GridView zur tablearischen Ausgabe in einem grafischen Fenster zur Verfügung mit dem Sie obendrein noch Filterfunktionen anwenden können. Ein einfaches Beispiel dazu sieht so aus:

ls c:\windows | out-gridview

Voraussetzung, dass der Befehl klappt ist allerdings die Installation des .NET-Framework 3.5 mit SP 1 oder auf einem Server 2008 R2 das hinzufügen des Features .NET-Framework 3.51.

3.2.4.3   WMI Eigenschaften einstellen

Um WMI Eigenschaften einzustellen bringt PowerShell 2.0 das Cmdlet Set-WmiInstance oder kurz swmi mit. Damit lässt sich z.B. die Bezeichnung von Laufwerk C: ändern:

swmi -path "\\.\root\CimV2:win32_logicaldisk.DeviceID='C:'" `
-arguments @{Volumename="System"}

Details über den Aufbau von WMI finden Sie im Buchteil Versionsunabhängig, im Kapitel über Objektorientierung und dort im Abschnitt WMI Objekte. Dem Parameter –Path folgt zunächst der Rechnername, der in diesem Fall einen Punkt darstellt für den lokalen Rechner. Weiterhin der Namespace in Form von root\CimV2 und die Objektklasse Win32_LogicalDisk. Um nur die Objektinstanz von Laufwerk C: zu bekommen, wird nach der Eigenschaft DeviceID auf Laufwerk C: hin gefiltert. Mit dem Schalter –Arguments können Sie dann der Eigenschaft VolumeName den neuen Inhalt System zuweisen.

3.2.4.4   Add-Type

Mit dem Add-Type Cmdlet, lässt sich das Laden von Assemblies vereinfachen. Statt:

[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Drawing")

können Sie einfach:

Add-Type -AssemblyName System.Drawing

schreiben.

3.2.5   Module in Version 2.0

Module sind die Nachfolger der PSSnapins. Sie lassen sich nicht nur einfacher anwenden, sondern auch ganz leicht selbst erstellen. Angefangen von den bei Windows mitgelieferten Modulen bis hin zur Erstellung eigener Module werden Sie alles in diesem Kapitel finden.

3.2.5.1   Standard Module

Welche Befehlserweiterungen auf Ihrem System zur Verfügung stehen ist abhängig davon, welches Betriebssystem Sie einsetzen und bei einem Server, welche Rollen installiert sind. So werden Sie auf einem Vista Client Applocker vergeblich suchen, weil dies ein neues Feature von Windows 7 und Server 2008 R2 ist. Auch in der Home Versionen von Windows 7 gibt es Applocker nicht. Welche Befehlserweiterungen Ihnen auf Ihrem System aktuell zur Verfügung stehen erfahren Sie durch Eingabe von:

Get-Module –list

Nur das Cmdlet zeigt Ihnen an welche Module bereits geladen sind. Da sollte im Normalfall keine Ausgabe erfolgen, denn noch haben Sie kein Modul geladen. Einige Module lassen sich nur korrekt importieren, wenn Sie die Skriptausführung z.B. mittels Set-ExecutionPolicy Cmdlet aktivieren.

Das Modul BitsTransfer sollte eigentlich überall vorhanden sein. Der Name des Moduls hört sich langweilig und kompliziert an. Ich glaube aber Sie werden sich sehr bald fragen, was Sie früher ohne dieses Ding gemacht haben. Bits steht für Background Intelligent Transfer Service. Bei der Installation des Acrobat-Reader taucht ein Häkchen auf mit dem Sie angeben können, das der Download nur stattfindet, wenn die Leitung frei ist, sprich nicht für irgendetwas anderes benötigt wird. Auch die Windows-Updates werden mithilfe von Bits eingespielt. Der Server muss diese Technik natürlich auch unterstützen, aber das ist bei den meisten professionellen Webservern der Fall. Greifen Sie sich zunächst einmal die zusätzlichen Cmdlets mit:

Import-Module Bits*

Sie dürfen die Namen der Module gerne mit *-Platzhalterzeichen abkürzen. Wenn Sie nun noch einmal nur Get-Module tippen, stellen Sie fest, dass BitsTransfer nun als Modul geladen ist. Welche Cmdlets Sie dazu bekommen haben verrät:

Get-Command –Module Bits*

Auch hier können Sie sich mehr Informationen zu den einzelnen Cmdlets mit Get-Help verschaffen. Im Kapitel mit Skripten arbeiten im Abschnitt über Funktionen, haben Sie ein eigenes Cmdlet zur Abholung von Websites geschrieben.

Um eine Internetseite herunterzuladen können Sie das Cmdlet Start-BitsTransfer verwenden:

Start-BitsTransfer "http://www.martinlehmann.de/wp” c:\test\website.htm

Damit wird meine Website bei Ihnen im Verzeichnis test in der Datei website.htm abgelegt. Das ist aber wieder einmal mehr mit Kanonen auf Spatzen geschossen. Wie wäre es einmal mit einem ordentlichen Download? Z.B. einem OpenSuSE–Linux DVD-Image? Das würde in der eben angegebenen Form nicht viel Spaß machen. Bislang haben Sie immer die Erfahrung gemacht, wenn die PowerShell-Konsole geschlossen wird, ist alles vorbei. Start-BitsTransfer verfügt über den Schalter –Asynchronous. Wenn Sie den noch hinten dran setzen, dürfen Sie Ihren PC sogar herunterfahren! Sobald der PC wieder eingeschaltet ist, wird der Download fortgesetzt, auch ohne dass Sie die PowerShell starten. Mit folgendem Befehl starten Sie den Download:

Start-BitsTransfer "http://ftp.uni-kl.de/pub/linux/opensuse/distribution/12.1/iso/openSUSE-12.1-DVD-i586.iso" c:\test\opensuse.iso –asynchronous

Jetzt wird immer, wenn die Leitung zum Internet frei ist ein Stück mehr der Datei heruntergeladen. Das geht natürlich auch in die andere Richtung. Des Weiteren funktioniert das auch mit ganz normalen Dateifreigaben. Mit etwas Nachdenken können Sie sich einen echt klasse Download-/Filemanager bauen. Starten Sie Ihren PC doch einmal neu und öffnen Sie anschliessend wieder die PowerShell Konsole.

Wenn Sie die Konsole schliessen ist das Modul bei einem erneuten Start nicht mehr geladen und muss erst erneut hinzugefügt werden. Es sei denn Sie packen das Laden des Moduls in eine Anweisung im Profil Skript.

Mit Get-BitsTransfer können Sie dann nachschauen ob der Download schon fertig ist. Wenn in der Spalte JobState Transferring steht dauert es noch. Wird aus dem Transferring ein Transfered, ist der Download abgeschlossen. Damit die Datei dann auch benutzbar ist, müssen Sie noch dieses Cmdlet absetzen:

Complete-BitsTransfer (Get-BitsTransfer)

Alle abgeschlossenen Downloads stehen damit als normale Datei am angegebenen Speicherort zu Ihrer Verfügung.

Möchten Sie einen Download abbrechen, können Sie das mit dem Cmdlet Remove‑BitsTransfer. Bei Get-Bitstransfer sehen Sie in der ersten Spalte eine JobId. Wenn Sie die hinter Remove-BitsTransfer übergeben wird dieser Download beendet. Wenn Sie alle abbrechen möchten:

Remove-BitsTransfer (Get-BitsTransfer)

Wenn Sie ein Modul wieder entladen möchten geht das mit:

Remove-Module NamedesModuls

Alle Module einzeln und vollständig zu erklären würde den Rahmen des Buches sprengen. Abgesehen davon gibt es auch noch die hervorragende Hilfe der PowerShell. Zu einigen Modulen werden Sie noch im Praxisteil des Buches weitere Informationen finden.

3.2.5.2   Module selbst entwickeln

Selbst Module mit eigenen Cmdlets zu erstellen ist ein Kinderspiel, wenn Sie den Abschnitt über Skripting und Funktionen im vorangegangnene Teil des Buches gelesen haben.

Als knappes Beispiel soll uns wieder der Befehl ll dienen:

<#

.SYNOPSIS

 Zeigt alle Eigenschaften von Dateien und Verzeichnissen an.

.DESCRIPTION

 Der Befehl soll den Alias aus SuSE-Linux ll implementieren. Auf einem SuSE-Linux System ist ll ein Alias auf ls –l. ls –l wiederum macht ein sogenanntes List Long, sprich es werden alle möglichen Informationen zu Verzeichnissen und Dateien dargestellt. Hier in der PowerShell wird ein Get-Childitem auf das entsprechende Verzeichnis durchgeführt und anschlissend an ein Select-Object * übergeben, was alle Informationen zu den von Get-Childitem gelieferten Datei- und Verzeichnisobjekten darstellt.

.EXAMPLE

 ll c:\

 Listet das Wurzelverzeichnis von Laufwerk C: mit allen Detailangaben.

.EXAMPLE

 „c:\“ | ll

 Übergibt das Verzeichnis über eine Pipe an den Befehl ll.

.LINK

 www.martinlehmann.de

#>

Function ll {

 Param(

  [Parameter(Mandatory=$true,ValuefromPipeline=$true)]

  [String[]]

  $Verzeichnis

 )

 Get-Childitem $Verzeichnis | Select *

}

Das Skript beginnt mit <# und leitet somit ein Kommentar bis zum schliessenden Element #> ein. Dieser Kommentar hat es aber im wahrsten Sinne des Wortes in sich. In diesem Kommentar-Bereich sind verschiedene Schlüsselbegriffe eingebaut, wie .Synopsis oder .Example. Auf diese Weise können die Hilfetexte für das Get-Help Cmdlet geschrieben werden. Welche Schlüsselbegriffe was genau machen erklärt Get-Help about_Comment_Based_Help. Ich muss mir ja hier nicht die Finger wund tippen um als Held da zu stehen, wenn es schon einen anderen Helden gab ;-).

Weiterhin interesant in diesem Zusammenhang ist auch die Kommentar-Anweisung #Requires. Damit können Sie ganz einfach die Fehlerbehandlung für Ihr Script durchführen, dass Befehle der Version 4 beinhaltet (z.B. DSC), jemand aber versucht das mit PowerShell Version 2.0 auszuführen. Ganz einfach am Anfang Ihres Scriptes diese Zeile rein:

#Requires –Version 4.0

Fertig! Das war’s! Versuch man nun das Script mit einer älteren Version auszuführen, gibt es automatisch eine durch PowerShell generierte Fehlermeldung, bevor es versucht dieser Version unbekannte Befehle zu interpretieren. Mit –Modules können Sie auch festlegen, ob bestimmte Module zur Scriptausführung benötigt werden. Noch mehr verrät: help about_requires.

Auf die Kommentarzeilen folgt eine ganz normale Funktionsdefinition, wie Sie dies bereits im vorangegangenen Buchteil kennengelernt haben. Die Besonderheit beim Modul liegt daran, dass Sie nicht wie in einem normalen Skript den Scope-Bezeichner Global: vor den Namen der Funktion schreiben müssen, damit die Funktion nach Ausführung des Skriptes auf der Konsole zur Verfügung steht. Bei Funktionen in einem Modul geht die PowerShell schon davon aus, dass Sie die Funktion als Cmdlet zur Verfügung stellen möchten und deklariert sie automatisch als global. Wenn Sie das nicht möchten, z.B. weil die Funktion nur Bestandteil einer anderen Funktion ist, können Sie durch einen Scope-Bezeichner wie Privat: festlegen, sodass diese Funktion eben nicht automatisch auf der Konsole zur Verfügung steht. Soweit die Theorie. In der Praxis klappt das mit dem Privat: in Modulen leider nicht. Aber ex gibt eine andere Möglichkeit gezielt Functionen zu veröffentlichen, bzw. zu verstecken mithilfe des Export-ModuleMember Cmdlet.

Export-ModuleMember geben Sie am Schluß Ihre Modules an und sagen damit aus, was Sie exportieren (sprich Global an die PowerShell Konsole übergeben) wollen. Dazu stehen Ihnen verschiedene Parameter zur Verfügung.

Export-ModuleMember –Function Funktion1,Funktion2 –Variable Var1,Var3

Angenommen Sie haben 3 Funktionen geschrieben mit den Namen Funktion1, Funktion2 und Funktion3, sowie 3 Variablen verwendet mit den Bezeichnern Var1, Var2 und Var3. Dann sind alle Funktionen und Variablen die Sie in Export-ModuleMember angeben öffentlich und alle die Sie nicht aufgeführt haben automatisch privat (also von der PowerShell Konsole aus nach laden des Moduls nicht sichtbar). In dem eben gelisteten Beispiel würden Sie nach einem Import‑Module die Funktionen: Funktion1 und Funktion2 sehen und benutzen können, aber nicht Funktion3. Bei den Variablen würden Sie Var1 und Var3 sehen und benutzen können, aber nicht Var2, da Sie die nicht im Export-ModuleMember gelistet haben.  Ähnliches gilt z.B. auch für Aliase. Mehr verrät wie üblich ein Get-Help Export-ModuleMember.

Sie können Ihr Skript zunächst ganz normal als .ps1-Datei abspeichern und testen. Wenn Sie zum Modul werden soll, brauchen Sie lediglich die Dateinamenerweiterung in .psm1 ändern und fertig. Ab sofort ist das kein PowerShell Skript mehr, sondern ein PowerShell Modul. Damit es als Modul vom PC erkannt wird muss die Datei nun nur noch im richtigen Verzeichnis liegen. Vielleicht ist Ihnen im Installationsverzeichnis der PowerShell (C:\Windows\System32\WindowsPowerShell\v1.0) schon einmal aufgefallen, dass es dort ein Unterverzeichnis Modules gibt. Hier müssen Sie nur noch ein Unterverzeichnis anlegen, dass genauso heißt wie Ihre psm1-Datei. Aber den Verzeichnisnamen ohne die Dateinamenerweiterung! Dann legen Sie Ihre Modul-Datei in das gleichnamige Verzeichnis und schon können Sie mit dem Cmdlet Get-Modules –list auch Ihr eigenes Modul sehen und natürlich auch mit Import-Module laden. Das ist das ganze Hexenwerk.

Damit würde Ihr Modul jedem Benutzer an diesem PC zur Verfügung stehen. Wenn Sie es nur für sich möchten, oder keine Berechtigungen auf das Systemverzeichnis haben um dort Ihr Modul abzulegen, können Sie die Modul-Dateien auch in Ihr Benutzerprofilverzeichnis legen.

Bei XP und Windows Server 2003 müssen Sie einen Ordner namens Modules unter:

C:\Dokumente und Einstellungen\Name des Benutzers\Eigene Dateien\WindowsPowerShell

anlegen. Bei Vista, Server 2008 und neueren Betriebssystemen unter:

C:\Benutzer\Name des Benutzers\Dokumente\WindowsPowerShell

In diesem Ordner müssen wieder genauso wie im Modules-Verzeichnis des Systems vorgehen, sprich noch einen Unterordner mit dem Namen der psm1-Datei anlegen und da Ihre Modul-Datei hinein kopieren.

3.2.5.3   Bestehende Cmdlets erweitern (ProxyCmdlets)

Als ich vor kurzem eine OU im AD löschen wollte, bin ich über ein kleines Problem gestolpert. Es gibt das Cmdlet Remove-ADOrganizationalUnit im Modul für ActiveDirectory. Es hat zwar einen Schalter –Recurse (um UnterOUs zu löschen), doch leider keinen Schalter –Force. Unter –Force hätte ich mir gewünscht, dass OUs die mit einem Löschschutz versehen sind, ebenfalls gelöscht werden.

Natürlich können Sie sich nun eine Funktion schreiben, die dies für Sie situationsbedingt erledigt. Doch richtig cool wäre doch, wenn Sie das bestehende Cmdlet, mit all seinen Optionen, einfach um den zusätzlichen Parameter –Force erweitern.

Microsoft hat hierfür ab PowerShell Version 2.0 ein .NET-Objekt bereitgestellt, mit dem Sie sich einfach den Code für ein Proxy-Cmdlet generieren lassen können. Möchten Sie also Get-ChildItem erweitern, baut Ihnen dieser 2-Zeiler ein entsprechendes Skript, dass Sie nur noch erweitern müssen:

$MetaData = New-Object System.Management.Automation.CommandMetaData(Get-Command Get‑ChildItem)

[System.Management.Automation.ProxyCommand]::Create($MetaData) > GetChildItemProxy.ps1

Dazu gibt’s auch von den Scripting-Guys noch jede Menge Erklärungen.

3.2.6   Kontrollfragen

Wie stellen Sie fest, welche Module Ihnen zur Verfügung stehen?

Wie stellen Sie fest welche Module bereits geladen sind?

Wie laden Sie ein Modul?

Wie lautet die Dateinamenerweiterung für ein Modul?

Wo müssen Sie Ihre Modul-Datei ablegen, damit Sie allen Benutzern zur Verfügung steht?

Wo müssen Sie Ihre Modul-Datei ablegen, damit Sie einem bestimmten Benutzer zur Verfügung steht?

Wie könnnen Sie in einem selbst erstellten Modul die Hilfetexte für Ihre Cmdlets generieren?

PowerShell 2.0: Module

3.2.7   PowerShell Remote Zugriff

Im Kapitel über Remote Zugriff erfahren Sie alles rund um den Fernzugriff mittels WinRM. Remoting mittels WMI ist eine andere Geschichte, die bereits im Buchteil Versionsunabhängig, im Kapitel Objektorientierung und dort im Abschnitt WMI Objekte behandelt wurde. Mit WinRM können Sie direkt PowerShell Cmdlets auf anderen PCs ausführen. Wie Sie einzelne Befehle auf 500 Rechnern gleichzeitig ausführen, oder sich einfach „nur“ auf die Konsole eines anderen Rechners schalten erfahren Sie in diesem Kapitel.

3.2.7.1   Voraussetzungen schaffen und konfigurieren

Wenn Sie PowerShell 2.0 auf einem älteren Betriebssystem nachrüsten, wird Ihnen aufgefallen sein, dass Sie PowerShell 2.0 nicht einzeln herunterladen können, sondern immer nur in Verbindung mit WinRM 2.0 (Windows Remote Management). Dies stellt die Technologie für den Remote Zugriff zur Verfügung. Zur Übertragung wird das SOAP-Protokoll über HTTP verwendet. In der Version 1.0 von WinRM wurde sogar noch der Port 80 (Standard für HTTP-Übertragungen) verwendet bzw. 443 für verschlüsselte Übertragungen mittels HTTPS. In der Version 2.0 hat man zwar das HTTP Übertragungsprotokoll beibehalten, es wird aber nicht mehr über Port 80, sondern über 5985 abgewickelt. Als Ersatz für 443, die verschlüsselte Übertragung, dient 5986. Das bedeutet weiterhin, dass falls sich Firewalls (dazu zählt auch die eingebaute Desktopfirewall von Windows) zwischen den beiden PCs befinden die miteinander kommunizieren sollen, die Ports entsprechend freigeschaltet werden müssen.

Die Übertragung über HTTP ist bereits verschlüsselt. Die Befehle können daher nicht mitgeschnitten oder bei der Übertragung manipuliert werden. Die Übertragung per HTTPS fügt nur noch eine zusätzliche Sicherheitsstufe durch eine zweite Verschlüsselung hinzu. Bei der Übertragung per HTTPS müssen Sie für die Computer Zertifikate für die Autenthifizierung und Verschlüsselung ausgestellt werden.

3.2.7.1.1    Remoting mittels Cmdlet aktivieren

Aus Sicherheitsgründen ist die Remoteverwaltung deaktiviert. Im einfachsten Falle verwenden Sie das Cmdlet Enable-PSRemoting, um die Remoteverwaltung zu aktivieren. Das funktioniert aber nur auf einer PowerShell Konsole, die Sie als Administrator mit erhöhten Rechten gestartet (Rechtsklick auf das PowerShell Symbol und Als Administrator ausführen anklicken) haben. Disable-PSRemoting macht das Ganze natürlich wieder rückgängig. Auch bei aktiver Remoteverwaltung ist es nur Benutzern, die auf dem Zielsystem zur Gruppe der lokalen Administratoren gehören gestattet, remote Befehle per PowerShell zu senden.

Duch das Cmdlet Enable-PSRemoting wird der Dienst Windows-Remoteverwaltung auf automatisch gesetzt und gestartet. Die Einstellung automatisch am Dienst bewirkt, dass er auch nach einem Neustart des Betriebssystems wieder gestartet wird. Des Weiteren wird eine Ausnahme für die Window Firewall auf dem System eingerichtet, damit die Verbindung zustande kommen kann und letztendlich noch der Endpunkt registriert, sprich die Verbindung der PowerShell mit dem Port hergestellt. Wenn Sie nur einzelne Systeme dafür freischalten möchten, ist dies die komfortableste Möglichkeit. Wenn Sie auch nicht tiefer in die manuelle Konfiguration einsteigen möchten, können Sie auch gleich im Abschnitt Cmdlets oder Skripte auf mehreren Rechnern ausführen lassen weiterlesen.

3.2.7.1.2    Remoting von remote aktivieren

Also eigentlich geht das ja nicht, aber:

Invoke-WmiMethod Win32_Process Create -Args "powershell –noprofile`
-command Enable-PsRemoting -Force" -Computer NameDesComputers

Durch diesen WMI-Methoden-Aufruf können Sie auf dem angegebenen Zielrechner das PowerShellRemoting aktivieren, wenn Sie dort zum „Club“ der lokalen Administratoren gehören.

3.2.7.1.3    Remoting über Active Directory Gruppenrichtlinien aktivieren

Starten Sie die Gruppenrichtlinienverwaltungskonsole und suchen Sie die Organisationseinheit aus welche die Computerkonten enthält, für die die Remoteverwaltung mittels PowerShell aktiviert werden soll und erstellen Sie dort ggf. eine neue Gruppenrichtlinie.

Die Ausnahme für die Windows Firewall Regel können Sie in der Gruppenrichtlinie unter diesem Punkt vornehmen:

Computer\Richtlinien\Windows-Einstellungen\Sicherheitseinstellungen\Windows-Firewall mit erweiterter Sicherheit\ Windows-Firewall mit erweiterter Sicherheit\Eingehende Regeln

Führen Sie an dieser Stelle einen Rechtsklick aus und klicken Sie Neue Regel… an. Wählen Sie Vordefiniert und dort Windows-Remoteverwaltung aus und klicken Sie 2 Mal auf Weiter und dann auf Fertigstellen. Wenn Sie nicht die Standardports verwenden möchten, müssen Sie selbst eine entsprechend angepasste Regel erstellen.

Den Dienst für die Windows-Remoteverwaltung können Sie unter diesem Punkt finden:

Computer\Richtlinien\Windows-Einstellungen\Sicherheitseinstellungen\Systemdienste

Wählen Sie dort den Dienst Windows-Remoteverwaltung aus, wählen Sie Diese Richtlinieneinstellung definieren und den Startmodus Automatisch aus. Klicken Sie auf OK.

Um den Listener zu aktivieren müsen Sie noch in diesem Abschnitt angeben von welchen IP-Adressen aus der Remotezugriff möglich sein soll:

Computer\Richtlinien\Administrative Vorlagen\Windows-Komponenten\Windows-Remoteverwaltung (WinRM)\WinRM-Dienst\Automatische Konfiguration von Listenern zulassen

Klicken Sie auf Aktiviert und tragen Sie in dem darunter liegenden Feld den gewünschten Adressbereich der Hosts ein, auf die der Zugriff gestattet werden soll (z.B.: 192.168.0.1‑192.168.0.254, 192.168.3.20-192.168.3.20). Wie im Beispiel zu sehen, müssen auch einzelne IP‑Adressen (192.168.3.20) als Bereich angegeben werden. Weiterhin anzumerken ist, dass es die IP‑Adressen der Systeme sind auf die zugegriffen wird und nicht die von denen aus die Verbindung aufgebaut wird!

3.2.7.1.4    Remoting in einer Arbeitsgruppe

Wenn die PCs nicht zum selben Active Directory gehören scheidet die einfache Aktivierung mittels Enable-PSRemoting oder Gruppenrichtlinien im Active Directory aus. Dann bleibt nur manuell Hand an das Webservice Management zu legen. Wenn Sie das nicht vorhaben, tun Sie sich diesen Abschnitt nicht an und lesen Sie gleich bei Cmdlets oder Skripte auf mehreren Rechnern ausführen lassen weiter.

Zunächst starten Sie die PowerShell Konsole als Administrator, wechseln Sie in das PSLaufwerk WSMan: und dort in den entsprechenden Unterordner:

cd WSMan:\localhost\client

Ein Get-ChildItem an dieser Stelle sollte in der Zeile Trusted Hosts und in der Spalte Value einen leeren Bereich enthalten. Hier stehen IP-Adressen oder Rechnernamen mit denen sich Ihr PC verbinden darf. Sollten hier bereits Angaben drinstehen, wollen Sie diese wahrscheinlich nicht überschreiben. Wenn Sie hier Werte hinzufügen möchten, müssen Sie bei dem nachfolgenden Befehl den Schalter –Concatenate mit angeben, ansonsten werden die bestehenden Einträge ersetzt, anstatt die neuen angehängt. Um auf einem Rechner außerhalb Ihrer Domänenumgebung zugreifen zu können, geben Sie den Befehl in folgender Syntax ein:

Set-Item .\TrustedHosts Name oder IP-Adresse des Rechners mit dem Sie sich verbinden möchten.

Ein Beispiel um auf einen Webserver Namens IIS zugreifen zu können:

Set-Item .\TrustedHosts IIS

Wenn Sie zusätzlich auch auf das komplette Subnet 192.168.0.0/24 den Zugriff gestatten möchten:

Set-Item .\TrustedHosts 192.168.0.* -Concatenate

Ohne den Schalter –Concatenate wäre nur noch das Subnet zulässig. Der Zugriff auf IIS wäre entfernt worden. Wenn Sie den Zugriff wieder allen entziehen möchten, setzen Sie einfach einen Leerstring:

Set-Item .\TrustedHosts ““

Möchten Sie nur einen einzelnen Wert aus der TrustedHosts entfernen:

$Original=(Get-Item .\TrustedHosts).Value

Set-Item .\TrustedHosts (($Original.split(",") | ? {$_ -notcontains` "ZuEntfernendeIPoderName"}) -join ",")

Im Unterordner DefaultPorts könnten Sie auf die gleiche Weise die Ports für die Netzwerkkommunikation ändern. Dabei sollten Sie nicht vergessen auch die Firewall-Regeln anzupassen!

Wenn Sie lieber mit Zertifikaten über HTTPS statt der IP-Adresse arbeiten möchten, müssen Sie sich zunächst ein Zertifikat zum Zwecke der Computer-Authentifizierung beschaffen und im Zertifikatsspeicher des Computers (nicht des Benutzers!) ablegen. Dies können Sie dann mittels des folgenden Cmdlet auf dem fernzusteuernden Rechner registrieren:

New-WSManInstance winrm/config/Listener `
–SelectorSet @{Address=’*’;Transport=”HTTPS”} `
–ValueSet @{
Hostname="Rechner1.Domäne.Land";
CertificateThumbprint="DigitalerFingerabdruckDesZertifikats"
}

Das Sternchen * in der 2. Zeile legt fest, von wo aus Sie sich mit dem Rechner verbinden können. In diesem Falle sind keine Einschränkungen gemacht. Auch hier ließen sich statt des Sternchens IP-Adressen angeben.

Hinter Hostname müssen Sie den Computernamen angeben auf den das Zertifikat ausgestellt wurde.

Hinter CertificateThumbprint müssen Sie den „Fingerabdurck“ des Zertifikates angeben (finden Sie im Zertifikat).

Um sich dann verbinden zu können dürfen Sie den Parameter –UseSSL nicht vergessen. Beispiel:

Enter-PSSession –ComputerName Rechner1.Domäne.Land -UseSSL

Über Zertifikate selbst ließe sich noch einmal so ein Buch füllen, daher möchte ich an dieser Stelle nicht weiter darauf eingehen und lieber auf einschlägige Literatur zu diesem Thema verweisen.

3.2.7.2   Cmdlets oder Skripte auf mehreren Rechnern ausführen lassen

Nachdem die u.U. etwas komplizierte Konfiguration abgeschlossen ist, wieder zu den einfachen Dingen des Lebens. Wenn Sie ein Cmdlet gerne auf einem anderen Computer ausführen lassen möchten, aber das Cmdlet keinen Schalter –Computername mitbringt, dann können Sie einfach das Cmdlet Invoke-Command aussen herum bauen. Die Syntax ist denkbar einfach:

Invoke-Command {Einzelnes Cmdlet oder Befehlskette, die remote ausgeführt werden soll} ‑Computername Rechnername1,Rechnername2,usw…

So hat Beispielsweise das Get-ChildItem Cmdlet keinen Schalter –Computername. Um nun den Inhalt von Laufwerk C: anderern Rechner einzusehen könnte das so aussehen:

Invoke-Command {Get-Childitem C:\} –Computername Rechner1, Rechner2

Auf Ihrem Bildschirm tauchen dann die Wurzelverzeichnisse beider Rechner auf. Sollte einer der Rechner ein Authentifizierungs Problem melden, müssen Sie einen Benutzernamen und ein Kennwort mitgeben, von einem Benutzer der auf dem Zielsystem zur Gruppe der lokalen Administratoren gehört. Dazu können Sie z.B. folgenden Schalter verwenden ‑Credential (Get‑Credential). Selbstverständlich können Sie auch die Anmeldeinformationen vorher in einer Variablen ablegen und diese an den Schalter übergeben:

$Anmeldeinfo=Get-Credential

Invoke-Command {Get-Childitem C:\} –Computername `
Rechner1, Rechner2 –Credential $Anmeldeinfo

Wenn Sie ein ganzes Skript remote ausführen lassen möchten, geht das auch ganz einfach:

Invoke-Command –Filepath C:\IhrSkript.ps1 –Computername Rechner

Denken Sie dabei aber immer daran, dass ein anderer Rechner eben ein anderer Rechner ist. Wenn Sie vom Skript aus beispielsweise auf eine Datei zugreifen, die zwar auf Ihrem PC vorhanden ist, aber nicht auf den Remoterechnern wird Ihr Skript wahrscheinlich schief laufen. Auch Variablen werden nicht in die RemoteShell vererbt!

$a=“test“
Invoke-Command {“Nicht existente Variable: $a“} –Computer Rechner

Diese Zeile wird nur den Text ausgeben, aber nicht den Inhalt von $a, weil $a in der RemoteShell nicht exisitiert, sondern nur auf dem aufrufenden Rechner. Mit Enter-PSSession haben Sie keine direkte Möglichkeit Daten zu übergeben, höchstens mit Tricks (z.B. Infos in einer Datei ablegen und dann aus der Remote-Session den Inhalt der Datei über’s Netzwerk laden). Mit Invoke‑Command können Sie aber direkt Infos mit Hilfe des Schalters –ArgumentList übergeben:

Invoke-Command {“Inhalt von `$a wird zu `$args: $args“} `
-Computer Rechner1 -ArgumentList $a

Des Weiteren sollten Sie beachten was Sie innerhalb der geschweiften Klammer angeben und was ausserhalb steht:

Invoke-Command {Get-Childitem C:\} –Computername Rechner1,`
Rechner2
| Out-File C:\test\Inhalt.txt

Holt den Verzeichnisinhalt der beiden PCs ab und schreibt es auf Ihrem Rechner in die Datei Inhalt.txt.

Invoke-Command {Get-Childitem C:\ | Out-File `
C:\test\Inhalt.txt} ‑Computername Rechner1, Rechner2

Holt den Verzeichnisinhalt auf dem jeweiligen PC ab und schreibt es auf den RemotePCs in die entsprechende Datei. Wenn es auf diesen PCs gar kein Verzeichnis test auf Laufwerk C: gibt, haben Sie schon gleich einen Fehler.

In Sachen Tuning ist es natürlich auch sinnvoll möglichst viel remote erledigen zu lassen, statt auf dem FernsteuerungsPC. Zum einen können Sie die CPUs der Fremdsysteme nutzen, zum anderen verringert sich die Netzwerklast, wenn Sie nur die benötigten Daten zum FernsteuerungsPC zurück schicken.

Langsam:

Invoke-Command {Get-Process} -Computer name1,name2,name3 | `
Where-Object {$_.VM -gt 30MB} | sort PSComputerName | select Name,VM

Schnell:

Invoke-Command {Get-Process | select Name,VM | Where-Object `
{$_.VM -gt 30MB}} -Computer name1,name2,name3 | sort PSComputerName

Apropos PSComputerNameWenn die Objekte zwischen den PCs übertragen werden, bekommen diese ein paar Eigenschaften dazu, verlieren aber auch Informationen. Eine der zusätzlichen Eigenschaften ist PSComputerName. Anhand dieser Eigenschaft können Sie erkennen von welchem (bei mehreren) RemotePC das jeweilige Objekt ist. Verloren gehen alle Objektmethoden. Möchten Sie auf Objektmethoden zugreifen muss dies innerhalb der gescheiften Klammern (also noch auf den RemotePCs) erfolgen. Sind die Objekte erst einmal auf Ihrem Rechner (nach der geschweiften Klammer zu) sind die Methoden nicht mehr verfügbar.

Wenn Sie eine Textdatei mit Computernamen in jeder Zeile vorliegen haben, können Sie die auch gerne direkt in das Invoke-Command Cmdlet importieren:

Invoke-command {Remote-Befehle} –Computer (gc DateiMitComputernamen.txt)

Da können natürlich 500 PCs drin stehen. Was ist denn dann im Netzwerk los? Keine Bange! Generell werden max. 32 PCs gleichzeitig befragt. Erst wenn die Ihre Ergebnisse zurück geliefert haben, kommt die nächste „Rutsche“. Sie können das Ganze mit dem Schalter –ThrottleLimit feintunen. Wenn Sie nur 10 oder gar 100 PCs auf einmal befragen möchten, geben Sie einfach die entsprechende Zahl hinter dem Schalter –ThrottleLimit an.

3.2.7.3   Die Kommandozeile auf einen entfernten Rechner umschalten

Um sich auf einen anderen Rechner direkt drauf zu schalten können Sie das Cmdlet Enter‑PSSession verwenden. Geben Sie dazu einfach den Namen oder die IP-Adresse des Remoterechners hinter dem Cmdlet an:

Enter-PSSession Rechnername

oder

Enter-PSSession IP-Adresse

Auch hier können Sie den Schalter –Credential wie beim Invoke-Command verwenden. Sowohl Invoke-Command, als auch Enter-PSSession haben natürlich noch etliche weitere Schalter die im jeweiligen Fall weiterhelfen können. Wenn Sie da einmal mit Get-Help nachschauen werden Sie feststellen, dass das ganz schön viele sind. Da wünscht man sich eine Möglichkeit die Schalter schon irgendwie vorzubelegen. Oft wird hier zum Cmdlet New-PSSession geraten, mit dem die Verbindung aufgebaut und in einer Variablen abgelegt wird. Der Aufruf zum Verbindungsaufbau wäre dann statt mit Computername oder IP-Adresse einfach mit der Sitzungsvariable:

Enter-PSSession $Verbindungsvariable

Ich habe aber eine wesentlich bessere Idee. Im ersten Buchteil, im Kapitel Objektmethoden in der Praxis und dort im Abschnitt über Besondere Variablentypen: Hash, Splatting und Unterausdrücke haben Sie das Splatting kennen gelernt. Packen Sie doch alle Schalterinformationen in einen Hash und „splatten“ Sie ihn an Enter-PSSession:

Enter-PSSession @Splatvariable

Das hat den ganz gravierenden Vorteil, dass die Sitzungen nicht den ganzen Tag offen stehen und nur dann aufgebaut werden, wenn man sie wirklich braucht. Das hat Sicherheits- und Netzwerktechnisch nur Vorteile, aber auch einen kleinen Haken. Hierzu ein kleiner Vergleich.

Beispiel - Remoting 1:

Invoke-Command {$Test=1} –ComputerName Rechner1

Invoke-Command {$Test} –ComputerName Rechner1

Beispiel - Remoting 2:

$Verbindungsvariable=New-PSSession –Computername Rechner1

Invoke-Command –Session $Verbindungsvariable  {$Test=1}

Invoke-Command –Session $Verbindungsvariable {$Test}

Remove-PSSession $Verbindungsvariable

In beiden Beipielen werden hintereinander die Cmdlets Invoke-Command verwendet. Im ersten Cmdlet wird die Variable $Test gesetzt und im zweiten ausgelesen. Im Beispiel Remoting 1 ist beim 2. Invoke-Command der Inhalt von $Test  (bzw. die komplette Variable) unbekannt, da hier nach dem 1. Invoke-Command die „bestehende“ PowerShell Konsole geschlossen und beim 2. Invoke-Command eine neue geöffnet wird. Im Beispiel Remoting 2 wird die Konsole durch New‑PSSession auf dem entfernten Rechner erstellt und erst mit Remove-PSSession wieder beendet. Falls Sie tatsächlich auf die Verbindungsvariable zurückgreifen möchten, können Sie sich mittels Get-PSSession einen Überblick verschaffen, was noch so alles an Sitzungen offen steht und Ressourcen frisst. Spätestens wenn Sie zehn Sitzungen vergessen haben zu schließen, werden Sie wissen was Ressourcen sind, denn eine elfte Sitzung werden Sie nicht mehr aufbauen können. ;-)

Es gibt noch ein paar Einschränkungen für Remotesitzungen. Wenn Sie auf Ihrem Rechner beispielsweise das Modul für BitsTransfer geladen haben, stehen Ihnen diese Cmdlets dieses Moduls in der Remotesitzung nicht zur Verfügung. Genauso umgekehrt. Module die Sie in der Remotesitzung geladen haben, gehen nicht mit zurück auf Ihren Rechner, wenn Sie die Sitzung wieder verlassen. Des Weiteren werden auch Einstellungen aus einem Profil Skript nicht mitgenommen. Ebensowenig wird ein auf dem Remoterechner vorhandenes Profil Skript ausgeführt. Also gar keine Profil Skripte um es kurz zu formulieren. Oder noch einmal ganz einfach zusammengefasst: In der Remotesitzung haben Sie eine ganz normale Standard-PowerShell Umgebung vorliegen, ohne Schnick und ohne Schnack. Alles was Sie dort an Komfort haben möchten, müssen Sie sobald Sie auf dem Zielsystem sind, manuell (z.B. ein Profil Skript) ausführen.

Eine Remotesitzung wieder verlassen können Sie mithilfe des Cmdlet Exit-PSSession. Achtung! Nicht einfach nur Exit, denn damit schliessen Sie den PowerShell-Prozess auf dem Fernsteuerungsrechner, nicht dem Zielsystem. Rein theoretisch können Sie sich von einem Rechner auf den Sie sich remote aufgeschaltet haben wieder auf einen anderen Rechner weiterverbinden. Danach am besten noch weiter auf Ihr ursprüngliches System von dem Sie gestartet sind. Tun Sie es nicht! Egal ob Sie dabei ein Ringelpitz aufgebaut haben oder sich einfach über 2 Hops verbinden. Schliessen Sie immer erst eine Sitzung, bevor Sie sich auf einen anderen Rechner verbinden. Ihre Netzwerktruppe wird es Ihnen danken.

3.2.7.4   Durch Windows Remoting Cmdlets auf einem Rechner ausführen, der dazu eigentlich nicht in der Lage ist

Auf Windows XP können Sie die Active Directory Cmdlets normaler Weise nicht ausführen. Sie haben aber die Möglichkeit auf einem Domänencontroller die Active Directory Cmdlets als Modul zu importieren. Dazu müssten Sie aber an einem Domänencontroller sitzen oder sich per Enter‑PSSession drauf schalten. Alternativ können Sie aber auch eine Sitzung auf dem Domänencontroller in die lokale PowerShell Konsole importieren. Das geht folgendermaßen:

$Sitzung=New-PSSession –Computer $DC –Credential $AnmeldeInfo

Invoke-Command {Import-Module ActiveDirectory} –Session $Sitzung

Import-PSSession –Session $Sitzung –Module ActiveDirectory

In der ersten Zeile wird eine Sitzung mit dem Domänencontroller hergestellt und in der Variablen $Sitzung hinterlegt. Dem Schalter -Computer folgt die IP-Adresse oder der Name des Domänencontrollers. In diesem Beispiel habe ich die Variable $DC verwendet, die natürlich vorher definiert werden muss (z.B. $DC=192.168.0.5)! Mit dem Schalter –Credential können Sie Anmeldeinformationen (aus den vorangegangenen Beispielen) für einen Domänenadministrator übergeben, falls Sie an Ihrem Computer nicht bereits als Domänenadministrator angemeldet sind und die PowerShell Konsole über Als Administrator ausführen gestartet haben.

Das Invoke-Command in der zweiten Zeile lädt in der angegebenen Sitzung das ActiveDirectory Modul.

Zu Schluß importieren Sie sich aus der Sitzung die Cmdlets aus dem zuvor geladenen ActiveDirectory Modul in Ihre lokale Konsole.

3.2.7.5   Multi-Hop Remoting

Grundsätzlich ist es auch Sicherheitsgründen nur möglich von einem Rechner auf einen anderen zuzugreifen. In größeren Firmen ist es nicht unüblich, dass Server in einem geschützten Bereich stehen und von Clients aus nicht direkt erreichbar sind, sondern nur über einen Gateway oder Jumphost genannten Server.

Wenn Sie also nun versuchen einen Ihrer Server im geschützten Bereich per PowerShell direkt zu erreichen wird das durch die Firewall geblockt. Sie können sich von Ihrem Client aus nur mit dem Jumpserver verbinden. Das geht natürlich auch per PowerShell. Allerdings können Sie von dort aus nicht noch einmal Enter-PSSession aufrufen um von dort aus weiter zum Zielserver zu gelangen.

Um dies dennoch auf sichere Art und Weise zu ermöglichen, müssen Sie eine Ressource based Kerberos contrained Delegation einrichten. Zugegeben böses „Wort“, auf Deutsch heißt das bestimmt noch viel schlimmer. Vergessen Sie das böse Wort und richten Sie es einfach ein. Dazu benötigen Sie auf Ihrem Client die RSAT-Tools und das Active-Directory Modul für PowerShell (oder, falls möglich, machen Sie es einfach am Domänencontroller). Der Rest ist auch nicht weiter schwer. Als Domain-Admin geben Sie auf Ihrem Client oder am Domänencontroller ein:

Set-ADComputer -Identity NameDesZielservers -PrincipalsAllowedToDelegateToAccount NameDesJumpHOsts

3.2.7.6   Konfiguration der Endpunkte (Anlaufstellen)

Endpunkte sind Schnittstellen, die Ihre Enter-PSSession oder Invoke-Command Anfragen auf dem entfernten Computer entgegen nehmen. Davon gibt es schon einige und die reagieren auch, sobald Sie Enable-PSRemoting mindestens einmal auf dem Zielsystem ausgeführt haben. Wie Sie sich mit einem bestimmten Endpunkt verbinden, um z.B. die PowerShell auf dem Zielcomputer in 32-Bit auszuführen, oder gar selbst eigene Schnittstellen (Endpunkte) anlegen, um z.B. nur einzelne Cmdlets zu erlauben, erfahren Sie in den nachfolgenden Abschnitten.

3.2.7.6.1    Endpunkte auflisten

Mit dem Cmdlet Get-PSSessionConfiguration können Sie sich einen Überblick der Remoting-Schnittstellen verschaffen:

Wenn Sie sich mittels Enter-PSSession auf diesen Rencher von einem anderen aus verbinden wöllten, würde dies nicht klappen. Denn hier steht in den Permission-Zeilen die Gruppe NT AUTHORITY\NETWORK Access Denied. Was soviel bedeutet wie, dass hier noch niemand Enable‑PSRemoting ausgeführt hat ;-). Fehlt dieser Eintrag und sind nur die Administratoren gelistet, klappt’s auch mit dem Verbindungsaufbau.

Wenn Sie keine weiteren Angaben machen, werden Sie natürlich mit dem „Standard“-Endpunkt microsoft.powershell verbunden, was im Normalfall der 64-Bit Version entspricht.

3.2.7.6.2    Mit einem Endpunkt Ihrer Wahl verbinden

Vielleicht brauchen Sie z.B. wegen 32-Bittiger Datenbanktreiber aber in der Remote-Konsole die 32‑Bit PowerShell. Dann können Sie sich auch geziel mit diesem EndPunkt verbinden. Beispiel:

Enter-PSSession –ComputerName Rechner1 –ConfigurationName microsoft.powershell32

Nun können Sie auch die 32 bittigen Datenbankzugriffe erfolgreich remote ausführen.

3.2.7.6.3    Eigene Endpunkte bauen

Um einen eigenen Endpunkt auf einem System einzurichten sind 2 Schritte nötig. Zuerst müssen Sie mit New-PSSessionConfigurationFile eine *.pssc-Konfigurationsdatei anlegen und dann mit deren Einstellungen, mittels Register-PSSessionConfiguration, den Endpunkt einrichten.

Stellen Sie sich vor Sie möchten einem Mitarbeiter der Personalabteilung ermöglichen einen neuen Benutzer mittels New-ADUser anzulegen. Auf seinem Client hat er normaler Weise weder das Active-Directory PowerShell Modul, noch die entsprechenden Berechtigungen bzw. Mitgliedschaft bei den Domänen Administratoren. Hier könnte es spannend sein einen entsprechenden Endpunkt anzulegen, der genau das ermöglicht.

3.2.7.6.3.1        New-PSSessionConfigurationFile

Die Konfigurationsdatei könnte so erstellt werden:

New-PSSessionConfigurationFile –ModulesToImport ActiveDirectory `
–VisibleCmdlets “New-ADUser“ –LanguageMode NoLanguage `
–SessionType RestrictedRemoteServer –Path C:\NewUser.pssc

-ModulesToImport ActiveDirectory legt fest, dass bei Verbindungsaufnahme das PowerShell ActiveDirectory Modul mit all seinen AD Cmdlets geladen wird. Alle auf dem Zielsystem verfügbaren Module können dementsprechend geladen werden.

-SessionType RestrictedRemoteServer sorgt dafür, dass nur die folgenden Cmdlets zur Verfügung stehen: Exit-PSSession, Get-Command, Get-FormatData, Get-Help, Measure-Object, Out-Default, und Select-Object. Alle anderen Cmdlets sind zunächst nicht verfügbar. Der Mitarbeiter der Personalabteilung kann sich also eigentlich nur verbinden und die Verbindung wieder abbauen. Also selbst die Cmdlets aus dem geladenen AD-Modul stehen nicht zur Verfügung. Wird der Parameter nicht angegeben, stehen die Cmdlets aus Microsoft.PowerShell.Core (also die Standard Cmdlets ohne weitere Module) zur Verfügung. Allerdings kann der Benutzer dann mit Import-Module auf dem Server verfügbare Module nachladen und die Cmdlets daraus auch benutzen.

-LanguageMode NoLanguage könnten Sie sich eigentlich sparen, da dies die Standardeinstellung ist, wenn man beim –SessionType RestrictedRemoteServer angibt. NoLanguage hat zur Folge, dass man keine Skriptblöcke, Variable oder Operatoren verwenden kann. FullLanguage würde alle PowerShell Fähigkeiten zur Verfügung stellen.

-VisisbleCmdlets New-ADUserist der eigentliche Clou an der Geschichte. Damit kann eine Whitelist definiert werden, welche Cmdlets außer denen zum Verbindungsabbau zulässig sind. In diesem Fall soll es nur New-ADUser sein. Sie können aber auch in einer Komma getrennten Liste weitere Cmdlets angeben. New-ADUser funktioniert natürlich nur, wenn auch das PowerShell Modul für Active-Directory (dies haben Sie ja bei –ModulesToImport angegeben) geladen ist. Das Quoten (“…“)ist wegen der – Zeichen in den Cmdlets hier natürlich äußerst hilfreich.

–Path C:\NewUser.pssc gibt letztlich an, in welche Datei die Konfiguration geschrieben werden soll.

Es gibt wie üblich noch einen ganzen Sack voll mehr Parameter, die die ExecutionPolicy, Umgebungsvariablen und vieles andere mehr festlegen. Mehr dazu verrät natürlich wieder Get‑Help.

Erklärungsbedürftig ist noch der Schalter –FunctionDefinitions. Darüber können Sie in die Konsole zusätzliche Funktionen mit einbauen. Allerdings ist die Syntax nicht ganz so, wie Sie es von normalen Functions her gewohnt sind. Die Funktion ist als Hash-Wert zu definieren. Dies setzt sich aus 3 Teilen zusammen: Name, Scriptblock und Options. Wenn Sie z.B. die Funktion .. (bekannt von SUSE-Linux oder Novell-Servern um ein Verzeichnis im Dateisystem noch oben zu gehen) implementieren möchten müssten Sie das so schreiben:

–FunctionDefinitions @{Name=“..“;Scriptblock={cd ..}}

Das waren jetzt aber nur 2 Teile…Ja, die Options sind, wie der Name schon sagt optional. Details dazu finden Sie in der Hilfe.

Name legt den Namen der Function fest und mit Skriptblock definieren Sie den Code der ausgeführt werden soll.

3.2.7.6.3.2        Register-PSSessionConfiguration

Schritt 1 ist damit durch. Nun müssen Sie die eben erzeugte Konfigurationsdatei im 2. Schritt mit Register-PSSessionConfiguration registrieren:

Register-PSSessionConfiguration –Name Personalabteilung `
–RunAsCredential Domäne\AdminBenutzer –ShowSecurityDescriptorUI `
–Path C:\NewUser.pssc

Jegliche Rückfragen sind natürlich zu bejaen.

-Name Personalabteilung legt den Namen der Schnittstelle fest, den der Mitarbeiter später beim Verbindungsaufbau angeben muss. Beipiel:
Enter-PSSession –Computer Rechner1 –ConfigurationName Personalabteilung

–RunAsCredential Domäne\AdminBenutzer gibt an, dass der Benutzer in der Remotesitzung nicht mehr unter seinem auf dem Client angemeldeten Benutzer agiert, sondern mit dem hier definierten. User Mitarbeiter wird also innerhalb dieser Sitzung zum Domänen Administrator mit all den damit einhergehenden Berechtigungen. Er kann dann also auch Benutzer im Active-Directory anlegen, aber genauso gut auch löschen! Das einzige was Ihn von unbefugten Aktionen wie dem Löschen von Benutzern abhält ist die Tatsache, dass Sie in der Konfigurationsdatei nur New-ADUser, aber nicht z.B. Remove-ADUser zugelassen haben. Das Cmdlet steht ihm einfach nicht zur Verfügung. Sie sehen also wie extrem wichtig es ist, wenn man die ‑RunAsCredential Option nutzt, den Benutzer im Umfang der Cmdlets einzuschränken.

–ShowSecurityDescriptorUI legt fest, dass Sie mit Hilfe der grafischen Oberfläche bestimmen können, was welcher Benutzer in der Remotesitzung PowerShell seitig (nicht AD seitig) anstellen kann. Wenn Sie Ihrem Mitarbeiter der Personalabteilung also hier Berechtigungen einräumen, wird nur dieser sich auf den neu geschaffenen Endpunkt verbinden können. Da er an seinem Client Computer angemeldet, kein Administrator ist schlägt der Verbindungsaufbau zu den Standardendpunkten mit vollem Befehlssatz fehl. Er kann sich nur auf den von Ihnen erstellten Endpunkt verbinden, wird dort aber durch das –RunAsCredential zum Domänenadmin, der ausschließlich den Befehl New-ADUser einsetzen kann.

–Path C:\NewUser.pssc stellt die Verbindung zu der mit New‑PSSessionConfigurationFile erstellen Konfigurationsdatei her.

Auch für dieses Cmdlet gibt es natürlich wieder gefühlte 10 Mio. Schalter mehr, deren Beschreibung Sie gerne Get-Help entlocken können.

Möchten Sie einen Endpunkt nachträglich ändern möchten hilft Ihnen dabei Set‑PSSessionConfiguration und entfernen können Sie den Endpunkt natürlich auch wieder mittels Unregister-PSSessionConfiguration.

3.2.8   Kontrollfragen

Über welches Protokoll und welchen Port läuft die Kommunikation von WinRM?

Wie aktivieren Sie auf einem einzelnen PC die Remotefunktion der PowerShell?

Wo können Sie feststellen, welche Rechner ausserhalb eines Active-Directory sich auf Ihr System remote verbinden dürfen?

Wie lautet das Cmdlet, um einen einzelnenBefehl, eine Verarbeitungskette oder  ein Skript einmalig auf einem anderen PC auszuführen?

Wenn Sie mehrere unterschiedliche Dinge auf einem anderen PC erledigen wollen, wie können Sie die Konsole auf einen anderen PC umschalten?

Wie können Sie eine Remotesitzung wieder beenden?

Sie müssen mehrere Informationen zum Verbindungsaufbau angeben. Wie machen Sie das am besten?

PowerShell 2.0: Remotezugriff

3.2.9   Jobs

Wie Sie mit Jobs ein einfaches Multitasking, sprich parallele Verarbeitung von Aufgaben, durchführen können ist in diesem Kapitel zu lesen.

Einige Cmdlets wie z.B. Get-WMIObject verfügen über den Schalter –AsJob. Damit wird dieses Cmdlet als Job oder zu Deutsch als Aufgabe im Hintergrund abgearbeitet. Die Ausgabe erfolgt dann nicht auf dem Bildschirm. Der Vorteil dabei ist, dass die Kommandozeile sofort wieder freigegeben wird und Sie nicht auf das Ergebnis warten müssen. Stellt sich nur die Frage: Wo bleibt dann das Ergebnis?

Mit dem Cmdlet Get-Job können Sie sich eine Liste bereits vergebener Jobs erstellen was. Im Moment dürfte da noch nichts weiter zurückgegeben werden, da Sie noch keinen Job erstellt haben.

Wie gesagt sind einige Cmdlets mit einem speziellen Schalter ausgestattet um als Job abzulaufen. Sie können jedoch auch ganze Skripte oder Befehlsketten mit Cmdlets, die nicht über einen solchen Schalter verfügen als Hintergrundaufgabe laufen lassen, mithilfe des Cmdlets Start-Job.

Start-Job {sleep 60;ls c:\}

Diese Zeile hätte zur Folge, dass alles was in den gescheiften Klammern steht, im Hintergrund in einem weiteren PowerShell-Prozess ausgeführt wird. In diesem Falle, um etwas Zeit tot zu schlagen, haben Sie hier den Alias Sleep verwendet, der auf das Cmdlet Start-Sleep deutet und 60 Sekunden lang einfach gar nichts tut. Durch das Semikolon wird nach dem ersten Befehl ein weiterer ausgeführt, der Ihnen den Verzeichnisinhalt von Laufwerk C: anzeigt. Dadurch, dass der Job in einer separaten Konsole läuft vererben sich auch keine Variablen in die Jobs. Sie haben also dasselbe Problem wie bei einer RemoteShell. Aber auch hier gibt es wie beim Invoke-Command einen Schalter –ArgumentList mit dem Sie Variablen Inhalte an Jobs übergeben können. Innerhalb des Jobs holen Sie diese dann über den Array $args ab (siehe auch: Cmdlets oder Skripte auf mehreren Rechnern ausführen lassen).

Mit Get-Job können Sie sich nun jederzeit informieren, ob die Aufgabe inzwischen fertig ist. In der Spalte State sollte für eine Minute lang Running stehen und dann zu Completed wechseln. Wenn Sie sehen, dass der Status gewechselt hat, wissen Sie, dass der Job fertig ist. Die Spalte HasMoreDate sagt beim Wert True aus, dass Daten zur Abholung bereit stehen.

Wenn Sie sich nun die Daten abholen möchten, können Sie das mit dem Cmdlet Receive-Job tun:

Receive-Job 1

In der ersten Spalte der Liste von Get-Job wird die Jobnummer angezeigt und in der zweiten Spalte der Job Name. Diese beiden Werte werden automatisch vergeben, es sei denn Sie geben durch zusätzliche Schalter beim Cmdlet Start-Job eine andere ID oder Namen vor. Das Cmdlet Receive-Job 1 holt daher die Ergebnisse des Jobs mit der ID 1 ab und zeigt diese auf der Standardausgabe (dem Bildschirm) an. Wenn Sie nun noch einmal Get-Job ausführen sehen Sie in der Spalte HasMoreDate hat sich der Status auf False geändert. Wenn Sie also einmal die Daten abgeholt haben sind diese nicht mehr im Job enthalten. Wenn Sie nun erneut versuchen Receive‑Job 1 auszuführen, wird nichts weiter passieren, da der Job eben keine Daten mehr hat. Der Job ist nun nutzlos geworden und kann daher gelöscht werden. Dazu können Sie das Cmdlet Remove-Job verwenden:

Remove-Job 1

Ein Weiteres Get-Job zeigt Ihnen, dass der Job nun nicht mehr in der Liste geführt wird.

Die Jobs laufen nur so lange im Hintergrund weiter, solange die Konsole von der Sie gestartet wurden läuft. Wenn Sie also einen Job starten und die Konsole schliessen, dann ist damit auch der Job und das Ergebnis davon weg!

Wenn Sie die Ergebnisse aus einem Job mehrfach abrufen möchten können Sie das auch, indem Sie beim Abholen des Jobs den Schalter –Keep mit angeben:

Receive-Job 1 –Keep

Wenn Sie vorher eine neue Konsole gestartet und einen neuen Job mit der ID 1 erstellt haben, können Sie die Daten mit dem eben angebebenen Beispiel beliebig oft abrufen. Solange bis Sie einmal den Schalter –keep weglassen.

Anstatt in einer Endlosschleife immer wieder den Jobstatus abzufragen, wann er denn endlich fertig ist, sollten Sie lieber das Cmdlet Wait-Job verwenden, da es weniger Rechnenaufwand für den PC darstellt. Starten Sie doch noch einmal einen Job wie oben und dann lassen Sie die Shell einfach so lange warten bis Job Nr. 1 (oder welche Nummer Sie inzwischen auch immer haben mögen) fertig ist:

Wait-Job 1

Wenn Sie möchten, starten Sie dabei den Taskmanager und schauen Sie doch einmal auf die CPU-Last.

Wenn Sie mehrere Jobs gestartet haben und warten möchten bis alle fertig sind:

Get-Job | Wait-Job

Achtung! Leider sind Jobs nicht ganz so einfach zu nutzten, wie z.B. Funktionen. Wie Sie wissen vererben sich alle Funktionen und Variablen eines Hauptskripts automatisch in die Funktionen. Das gilt nicht für Jobs. Ein Job ist quasi wie eine separat gestartete PowerShell-Console zu verstehen. Alles was innerhalb des Jobs aus dem aufrufenden Skript im Job zur Verfügung haben möchten, müssen Sie mit Hilfe des Schalters –ArgumentList mitteilen bzw. übergeben. Eine Übergabe von Funktionen ist so z.B. nicht möglich. Die könnten Sie aber in ein separates Skript schreiben und dann mittels Dot-Sourcing sowohl im Hauptscript, als auch im Job einbinden. ;-)

3.2.9.1   Jobs und der Schalter –Credential

Mit –Credential (Get-Credential) können Sie den Inhalt des Jobs bequem unter einem anderen Benutzer ablaufen lassen. Leider gibt es da wohl einen Bug in der Form:

Lösung:

[environment]::CurrentDirectory='c:\temp'

Einfach direkt nach den Param() Block diese Zeile ^^ einbauen und alles wird gut ;-).

3.2.10                     Kontrollfragen

Mit welchem Cmdlet können Sie einen Befehl im Hintergrund ausführen lassen?

Wie können Sie feststellen welche Hintergrundjobs aktuell vorliegen?

Wie können Sie sich die Ausgabe eines Hintergrundjobs anzeigen lassen und wie sorgen Sie dafür, dass Sie das Ergebnis mehrfach abholen können?

Wie können Sie einen Hintergrundjob abbrechen?

Wie können Sie einen Hintergrundjob aus der Liste löschen?

PowerShell 2.0: Jobs

3.2.11                     Ereignisse

Mit sogenannten Events (Ereignissen) können Sie Ihr Skript über Dinge die woanders (ausserhalb Ihres Skriptes) passieren informieren lassen. Stellen Sie sich vor Sie möchten wissen, ob sich in einem bestimmten Verzeichnis auf Ihrer Festplatte etwas ändert. Dann müssten Sie ein Skript schreiben, dass in einer Endlosschleife, den Dateiinhalt abfragt und mit der Vorgängerversion vergleicht. Ich schätze, dass würde selbst einen CoreDuo ca. 50% Rechenzeit kosten. Effizienter ist das .NET damit zu beauftragen, dass wenn sich etwas in dem Verzeichnis zuckt, Ihrem Skript sofort ein Signal zu schicken. Das Überwachen solcher Signale funktioniert ohne Schleifemithilfe eines Jobs und braucht daher auch kein permanentes interpretieren des PowerShellSkriptCodes und somit keine Rechenleistung.Der Job wird automatisch gestartet, wenn das Ereignis stattfindet. Wie man diese Tricks einsetzt, erfahren Sie hier am Beispiel einer Prozessüberwachung.

Starten Sie notepad und tippen Sie danach die folgende Zeile:

$Tracker=ps | ? {$_.name -like "notepad"}

$Tracker erstellt ein Objekt vom Notepad Process. Wenn Sie mögen können Sie gerne einmal $Tracker an Get-Member mithilfe der Pipe übergeben und sich die Details zu diesem Objekt ansehen:

$Tracker | Get-Member

Sie werden feststellen, dass es dort nicht nur Methoden und Eigenschaften, sondern auch ein paar Einträge mit dem MemberType Event gibt. Diese Events (Disposed, ErrorDataReceived, Exited, OutputDataReceived) können überwacht werden.

$Aktion={Start-Process wordpad}

In der Variablen $Aktion wird Skriptcode hinterlegt, der ausgeführt werden soll, wenn ein bestimmtes Ereignis eintritt. Hier steht einfach nur, dass ein Prozess Namens Wordpad gestartet werden soll.

Register-ObjectEvent -InputObject $Tracker -EventName Exited -Action $Aktion

Dann registrieren Sie die Objektüberwachung mittels des Cmdlet Register-ObjectEvent als Job. Wer überwacht werden soll, geben Sie mit dem Schalter -InputObject an. Das zu überwachende Objekt muss dazu über entsprechende Event MemberTypes verfügen. In diesem Falle verwenden Sie $Tracker mit dem Notepad Prozess Objekt. Mit dem Schalter -EventName geben Sie den MemberType des Ereignisses an. Hier soll die Aktion starten, wenn das Programm beendet wird, also überwachen Sie das Ereignis Exited (Event aus der Spalte MemberType). Der Schalter ‑Action legt noch fest was geschehen soll, wenn das Ereignis eintritt. Entweder schreiben Sie es in geschweiften Klammern direkt hinter den Schalter, oder wenn es etwas mehr Code ist, tragen Sie hier einfach eine Variable ein. Die Variable haben Sie hier bereits in Form von $Aktion vorbereitet. Tja, nun schliessen Sie doch einmal das Notepad und voilà Wordpad wird gestartet.

Wichtig zu wissen ist, dass an dieser Stelle eine separate Sitzung gestartet wird. Wenn Sie in dem Code nach dem Schalter –Action  Text ausgeben, hat das nichts mehr mit der PowerShell Konsole zu tun, an der Sie die Eingaben machen. Sie können allerdings mit dem Cmdlet Write-Host explizit in die Konsole schreiben lassen. Aber einfach nur einen Text in Anführungszeichen klappt nicht, da die separate Sitzung nicht mehr die PowerShell ist, landet die Ausgabe im Job. Sie wissen schon…Receive-Job aus dem vorangegangenen Kapitel…Wenn Sie möchten können Sie auch gleich eine Job Verfolgervariable anlegen:

$Job=Register-ObjectEvent -InputObject $Tracker -EventName Exited -Action $Aktion

Dann könnten Sie die Rückgabewerte Ihres “Aktionscodes” abholen indem Sie einfach tippen:

Receive-Job $Job

Wenn Sie auf das Eintreten eines Ereignisses warten möchten können Sie dies entsprechend mit:

Wait-Job $Job

Eine Beschreibung zu New-Event spare ich mir, da es nur rudimentär unterstützt wird. So ist es z.B. nicht möglich an ein eigens erstelltes PSCustomObject benutzerdefinierte Events anzuhängen.

Weil ich gerade noch einmal die Jobs erwähnt habe…wie wäre es mit einem sich selbst terminierenden Job?

 

Register-ObjectEvent $Job -EventName StateChanged -Action {

    if ($eventArgs.JobStateInfo.State -eq [System.Management.Automation.JobState]::Completed) {

     # Löscht den original Job

     $sender | Remove-Job -Force

     # und die Event Registration

     $eventSubscriber | Unregister-Event -Force

     $eventSubscriber.Action | Remove-Job -Force

    }

   }

 

3.2.12                     Transaktionen

Den Begriff Transaktion kennen Sie sicher von Ihrer Bank. Wenn man von Ihrem Konto abbucht ist das Geld erst einmal von Ihrem Konto weg und erst danach wird es auf dem Empfänger-Konto gutgeschrieben. Stellen Sie sich vor genau dazwischen stürzen die Bankcomputer ab.  Bei Ihnen ist das Geld weg, aber beim Empfänger noch nicht eingegangen. Das wäre doch echt ärgerlich! Die Bank arbeitet mit sogenannten Transaktionen. Das bedeutet ein Vorgang ist entweder in seiner Gesamtheit abgeschlossen, oder wird zurückgesetzt. Im eben beschrieben Falle würde der Betrag auf Ihrem Konto also wieder gut geschrieben werden und Sie könnten die Buchung erneut versuchen. Man spricht auch von Transaktionssicherheit. In PowerShell 2.0 werden leider nur Zugriffe auf die Registrierdatenbank transaktionssicher unterstützt. Selbst beim Dateisystem ist diese Funktionalität leider nicht verfügbar, ganz zu schweigen von kompletten Skriptabschnitten.

Sie können damit also Beispielsweise einen, oder mehrere Registrierschlüssel anlegen und darin entpsrechende Einträge. Wenn das nicht nur 2 oder 3 sind, sondern richtig viele, dann kann das ein Weilchen dauern. Wenn Ihnen genau da mittendrin der PC abstürzt, kann das ziemlich häßlich werden. Wenn Sie die Zugriffe mit Transaktionen versehen, ist das halb so wild, weil alle „halb“ durchgeführten Änderungen zurück genommen werden und man wieder bei derselben Ausgangssituation erneut versuchen kann die Einträge zu schreiben.

$Key="HKCU:\Software\TransactionTest"

New-Item $Key

1..500 | Foreach {New-ItemProperty $Key -Name $_ -Value $_}

In der ersten Zeile des Skriptes wird in der Variablen $Key Der Pfad zu einem Schlüssel in der Registrierdatenbank hinterlegt. Wenn Sie mögen, starten Sie das Programm Regedit und schauen Sie doch einmal nach. Der Schlüssel Software ist in jeder Registry vorhanden, jedoch nicht der Schlüssel TransactionTest darunter. Durch die zweite Zeile New-Item $Key wird der Schlüssel TransactionTest in der Registry erstellt. Die letzte Zeile generiert Zahlen von 1 bis 500 und gibt diese über eine Pipe an eine Foreach Schleife. Dadurch wird der Befehl in den geschweiften Klammern 500 Mal wiederholt. New-ItemProperty legt einen Wert im zuvor erstellten $Key mit dem Namen und Wert $_ an. $_ enthält bei jeden Durchlauf die entsprechende Zahl. Also zunächst eine 1, dann eine 2, dann 3 usw. bis 500. Somit werden also 500 durchnummerierte Einträge in den Registrierschlüssel geschrieben. Das Problem bei diesem Skript liegt darin, dass wenn etwas schief geht ein Haufen Fehler produziert werden, wenn man versucht es noch einmal zu starten. Lassen Sie es einmal komplett durchlaufen. Wenn Sie dabei merken, dass es auf Ihrem Rechner zu schnell, oder zu langsam geht, können Sie für die weiteren Versuche einfach die 500 gegen z.B. 50 oder 5000 austauschen. Dementsprechend werden mehr oder weniger Einträge geschrieben. Wenn das Skript komplett durchgelaufen ist, öffnen Sie regedit, gehen Sie in HKEY_CURRENT_USER, klappen Sie den Eintrag Software auf und suchen Sie nach dem Eintrag TransactionTest. Schauen Sie einmal hinein und löschen Sie anschliessend den Schlüssel TransactionTest über das Programm regedit.

Jetzt starten Sie das Skript noch einmal und brechen Sie es mitten drin einfach mit Strg+C ab. Damit simulieren wir einen Absturz oder Fehler. Starten Sie ein weiteres Mal. Ist häßlich, oder? Das Skript versucht nun Einträge anzulegen, die es bereits gibt und es hagelt Fehlermeldungen. Bei anderen Registriereinträgen könnten natürlich noch weitaus schlimmere Sachen passieren, als ein paar rote Fehlermeldungen in einem PowerShellSkript.

Nun stellen Sie das Ganze auf Transaktionen um:

$Key="HKCU:\Software\TransactionTest"

Start-Transaction -Timeout 2

New-Item $Key -UseTransaction

1..500 | Foreach {New-ItemProperty $Key -Name $_ -Value $_ -UseTransaction}

Complete-Transaction

Die erste Zeile bleibt unverändert. In der zweiten Zeile geben Sie an, dass Sie von hierab entweder alle Einträge schreiben, oder alles wieder zurückgesetzt wird, wenn etwas schief läuft. Der Schalter ‑Timeout 2 sogar dafür, dass die nachfolgenden Aktionen innerhalb zwei Minuten abgeschlossen sein müssen, ansonsten wird auch wieder alles zurückgesetzt. Das ist hilfreich wenn das Skript sich irgendwie tot läuft. Die dritte Zeile kennen Sie bereits. Allerdings ist hier noch ein zusätzlicher Schalter –UseTransaction angegeben, der besagt, dass diese Aktion zur Transaktion dazugehört. Auch die vorletzte Zeile ist gleich, abgesehen wieder vom Schalter ‑UseTransaction. Das abschliessende Complete-Transaction am Ende des Skripts schaltet die gesamten Änderungen auf einmal „Live“ und beendet damit die Transaktion. Bitte beachten Sie, dass dies nur mit Registriereinträgen passiert. Das bedeutet, selbst wenn Sie dieselben Befehle verwenden, allerdings auf das Dateisystem, geht dies nicht! Nur die Registrierdatenbank unterstützt derzeit Transaktionen.

Probieren Sie das Skript aus, indem Sie wieder mittendrin einfach Strg+C drücken. Mit Get‑Transaction sehen Sie, dass die Transaction beim Status RolledBack anzeigt. Das bedeutet, dass die Änderungen zurück genommen wurden. Sie können das Skript nun einfach erneut laufen lassen und zwar ohne Fehlermeldungen!

Wenn Sie möchten können Sie die Transaktion auch manuell zurücksetzen lassen, durch dass Cmdlet Undo-Transaction. Auch zu diesen Cmdlets gibt es natürlich wieder einige zusätzliche Schalter die es zu erforschen gilt, wenn Sie das Verhalten noch anpassen möchten.

3.3     PowerShell 3.0

Hier werden die Neuerungen der Powershell 3.0 erklärt.

3.3.1   Hilfe

Die Hilfe von PowerShell 3.0 hat leider wieder einige Nachteile mit sich gebracht. Denn Sie funktioniert erst einmal nur halb, weil der Rest im Internet liegt. Damit dies annähernd an die Qualität von Verison 2.0 heranreicht, benötigen Sie also eine Internetverbindung. Viel Spaß wenn Sie mal eben schnell die Hilfe auf einem Server in einem abgeschotteten Netzwerk lesen wollen.

Natürlich funktioniert hier auch die Ausführung der Cmdlets genauso wie in Version 2.0. Nur leider bekommen Sie nur die Hälfe an Informationen, nämlich das was aus der Programmierung hervorgeht. Das wertvollste, die Examples (Beispiele), können Sie also schon einmal vergessen.

Die erweiterten Hilfetexte wurden angeblich aus Gründen der Aktualität ins Internet verfrachtet. Leider hat man dies bislang nur für die Englischsprachige Version der PowerShell gemacht. Andere Sprachen fehlen komplett, sprich es gibt gar keine erweiterte Hilfe, was natürlich gerade für den Einsteiger ganz übel ist.

3.3.1.1   Get-Help funktionstüchtig machen

Zunächst einmal kann sich die Online Hilfe in einer Englischen Version mit diesem Cmdlet herunterladen (Achtung UAC - muss mit erhöhten Rechten gestartet werden):

Update-Help

Vielleicht wird das auch irgendwann einmal in der Deutschen Fassung klappen. So lange können Sie sich mit einem billigen Trick weiterhelfen. Wenn Sie irgendwo eine Englische Version „herumliegen“ haben, kopieren Sie sich einfach die Inhalte von

C:\Windows\System32\WindowsPowerShell\v1.0\en-US
nach
C:\Windows\System32\WindowsPowerShell\v1.0\de-DE

Dann können Sie zumindest die Englische Hilfe lesen. Haben Sie keine Englische Version greifbar, können Sie auch die Englische Hilfe in der Deutschen Version herunterladen mittels:

Update-Help –UICulture en-us

Damit bekommen Sie das Unterverzeichnis en-US wie oben beschrieben und kopieren sich einfach wieder die Inhalte von en-US nach de-DE.

Alternativ können Sie mit Save-Help auch direkt in de-DE die Englische Version herunterladen:

Save-Help –UICulture en-us –DestinationPath “C:\Windows\System32\
WindowsPowerShell\v1.0\de-DE

Auf die Schnelle mal Infos zu einem einzelnen Befehl bekommen Sie mit:

Get-Help Get-Childitem –Online

Daraufhin öffnet sich Ihr standard Webbrowser und stellt den Inhalt, den Sie auch mit dem Schalter ‑full bekommen würden auf Englisch im Browser dar.

3.3.2   Show-Command

Dieses Cmdlet öffnet ein grafisches Fenster mit allen Parametern des Cmdlet, aber auch den CommonParameters. Dann kann man sich mit der Maus seinen Befehl zusammen klicken. Nicht wirklich spannend, aber vielleicht für einen Einsteiger hilfreich:

Show-Command Get-ChildItem

3.3.3   Vereinfachte Schreibweisen

Achtung! Wenn Sie diese Schreibweisen verwenden, sind Ihre Skripte nicht mehr abwärtskompatibel und können nur noch in Version 3.0 ausgeführt werden. Des Weiteren ist noch zu erwähnen, dass dies nur bei einfachen Abfragen zuverlässig funktioniert. Sobald Sie mit mehreren Vergleichs-Operatoren hantieren, empfehle ich aus Sicherheitsgründen die Verwendung der auf den alten Versionen bekannten Schreibweisen. In Skripten rate ich komplett von der Verkürzung ab.

3.3.3.1   Where-Object

Statt

Where-object {$_.EigenschaftVergleichsoperatorBegriff“}

können Sie ja bekannter Maßen auch

? {$_.EigenschaftVergleichsoperatorBegriff“}

schreiben. In PowerShell 3.0 können Sie das noch toppen mit:

? EigenschaftVergleichsoperatorBegriff

Die geschweiften Klammer und das $_. können also entfallen. Praxisbeispiel:

ls c:\windows | ? length –gt 1000

Die Sache hat aber auch einen kleinen Haken. Sie können immer nur einen einfachen Vergleich machen. Mehrere logisch miteinander verknüpfte Vergleiche wie mit –and bzw. –or funktionieren nicht. So etwas schlägt also fehlt:

ls c:\windows | ? length –gt 1000 –and length –lt 2000

Das müssen Sie also nach wie vor ausschreiben:

ls c:\windows | ? {$_.length –gt 1000 –and $_.length –lt 2000}

3.3.4   Neue Systemvariable

Auch ganz toll: Statt $_ dürfen Sie nun auch $PSItem schreiben. Das Ergebnis ist dasselbe, nur ist die Schreibweise $PSItem natürlich inkompatibel mit Vorgängerversionen. Also vergessen sie den Quatsch am besten gleich wieder.

3.3.4.1   Foreach

Auch bei Foreach können Sie mit dem Alias % abkürzen. Wenn in einem Verzeichnis C:\test mindestens zwei Dateien ablegen, die Sie anschließen mit einer .NET-Methode löschen möchten, so lautet die Befehlskette:

ls c:\test | foreach {$_.delete()}

In Powershell 3.0 könnte das Ganze auch so ausschauen:

ls c:\test | % delete

Ähnlich wie bei Where-Object können hier die geschweiften Klammern und das $_. entfallen. Bei .NET-Methodenaufrufen sogar noch die runden Klammern.

3.3.5   Workflows

Ein Workflow ermöglicht Aufgaben quasi „gleichzeitig“ ablaufen zu lassen. Der Grunsätzliche Aufbau von Workflows ähnlet dem von Funktionen. Allerdings können Sie innerhalb eines Workflows steuern was nacheinander und was „zeitgleich“ ablaufen kann:

Workflow NameDesWorkflow {

 Parallel {

  Get-wmiObject –Class Win32_Operatingsystem

  Get-Service | select –first 3

  Get-Process | select –first 3

 }

}

Wenn Sie innerhalb eines Parallel Blocks gewisse Befehle in einer bestimmten Reihenfolge abarbeiten möchten, weil z.B. Das Ergebnis eines vorangegangenen Befehls in einem nachfolgenden Befehl weiterverarbeitet wird, können Sie diese Befehle noch einmal in einem Sequence {…Ihr Code…} Block einschliessen.

Das klingt nun alles wunderbar nach Multithreading und Performancesteigerung, aber Achtung! Geben Sie den oberen Abschnitt doch noch einmal als Funktion ein:

Function NameDerFunktion {

 Get-wmiObject –Class Win32_Operatingsystem

 Get-Service | select –first 3

 Get-Process | select –first 3

}

Dann führen Sie einmal beide in ein Measure-Command eingewickelt aus:

Measure-Command {NameDesWorkflow}

Measure-Command {NameDerFunktion}

Na, überrascht? Der Aufbau der Umgebung für diese Form der Verarbeitung schluckt auch einiges an Rechenzeit, daher sollten Sie sich gut überlegen, wo der Einsatz wirklich mehr Leistung bringt. Hier ein Skript, bei dem der Einsatz von Workflows tatsächlich einen ordentlichen Geschwindigkeitsschub bringt:

10  # quick subnetcheck for PowerShell 3.0 by Martin Lehmann

11  # Example:

12  # .\subnetchk.ps1 10.2.182.0/23

13  # lasts not even 4 Min. with 3/4 not reachable (offline)!

14  param (

15   [parameter(Mandatory=$true)]

16   [string]

17   [ValidatePattern("10\.[1-2]?[0-9]?[0-9]\.[1-2]?[0-9]?[0-9]\.[1-2]?[0-9]?[0-9]/[0-9]?[0-9]")]

18   $subnet

19  )

20  workflow CheckNet {

21   param ([String]$Net,[byte]$Okt,[byte[]]$c)

22   if ($Okt -eq 4) {

23    foreach -parallel ($i in $c) {

24     inlinescript {

25      $Obj=New-Object -typename pscustomobject

26      $Obj | Add-Member -MemberType NoteProperty -Name "IP" -Value "$($Using:Net).$($Using:i)"

27      try {

28       Test-Connection -computer "$($Using:Net).$($Using:i)" -count 1 -ea Stop > $null

29       $Name=(ping -n 1 -a "$($Using:Net).$($Using:i)")[1].split(" ")[1]

30       $Obj | Add-Member -MemberType NoteProperty -Name "Status" -Value "available"

31       $Obj | Add-Member -MemberType NoteProperty -Name "ComputerName" -Value "$Name"

32      }

33      catch {

34       $Obj | Add-Member -MemberType NoteProperty -Name "Status" -Value "not available"

35       $Obj | Add-Member -MemberType NoteProperty -Name "ComputerName" -Value "N/A"

36      }

37      $Obj

38     }

39    }

40   }

41   if ($Okt -eq 3) {

42    foreach ($i in $c) {

43     foreach -parallel ($i2 in 0..255) {

44      if (!(($i2 -eq 0) -and ($i -eq $c[0])) -and !(($i2 -eq 255) -and ($i -eq $c[-1]))) {

45       inlinescript {

46        $Obj=New-Object -typename pscustomobject

47        $Obj | Add-Member -MemberType NoteProperty -Name "IP" -Value "$($Using:Net).$($Using:i).$($Using:i2)"

48         try {

49          Test-Connection -computer "$($Using:Net).$($Using:i).$($Using:i2)" -count 1 -ea Stop > $null

50          $Obj | Add-Member -MemberType NoteProperty -Name "Status" -Value "available"

51          $Name=(ping -n 1 -a "$($Using:Net).$($Using:i).$($Using:i2)")[1].split(" ")[1]

52          $Obj | Add-Member -MemberType NoteProperty -Name "ComputerName" -Value "$Name"

53         }

54         catch {

55          $Obj | Add-Member -MemberType NoteProperty -Name "Status" -Value "not available"

56          $Obj | Add-Member -MemberType NoteProperty -Name "ComputerName" -Value "N/A"

57         }

58         $Obj

59        }

60       }

61      }

62     }

63    }

64   }

65   [byte]$mask=$subnet.split("/")[1]

66   $net=$subnet.split("/")[0]

 

67   if ($mask -lt 16) {throw "Subnetmask < 16 currently not supported!"}

68   if ($mask -gt 32) {throw "Subnetmask > 32? IPv6 currently not supported!"}

69   if ($mask -ge 24) {

70   $hosts=[math]::pow(2,(32-$mask))

71   $nets=[math]::pow(2,($mask-24))

72   $GoodNet=$false

73   $start=$net.split(".")[3]

74   for ($i=0; $i -lt $nets;$i++) {

75    if ($start -eq ($hosts*$i)) {

76     $GoodNet=$true

77    }

78   }

79   if (!$Goodnet) {throw "Wrong Subnetmask"}

80   Checknet $Net.substring(0,$Net.lastindexof(".")) 4 (([byte]$start+1)..([byte]$start+$hosts-2)) | select IP,Status,ComputerName | sort @{Expression="Status";Descending=$false},@{Expression="IP";Descending=$false}

81   }

82   if ($mask -lt 24 -and $mask -ge 16) {

83   $loops=[math]::pow(2,(24-$mask))

84   $nets=[math]::pow(2,($mask-16))

85   $GoodNet=$false

86   $start=$net.split(".")[2]

87   for ($i=0; $i -lt $nets;$i++) {

88    if ($start -eq ($loops*$i)) {

89     $GoodNet=$true

90    }

91   }

92   if (!$Goodnet) {throw "Wrong Subnetmask"}

93   $Net=$Net.substring(0,$Net.lastindexof("."))

94   Checknet $Net.substring(0,$Net.lastindexof(".")) 3 (([byte]$start)..([byte]$start+$loops-1)) | select IP,Status,ComputerName | sort @{Expression="Status";Descending=$false},@{Expression="IP";Descending=$false}

95  }

Das Skript pingt ein komplettes Subnetz durch. Da hier auf Timeouts gewartet werden muss, ist dies bei parallelem anstarten der Verbindungsversuche effizienter, als für jede einzelne Station auf Rückmeldung zu warten, bevor man die nächste Station angeht. Wenn Sie wollen, schreiben Sie das oben angegebene Skript doch einmal zu einer Funktion um. Nehmen Sie aber ein kleines Subnetz (z.B.: /28) zum testen!

Hierbei lernen Sie auch gleich noch ein paar Eigenheiten von Workflows kennen:

1.       Sie dürfen keine Aliase verwenden.

2.       Alle Parameter müssen mit ausgeschriebenem Bezeichner an ein Cmdlet übergeben werden.

3.       In Workflows werden ein paar spezial Formen von Cmdlets unterstützt, wie z.B.: Foreach –Parallel. Bei Foreach –Parallel wird die Schleife nicht nacheinander, sondern parallel abgearbeitet. Bei eine /24 Netzwerk, werden also alle 254 Stationen mehr oder weniger zeitgleich versucht zu erreichen, nicht nacheinander!

4.       Nicht alle Cmdlets und Befehlsabläufe sind Workflow tauglich. So z.B. ein Umleitungsoperator führt zu dieser Fehlermeldung:
Diese Probleme können Sie durch einschliessen der Anweisung in einen InlineScript {} Block umgehen. Leider stehen in diesem Block aber die zuvor gesetzten Variablen nicht zur Verfügung. Doch auch hier gibt es einen Workaround. Haben Sie beispielsweise eine Variable $a=10 vor dem InlineSkript {} Block gesetzt, können Sie innerhalb des SkriptBlock durch Ergänzung des Zauberwortes Using: doch darauf zugreifen. Beispiel:
$a=10
InlineSkript {
 $Using:a=$Using:a+10
}
$a

3.3.6   Scheduled Jobs

Scheduled Jobs unterscheiden sich von den „normalen“ Jobs die mit PowerShell 2.0 eingeführt wurden (Kapitel: PowerShell 2.0, Abschnitt: Jobs) dadurch, dass Sie nicht im RAM der aktuellen PowerShell Sitzung gehalten werden, sondern auf der Festplatte hinterlegt werden. Das hat den Vorteil, dass Sie also beruhigt die aktuelle Konsole schließen können, ohne dass dadurch auch der Job abgebrochen wird. Sie können sogar den Computer rebooten.

3.3.6.1   Job Trigger festlegen

Zunächst sollten Sie sich Gedanken machen zu welchem Zeitpunkt ein Skript ausgeführt werden soll. Diesen Zeitpunkt können Sie mithilfe des Cmdlets New-JobTrigger festlegen. Zur Verfügung stehen folgende Parameter und damit entsprechende Zeitdefinitionen:

-Once
Einmalig

-Daily
Täglich

-Weekly
Wöchentlich

-AtLogOn
Beim Anmelden eines Benutzers

-AtStartup
Wenn der PC bootet

Lieder gibt es weder einen Trigger(Auslöser) für Abmelden noch Herunterfahren.

Um einen ganz einfachen Trigger für die tägliche Aktivierung um 9:00 Uhr zu erstellen und in einer Variablen zu hinterlegen, geben Sie bitte folgendes ein:

$timetrigger=New-JobTrigger -Daily -At "9:00 AM"

Selbstverständlich dürfen Sie Ihrer Neugier auf hier wieder freien Lauf lassen:

$timetrigger | select *

Mehr über die unterschiedlichen Möglichkeiten einen Trigger zu definieren verrät Ihnen wieder:

help New-JobTrigger –full

3.3.6.2   Scheduled Job anlegen

Einen Auslöser haben Sie im vorangegangenen Abschnitt erstellt ($timetrigger). Nun können Sie eine Aktion mit diesem Auslöser durch das Cmdlet Register-ScheduledJob verknüpfen. Dies können ein paar simple PowerShell Cmdlets (z.B.: Get-Process) sein:

Register-ScheduledJob -Name ScheduledJob -ScriptBlock {Get-Process} -Trigger $timetrigger

…oder aber auch ein ganzes Skript:

Register-ScheduledJob -Name ScheduledJob -FilePath C:\IhrSkript.ps1 -Trigger $timetrigger

Mit dem Schalter –ArgumentList können Sie auch gerne wieder Daten an den Job übergeben.

Durch den Schalter -ScheduledJobOption können Sie weitere Rahmenbedingungen festlegen unter denen der Job ausgeführt wird. Entweder geben Sie direkt nach dem Schalter die Parameter in Form eines Hash an:

@{WakeToRun; StartIfNotIdle}

…oder übergeben eine Variable, die Sie mit dem Cmdlet New-ScheduledJobOption definieren können. Welche Optionen alle zur Verfügung stehen verrät:

help New-ScheduledJobOption –full

Hat man den Job nicht über die ScheduledJobOptions „versteckt“ findet man Ihn im Taskscheduler (der Aufgabenplanung) wieder:

3.3.6.3   Einem Scheduled Job mehrere Trigger zuordnen

Selbst verständlich können Sie einem Scheduled Job auch mehrere Auslöser hinzufügen:

Add-JobTrigger -Trigger (New-JobTrigger -AtStartup) -Name NameDesBereitsExisitierendenJobs

3.3.6.4   Scheduled Jobs und Jobtrigger ändern und löschen

Selbstverständlich können Sie die angelegten Scheduled Jobs und deren Trigger auch nachträglich verändern oder löschen.

Zum Abrufen der Konfiguration stehen Ihnen diese Cmdlets zur Verfügung:

Get-JobTrigger

Get-ScheduledJob

Get-ScheduledJobOption

Zum abändern bieten sich diese Cmdlets an:

Set-JobTrigger

Set-ScheduledJob

Set-ScheduledJobOption

Um Scheduled Jobs oder deren Trigger zu pausieren:

Disable-JobTrigger

Disable-ScheduledJob

Enable-JobTrigger

Enable-ScheduledJob

Und letztendlich zum löschen:

Remove-JobTrigger

Unregister-ScheduledJob

Wenn Sie das Buch bis hierhin aufmerksam gelesen haben, erspare ich mir die Kommandos hier im Detail zu erläutern. Schließlich will ich Sie auch nicht langweilen. Lesen Sie ggf. noch einmal den Abschnitt Jobs im Kapitel PowerShell 2.0.

3.3.7   PSSession Disconnect

Ab Version 3.0 können Sie RemoteSitzungen (Grundlagen siehe Kapitel PowerShell 2.0 Abschnitt Remote Zugriff) auch vorübergehend trennen, um sich später wieder damit zu verbinden.

Mittels Get-PSSession Computername können Sie sich die auf dem angegebenen System existierenden Sitzungen anzeigen lassen und ob diese Sitzungen aktuell verbunden sind.

Disconnect-PSSession kann die Verbindung trennen, ob diese zu beenden. Beispiel in Kombination mit Get-PSSession:

Get-PSSession -ComputerName NameDesRemoteComputers -Name NameDerSitzung | Disconnect-PSSession

Ähnlich funktioniert die Wiederaufnahme des Kontaktes:

Connect-PSSession -ComputerName NameDesRemoteComputers -Name NameDerSitzung

3.3.8   New-PSDrive -Persist

New-PSDrive hat den zusätzlichen Schalter –Persist erhalten, mit dem man ähnlich wie NET USE nicht nur für die aktuelle Sitzung der Konsole eine Laufwerksverbindung herstellen kann, sondern für den gesamten Userkontext. Das bedeutet, das Laufwerk taucht dann z.B. auch im Windows Explorer auf. Aber es ist kein NET USE! Das merken Sie spätestens dann, wenn Sie über PowerShell Remoting arbeiten. Dort klappt das NET USE nämlich nach wie vor ohne jedes Murren. New-PSDrive –Persist hingegen beklagt weiterhin Zugriffsprobleme (wie in Abschnitt über UNC-Pfade in RemoteSession beschrieben).

3.3.9   Out-Gridview –Passthru

Out-Gridview fand ich als abschliessendes Cmdlet einer Pipe nie besonders nützlich. Durch den neuen Schalter –Passthru ist es aber eine sehr interresante Möglichkeit zur Eingrenzung von Werten aus einer Tabelle geworden.

$SelektierteProzesse=Get-Process | Out-GridView -PassThru
$SelektierteProzesse | Stop-Process

Dadurch brauchen Sie sich selbst nicht mehr um die Gestaltung einer komplexen GUI zu kümmern.

3.3.10                     Module in Version 3.0

Als zusätzliche Module sind PSWorkflow und PSScheduledJob hinzugekommen. Da Sie aber ab Version 3.0 Module nicht mehr laden müssen, um die Cmdlets zu verwenden erspare ich mir hier weitere Ausführungen und verweise auf die vorangegangenen Abschnitte in dem die Möglichkeiten beschrieben stehen.

3.3.11                     PowerShell Remoting

Auch beim PowerShell Remoting sind einige Erweiterungen hinzugekommen, die in diesem Kapitel beschrieben stehen.

3.3.11.1                Zusätzliche Remoting Cmdlets

Wie Sie wissen, können Sie eine Sitzung (mit einer Verbindungsvariable – siehe PowerShell 2.0, PowerShell Remote Zugriff im Abschnitt Die Kommandozeile auf einen entfernten Rechner umschalten) über das Cmdlet Exit-PSSession beenden. Dabei wird allerdings der aktuelle Prozess (die Konsole) auf dem entfernten Rechner beendet. Aller definierten Variablen sind damit natürlich auch weg. Wenn Sie sich erneut Verbinden müssen Sie diese wieder neu definieren. Mit Disconnect-PSSession können Sie die Verbindung trennen, ohne den Prozess auf dem RemoteSystem zu beenden und sich anschließend mit Reconnect-PSSession wieder auf diese Konsole drauf schalten. Dabei bleiben alle Variablen und deren Inhalte auf dem RemoteSystem auch über die Trennung der Verbindung hinweg erhalten. Das können Sie sich so ähnlich vorstellen, wie wenn Sie eine Remotedesktop-Verbindung trennen, anstatt sich abzumelden.

3.3.11.2                Web Access

Mit PowerShell Web Access haben Sie die Möglichkeit einen mit IIS ausgestatteten Windows Server 2008 oder höher als Gateway einzusetzen. Dieser ermöglicht Ihnen den PowerShell Zugriff mittels eines Webbrowsers (Cookies müssen zumindest für diese Website erlaubt werden).

3.3.11.2.1             Einfache Einrichtung von PowerShell Web Access

Führen Sie die nachfolgenden 4 Schritte zur Einrichtung von PowerShell Web Access aus:

1.       Installieren Sie auf einem Windows Server 2008 die Rolle Webserver(IIS) und das Feature Windows PowerShell Web Access (z.B. über PowerShell selbst: Install‑WindowsFeature Web‑Server, NET‑Framework‑45‑ASPNET, WindowsPowerShellWebAccess).

2.       Ersetzen Sie im folgenden Befehl den kursiv geschriebenen Teil, durch die Ihrem Webserver entsprechenden Bezeichnungen:
Install-PswaWebApplication –webApplicationName MeinePowerShell ‑WebsiteName www.IhreInternetAdresse.de
Wenn Sie kein Zertifikat für die SSL-Verschlüsselung haben, können Sie zu Testzwecken den Schalter –UseTestCertificate angeben. Dies sollte aber unter keinen Umständen bei Produktivsystemen gemacht werden. Unter anderem, weil dieses Zertifikat nach 90 Tagen ungültig wird. Achtung: -Websitename gibt nicht zwingend die URL an, sondern den Name unter dem Ihre Internetseite im IIS gelistet ist (z.B. auch DefaultWebsite).

3.       Nun müssen Sie noch festlegen, wer sich auf welche Ihrer Computer verbinden darf. Auf diesen Computern muss natürlich PowerShell Remoting aktiviert sein (Lesen Sie dazu ggf. das Kapitel PowerShell Remote Zugriff). Der Zugriff wird über sogenannte PswaAuthorizationRules gesteuert:
Add-PswaAuthorizationRule –ComputerName ZielComputerAufDemSieDieKonsoleFreigebenMöchten –UserName DomänenName\BenutzerDemSieDiesenZugriffErlaubenMöchten ‑ConfigurationName Microsoft.PowerShell
Auch hier sind die kursiven Bestandteile wieder entsprechend zu ersetzen. Mit
Get-PswaAuthorizationRule können Sie sich einen überblick über die erstellten Regeln geben lassen und mit Remove-PswaAuthorizationRule einzelne Regeln wieder löschen. Statt einzelne Benutzer anzugeben können Sie natürlich auch mit Gruppen aus Ihrem Active-Directory arbeiten:
Add-PswaAuthorizationRule –ComputerGroupName GruppeVonZielComputernAufDenenSieDieKonsolenFreigebenMöchten ‑UserGroupName DomänenName\BenutzerGruppeDerSieDiesenZugriffErlaubenMöchten ‑ConfigurationName Microsoft.PowerShell

4.       Das war’s auch schon. Ab sofort können Sie sich mit einem Browser auf den Webserver unter der entsprechenden URL verbinden:
https://www.IhreInternetAdresse.de/MeinePowerShell

Beim Benutzernamen bitte nicht vergessen, die Domäne voranzustellen. Bei Computernamen geben Sie an zu welchem Computer Sie sich weiterverbinden möchten. PowerShell Web Access wird also nicht auf dem Computer eingerichtet, auf dem Sie die Konsole bereitstellen möchten, sondern auf irgendeinem Computer mit installiertem IIS, der die Anforderung entsprechend weiter reicht. Sie können sich nur mit Computern verbinden, die vorher durch ein PswaAuthorization Rule (auf dem Webserver Gateway) erlaubt wurden und natürlich auch nur mit den in derselben Regel angegebenen Benutzern.

Mehr Möglichkeiten und Details zur Konfiguration finden Sie bei Microsoft im Internet unter:
http://technet.microsoft.com/de-de/library/hh831611.aspx

Weiter Informationen zu unterstützten Browsern und Verhalten der Weboberfläche wie Tastenbelegung (Stichwort: Strg+C) finden Sie unter:
http://technet.microsoft.com/de-de/library/hh831417.aspx

Tiefer möchte ich an dieser Stelle nicht gehen, da die PowerShell in meinen Augen viel zu mächtig ist, als Sie über das Internet bereit zu stellen. Diese Kapitel ist daher nur der Vollständigkeit halber enthalten.

3.3.12                     WMI vs. CIM

Wie im Abschnitt WMI Objekte ausführlich behandelt, können Sie mittels WMI auf DCOM zugreifen. In Version 3.0 hat WMI nun einen Bruder in Form von CIM erhalten.

Die Cmdlets funktionieren ähnlich, bieten an der einen oder anderen Stelle jedoch Unterschiede. So läßt sich beispielsweise:

Gwmi –list

durch:

Gcls

abkürzen. Gcls ist ein Alias für Get-CimClass.Dieser fügt bei Bedarf automatisch den –List Schalter ein.

Wenn es nach Microsoft geht ist WMI veraltet und wird in zukünftigen Windows Versionen möglicher Weise nicht mehr unterstützt, da angeblich die Firwallkonfiguration für RPC so schwierig ist. Da sind wir nun auch schon beim wesentlichen Unterschied! WMI verwendet RPC, während CIM über Windows-Remoting zugreift. Für Windows-Remoting müssen natürlich nicht nur die Ports in der Firewall auf sein, sondern auch noch PowerShell-Remoting auf dem Zielsystem aktiviert. Wo ist also der Konfigurationsaufwand größer und wo in installierte Basis von Systemen derzeit (2016) größer? WMI ist also nach wie vor die bessere Wahl.

CIM hat allerdings noch ein weiteres Feature, was sich sowohl als Vor-, als auch als Nachteil erweist. CIM kann Sitzungsbasiert arbeiten.

$Sitzung=New-CimSession –Computername Rechner1, Rechner2, Rechner3

Dies baut eine dauerhaft geöffnete Verbindung zu den 3 Rechnern auf.

Sie können auch die Cim-Cmdlets dazu „überreden“ RPC zu verwenden, indem Sie zusätzlich eine Protokolloption mit angeben:

$RPC=New-CimSessionOption -Protocol DCOM

$Sitzung=New-CimSession –Computername Rechner1 -SessionOption $RPC

Vorteil einer Sitzung: Man kann ohne erneuten Verbindungsaufbau schnell hintereinanderer mehrere Abfragen durchführen.

Get-CimInstance –CimSession $Sitzung Win32_LogicalDisk

Nachteil einer Sitzung: Die Verbindungen bleiben die ganze Zeit geöffnet, bis man die Verbindung(en) wieder abbaut.

Remove-CimSession $Sitzung

3.3.13                     Anti-Malware Scan Interface (AMSI)

AMSI ist eine Schnittstelle, die es Windows Defender und 3-Herstellern von Sicherheitssoftware ermöglichen soll schadhaften Code kurz vor der Ausführung zu entdecken und zu verhindern. Auch PowerShell wird ab Version 3.0 von AMSI durchleuchtet. Sollten Sie einmal Probleme haben, die Sie darauf zurückführen, versuchen Sie einfach die PowerShell im Kompatibilitätsmodus mit Version 2.0 auszuführen. Hier wurde AMSI noch nicht unterstützt und lässt daher jeglichen PowerShellCode zu. Aufruf:

PowerShell.exe -Version 2.0 -File IhrScript.ps1

Um für mehr Sicherheit zu sorgen, können Sie an Windows 8 / Server 2012 die Abwärtskompatibilität mit PowerShell 2.0 abschalten.

Wenn Sie PowerShell 3.0 und höher verwenden möchten, können Sie AMSI auch dediziert für PowerShell abschalten (allerdings poppt dann ein UAC-Dialog auf und weißt auf die „Missetat“ hin):

Set-MpPreference -DisableRealTimeMonitoring $true -DisableIOAVProtection $true

3.4     PowerShell 4.0

Hier werden die Neuerungen der Powershell 4.0 erklärt.

3.4.1   ISE Steuerung

3.4.1.1   Regionen – Bereiche zum auf und zuklappen selbst definieren

Mit der neuen Kommentar-Option region können Sie selbst bestimmen wo im ISE Bereiche die mit +/- auf- und zugeklappt werden können zur Verfügung stehen. Syntaktisches Beipiel:

#region BezeichnerIhrerWahl

Hier steht Ihr Code-Bereich der auf-/zugeklappt werden kann

#endregion

In ISE wird dann vor #region BezeichnerIhrerWahl ein – Zeichen eingeblendet. Wenn Sie darauf klicken verschwindet der komplette Bereich bis zum #endregion Tag aus der Ansicht und das Minus wandelt sich in ein + Zeichen. Nur noch die Zeile #region BezeichnerIhrerWahl ist zu sehen. Mit einem Klick auf + können Sie den Bereich natürlich wieder anzeigen lassen.

Das eignet sich besonders gut, um z. B. die kompletten Aufbau für die Hilfe im Funktionskopf ein-/ausblenden zu lassen.

#region Hilfetext

<#

.SYNOPSIS

 Zeigt alle Eigenschaften von Dateien und Verzeichnissen an.

.DESCRIPTION

 Der Befehl soll den Alias aus SuSE-Linux ll implementieren. Auf einem SuSE-Linux System ist ll ein Alias auf ls –l. ls –l wiederum macht ein sogenanntes List Long, sprich es werden alle möglichen Informationen zu Verzeichnissen und Dateien dargestellt. Hier in der PowerShell wird ein Get-Childitem auf das entsprechende Verzeichnis durchgeführt und anschlissend an ein Select-Object * übergeben, was alle Informationen zu den von Get-Childitem gelieferten Datei- und Verzeichnisobjekten darstellt.

.EXAMPLE

 ll c:\

 Listet das Wurzelverzeichnis von Laufwerk C: mit allen Detailangaben.

.EXAMPLE

 „c:\“ | ll

 Übergibt das Verzeichnis über eine Pipe an den Befehl ll.

.LINK

 www.martinlehmann.de

#>

#endregion

Function ll {

 Param(

  [Parameter(Mandatory=$true,ValuefromPipeline=$true)]

  [String[]]

  $Verzeichnis

 )

 Get-Childitem $Verzeichnis | Select *

}

3.4.1.2   ISE um eigene Funktionen erweitern

Sie können das Integrated Scripting Environment um eigene Funktionen erweitern. Alles was Sie dafür brauchen ist ein bisschen Forscherdrang und die Variable $psISE. Diese Variable steht Ihnen nur innerhalb der ISE selbst zur Verfügung, nicht in der normalen Konsole. Dort wäre sie ja auch nutzlos.

Die Variable lässt sich wie alle anderen mit Get-Member im unteren (blauen) Teil erforschen. Dabei könnten Sie u.a. auf das Unterobjekt $psISE.currentfile.Editor.SelectedText stoßen. Das enthält den Textbereich, den Sie im oberen Bereich markiert haben. $psISE.currentfile.Editor.Text enthält das komplette Skript. $psISE.currentfile.Editor.CaretLine enthält die aktuelle Cursor Zeile und $psISE.currentfile.Editor.CaretColumn die aktuelle Cursor Spalte. Beide zusammen ergeben die aktuelle Textcursorposition. Mit der Methode $psISE.CurrentPowerShellTab.AddOnsMenu.Submenus.Add() können Sie zusätzliche Einträge im Addon-Menüpunkt erstellen.

So können Sie mit dem folgenden Skript im Handumdrehen eine Funktion bauen die Ihnen die markierten Bereiche ein- oder ausrückt:

10.        Function Space {

11.         [Array]$All=$psISE.currentfile.Editor.Text.split(13)

12.         [Array]$Marked=$psISE.currentfile.Editor.SelectedText.split(13)

13.         $Markedcount=0

14.         $New=""

15.         $Zeile=$psISE.currentfile.Editor.CaretLine-1

16.         $Spalte=$psISE.currentfile.Editor.CaretColumn-1

17.         if ($Marked.count -eq 1) {

18.          if ($Insert) {

19.           $All[$Zeile]=" "+$All[$Zeile]

20.          } else {

21.           $All[$Zeile]=$All[$Zeile].substring(1)

22.          }

23.         } else {

24.          $Counter=0

25.          if ($All[$Zeile].substring($Spalte) -eq $Marked[0]) {

26.           $Marked | foreach {

27.            if ($Insert) {

28.             $All[$Zeile+$Counter]=" "+$All[$Zeile+$Counter]

29.            } else {

30.             $All[$Zeile+$Counter]=$All[$Zeile+$Counter].substring(1)

31.            }

32.          $Counter++

33.           }

34.          } else {

35.           $Marked | foreach {

36.            if ($Insert) {

37.             $All[$Zeile-$Counter]=" "+$All[$Zeile-$Counter]

38.            } else {

39.             $All[$Zeile-$Counter]=$All[$Zeile-$Counter].substring(1)

40.            }

41.            $Counter++

42.           }

43.          }

44.         }

45.         foreach ($Line in $All) {

46.          $New+=$Line+[char]13

47.         }

48.         $psISE.currentfile.Editor.Text=$New.substring(0,$New.length-1)

49.        }

50.       

50.$psISE.CurrentPowerShellTab.AddOnsMenu.Submenus.Add("Leerzeichen einfügen",{$Insert=$true;Space},"Alt+I") | out-null

51.        $psISE.CurrentPowerShellTab.AddOnsMenu.Submenus.Add("Leerzeichen entfernen",{$Insert=$false;Space},"Alt+E") | out-null

Hier gibt es leider ein paar Gemeinheiten. Zunächst einmal müssen die den String-Text in einen Array umbauen. Dabei hilft die Methode .split(13). Diese sorgt dafür, dass das Carriage Return (13=Zeilenumbruch) zum Array-Trenner wird. Wenn nur eine Zeile markiert ist (Zeile 17), brauchen Sie nur die einzelne Zeile entweder rein-/rausrücken. Werden mehrere Zeilen markiert, muss zunächst rausgefunden werden, ob der Textcursor am oberen, oder unteren Ende der Markierung steht. Dafür dient der String-Vergleich in Zeile 25. Je nachdem müssen die Zeile von der Cursorposition auf- oder abwärts nach links bzw. rechts verschoben werden. Bei bauen des Arrays ging das Zeichen für die neue Zeile verloren, um das für jedes Zeile wieder am Ende einzufügen gibt es die Zeilen 45-47. In Zeile 48 wird dann in die Variable $psISE.currentfile.Editor.Text  der bearbeitete Text zurück geschrieben. Allerdings ohne das letzte Carriage  Return, denn sonst hätten Sie bei jedem Funktionsaufruf eine Zeile mehr an Ende des Skriptes. Die letzten beiden Zeilen enthalten dann die ergänzten Menüeinträge unter Add-Ons in der ISE mit dem jeweiligen Funktionsaufruf inkl. Tastatur-Shortcut. Damit dies dann jedes Mal beim Aufruf der ISE zur Verfügung steht und nicht jedes Mal manuell aufgerufen werden muss, nennen Sie die Datei Microsoft.PowerShellISE_profile.ps1 und legen sie unter C:\Users\Benutzername\Documents\WindowsPowerShell ab.

3.4.2   Vereinfachte Schreibweisen, auch in Workflows

Achtung! Wenn Sie diese Schreibweisen verwenden, sind Ihre Skripte nicht mehr abwärtskompatibel und können nur noch in Version 4.0 ausgeführt werden. Anstatt pipen können nachfolgende Befehle wie eine Methode aufgerufen werden. Beispiel:

(Get-Process).Where-Object({$_.Name -like "svc*"})

oder auch mit Aliasen:

(ps).where({$_.Name -like "svc*"})

Aber nicht übertreiben…

(ps).?({$_.Name -like "svc*"})

…denn die letzte Variante schlägt fehl. ;-)

In Workflows sind nun teilweise auch abgekürzte Parameter möglich.

3.4.3   Zusätzliche Cmdlets, Parameter

In diesem Abschnitt werden die neuen kleinen Bonbons der Version 4.0 aufgezählt.

3.4.3.1   Get-Process

Get-Process wurde um den Parameter –IncludeUserName erweitert. Da auch in den versteckten Eigenschaften der Benutzername in den älteren PowerShell-Versionen nicht enthalten ist, ist dies durchaus eine sinnvolle Erweiterung.

3.4.3.2   Scheduled Jobs

Die Cmdlets für die Scheduled Jobs sind erweitert worden. Allerdings nur, wenn Sie auch Windows 8/Windows Server 2012 oder höher einsetzen. Bei PowerShell 4.0 auf einem Windows Server 2008 z.B. fehlen diese Parameter nach wie vor.

Der Parameter –RunNow erlaubt nun auch einen Job sofort zu starten. Die Cmdlets New-JobTrigger und Set-JobTrigger haben den Parameter –RepeatIndefinitely erhalten, womit sich ein Job auf unbestimmte Zeit wiederholen lässt.

3.4.3.3   Workflows

Mit dem Cmdlet Suspend-Workflow können Sie (darf auch innerhalb eines Workflows eingesetzt werden) die Abarbeitung der Workflows anhalten. Die Workflows selbst werden als Job verwaltet. Das hat zur Folge, dass Sie mit Get-Job nicht nur Jobs, sondern auch aktuelle Workflows sehen. Haben Sie einen ausgesetzten Workflow in der Jobliste identifiziert, können Sie diesen mittels Resume-Job fortsetzen lassen.

Weiterhin können Sie nun innerhalb Workflows den Common-Parameter Schalter ‑PSPersist $true nach beliebigen Cmdlets angeben. Dies sorgt dafür, dass nach Ausführung des Cmdlet ein CheckPoint erstellt wird. Wird dann (Warum auch immer – z.B. Computer startet neu) der Workflow unterbrochen, kann dieser später an genau derselben Stelle fortgesetzt werden. Variableninhalte etc. werden im CheckPoint ebenfalls vermerkt .Natürlich kostet das Setzen eines jeden Checkpoint Leistung. Also wenn’s schnell gehen soll, setzen Sie den Schalter sparsam ein.

Beispiel:

Workflow Interuptus {

 Get-Service –PSPersist $true # Setzt den Checkpoint

 Suspend-Workflow # Workflow pausieren, nun können Sie rebooten

 Get-Process # Hier soll’s dann nach dem Reboot weiter gehen

}

Interuptus

Wenn Sie dies ausgeführt haben und Ihren Rechner neu starten, werden Sie hinterher über Get‑Job Diesen Workflow wieder finden. Mittels Resume-Job Nr. können Sie den Workflow fortsetzen und mit Receive-Job Nr. die Ausgabe der Prozessliste abholen.

Anstatt dem Common-Parameter –PSPersist können Sie auch das Cmdlet CheckPoint‑Workflow verwenden, um einen CheckPoint zu setzen. Geschmackssache! Ein CheckPoint-Workflow Cmdlet in einer eigenen Zeile ist vielleicht übersichtlicher. Wenn Sie sowieso laufend –PSPersist verwenden, können Sie auch gleich die Umgebungsvariable $PSPersistPreference auf $true setzen.

3.4.3.4   Test-NetConnection

Auch dieses Cmdlet steht Ihnen nur zur Verfügung, wenn Sie auch Windows 8/Windows Server 2012 oder höher einsetzen. Bei PowerShell 4.0 auf einem Windows Server 2008 z.B. fehlt dieses Cmdlet.

Dieses Cmdlet macht eigentlich das Gleiche wie das Test-Connection Cmdlet, kann allerdings noch etwas mehr Details liefern, warum eine Verbindung fehlschlägt.

3.4.3.5   FileHashes erstellen

Haben Sie eine unübersichtliche Fotosammlung, die Sie nach Dubletten durchforsten möchten?

Das Cmdlet Get-Filehash hilft Ihnen dabei durch generieren des Hashwertes einer Datei. Haben 2 Dateien denselben Hashwert, sind die beiden Dateien inhaltlich gleich. Über den Parameter ‑Algorithm könnte man noch den Algorithmus festlegen, aber der Standard SHA256 sollte für die meisten Aufgaben passen:

Get-FileHash C:\PfadUnd\NamederDateiVonDerDerHashGebildetWerden.Soll

3.4.3.6   Web Zugriffe, REST konform

Die beiden Cmdlets Invoke-RestMethod und Invoke-WebRequest gibt es zwar auch schon in Version 3.0, aber ab Version 4.0 funktionieren Sie auch ;-).

Mittels Invoke-WebRequest kann man recht einfach Inhalte per FTP oder HTTP(S) aus dem Internet abrufen. Z.B.:

Invoke-WebRequest www.martinlehmann.de

REST steht für Representational State Transfer und stellt eine Möglichkeit zur standardisierten Kommunikation auf http-Basis zwischen Computern dar. Eine tolle Erklärung darüber findet man unter: http://de.wikipedia.org/wiki/Representational_State_Transfer.

Um Bespielsweise den PowerShell RSS-Feed vom Microsoft auszulesen könnte man zunächst einmal mit:

Invoke-RestMethod blogs.msdn.com/powershell/rss.aspx | gm

schauen, was man hier alles “anstellen” kann. Mit

Invoke-RestMethod blogs.msdn.com/powershell/rss.aspx | select ` title, description

können Sie sich dann über die Inhalte her machen.

3.4.4   Desired State Configuration

Die DSC (Desired State Configuration) ermöglicht Systeme in einen gewünschten Zustand zu bringen. In erster Linie ist diese Funktion also für Rollouts interessant. Landläufig sagt man auch gerne „Druckbetankung“. Man könnte es auch ein bisschen mit Gruppenrichtlinien vergleichen. Auf allen Maschinen die Sie als Parameter übergeben, werden entsprechende Einstellungen bzw. Veränderungen vorgenommen.

Häßlich: Leider wird mit WMF 4.0 nur eine Basisausstattung mit der Grundfunktionalität geliefert. Weitere Ergänzungen, wie z.B. der Zugriff auf so grundlegende Dinge wie Active-Directory, werden in sogenannten Waves zur Verfügung gestellt. Diese kommen als *.zip Dateien daher und müssen ins Module Verzeichnis entpackt werden. Weiterhin sind die Erweiterungen noch als experimental gekennzeichnet. Mit Fehlfunktionen ist daher zu rechnen. Aktuell, da ich diese Zeilen schreibe, ist Wave 9.

Voraussetzungen:

·        Bei Windows 8.1 und Windows Server 2012 R2 muss KB2883200 installiert sein.

·        Bei Windows 7 und Windows Server 2008 muss das komplette .NET Framework 4.5 vor dem Windows Management Framework (WMF) mit PowerShell 4.0 installiert werden.

·        PowerShell Remoting muss auf den Zielsystemen aktiviert sein! Siehe Abschnitt PowerShell Remote Zugriff

Das in PowerShell 4.0 neu dazugekommene Zauberwort ist Configuration NameDerKonfiguration {Ihr Scriptcode}. Ähnlich wie in Funktionen oder Workflows können Sie hier mit Param ($ZuÜbergebendeVariablen) entsprechende Schnittstellen definieren. Mithilfe des Abschnitts Node Computername {Ihr ScriptCode} können Sie festlegen auf welchen Computern das Gewünschte passieren soll. Ein Konfigurationsabschnitt (Configuration) kann mehrere unterschiedliche Node Abschnitte enthalten.

3.4.4.1   Rolle hinzufügen/entfernen

Um eine aus dem Servermanager bekannt Rolle oder Feature hinzuzufügen oder zu entfernen gibt es die Möglichkeit innerhalb eines Node Abschnittes einen weiteren, namens WindowsFeature NameIhrerWahl {…} einzufügen. Dieser sorgt dafür, dass die von Ihnen angegebenen Rollen und Features auf dem entsprechenden Computer hinzugefügt bzw. entfernt werden. Leider muss für jedes Feature ein separater Abschnitt erstellt werden. So können Sie im handumdrehen einem Rechner andere Aufgaben zukommen lassen. Der Aufruf, um dies gleich auf 3 Computern Ihrer Wahl durchzuführen erfolgt in der letzten Zeile.

10.        Configuration AddFeatures {

11.         Param ([String[]]$Computer=localhost)

12.         Node $Computer {

13.          WindowsFeature XPS {

14.           Ensure = "Present" # zum deinstallieren: "Absent"

15.           Name = "XPS-Viewer"

16.          }

17.          WindowsFeature WLAN {

18.           Ensure = "Present" # zum deinstallieren: "Absent"

19.           Name = "Wireless-Networking"

20.          }

21.         }

22.        }

23.        AddFeatures Computer Rechner1,Rechner2,Rechner3

Dies erzeugt im aktuellen Verzeichnis ein Unterverzeichnis mit der Bezeichnung AddFeatures  (Aus Zeile 10) und darin sogenannte MOF (MOF = Managed Object Format) Dateien für jeden Node (im Beipiel: Rechner1.mof, Rechner2.mof und Rechner3.mof). Wenn Sie ein anderes Verzeichnis wünschen geben Sie es mittels –OutputPath an:

AddFeatures –OutputPath C:\VerzeichnisIhrerWahl

3.4.4.2   Konfiguration auf die Zielcomputer anwenden

Um die Konfiguration der Computer dann tatsächlich zu starten, können Sie die gespeicherte Konfiguration mittels des Cmdlets Start-DSCConfiguration ausführen:

Start-DscConfiguration -Wait -Verbose -Path C:\VerzeichnisIhrerWahl

-Verbose gibt detaillierte Informationen während des Vorgangs aus und –Wait sorgt dafür, dass die Konfiguration erst komplett abgeschlossen wird, bevor es weiter geht.

Möchten Sie die Konfiguration erneut auf einem Zielcomputer sicherstellen, brauchen Sie nicht noch einmal alle Schritte durchalufen, sondern können einfach mittels

Invoke-CimMethod –Namespace ` root/Microsoft/Windows/DesiredStateConfiguration –ClassName ` MSFT_DSCLocalConfigurationManager –MethodName ` PerformRequiredConfigurationChecks –ComputerName NamesDesComputers ` –Arguments @{Flags=[uint32] 1}

die Konfiguration erneut ausrollen. Anstatt nur einem Computernamen können Sie natürlich auch mehrere angeben.

3.4.4.3   Dateien und Ordner kopieren/löschen und Abhängigkeiten definieren

Sie können mit DSC nicht nur einfach Rollen hinzufügen/entfernen, sondern auch gleich noch bestimmte Ordner-Strukturen oder Dateien auf die Systeme kopieren lassen. Entfernen ist natürlich auch möglich. Der grau markierte Bereich ist identisch mit dem Skript im vorangegangenen Abschnitt.

10. Configuration FileServerMitDateien {

11.  Param ([String[]]$Computer)

12.  Node $Computer {

13.   WindowsFeature Dateiserver {

14.    Ensure = "Present" # zum deinstallieren: "Absent"

15.    Name = "File-Services"

16.   }

17.          File KopierAktion {

18.            Ensure = "Present"  # Mit "Absent" löscht man

19.            Type = "Directory" # Standard ist "File"

20.            Recurse = $true # inklusive komplettem Inhalt

21.            SourcePath = "\\Servername\Share" # Pfad wo er die Dateien bzw. Ordner her holt

22.            DestinationPath = "C:\Share" # Pfad wo die Ordner-Struktur auf dem unter Node genannten Computer abgelegt werden sollen

23.            DependsOn = "[WindowsFeature]Dateiserver" # Stellt sicher, dass zuerst die WindowsFeatures aus dem Abschnitt Dateiserver abgearbeitet sind

24.          }

25.  }

26. }

27. FileServerMitDateienComputer Rechner1,Rechner2,Rechner3

Der Abschnitt File NameDesAbschnitts {} entählt Informationen, was von wo nach wo kopiert, bzw. gelöscht werden soll. Da die Erklärung bereits im Skript Code enthalten ist, muss ich die Bedeutung sicher nicht weiter ausführen.

3.4.4.4   Mittels DSC lokale Gruppen konfigurieren

Auch lokale Gruppen können mittels DSC festgelegt werden. Hier mal ohne mit Variablen zu arbeiten und nur die pure Konfiguration, ohne den entsprechenden Aufruf (siehe Abschnitt: Rolle hinzufügen/entfernen)

10.        Configuration Gruppenzuordnung {

11.         Node Rechnername {

12.          Group NameFuerGruppenKonfiguration {

13.           Ensure = "Present" # Sorgt dafür, dass die Gruppe mit dem nachfolgenden Namen vorhanden ist

14.           GroupName = "NameDerLokalenGruppe"

15.           Members = "NamenVonLokalenBenutzern", " OderLokalenGruppenDieInDieserGruppeMitgliedSeinSollen"

16.          }

17.         }

18.        }

So, und nun zur schlechten Nachricht: Die DSC läuft mit lokalen Systemrechten. Leider können Sie keine Benutzer oder Gruppen aus dem Active-Directory aufnehmen. Das hat Microsoft aktuell noch nicht implementiert. Es kommt zur Meldung, dass der „principal“ nicht gefunden werden konnte. Eigentlich könnte es so einfach sein, denn das Stichwort Credential kann benutzt werden, um einen User samt Passwort anzugeben:

Credential = $Credential

Nur leider haben die dass so blöd programmiert, dass man trotz der in diesem Falle vorliegenden Verschlüsselung, die Speicherung des Kennworts im MOF-File als Klartext zuweisen muss. Damit das überhaupt so funktioniert, müssen Sie noch zusätzlich Folgendes außerhalb des Configuration Abschnitts angeben:

10.        $configData = @{

11.         AllNodes = @(@{

12.          NodeName = Rechnername

13.          PSDscAllowPlainTextPassword = $true

14.         })

15.        }

…und die Konfiguration ein wenig anpassen:

16.        Configuration Gruppenzuordnung {

17.         param([PSCredential]$Credential)

18.         Node Rechnername {

19.          Group NameFuerGruppenKonfiguration {

20.           Ensure = "Present"

21.           GroupName = "NameDerLokalenGruppe"

22.           Members = "NamenAuchVonGlobalenBenutzern", " OderGlobalenGruppenDieInDieserGruppeMitgliedSeinSollen"

23.           Credential = $Credential

24.          }

25.         }

26.        }

 

Die Erstellung des MOF-Files geht dann folgender Maßen von statten:

Gruppenzuordnung -ConfigurationData $configData –Credential (Get-Credential)

Wenn Sie jetzt ins MOF-File schauen, werden Sie dort das eingegebene Kennwort im Klartext wieder finden. Wenn Sie einen Benutzer ausschließlich mit lesenden Berechtigungen auf das Active-Directory haben, ist dass Klartext-Kennwort möglicher Weise ein Option. Dann könnten Sie diesen Ansatz wählen.

Wenn Sie genug Zeit haben, für jeden potentiellen Zielcomputer Zertifikatsdateien mit öffentlichen Schlüssel zu erstellen und diese dann auch noch in die DSC-Konfiguration einbinden wollen, ist es Ihnen angeblich (ich hab den Nerv nicht - sorry) möglich, die MOF-Files zu verschlüssel. Damit hätten Sie dann das Kennwort gesichert hinterlegt. Aber ist das wirklich den Aufwand wert? Gut, wenn Sie beim MAD arbeiten, wird es sich vielleicht nicht vermeiden lassen. Daher sei der Vollständigkeit halber auf folgende Information (in Englisch) bei Microsoft zum verschlüsseln von MOF-Files verwiesen:

https://technet.microsoft.com/de-de/library/dn781430.aspx

Ernsthaft? Ich lege lieber einen Read-Only Benutzer an oder laufe zu jedem Client (auch die in Brasilien) und konfiguriere die von Hand, das ist einfacher und macht weniger Arbeit! Von mir aus könnten die MOF-Files von Haus aus verschlüsselt sein, dann müsste man sich weniger Gedanken um die Sicherheit machen.

3.4.4.5   Mittels DSC msi Pakete (Software) installieren

Voraussetzung um ein Programm zu installieren ist, dass die Software als msi Datei vorliegt. Der graue Teil sollte bekannt sein - Beispiel:

10. Configuration SoftwareInstallation {

11.  param([string[]]$Computer="localhost")

12.  Node $Computer {

13.   File CopyInstallationFile {

14.    Ensure ="Present"

15.    SourcePath ="\\Server\Freigabe\Installationdatei.msi"

16.    DestinationPath="C:\Temp\DSC-MSI-Installer"

17.    Type ="File"

18.   }

19.          Package InstallingInstallationFiles {

20.           Ensure ="Present"

21.           Path ="C:\Temp\DSC-MSI-Installer\7z920-x64.msi"

22.           Name = "7-Zip"

23.           ProductId = "23170F69-40C1-2702-0920-000001000000"

24.           DependsOn = "[File] CopyInstallationFile"

25.          }

26.  }

27. }

Auch hier ist wieder das DependsOn wichtig, da es dafür sorgt, dass die msi-Datei zunächst fertig auf den Client kopiert wird, bevor versucht wird, das MSI-Paket zu installieren. Die Abhängigkeiten sind entsprechend farblich hervorgehoben. Das einzige was nun noch geklärt werden muss ist das Feld ProduktId. Wie kommt man nun an diese Nummer? Die Nummer können Sie herausfinden, indem Sie die Software mal irgendwo installieren. Auf diesem Gerät führen Sie dann folgendes aus:

gwmi win32_softwarefeature | ? {$_.caption -like "7-Zip"} | select IdentifyingNumber

Anhand dieser Nummer erkennt DSC, ob die Software bereits installiert ist.

3.4.4.6   Welche Desired State Ressourcen stehen zur Verfügung

Mittel Get-DSCResource können Sie sich eine Auflistung der auf dem aktuellen System zur Verfügung stehenden Ressourcen geben lassen. Haben Sie dann ein Paket gefunden, dass Ihnen vielleicht weiterhilft, können Sie es etwas genauer unter die Lupe nehmen mit:

Get-DSCResource –Name NamedesPackets –Syntax

Dies zeigt die entsprechenden Keywords die Sie zur Konfiguration verwenden können und was für Werte zugewiesen werden können.

3.4.4.7   DSC Tools und ResourceKit

Um halbwegs den Überblick zu behalten empfiehlt sich der Download der PowerShell Community Extensions von Codeplex: http://pscx.codeplex.com/releases . Nach dem Download ist das msi Paket zu installieren. Interessanter Weise  finden Sie die PSCX Erweiterungen weder im Modules Verzeichnis, noch mit Get-Module –List. Trotzdem können Sie (weil im Programme-Verzeichnis) mit Import-Module PSCX importieren.

Danach lassen sich dann die DSC Tools https://gallery.technet.microsoft.com/scriptcenter/DSC-Tools-c96e2c53 herunterladen und installieren. Kopieren Sie dazu den heruntergeladenen Order DSCTools in Module Verzeichnis. Nach einem Import-Module DSCTools können Sie Install‑DSCResourceKit ausführen.

3.4.4.8   DSC PullServer und Clients einrichten

Ein Pull Server ermöglicht eine zentrale Verwaltung der Konfiguration mehrerer angebundener Systeme. Damit können die Konfigurationen in zeitlichen Intervallen ähnlich wie bei Gruppenrichtlinien immer wieder angewendet und verteilt werden.

Der Pullserver kann die Konfigurationsdateien entweder per SMB oder per http bereitstellen. Hier wird der aufwendigere Weg über http beschrieben, aber auch die Anpassungen für SMB sind in den Skripten enthalten. Die Konfigurationsdateien werden in der Regel in dem Verzeichnis C:\Program Files\WindowsPowerShell\DscService\Configuration abgelegt. Das Verzeichnis muss mit den entsprechenden NTFS-Berechtigungen versehen werden und bei SMB (statt http) Zugriff auch entsprechend frei gegeben werden. Wenn Sie SMB verwenden, können Sie den zusätzlichen Compliance-Serverdienst nicht nutzen (geht nur mit http). Dieser kann Meldungen der DSC-Clients entgegennehmen um deren aktuellen Status zu dokumentieren. Sprich in wie weit der jeweilige DSC-Client die gewünschte Konfiguration übernommen hat.

Zunächst müssen die benötigen Rollen (IIS) und Features installiert bzw. aktiviert werden:

Install-WindowsFeature DSC-Service –IncludeManagementTools

Danach können Sie mit diesem Script den Pullserver aufsetzen:

10.        Configuration PullServer {

11.         param ([String[]]$Computername="Localhost")

12.         Import-DscResource -Module xPSDesiredStateConfiguration

13.         Node $Computername {

14.          WindowsFeature DSCServiceFeature {

15.           Ensure="Present"

16.           Name="DSC-Service"

17.          }

18.          WindowsFeature WinAuth {

19.           Ensure="Present"

20.           Name="web-Windows-Auth"           

21.          }

22.          xDSCWebService PSDSCPullServer {

23.           Ensure="Present"

24.           EndPointName="PSDSCPullServer"

25.           Port=8001

26.           PhysicalPath="$env:SystemDrive\inetpub\wwwroot\PSDSCPullServer"

27.           CertificateThumbPrint="AllowUnencryptedTraffic"

28.           ModulePath="$env:ProgramFiles\WindowsPowerShell\DSCService\Modules"

29.           ConfigurationPath="$env:ProgramFiles\WindowsPowerShell\DSCService\Configuration"

30.           State="Started"

31.           DependsOn="[WindowsFeature]DSCServiceFeature"

32.          }

33.          xDSCWebService PSDSCComplianceServer {

34.           Ensure="Present"

35.           EndPointName="PSDSCComplianceServer"

36.           Port=8002

37.           PhysicalPath="$env:SystemDrive\inetpub\wwwroot\PSDSCComplianceServer"

38.           CertificateThumbPrint="AllowUnencryptedTraffic"

39.           State="Started"

40.           IsComplianceServer=$true

41.           DependsOn=@("[WindowsFeature]DSCServiceFeature","[xDSCWebService]PSDSCPullServer")

42.          }

43.         }

44.        }

45.         

46.        PullServer

47.        Start-DSCConfiguration .\pullserver -Wait -Verbose

Die kursiven Werte können Sie natürlich nach Ihren Vorstellungen/Bedürfnissen anpassen.

Ob der Server läuft können Sie durch den Aufruf http://localhost:8001/PSDSCPullserver.svc im Webbrowser testen. Der Inhalt der Website sollte dann so aussehen:
<?xml version="1.0" encoding="UTF-8"?>

<service xmlns:atom="http://www.w3.org/2005/Atom" xmlns="http://www.w3.org/2007/app" xml:base="http://localhost:8001/PSDSCPullServer.svc/"><workspace><atom:title>Default</atom:title><collection href="Action"><atom:title>Action</atom:title></collection><collection href="Module"><atom:title>Module</atom:title></collection></workspace></service>

In PowerShell 4.0 können die Clients nur über eine GUID Ihr Konfigurationsskript zugeordnet bekommen. Ich empfehle die GUID des Computerobjektes aus dem Active-Directory als Referenz zu verwenden. Ansonsten wird es schnell sehr unübersichtlich. Hier ein Skript, das ein Verzeichnis auf Ihren Client kopiert:

10.        Configuration TestConfig {

11.         Param(

12.          [Parameter(Mandatory=$True)]

13.          [String[]]$NodeGUID

14.         ) 

15.         Node $NodeGUID {

16.          File KopierAktion {

17.           Ensure = "Present"

18.           Type = "Directory"

19.           Recurse = $True

20.           SourcePath = "\\FQDN.PUll.Server\Freigabe\Verzeichnis"

21.           DestinationPath = "C:\Verzeichnis\auf\Pull\Client"

22.          }

23.         }

24.        }

25.         

26.        $Computers = @("Rechner1","Rechner2","etc") # Namen dürfen hier nicht FQDN sein

27.         

28.        write-host "Generating GUIDs and creating MOF files…"

29.        foreach ($Node in $Computers) {

30.         $ADGUID=(Get-ADComputer $Node).ObjectGUID

31.         $GUID = if ($ADGUID) {$ADGUID} else {[guid]::NewGuid()}

32.         $NewLine = "{0},{1}" -f $Node,$GUID

33.         TestConfig -NodeGUID $GUID

34.         if ((import-csv "$env:SystemDrive\Program Files\WindowsPowershell\DscService\Configuration\dscnodes.csv" -header "Name","GUID").GUID -contains $GUID) {

35.          write-host "Table not updated, because GUID already exists!"

36.         } else {

37.          $NewLine | add-content -path "$env:SystemDrive\Program Files\WindowsPowershell\DscService\Configuration\dscnodes.csv"

38.         }

39.        }

40.         

41.        write-host "Creating checksums…"

42.        New-DSCCheckSum -ConfigurationPath .\TestConfig -OutPath .\TestConfig -Verbose -Force

43.         

44.        write-host "Copying configurations to pull service configuration store…"

45.        $SourceFiles = (Get-Location -PSProvider FileSystem).Path + "\TestConfig\*.mof*"

46.        $TargetFiles = "$env:SystemDrive\Program Files\WindowsPowershell\DscService\Configuration"

47.        Copy-Item $SourceFiles $TargetFiles -Force

48.        Remove-Item ((Get-Location -PSProvider FileSystem).Path + "\TestConfig\")

Zeile 20,21 und 26 müssen Sie die kursiv geschriebenen Werte auf Ihre Umgebung anpassen. Das Skript nimmt als ConfigurationID automatisch, die GUID aus dem Active-Directory. Sollte der Rechner im AD nicht gefunden werden, wird automatisch eine generiert. Egal welchen Weg das Skript geht, auf jeden Fall wird die Zuordnung von Computernamen zu GUID in der folgenden Datei niedergeschrieben:

$env:SystemDrive\Program Files\WindowsPowershell\DscService\Configuration\dscnodes.csv

Falls Ihr DSC-Client also nicht zur Domäne gehört, können Sie hier die GUID für das Client Konfigurationsskript nachlesen.

Um Clients zu ermutigen Ihre Konfiguration vom Pullserver zu ziehen ist folgendes kleines Skript nötig:

10.        # AD Befehle aktivieren, falls nicht vorhanden

11.        if (!(Get-WindowsFeature "RSAT-AD-PowerShell").Installed) {Install-WindowsFeature "RSAT-AD-PowerShell"}

12.         

13.        Configuration PullClient {

14.        Param(

15.          [String]$NodeGUID=(Get-ADComputer $Env:Computername).ObjectGUID

16.         )

17.         if (!$NodeGUID) {throw "No GUID given or detected"}

18.         LocalConfigurationManager {

19.          ConfigurationID = $NodeGUID

20.          RefreshMode = "PULL"

21.          DownloadManagerName = "WebDownloadManager" # falls, per http veröffentlicht, oder nächste Zeile per SMB

22.          # DownloadManagerName = "DSCFileDownloadManager"

23.          RebootNodeIfNeeded = $true

24.          RefreshFrequencyMins = 30

25.          ConfigurationModeFrequencyMins = 30

26.          ConfigurationMode = "ApplyAndAutoCorrect"

27.          DownloadManagerCustomData = @{ServerUrl = "http://FQDN.PUll.Server:8001/PSDSCPullServer.svc"; AllowUnsecureConnection = “TRUE”} # falls, per http veröffentlicht, oder nächste Zeile per SMB

28.          # DownloadManagerCustomData = @{SourcePath = '\\FQDN.PUll.Server\DscService\Configuration\'}

29.         }

30.        }

31.        PullClient -Output "." # Optional kann bei nicht Domänenmitgliedern hier noch GUID mittels –GUID GUID mitgeteilt werden

32.        Set-DscLocalConfigurationManager -Computer localhost -Path .\PullClient -Verbose

In Zeile 27 bzw. 28 müssen Sie noch den DNS-Namen Ihres PullServers einsetzen. Wenn Sie einen anderen Port als 8001 beim Serversetup verwendet haben, müssen Sie natürlich auch die Portnummer anpassen.  In Zeile 31 müssen Sie noch die GUID mitteilen, falls der PullClient nicht zur Active-Directory Domäne gehört.

Nach einem Reboot und spätestens einer halben Stunde (kleines mögliches Poll-Intervall) sollte dann das gewünschte Verzeichnis auf dem PullClient auftauchen.  Wenn nicht schauen Sie einmal in die Ereignisanzeige unter Anwendungs- und Dienstprotokolle/Microsoft/Windows/Desired State Configuration/Operational. Bei EventID 4104 haben Sie beim PullServer per http voraussichtlich beim Webserver auf die DSC-Sites die Windows-Authentifizierung nicht installiert oder nicht aktiviert.  Natürlich könnte auch die GUID oder die Dateiberechtigungen nicht passen. Bei 4131 stimmt der FQDN nicht. Bei EventID 4097 stimmt mit der Pfadangabe von der aus Sie das Verzeichnis kopieren wollen nicht (mittleres TestConfig-Skript, Zeile 20).

3.4.4.9   Weitere Möglichkeiten

Weitere Informationen finden Sie unter: http://technet.microsoft.com/en-us/library/dn249921.aspx

Leider nur in Englisch. Sollten genügend Rückfragen dazu bei mir eintrudeln, werde ich die Abschnitte zu DSC gerne erweitern.

3.5     PowerShell 5.0

Hier werden die Neuerungen der Powershell 5.0 erklärt.

3.5.1   Paketmanager/Softwareinstallation

Vielleicht kennen Sie apt, yum oder zypper? Das sind unter Linux Werkzeuge für automatisierte Softwareinstallation. Wenn Sie unter Windows Libre-Office installieren möchten, gehen Sie mit Ihrem Webbrowser auf die Seite des Herstellers, laden die Software herunter und installieren diese danach. Unter Linux tippen Sie einfach apt install libreoffice und schwupps schon finden Sie in Ihrem „Startmenü“ die Office-Suite wieder. Das klappt übrigens genausogut mit der 3D-Animations-Software Blender, mit der u.a. der Film Monster AG erstellt wurde. Jegliche Software wird dann natürlich auch automatisch aktualisiert (sofern Sie dies wünschen). Dies kommt nun auch langsam bei Windows an.

Zunächst einmal stellt sich natürlich die Frage, wo denn die Software überhaupt her kommt. Mit dem Cmdlet Get-PackageProvider bekommen Sie eine Auflistung über die Paketquellen, die aktuell auf Ihrem System konfiguriert sind bzw. bereits standardmäßig vorgegeben. Find‑PackageProvider sehen Sie welche Softwarequellen Sie noch hinzufügen können. Über Import-PackageProvider NameDerQuelle können Sie dann die gewünschte Softwarequelle (auch Repository genannt) hinzufügen.

Mit dem Cmdlet Find-Package können Sie in den eigerichteten Paketquellen nach installierbarer Software suchen. Mit Install-Package NameDesPakets können Sie dann die Software Ihrer Wahl automatisiert herunterladen und installieren lassen. Mittels Get-Package sehen Sie die bereits installierten Pakete.

Weiterhin steht Ihnen damit auch die Cmdlets Install-Module und Save-Module zur Verfügung. Damit haben Sie die Möglichkeit automatisch PowerShell-Module aus der PowerShell‑Gallery zu installieren, oder im Falle von Save-Module, erst mal nur herunterzuladen, um reinzugucken was da drin steht.

3.5.2   RunSpaces - Multithreaded statt Multiprocessed Jobs

Über das eben bereits erwähnte Install-Module können Sie das Modul PoshRSJob aus der PowerShell‑Gallery installieren. Damit stehen Ihnen die Cmdlets Get-RSJob, Receive-RSJob, Remove-RSJob, Start-RSJob, Stop-RSJob und Wait-RSJob zur Verfügung. Diese funktionieren ähnlich den unter Jobs beschriebenen Cmdlets. Der Vorteil bei diesen Cmdlets ist, dass hier kein komplett neuer Prozess mit der gesamten Verwaltungsstruktur aufgebaut werden muss, sondern lediglich ein neuer RunSpace (ein neuer Thread statt einem Prozess) angelegt wird. Das schont den Speicherverbrauch und ist natürlich auch ca. doppelt so schnell in der Verarbeitung. Als ich hier über diese Technik gelesen habe, wollte ich selbst ein Modul dazu schreiben, dass den Umgang mit RunSpaces vereinfacht, aber warum das Rad neu erfinden, wenn Sie es doch direkt aus der PowerShell-Gallery laden können?

3.5.3   PowerShell Direct

Mittels PowerShell Direct können Sie direkt vom Hyper-V Host auf die Gäste zugreifen, ohne über PSRemoting (das Netzwerk) zu gehen. Die Cmdlets Enter-PSSession, sowie Invoke‑Command haben einen zusätzlichen Schalter -VMName erhalten. Damit können Sie den Namen der virtuellen Maschine angeben, in der Sie das bzw. die Kommando(s) ausführen möchten. Natürlich geht dies nur, wenn Sie sich als lokaler Administrator der Gastmaschine ausweisen. Daher müssen Sie zusätzlich immer den -Credential Schalter verwenden, falls Sie auf dem Hyper-V Host nicht zur lokalen Gruppe der Administratoren auf dem Gast gehören. Den Namen der virtuellen Maschinen, sowie deren Konfigurationsversionsnummer erfahren Sie mit Get-VM. Die Konfigurationsversion muss mindestens Version 6.2 sein. Ältere Konfigurationsversionen werden nicht unterstützt. Ist der Gast heruntergefahren, kann die Gastkonfigurationsverison über den Kontext-Menüeintrag per Rechtsklick auf die VM in Hyper-V auf die neuste Version aktualisiert werden, oder über das PowerShell-Cmdlet Update-VMVersion.

3.5.4   Umgang mit komprimierten Dateien/Verzeichnissen

Ab PowerShell 5.0 können Sie nun direkt gezippte (komprimierte) Archive erstellen, ohne den Umweg über die COM-Schnittstelle.

Wenn Sie ein komplettes Verzeichnis in eine Zip-Datei packen möchten geben Sie ein:

Compress-Archive -Path C:\PfadZumVerzeichnis\* -DestinationPath C:\PfadZumZIPFile\Komprimiert.Zip

Dabei wird automatisch der höchste Kompressionsgrad genutzt. Soll es etwas schneller gehen (erzeugt dafür eine größere Datei) können Sie den Schalter -CompressionLevel Fastest verwenden (oder gar NoCompression). Leider ist die maximale ZIP-Dateigröße derzeit auf 2 GB limitiert. Kennwörter, oder andere Formate werden leider auch nicht unterstützt.

Um die ZIP-Datei wieder zu entpacken tippen Sie:

Expand-Archive -Path C:\PfadZumZIPFile\Komprimiert.Zip -DestinationPath C:\PfadZumVerzeichnis

Sollte das Zielverzeichnis nicht leer sein und Sie möchten den bestehenden Inhalt ohne Rückfragen überschreiben, können Sie den Schalter -Force ergänzen.

3.5.5   PowerShell 5.0 Klassen und Enumeratoren

Mit PowerShell 5.0 kann man ähnlich der Programmiersprache C Klassen und Enumeratoren erstellen, aber leider werden auch in Version 5.1 noch keine Events (Ereignisse) unterstützt.

3.5.5.1   Einfache Objekt Klasse erstellen und verwenden

Am folgenden Beispiel eines Kopfes möchte ich das Erstellen einer einfachen Klasse erläutern:

10.        Class Kopf {

11.            static [String]$Beschreibung = "Kann aus weiteren Objekten wie Augen, Mund, Ohren, etc. bestehen"

12.            $VerbindungZuTorsoHorizontal = "Mitte"

13.            $VerbindungZuTorsoVertikal = "Oben"

14.            Sag ([String]$Was) {Write-Host $Was -ForegroundColor Green}

15.        }

Zunächst einmal sieht die Grundstruktur ähnlich einer Funktion aus:

Class Kopf {

    Inhalt

}

Lediglich das Wörtchen Function ist durch Class zu ersetzen.

Das "Zauberwort" static (aus Zeile 11) sagt aus, dass der nachfolgende Teil direkt an der Klasse ausgeführt oder abgerufen werden kann und nicht erst ein Objekt aus der Klasse abgeleitet werden muss.

Die Zeile 11:

static [String]$Beschreibung = "Kann aus weiteren Objekten wie Augen, Mund, Ohren, etc. bestehen"

legt eine Eigenschaft vom Typ String (text) mit der Bezeichnung Beschreibung direkt an der Klasse fest und weißt Ihr den Wert hinter dem = zu.

Die Klasse abzurufen funktioniert genauso, wie bei den bestehenden .NET-Klassen.

[Math]::pi

ruft die Eigenschaft Pi der Klasse Math auf und gibt die Zahl Pi zurück. Bei unserem Beispiel gibt:

[Kopf]::Beschreibung

den zugewiesenen Text aus.

[Math] | Get-Member -static

zeigt alle direkt an der .NET-Klasse abrufbaren Methoden und Eigenschaften an. Genauso funktioniert das auch bei unserem Kopf:

[Kopf] | Get-Member -static

zeigt unsere Eigenschaft Beschreibung an.

Die folgenden 3 Zeilen tauchen nicht auf, da das "Zauberwort" static fehlt. Diese sind erst verfügbar, wenn wir aus unserer Klasse ein Objekt ableiten (instanziieren).

Das geschieht entweder durch:

$o = [Kopf]::new()

oder

$o = New-Object Kopf

Das erstellte Objekt ist dann in der Variablen $o hinterlegt und kann dann natürlich auch einfach durch $o benutzt werden.

$o + Enter sollte dann so aussehen:

VerbindungZuTorsoHorizontal VerbindungZuTorsoVertikal

--------------------------- -------------------------

Mitte                       Oben


 

Schickt man $o an Get-Member sieht man auch die Methode Sag:

   TypeName: Kopf

Name                        MemberType Definition

----                        ---------- ----------

Equals                      Method     bool Equals(System.Object obj)

GetHashCode                 Method     int GetHashCode()

GetType                     Method     type GetType()

Sag                         Method     void Sag(System.Object Was)

ToString                    Method     string ToString()

VerbindungZuTorsoHorizontal Property   System.Object VerbindungZuTorsoHorizontal {get;set;}

VerbindungZuTorsoVertikal   Property   System.Object VerbindungZuTorsoVertikal {get;set;}

Die Methode Sag nimmt in der Variablen $Was einen String entgegen. In der folgenden geschweiften Klammer steht was damit geschehen soll. Im Beispiel wird der entgegengenommene Text in grün auf den Bildschirm geschrieben.

$o.Sag("Heute ist ein schöner Tag")

führt also zur Ausgabe:

Heute ist ein schöner Tag

3.5.5.2   Konstruktor

Wenn ein Objekt aus einer Klasse instanziiert wird geschieht das immer mithilfe eines sogenannten Konstuktors.

Grundsätzlich gibt es immer einen StandardKonstruktor. Den haben Sie auch schon benutzt:

[Kopf]::new()

Letztendlich ist das die statische Methode new() direkt an der Klasse. Die ist etwas versteckt, kann man sich aber auch anzeigen lassen:

[Kopf] | Get-Member -static -force

Ergibt:

  TypeName: Kopf

 

Name             MemberType Definition

----             ---------- ----------

Equals           Method     static bool Equals(System.Object objA, System.Object objB)

get_Beschreibung Method     static string get_Beschreibung()

new              Method     Kopf new()

ReferenceEquals  Method     static bool ReferenceEquals(System.Object objA, System.Object objB)

set_Beschreibung Method     static void set_Beschreibung(string )

Beschreibung     Property   static string Beschreibung {get;set;}

Es steht zwar nicht in unserer Klasse, aber Sie können sich eigentlich immer folgende Zeile dazu denken:

Kopf () {}

Der Methodenname des Konstruktors ist also immer gleich dem Namen der Klasse. Ist die Klasse erst einmal definiert, taucht diese Methode nicht mit dem Namen der Klasse auf, sondern als New.

Soll etwas Besonderes beim Erstellen des Objektes passieren, können Sie diese Methode einfach mit Inhalten füllen. Diese überschreiben dann den default constructor.

Kopf () {write-host "Kopf Objekt wird instanziiert!"}

Baut man die eben erwähnte Zeile in die geschweifte Klammer innerhalb der Klassendefinition. Sieht alles (z.B. Get-Member) genauso aus wie zuvor, aber beim Instanziieren des Objekts wird zusätzlich ein Text auf dem Schirm geschrieben:

[Kopf]::new()

Kopf Objekt wird instanziiert!

VerbindungZuTorsoHorizontal VerbindungZuTorsoVertikal

--------------------------- -------------------------

                      Mitte                      Oben

3.5.5.3   Enumerator

Ein Enumerator ist eine Auflistung von möglichen Werten die einer Eigenschaft zugewiesen werden können.

Der Aufbau könnte so aussehen:

Enum Ausrichtung {

    Undefiniert = 0

    Links = 1

    Rechts = 2

    Oben = 3

    Unten = 4

    Mitte = 5

    VorneLinks = 6

    VorneRechts = 7

    HintenLinks = 8

    HintenRechts = 9

    Hinten = 10

    Vorne = 11

}

Entsprechend können wir nun auch die Kopf-Klasse aufpeppen:

Class Kopf {

    static [String]$Beschreibung = "Kann aus weiteren Objekten wie Augen, Mund, Ohren, etc. bestehen"

    [Ausrichtung]$VerbindungZuTorsoHorizontal = "Mitte"

    [Ausrichtung]$VerbindungZuTorsoVertikal = "Oben"

    Sag ([String]$Was) {Write-Host $Was -ForegroundColor Green}

}

Geändert haben sich nur die 2 Zeilen mit $VerbindungZu.... Hier wurde der Enumerator [Ausrichtung] vorangestellt.

Wenn nun erneut ein Objekt aus der Klasse abgeleitet wird sorgt Enum dafür, dass nur die definierten Werte, oder die Zahlen zugeordnet werden können. Jeder andere Wert führt zu einer entsprechenden Fehlermeldung.

$o.VerbindungZuTorsoHorizontal = 34

oder

$o.VerbindungZuTorsoHorizontal = "ObenMitte"

führt zu einer Fehlermeldung, während

$o.VerbindungZuTorsoHorizontal = 5

den Wert Mitte zuweist. Weiterhin kann die Klasse System.Enum nun herausfinden, welche Werte der Eigenschaft zugewiesen werden dürfen:

[system.enum]::getnames("Ausrichtung")

3.5.5.4   Konstruktoren überladen (Overload)

Gibt man die Methode new des Konstruktors ohne Klammern an, sieht man auf welche Art und Weise man Objekte erzeugen kann.


 

[Kopf]::new

zeigt:

OverloadDefinitions

-------------------

Kopf new()

Im Moment gibt es also nur den default constructor.

Bauen wir uns einen zusätzlichen Enumerator und passen die Kopfklasse ein wenig an.

Enum Intelligenz {

    Schlau = 1

    Normal = 2

    Dumm = 3

}

Class Kopf {

    static [String]$Beschreibung = "Kann aus weiteren Objekten wie Augen, Mund, Ohren, etc. bestehen"

    [Ausrichtung]$VerbindungZuTorsoHorizontal = "Mitte"

    [Ausrichtung]$VerbindungZuTorsoVertikal = "Oben"

    [Intelligenz]$Intelligenz = "Normal"

    Sag ([String]$Was) {Write-Host $Was -ForegroundColor Green}

    Kopf ($Intelligenz) {$this.Intelligenz = $Intelligenz}

}

[Kopf]::new

zeigt uns nun an, dass der default constructor durch unseren eigenen (in der letzten Zeile) überschrieben wurde.

Interessant hierbei ist auch die Variable $this. Innerhalb einer Klasse enthält $this zur Laufzeit des Objektes das Objekt selbst. Somit können also die Eigenschaften des jeweiligen Objektes gesetzt oder abgerufen werden, sowie die Methoden ausgeführt werden.

OverloadDefinitions

-------------------

Kopf new(System.Object Intelligenz)

Möchten Sie den default constructor behalten, müssen Sie die folgende Zeile in Ihrer Klassen definition einbauen:

Kopf () {}

Dann erhalten Sie bei [Kopf]::new folgende Ausgabe:

OverloadDefinitions

-------------------

Kopf new()

Kopf new(System.Object Intelligenz)

Hier sehen Sie nun tatsächlich eine sog. Überladung.

Sie können also entweder einfach ein neues Objekt ohne Angabe der Intelligenz erzeugen:

[Kopf]::new()

führt zu:

VerbindungZuTorsoHorizontal VerbindungZuTorsoVertikal Intelligenz

--------------------------- ------------------------- -----------

                      Mitte                      Oben      Normal

oder bei der Erstellung direkt angeben wie schlau dieser Kopf ist:

[Kopf]::new("Schlau")

[Kopf]::new("Dumm")

Selbstverständlich können Sie beliebig viele Überladungen anlegen und zwar nicht nur bei einem Konstruktor, sondern auch bei jeder "normalen" Methode.

3.5.5.5   Zusammengesetzte Klassen

Klassen können auch auf andere Klassen zurückgreifen und deren Bestandteile in sich aufnehmen.

Ein Säugetier hat immer einen Kopf und einen Torso (Körper). Also basteln wir uns eine Schablone für Säugetiere aus Kopf und Torso Klasse und bauen uns noch ein paar Eigenschafen dazu:

Enum Ausrichtung {

    Undefiniert = 0

    Links = 1

    Rechts = 2

    Oben = 3

    Unten = 4

    Mitte = 5

    VorneLinks = 6

    VorneRechts = 7

    HintenLinks = 8

    HintenRechts = 9

    Hinten = 10

    Vorne = 11

}

 

Class Torso {

    static [String]$Beschreibung = "Hält alles zusammen"

}

Class Kopf {

    static [String]$Beschreibung = "Kann aus weiteren Objekten wie Augen, Mund, Ohren, etc. bestehen"

    [Ausrichtung]$VerbindungZuTorsoHorizontal = "Mitte"

    [Ausrichtung]$VerbindungZuTorsoVertikal = "Oben"

    Sag ([String]$Was) {Write-Host $Was -ForegroundColor Green}

}

 

Class Saeugetier {

    static [String]$Beschreibung = "Tier, das lebende Junge zur Welt bringt und säugt."

    [datetime]$Geburtstag = (get-date)

    [String]$Name

    $Torso = [Torso]::new()

    $Kopf = [Kopf]::new()

    $GUID = [GUID]::NewGuid()

}

In unserer Klasse Saeugetier haben wir zunächst wieder die Klassenbeschreibung. Zusätzlich habe ich hier noch die 2 Eigenschaften Name und Geburtstag eingefügt. Der Geburtstag wird gleich mit dem aktuellen Datum beim Erstellen eines Objekts basierend auf der Klasse eingetragen. Auch diese vorbelegte Eigenschaft kann im Nachhinein geändert werden.

Auf diese beiden Eigenschaften folgt dann die Erstellung von Objekten der beiden Klassen Kopf und Torso als Bestandteile unseres Säugetiers.

Zusätzlich bekommt jedes Geschöpf noch einen “Fingerabdruck“ in Form einer GUID (Global Unique IDentifier).

Ein Säugetier Objekt erstellen, geht wie bereits bekannt:

$s = [Saeugetier]::New()

Abruf von $s sieht dann so oder ähnlich aus:

Geburtstag : 2019-01-18 09:38:53

Name       :

Torso      : Torso

Kopf       : Kopf

GUID       : a3e412fa-bbfb-42f8-9a5f-48bd45399706

Natürlich können Sie dann auch die Sag Methode des Kopf Objektes am Säuger benutzen:

$s.Kopf.Sag("Hallo")

Ergebnis:

Hallo

3.5.5.6   Interaktion von Objekten

Jetzt wird's spannend. Bislang haben wir Gott gespielt. Aber nun kommt der mehr oder weniger freie Wille ins Spiel. Sex!

Dazu brauchen wir zunächst noch einen Enumerator für das Geschlecht:

Enum Geschlecht {

    Männlich = 1

    Weiblich = 2

}

Die Klasse Saeugetier wird noch um eine Methode Sex und eine Eigenschaft Geschlecht erweitert:

Class Saeugetier {

    static [String]$Beschreibung = "Tier, das lebende Junge zur Welt bringt und säugt."

    [datetime]$Geburtstag = (get-date)

    [String]$Name

    $Torso = [Torso]::new()

    $Kopf = [Kopf]::new()

    $GUID = [GUID]::NewGuid()

    [Geschlecht]$Geschlecht = (get-random -min 1 -max 3)

    static hidden Saeugetier () {}

    [Saeugetier]Sex ($Partner) {

        if ($Partner.Geschlecht -eq $this.Geschlecht) {

            throw "So geht das nicht!"

        } else {

            return (New-Object Saeugetier)

        }

    }

}

Das Geschlecht des Kind Objektes wird beim Sex per Zufall festgelegt (1 = Männlich oder 2 = Weiblich).

Die Methode Sex braucht einen Partner und gibt ein Objekt vom Typ Saeugetier zurück. Daher müssen wir auch hier erst einmal wieder Gott spielen und einen Adam und eine Eva bauen:

$s1 = New-Object Saeugetier

$s2 = New-Object Saeugetier

Gerne können Sie es dann direkt einmal versuchen auszuführen:

$s1.sex($s2)

Mit etwas Glück klappt es, oder Sie bekommen die Fehlermeldung "So geht das nicht!".

Das Geschlecht von $s1 und$s2 wird ja per Zufall festgelegt. Wenn beide männlich oder beide weiblich sind, klappt das natürlich nicht.

Sollten Sie also über die Fehlermeldung gestolpert sein, müssen bei einem der beiden Objekte erst die Eigenschaft Geschlecht ändern, damit es klappt:

$s2.Geschlecht = (das was es vorher nicht war Männlich -> Weiblich, oder Weiblich -> Männlich)

Wenn Sie wissen, dass es klappt, können Sie nun eine kleine Familie erstellen:

$ZweiteGeneration = @()

1..5 | foreach { $ZweiteGeneration += $s1.sex($s2) }


 

3.5.5.7   Vererbung von Klassen

Wie wäre es mit einem Hund? Ein Hund ist zweifelsfrei ein Säugetier. Also warum nehmen wir nicht das, was wir schon haben und basteln einfach noch ein bisschen dazu. Man muss das Rad ja nicht immer komplett neu erfinden.

Da ein Hund nicht nur aus einem Kopf und einem Torso besteht, brauchen wir natürlich noch ein paar Beine und einen Schwanz. Wir benötigen noch einen Enumrator, eine Schwanz und eine Bein Klasse, sowie den Hund zusätzlich zu dem, was wir schon haben:

Enum Schwanzzustand {

    Normal = 1

    Wedelt = 2

    Eingeklemmt = 3

}

 

Class Bein {

    static [String]$Beschreibung = "Für die Fortbewegung geeignet"

    [Ausrichtung]$VerbindungZuTorsoVertikal = "Unten"

    [ValidateSet("Links","Rechts","VorneLinks","VorneRechts","HintenLinks","HintenRechts")][Ausrichtung]$VerbindungZuTorsoHorizontal

    Bein ([Ausrichtung]$VerbindungZuTorsoHorizontal) {$this.VerbindungZuTorsoHorizontal = $VerbindungZuTorsoHorizontal}

}

Class Schwanz {

    static [String]$Beschreibung = "Auch ein Ausdruck des Gemütszustandes"

    [Ausrichtung]$VerbindungZuTorsoHorizontal = "Hinten"

    [Ausrichtung]$VerbindungZuTorsoVertikal = "Oben"

    [Schwanzzustand]$Zustand = 1

    Wedeln () {$this.Zustand = 2}

    Angst () {$this.Zustand = 3}

}

 

Class Hund : Saeugetier {

    static [String]$Beschreibung = "Hat 4 Beine, einen Kopf und einen Schwanz die durch den Torso zusammengehalten werden."

    [Array]$Beine = ([Bein]::new("VorneLinks")) , ([Bein]::new("VorneRechts")) , ([Bein]::new("HintenLinks")) , ([Bein]::new("HintenRechts"))

    $Schwanz = [Schwanz]::new()

    static hidden Hund () {}

}

Hier sind gleich ein paar Neuerungen erklärungsbedürftig.

In der Klasse Bein finden Sie die Zeile mit [ValidateSet("Links",...

Durch das ValidateSet wird eine Eingrenzung des Enumerators auf nur bestimmte Positionsangaben erwirkt.

Bei einem Hund macht "Links" und "Rechts" wenig Sinn, aber vielleicht machen wir uns später ja auch noch an die Krönung der Schöpfung. Daher ist die Einschränkung an der Klasse Bein nicht ganz so restriktiv, wie für den Hund sinnvoll.

Das ValidateSet kommt Ihnen sicher aus help about_Functions_Advanced_Parameters bekannt vor. Genau das können Sie hier auch einsetzen.

Nun zur Objektklasse Hund. Auffällig ist hier zunächst einmal der Kopf der Klasse:

Class Hund : Saeugetier {

: Saeugetier sagt aus, dass diese Klasse alle Eigenschaften und Methoden aus der Klasse der Saeugetiere übernimmt (auch den Sex :-) ).

Aus der Klasse der Saegetiere wird auch die Anweisung static $Beschreibung übernommen, doch da wir in der abgeleiteten Klasse Hund ebenfalls static $Beschreibung angeben, wird das vererbte aus Klasse Saeugetier überschrieben.

Weiterhin fügen wir eine Eigenschaft Beine vom Typ eines Arrays hinzu (sind ja mehrere, normaler Weise 4). Hinter dem = werden dann die einzelnen Bein Objekte mit Ihrer jeweiligen Position generiert.

Der Standardkonstruktor Hund () {} wird durch Angabe des Wortes hidden versteckt. Selbst mit

[Hund] | Get-Member -static -force

ist der default constructor nicht zu sehen. Trotzdem können Sie Ihn nutzen, um Ihren Hunde-Adam und Ihre Hunde-Eva zu erstellen:

$h1 = New-Object Hund

$h2 = New-Object Hund

oder

$h1 = [Hund]::new()

$h2 = [Hund]::new()

Jetzt noch prüfen, ob der Zufall auch ein Männchen und ein Weibchen generiert hat. Wenn nicht bei einem der beiden bitte noch die Eigenschaft Geschlecht anpassen und dann dürfen unsere beiden Hunde Prototypen auch ein bisschen Spaß (Sex) haben. Diese Methode wurde aus der Saeugetier Klasse vererbt.

$ZweiteGenerationHunde = @()

1..5 | foreach { $ZweiteGenerationHunde += $s1.sex($s2) }

Warum ich den default constructor versteckt habe? Ein Wesen aus dem nichts zu erschaffen ist Gott (Ihnen) vorbehalten. Die normal Sterblichen sollen sich per Sex fortpflanzen. Gott sei Dank sind wir nicht im 14. Jahrhundert, sonst käme ich dafür sicher auf den Scheiterhaufen.

3.5.5.8   Methoden parallel ablaufen lassen

Der 6. Tag...machen wir uns an die Krönung der Schöpfung, den Menschen.

Auch der Mensch ist ein Säugetier. Daher können wir, wie beim Hund, diese Klasse wieder nutzen. Unser Mensch besteht auch wieder aus einem Kopf und einem Torso. Zusätzlich bauen wir noch 2 Beine und zwei Arme dran.

Arme kann man z.B. heben und senken. Der Einfachheit halber reduziere ich das einmal auf eine Methode Heben mit einer Prozentangabe wie hoch der Arm gehoben ist. Den Arm zu heben könnte man dann mit dem Kopf zusammen für eine Begrüßungsmethode nutzen. Auf die Hände verzichte ich hier auch, um das Beispiel einigermaßen simpel zu halten. Das Problem ist nun, wenn ich die Methode Hallo zum Begrüßen aufrufen möchte, muss ich warten, bis die Prozedur abgeschlossen ist. Aber wenn Person A der Person B die Hand (bzw. hier den Arm) schüttelt, soll das ja gleichzeitig ablaufen und nicht erst der eine seinen Arm 3 x hoch und runter bewegen und dann der andere.

Irgendwie müssen wir hinbekommen, dass man die Methode startet und sich dann nicht weiter drum kümmern muss. Das Problem dabei ist, dass Jobs in einem eigenen Prozess ablaufen und auch Events nicht verfügbar sind. Die einzige Möglichkeit ist hier auf Runspaces zurück zu greifen, die über einen synchronisierten Hash gemeinsame Variablen nutzten können (siehe Abschnitt Runspaces). Diese Technik müssen wir dann irgendwie in unserer Objektklasse unterbringen. So sieht das komplette Script aus:

1.  # Script zur Erklärung von PowerShell 5 Klassen und Enumeratoren

2.   

3.  # Auflistungen

4.  Enum Ausrichtung {

5.      Undefiniert = 0

6.      Links = 1

7.      Rechts = 2

8.      Oben = 3

9.      Unten = 4

10.            Mitte = 5

11.            VorneLinks = 6

12.            VorneRechts = 7

13.            HintenLinks = 8

14.            HintenRechts = 9

15.            Hinten = 10

16.            Vorne = 11

17.        }

18.        Enum Schwanzzustand {

19.            Normal = 1

20.            Wedelt = 2

21.            Eingeklemmt = 3

22.        }

23.        Enum Geschlecht {

24.            Männlich = 1

25.            Weiblich = 2

26.        }

27.        Enum Intelligenz {

28.            Schlau = 1

29.            Normal = 2

30.            Dumm = 3

31.        }

32.        # Definition von Körperteilen

33.        Class Torso {

34.            static [String]$Beschreibung = "Hält alles zusammen"

35.        }

36.        Class Kopf {

37.            static [String]$Beschreibung = "Kann aus weiteren Objekten wie Augen, Mund, Ohren, etc. bestehen"

38.            [Ausrichtung]$VerbindungZuTorsoHorizontal = "Mitte"

39.            [Ausrichtung]$VerbindungZuTorsoVertikal = "Oben"

40.            [Intelligenz]$Intelligenz = "Normal"

41.            Sag ([String]$Was) {Write-Host $Was -ForegroundColor Green}

42.            Kopf () {}

43.            Kopf ($Intelligenz) {$this.Intelligenz=$Intelligenz}

44.        }

45.        Class Arm {

46.            static [String]$Beschreibung = "Eignen sich zur Begrüßung"

47.            [Ausrichtung]$VerbindungZuTorsoVertikal = "Mitte"

48.            [ValidateSet("Links","Rechts")][Ausrichtung]$VerbindungZuTorsoHorizontal

49.            $Hoehe = [hashtable]::Synchronized(@{Prozent = 0})

50.            Arm ([Ausrichtung]$VerbindungZuTorsoHorizontal) {$this.VerbindungZuTorsoHorizontal = $VerbindungZuTorsoHorizontal}

51.            Heben ([Byte]$Hoehe) {$this.Hoehe = $Hoehe}

52.        }

53.        Class Bein {

54.            static [String]$Beschreibung = "Für die Fortbewegung geeignet"

55.            [Ausrichtung]$VerbindungZuTorsoVertikal = "Unten"

56.            [ValidateSet("Links","Rechts","VorneLinks","VorneRechts","HintenLinks","HintenRechts")][Ausrichtung]$VerbindungZuTorsoHorizontal

57.            Bein ([Ausrichtung]$VerbindungZuTorsoHorizontal) {$this.VerbindungZuTorsoHorizontal = $VerbindungZuTorsoHorizontal}

58.        }

59.        Class Schwanz {

60.            static [String]$Beschreibung = "Auch ein Ausdruck des Gemütszustandes"

61.            [Ausrichtung]$VerbindungZuTorsoHorizontal = "Hinten"

62.            [Ausrichtung]$VerbindungZuTorsoVertikal = "Oben"

63.            [Schwanzzustand]$Zustand = 1

64.            Wedeln () {$this.Zustand = 2}

65.            Angst () {$this.Zustand = 3}

66.        }

67.         

68.        # Basis Klasse für Säugetiere

69.        Class Saeugetier {

70.            static [String]$Beschreibung = "Tier, das lebende Junge zur Welt bringt und säugt."

71.            [datetime]$Geburtstag = (get-date)

72.            [String]$Name

73.            $Torso = [Torso]::new()

74.            $Kopf = [Kopf]::new()

75.            $GUID = [GUID]::NewGuid()

76.            [Geschlecht]$Geschlecht = (get-random -min 1 -max 3)

77.            static hidden Saeugetier () {}

78.            [Saeugetier]Sex ([Saeugetier]$Partner) {

79.                $PartnerKlasse = $Partner.GetType().Name

80.                $MeineKlasse = $this.GetType().Name

81.                if ($Partner.Geschlecht -eq $this.Geschlecht -or $PartnerKlasse -ne $MeineKlasse) {

82.                    throw "So geht das nicht!"

83.                } else {

84.                    return (New-Object $MeineKlasse)

85.                }

86.            }

87.        }

88.         

89.        # Abgeleitete Klassen aus den Säugetieren

90.        Class Mensch : Saeugetier {

91.            static [String]$Beschreibung = "Hat 2 Arme, 2 Beine und einen Kopf die durch den Torso zusammengehalten werden."

92.            [Array]$Arme = ([Arm]::new("Links")) , ([Arm]::new("Rechts"))

93.            [Array]$Beine = ([Bein]::new("Links")) , ([Bein]::new("Rechts"))

94.            static hidden Mensch () {}

95.            hidden $Background = [hashtable]::Synchronized(@{

96.                Runspace = $null

97.                PowerShell = $null

98.                RunSpaceHandle = $null

99.            })

100.           Hallo () {

101.               # Kopf Action

102.               $this.Kopf.Sag("Hallo")

103.        

104.               # Arme Action

105.               # Erstelle Runspace zur Hintergrundverarbeitung und lege ihn in der versteckten Eigenschaft Background ab

106.               $this.Background.Runspace = [runspacefactory]::CreateRunspace()

107.               $this.Background.RunSpaceHandle = $null

108.               $this.Background.PowerShell = [PowerShell]::Create()

109.               $this.Background.Runspace.Open()

110.               # Mit Runspace gemeinsam genutzte Variablen festelegen

111.               $this.Background.Runspace.SessionStateProxy.SetVariable('Hoehe',$this.Arme[1].Hoehe)

112.               $this.Background.Runspace.SessionStateProxy.SetVariable('Background',$this.Background)

113.               $this.Background.Powershell.Runspace = $this.Background.Runspace

114.               # Hintergrund Script festlegen

115.               $null = $this.Background.Powershell.AddScript({

116.                   1..3 | foreach {

117.                       $Hoehe.Prozent = 30

118.                       sleep 1

119.                       $Hoehe.Prozent = 20

120.                       sleep 1

121.                   }

122.                   $Hoehe.Prozent = 0

123.                   # Verbauchten Runspace aufräumen (Selbstlöschung)

124.                   $Background.Powershell.EndInvoke($Background.RunSpaceHandle)

125.                   $Background.PowerShell.dispose()

126.               })

127.               # Starten des Hintergrundprozesses

128.               $this.Background.RunSpaceHandle = $this.Background.Powershell.BeginInvoke()

129.           }

130.           Hallo ([Mensch]$Partner) {

131.            $this.Hallo()

132.            $Partner.Hallo()

133.           }

134.       }

135.        

136.       Class Hund : Saeugetier {

137.           static [String]$Beschreibung = "Hat 4 Beine, einen Kopf und einen Schwanz die durch den Torso zusammengehalten werden."

138.           [Array]$Beine = ([Bein]::new("VorneLinks")) , ([Bein]::new("VorneRechts")) , ([Bein]::new("HintenLinks")) , ([Bein]::new("HintenRechts"))

139.           $Schwanz = [Schwanz]::new()

140.           static hidden Hund () {}

141.       }

142.        

143.       # Adam & Eva (Menschen)

144.       $m1 = [Mensch]::New()

145.       $m1.Geschlecht = "Männlich"

146.       $m1.Name = "Sokrates" # Ein weiser Mann

147.       $m2 = [Mensch]::New()

148.       $m2.Geschlecht = "Weiblich"

149.       $m2.Name = "Xanthippe" # Frau des Sokrates...mehr schreib ich nicht

150.       # Erstelle Population

151.       $ArrayDerMenschheit=@()

152.       for ($i=0;$i -lt (get-random -min 3 -max 5);$i++) {

153.           $ArrayDerMenschheit += $m1.sex($m2)

154.       }

155.       $ArrayDerMenschheit

156.        

157.       # Beobachten der Begrüßung der ersten beiden und noch einem 3 als Referenz

158.       $ArrayDerMenschheit[0].Hallo($ArrayDerMenschheit[1])

159.       do {

160.           write-host "`rM1: $($ArrayDerMenschheit[0].Arme[1].Hoehe.Prozent), M2: $($ArrayDerMenschheit[1].Arme[1].Hoehe.Prozent), M3: $($ArrayDerMenschheit[2].Arme[1].Hoehe.Prozent)" -NoNewline

161.       } while ($ArrayDerMenschheit[0].Arme[1].Hoehe.Prozent -ne 0)

162.        

163.       # Adam & Eva (Hunde)

164.       $h1 = [Hund]::New()

165.       $h1.Geschlecht = "Männlich"

166.       $h1.Name = "Ares"

167.       $h2 = [Hund]::New()

168.       $h2.Geschlecht = "Weiblich"

169.       $h2.Name = "Aphrodite"

170.       # Erstelle Population

171.       $ArrayDerHundheit=@()

172.       for ($i=0;$i -lt (get-random -min 2 -max 5);$i++) {

173.           $ArrayDerHundheit += $h1.sex($h2)

174.       }

175.       $ArrayDerHundheit

Die Musik spielt hier in der Klasse Mensch. Den Rest sollten Sie aus den vorangegangenen Erläuterungen kennen, oder sich zumindest einfach erklären können.

Die ersten 4 Zeilen der Klasse Mensch sollten auch klar sein. Interessant wird es in der 5. Zeile der Klasse Mensch:

hidden $Background = [hashtable]::Synchronized(@{

    Runspace = $null

    PowerShell = $null

    RunSpaceHandle = $null

})

Dies erstellt eine versteckte Eigenschaft Background. In dieser verstecken wir die zwischen den Runspaces synchronisierte Hash-Variable mit den Inhalten: Runspace, PowerShell und RunspaceHandle.

Die Methode Hallo () ruft die Methode Sag ("Hallo") am Kopf auf. Das dauert nicht lange, deshalb wird dies hier auch nicht als Hintergrundaufgabe abgearbeitet.

Aber das Hände- bzw. Arme schütteln dauert etwas.

Die ersten 4 Zeilen der Arme Aktion erstellen den Runspace in dem die "Show" abläuft:

$this.Background.Runspace = [runspacefactory]::CreateRunspace()

$this.Background.RunSpaceHandle = $null

$this.Background.PowerShell = [PowerShell]::Create()

$this.Background.Runspace.Open()

Dann werden die beiden synced Hastables bekannt gemacht (Zeilen 111 +112):

$this.Background.Runspace.SessionStateProxy.SetVariable('Hoehe',$this.Arme[1].Hoehe)

$this.Background.Runspace.SessionStateProxy.SetVariable('Background',$this.Background)

Das geht los mit der Höhe des (rechten, weil Arme[1]) Arms. Hierzu wurde auch die Eigenschaft Hoehe der Klasse Arm zu einem synced Hash umgebaut. Als zweites wird dann noch die Background Eigenschaft mit dem Runspace bekannt gemacht.

Als nächstes wird der erstellte Runspace an das PowerShell Objekt geknüpft (Zeile 113):

$this.Background.Powershell.Runspace = $this.Background.Runspace

Nach dem # Hintergrund Script festlegen wird dieser PowerShell das Script übergeben, das im Hintergrund ablaufen soll:

$null = $this.Background.Powershell.AddScript({

    1..3 | foreach {

        $Hoehe.Prozent = 30

        sleep 1

        $Hoehe.Prozent = 20

        sleep 1

    }

    $Hoehe.Prozent = 0

    # Verbauchten Runspace aufräumen (Selbstlöschung)

    $Background.Powershell.EndInvoke($Background.RunSpaceHandle)

    $Background.PowerShell.dispose()

})

Innerhalb des Scriptes sind die mit der Methode SetVariable übergebenen Variablen unter dem Namen vor dem Komma bekannt. So findet in einer kleinen Schleife das Armschütteln statt. Die letzten beiden Zeilen des Scripts, geben nach verrichteter Arbeit den Runspace und die PowerShell wieder frei.

Zu guter letzt (Zeile 128) startet die Methode Hallo () das Script im Runspace mittels der Methode BeginInvoke ():

$this.Background.RunSpaceHandle = $this.Background.Powershell.BeginInvoke()

Da man zum "Armeschütteln" noch einen Partner braucht, wird noch eine Überladung der Methode Hallo hinzugefügt:

Hallo ([Mensch]$Partner) {

 $this.Hallo()

 $Partner.Hallo()

}

Die ruft die zuvor definierte Hintergrund Methode an sich selbst und am übergebenen Partner Objekt auf.

So nun brauchen wir einen menschlichen Adam und eine Eva (wurden aus datenschutzrechtlichen Gründen umbenannt ;-):

$m1 = [Mensch]::New()

$m1.Geschlecht = "Männlich"

$m1.Name = "Sokrates" # Ein weiser Mann

$m2 = [Mensch]::New()

$m2.Geschlecht = "Weiblich"

$m2.Name = "Xanthippe" # Frau des Sokrates...mehr schreib ich nicht

Auf den beiden beruhend, erstelle ich im Beispiel eine kleine Population mit 3-5 Menschen:

$ArrayDerMenschheit=@()

for ($i=0;$i -lt (get-random -min 3 -max 5);$i++) {

    $ArrayDerMenschheit += $m1.sex($m2)

}

$ArrayDerMenschheit

Um das 1. Kind von Sokrates und Xanthippe, dem 2. den Arm schütteln zu lassen, habe ich diese Anweisung gegeben:

$ArrayDerMenschheit[0].Hallo($ArrayDerMenschheit[1])

Um dem ganzen Schauspiel zuzusehen, folgt direkt darauf:

do {

    write-host "`rM1: $($ArrayDerMenschheit[0].Arme[1].Hoehe.Prozent), M2: $($ArrayDerMenschheit[1].Arme[1].Hoehe.Prozent), M3: $($ArrayDerMenschheit[2].Arme[1].Hoehe.Prozent)" -NoNewline

} while ($ArrayDerMenschheit[0].Arme[1].Hoehe.Prozent -ne 0)

Was zu dieser Ausgabe führt:

Hallo

Hallo

M1: 30, M2: 30, M3: 0

M1 zeigt die Höhe beim 1. Kind, M2 beim 2. Kind und M3 beim 3. Kind an. Hier lässt sich nun schön ablesen, wie die Armhöhe bei den ersten beiden zwischen 30 und 20 Prozent schwankt und nach 3 x auf und ab wieder auf 0 zurück geht. Bei Referenz Kind Nr. 3 bleibt es bei 0, denn der hat vielleicht gerade Spaß mit Nr. 4.

3.5.5.9   Event Trick(ers)

Eingangs dieses Kapitels schrieb ich. Events werden nicht unterstützt. Das ist soweit korrekt. Allerdings dürfen wir PowerShell auch gerne austricksten:

Class Trick {

    # Properties

    hidden [String]$_MyProperty=$null

    # Constructors

    Trick () {

        $this | Add-Member -Name MyProperty -MemberType ScriptProperty -Value {

            # Getter

            return $this._MyProperty

        } -SecondValue {

            # Setter

            param ($Value)

            $this._MyProperty = $Value

            $this.OnPropertyChange()

        }

    }

    OnPropertyChange () {

        # Wenn sich die Eigenschaft geändert hat mach irgendwas, z.B.:

        "Die Eigenschaft MyProperty ist jetzt: $($this._MyProperty)." | oh

    }

}

Sie basteln sich eine Klasse namens Trick. Dieser geben Sie eine versteckte Eigenschaft mit einem vorangestellten Unterstrich. Dann erstellen Sie einen Konstruktor für Trick, der den Standardkonstruktor überschreibt und natürlich gerne beliebige weitere Overloads dazu.

Der Trick ist nun in den Konstruktoren eine tatsächlich sichbare Eigenschaft ohne den vorangestellten Unterstrich mit Add-Member als Scriptproperty einzuhängen. Ob Sie einen Unterstrich, oder H wie hidden nehmen, oder das Ding Hutzlibutzli taufen ist egal. Für die Übersicht finde ich es gut, wenn man grundsätzlich einfach irgendein immer wieder gleiches Zeichen voranstellt.

-Value überschreibt den Default Getter (Get = Eigenschaft abfragen) einer Eigenschaft und…

-Value2 überschreibt den Default Setter (Set = Eigenschaft setzen) einer Eigenschaft.

In Value wird nun einfach der Wert aus Ihrer versteckten Eigeschaft ausgelesen und zurück gegeben.

In Value2 wird die Eigenschaft auf den übergebenen Wert gesetzt und die Methode OnPropertyChange aufgerufen. Die dürfen Sie gerne auch anders nennen oder gleich Ihren Code hier reinschreiben, der passieren soll, wenn die Eigenschaft gesetzt wird.

Jedes Mal, wenn nun die Eigenschaft MyProperty verändert wird, führt unsere Klasse automatisch diese Methode aus. Also habe Sie sich quasi Ihren eigenen Event geschaffen.

Wenn dann die Klasse benutzen könnte das so aussehen:

PS C:\Users\...> $Object=[Trick]::new()

PS C:\Users\...> $Object.MyProperty="Test"

Die Eigenschaft MyProperty ist jetzt: Test.

PS C:\Users\...> $Object.MyProperty="TestA"

Die Eigenschaft MyProperty ist jetzt: TestA.

Sobald sich die Eigenschaft ändert, wird der in OnPropertyChange definierte Text ausgegeben. Aber Sie können natürlich auch gerne eine Datenbank aktualiseren, grafische Fenster öffnen oder was Ihr Herz sonst noch so begehrt. PowerShell kann viel…sogar das was eigentlich nicht geht ;-p

3.6     PowerShell Core (Versionen: 6.x & 7.x)

Die Grundlagen (Wo bekomme ich es her? Wie installieren/aufrufen?) wurde bereits im Kapitel Andere Plattformen erklärt.

Hier werden die Besonderheiten dieser Versionen erläutert. PowerShell 6 werde ich im Rahmen dieses Buches nicht weiter erläutern, da inzwischen Version 7.2 verfügbar ist und die auch überall dort läuft, wo 6 auch funktioniert. PowerShell 6 setzt noch auf einem älteren .NET Core auf und erst mit .NET Core 3.1 und PowerShell 7 stehen auch Cmdlets mit GUI Ausgabe zur Verfügung die Sie bereits von den „Windows only“ Powershell Versionen her kennen, wie z.B. Out-Gridview.

Achtung! Ab PowerShell 7.2 werden Windows Betriebssysteme älter als Windows Server 2012 R2  (Server) bzw. Windows 10 (Client) nicht mehr unterstützt. Windows Server 2008 R2 und 2012 auch nur noch eingeschränkt bereits ab Version 7.0. Mac OS 10.13 wird auch nur bis 7.1 unterstützt. Für Details und die einzelnen Linux Distributionen und Versionen stehen unter https://docs.microsoft.com/de/powershell/scripting/install/PowerShell-Support-Lifecycle?view=powershell-7.1 zur Verfügung.

Automatische Updates können ab Version 7.2 mittels WSUS und/oder Microsoft Update eingespielt werden. Dazu muss lediglich während der Installation das Häkchen für das WSUS und/oder Microsoft Update gesetzt werden. Bei WSUS müssen natürlich auch die Pakete auf dem WSUS Server entsprechend bereitgestellt werden. Wenn die ein msi Installer-Paket verwenden, werden die beiden Optionen automatisch aktiviert.

3.6.1   Foreach -Parallel

Foreach -Parallel kennen Sie bereits aus dem Abschnitt über Workflows. Das tolle an PS 7 ist, dass Sie hier dafür keine aufwändigen Workflows mehr brauchen, sondern es direkt (auch ohne Workflows) nutzen können. Bis zu Version 7.2 hat die Sache allerdings noch einen kleinen Haken. Es klappt nur mit einer Pipeline.

Geht:

Get-ADComputer -filter * | Foreach -parallel { Get-CIMClass ` Win32_Process -Computer $_.DNSHostName }

Geht nicht:

Foreach -parallel ( $Computer in ( Get-ADComputer -filter * ) ) `
{ Get-CIMClass Win32_Process -Computer $Computer.DNSHostName }

Dabei spielt es auch keine Rolle wo Sie das -Parallel positionieren – alles schon ausprobiert 😉.

Der Workflow wird quasi automatisch erzeugt. Das beudetet aber auch, dass das was in der Schleife steht wieder in einem eigenen Speicherbereich abläuft. Sprich, Variablen aus dem Rest des Scriptes, die Sie hier einsetzen möchten müssen Sie genau wie im Workflow auch mit dem Wort Using kennzeichnen. Beispiel:

$Using:a

Um auf $a von einer anderen Stelle in Ihrem Script zuzugreifen. Des Weiteren wird auch der Schalter -Throttlelimit unterstützt. Damit können Sie angeben, wieviele Aufgaben maximal gleichzeitig abgearbeitet werden sollen. Haben Sie also 100 Computer würde er max. 5 gleichzeitig abfragen, wenn Sie -Throttlelimit 5 angeben.

3.6.2   Ternärer Operator

Klingt hoch wissenschaftlich, ist aber lediglich eine verkürze If-Then-Else (Wenn-Dann-Sonst) Schreibweise.

Syntax:

Wenn ? Dann : Sonst

Beispiel:

( Get-Process Notepad 2> $null ) ? 'Notepad läuft' : 'Notepad läuft nicht'

Haben Sie ein Notepad gestartet wird das, was zwischen ? und : steht ausgeführt. Haben Sie kein Notepad gestartet (Get-Process liefert nichts zurück, ist daher $false), wird das ausgeführt, was hinter dem Doppelpunkt steht.

Ich persönlich würde das vielleicht in einem One-Liner einsetzen, doch für eine bessere Lesbarkeit des Scripts, doch lieber zum guten, alten if (Bedingung) {Mach was} else {Mach was anderes} zurückgreifen.

3.6.3   Pipeline-Kettenoperatoren

Schöne Grüße von der bash! Die Linuxer kennen das schon seit „100 Jahren“ : && bzw. ||

Für die Windows Leute: Was der Unterschied zwischen ; und der | ist, sollte Ihnen an dieser Stelle des Buches mehr als klar sein. Ein ; ist schlicht ein Befehlstrenner. Heißt für PowerShell: Achtung! Ein Cmdlet ist hier zu Ende und nach dem ; beginnt ein neues Cmdlet. Das Linke wird zuerst ausgeführt und danach das rechts vom ;. Die beiden Cmdlets haben aber erst einmal nichts miteinander zu tun. Bei der | wird die Ausgabe des linken Cmdlets zur Eingabe der rechten Cmdlets (siehe Kapitel Pipelineverarbeitung falls Sie tatsächlich eine Auffrischunng benötigen).

Die && und || funktionieren grundsätzlich so wie das ; und nicht die |. Der Unterschied liegt darin, dass hier zum einfachen Trennen von Cmdlets eine Bedingung hinzukommt.

Syntaktisch:

Probier mal was && Und mache das hier nur, wenn das Links geklappt hat ($? = $true).

Probier mal was || Und mache das hier nur, wenn das Links schiefgelaufen ist ($? = $false).

Probier mal was ; Is mir egal….Roboter mit Senf 😉 (Wer’s nicht kennt…YouTube)

Beispiel:

Ls gibtsnix:\erstrechtnix 2> $null || ‘Hättest Du Dir denken können‘

Ls C:\ 2> $null || ‘Das wirst Du auf Windows nicht lesen‘

Ls gibtsnix:\erstrechtnix 2> $null && ‘OS spielt keine Rolle‘

Ls C:\ 2> $null && ‘Das wirst Du auf Linux nicht lesen‘

3.6.4   Null-Sammeloperator

Gehen Ihnen auch die Fehlermeldungen auf den Nerv, wenn in irgeneiner Variablen zur Laufzeit $null drin steht - also nichts?

Das können Sie nun ganz einfach abfangen mit ??.

$Niete = $null

$Jackpot = 10000000

$Niete ?? 100

Gibt die Zahl 100 zurück (Trostpreis).

$Jackpot ?? 100

Gibt Ihnen die ganzen 10 Millionen. Die 100 kommt nicht zum Einsatz, da in $Jackpot was drin ist (egal wieviel, oder was – Hauptsache kein $null).

Das Ganze läßt sich auch noch direkt zuweisen:

$Einsatz = Read-Host -Prompt ‘Ihren Einsatz bitte‘

$Gewinn = $Einsatz * ( Get-Random -Min 0 -Max 10 ) # Glücksrad

$Gewinn ??= $Einsatz

Wenigstens gibt es den Einsatz zurück, auch wenn manchmal eigentlich die Bank gewinnt. Wenn Sie natürlich 0 eingesetzt haben, gibt’s auch nichts zurück. Wer nicht wagt, der nicht gewinnt 😉.

3.6.4.1    Methoden nur dann ausführen, wenn das Objekt vorhanden ist

Blöd ist, wenn man .NET Methoden aufrüfen möchte, aber das Objekt $null ist. Dann gibt es eine Fehlermeldung.

$NotepadProcess = Ps *Notepad*

$NotepadProcess ist $null, weil (hoffentlich) kein Notepad gestartet ist.

$NotepadProcess.Kill()

Führt also folglich zu einer Fehlermeldung.

Notepad

Damit starten Sie ein Notepad.

$NotepadProcess = Ps *Notepad*

Jetzt ist auch ein Prozess-Objekt in $NotepadProcess drin.

$NotepadProcess.Kill()

Folglich klappt auch das Abschießen des Prozesses und zwar ohne Fehlermeldung.

${NotepadProcess}?.Kill()

Diese etwas wirre Schreibweise (Variablenname in geschweiften Klammern, gefolgt von einem Fragezeichen) führt wenigstens zu keiner Fehlermeldung, wenn im Moment kein Notepad läuft.

Notepad

Damit starten Sie nochmal ein Notepad.

${NotepadProcess}?.Kill()

Und hier wird nun der Notepadprozess gekillt, weil die Variable in der geschweiften Klammer ein Prozessobjekt mit der Methode Kill() enthält.

3.6.5   Fehlermeldungen

Fehlermeldungen werden in PowerShell-Core im sogenannten ConciseView (Concise view = prägnante Ansicht, ohne kryptisches Gebrabbel) angezeigt. Die alte Ansicht können Sie aber auch haben indem Sie:

$ErrorView = ‘NormalView‘

…eingeben.

Neu ist das Cmdlet Get-Error, welches meiner bescheidenen Meinung nach die Fehlermeldung sehr schön übersichtlich und vollständig (mit kryptischem Gebrabbel, aber übersichtlich 😉 ) darstellt:

Get-Error $Error[0]

Wenn Sie so wollen, die übersichtliche Variante von:

$Error[0] | Select *

4        Praxisbeispiele

In diesem Buchteil finden Sie viele Praxisbeispiele die sich in verschiedene Teilbereiche aufgliedern. Die Beschreibungen hier setzen viel Wissen aus den vorangegangen Teilen des Buches voraus und beschreiben nicht alles im Detail, sondern nur was vorher noch nicht erwähnt wurde. Bei Verständnisproblemen, lesen Sie bitte noch einmal weiter vorne im Buch in den entsprechenden Kapiteln.

4.1     Allgemeine Tipps rund um PowerShell

In den nächsten Abschnitten finden Sie allgemeine Tipps rund um die PowerShell.

4.1.1   Rückfragen automatisch mit Ja beantworten lassen

In manchen Situationen fragt die PowerShell von sich aus, ob Sie eine Aktion auch wirklich möchten. Z.B. beim setzen der ExecutionPolicy, dann geben Sie den Schalter aus den CommonParameter ‑Confirm noch ein $false mit auf den Weg. Die ExecutionPolicy ist besonders hartnäckig und der Schalter –Confirm:$false alleine würde noch nicht zum Erfolg führen. In diesem besonderen Fall benötigen Sie noch den Schalter –Force. Damit es gleich für den gesamten Rechner gilt, sollten Sie vor ausprobieren des Cmdlet die PowerShell als lokaler Administrator starten. Je nachdem wie Ihre Policy gerade steht tauschen Sie ggf. das Unrestricted gegen ein Restricted aus:

Set-ExecutionPolicy Unrestricted -Force -Confirm:$False

4.1.2   WMI Events (Might & Magic)

Mit WMI haben Sie die Möglichkeit das System im Hintergrund auf bestimmte Zustände hin überwachen zu lassen (z.B., wenn die Festplatte einen bestimmten Füllstand erreicht, kann automatisch ein Löschen von temporären Dateien erfolgen). Wie Sie das hinbekommen, beleuchtet dieses Kapitel.

Vielleicht haben Sie sich auch schon einmal gewundert, dass es bei WMI keine Events gibt. Führt man z.B. ein:

gwmi Win32_Operatingsystem | gm

aus, sieht man lediglich Methoden und Properties. Das liegt daran, dass die Events sehr gut versteckt sind, dafür aber weit über WMI hinaus reichen.

Wenn sie ein:

gwmi -List *Event

ausführen bekommen Sie alle WMI Event Klassen angezeigt. OK, da fehlen noch ein paar:

gwmi -List *Trace

zeigt den Rest.

Egal auf welche dieser Klassen Sie hier ein Get-Member probieren, es wird dunkel bleiben. Probieren Sie doch einmal:

gwmi Win32_ProcessStartTrace | gm

oder gerne auch

gwmi Win32_ProcessStartTrace | select *

Es bleibt dunkel bzw. gibt einen Fehler aus, weil es kein Objekt generiert wie bei allen anderen nicht Event-Klassen.

Schauen wir uns das Win32_ProcessStartTrace doch einmal näher an:

(gwmi -List Win32_ProcessStartTrace).Properties

Wie der Name schon sagt, wird hier überwacht ob ein Prozess gestartet wird.

Hier sehen Sie nun ein paar Eigenschaften aufgelistet, die Sie als Filter in einer WQL Abfrage nutzen können. Z.B.:

$Query = "select * from Win32_ProcessStartTrace where ProcessName='Notepad.exe'"

So was soll denn nun passieren, wenn ein Prozess namens Notepad.exe gestartet wird? Das können Sie in einer Aktion festlegen. Für den Anfang schlage ich folgendes vor:

$Action = {$Event | oh}

Nun teilen Sie PowerShell mit, dass die Action ausgeführt werden soll, wenn Notepad gestartet wird:

Register-CimIndicationEvent -SourceIdentifier BeliebigerName -Query $Query -Action $Action

Starten Sie nun Notepad und siehe da, plötzlich bewegt sich was in der PowerShel. Hier sehen Sie nun gleich, was es mit der Systemvariablen $Event auf sich hat. Da stehen nämlich Infos drin, die Sie in Ihrem Action-Code auswerten können. In die geschweiften Klammern der $Action Variable können Sie beliebigen PowerShell Code hineinschreiben, was auch immer bei Starten des Notepads geschehen soll.

Wenn sie das nicht mehr möchten schließen Sie entweder einfach die Konsole, oder löschen Sie die Eventüberwachung wieder durch:

Unregister-Event - SourceIdentifier  BeliebigerName

Die Möglichkeiten der Überwachung sind aufgrund der beschränkten Anzahl der angebotenen Eventklassen etwas mager, wenn auch die wichtigsten Dinge wie Registry, Prozesse und einiges andere bereits enthalten sind. Für darüber hinaus gehende Überwachung sind diese Klassen die mit doppeltem Unterstrich, wie z.B. diese hier:

 __InstanceOperationEvent

__InstanceModificationEvent

__InstanceCreationEvent

recht interessant. Das macht die WQL Abfrage zwar etwas komplizierter, aber auch mächtiger. Bleiben wir bei unserem Notepad-Start-Beispiel:

$Query = "select * from Win32_ProcessStartTrace where ProcessName='Notepad.exe'"

Letztendlich wird hier der Win32_Process Klasse eine neue Instanz hinzugefügt, daher benötigen Sie __InstanceCreationEvent in der folgenden Query:

$Query = "

Select * from __InstanceCreationEvent

Within 5

Where TargetInstance isa 'Win32_Process' and

TargetInstance.Name = 'Notepad.exe'

 "

Die erste Zeile legt fest, welche Art (Instanz löschen, erstellen, Klasse ändern etc..) von Ereignis Sie überwachen möchten. In dem Falle wird beim StartProcess eine neue Instanz der Klasse Win32_Process erstellt, daher ist __InstanceCreateEvent hier die richtige Wahl.

Die zweite Zeile Within 5 legt das Abfrage Intervall fest (alle 5 Sekunden).

Zeile 3: where TargertInstance isa (ist ein) Win32_Process ist quasi wie bei simplen WMI Abfragen das "Select * from Win_32Process" dann kommt noch das "and" (= und, das bedeutet auch die Folgezeile (4) muss erfüllt sein) TargetInstance.Name muss Notepad sein (und kein anderer Prozess). TargetInstance.Eigenschaft kann natürlich auch jede andere Eigenschaft sein, die in einem Win32_Process stecken, je nachdem was Sie vorhaben. Das bedeutet Sie können alles was Ihre WMI Klassen hergeben überwachen!

Eine Abfrage dieser Art:

$Query = "

Select * from __InstanceModificationEvent

Within 5

Where TargetInstance isa 'Win32_Volume' and

TargetInstance.DriveLetter = 'D:' and

TargetInstance.Freespace < 100000000000 and

PreviousInstance.Freespace >= 100000000000

 "

Würde nur dann auslösen, wenn die freie Kapazität von Laufwerk D: erstmalig unter 100 GB fällt, dank der letzten Zeile PreviousInstance.* die den direkt zuvor gemessenen Wert enthält. War er also direkt davor schon einmal unter 100GB, trifft die Bedingung nicht mehr zu und das Ereignis wird nicht nach 5 Sekunden schon wieder abgefeuert.

Das ist ja alles schon super schick, aber funktioniert eben nur solange die PowerShell Konsole geöffnet ist. Wie wäre es, das Ganze sogar Reboot fest zu machen, sprich die Überwachung geht sogar nach einem Reboot weiter?

Hierfür müssen Sie sich einmal außerhalb von root/cimV2 bewegen, denn nun können wir nicht einfach die Action vorgeben, denn das ist etwas komplizierter. Der hierfür interessante Namespace ist root/subscription. Hier müssen Sie zum einen Ihren Ereignissensor hinterlegen zum anderen finden Sie hier eine Klasse als Ersatz für Ihre Action.

gwmi -list *consumer -Namespace root/Subscription

zeigt Ihnen diese Aktionsmöglichkeiten an:

LogFileEventConsumer                      (Um Datei basierte Logs zu schreiben)
ActiveScriptEventConsumer             (Um ein VBS auszuführen, PS geht damit nicht – echt jetzt?)
NTEventLogEventConsumer             (Um in die Ereignisanzeige zu schreiben)
SMTPEventConsumer                         (Um Mails zu verschicken)
CommandLineEventConsumer        (Um EXE Dateien auszuführen wie z.B. powershell.exe)

Die Properties verraten mal wieder was Sie letztendlich alles konfigurieren können für die jeweilige Aktion.

Sie brauchen 3 Dinge (neue Instanzen von WMI Klassen):

__EventFilter                                     Eine Bedingung für die Auslösung,
CommandLineEventConsumer     eine Aktion die dann Ausgeführt wird…
__FilterToConumerBinding            und einen Ort wo Sie beides miteinander ablegen können, damit es wirkt.

Den __EventFilter kennen Sie bereits, er muss nur nochmal etwas modifiziert werden. Bleiben wir bei unserem Notepad Start:

$FilterInfo = @{

EventNameSpace = 'root/cimV2';

Name = "NotepadStartInfo";

Query = "

Select * from __InstanceCreationEvent

Within 5

Where TargetInstance isa 'Win32_Process' and

TargetInstance.Name = 'Notepad.exe'

";

QueryLanguage = 'WQL'

}

$Filter = New-CimInstance -NameSpace root/subscription -ClassName __EventFilter -Property $FilterInfo

Wir haben nur noch ein paar zusätzliche, nötige Argumente (alles außer Query – das kennen Sie schon) hinzugefügt und das Ganze als neue Instanz von __EventFilter dem root/subscription Namespace hinzugefügt. Punkt 1 von den 3 Dingen ist also schon mal erledigt.

Prüfen wir das einmal. Starten Sie hierzu bitte das Programm wbemtest.exe.

Klicken Sie auf Connect und ersetzen Sie bei NameSpace root/cimV2 mit root/subscriptions und dann auf den nun angezeigten Connect Knopf. Jetzt können Sie auf den EnumInstances Knopf klicken. Da Sie eine Instanz der Klasse __EventFilter angelegt haben, tragen Sie das bitte unter „SuperClass“ ein und klicken auf OK. Da sollten Sie nun Ihre NotepadStartInfo sehen.

Kommen wir nun zur Aktion, die ausgeführt werden soll. Wie wäre es mit einem Fenster, dass angezeigt werden soll? Dazu erstellen Sie zunächst ein kleines PowerShell Script:

param( $Time )

$StartTime = [DateTime]::FromFileTime( $Time )

"NotePad started at: $( $StartTime )" > C:\Temp\test.txt

Speichern Sie dies unter C:\Temp\WMITest.ps1 und testen Sie es.

Dieses Script soll beim Öffnen von Notepad automatisch gestartet werden, und zwar egal ob eine PowerShell Konsole auf ist, oder der Rechner sogar neu gestartet wurde. Für die Definition der Aktion geben Sie folgendes ein:

$ActionInfo = @{

Name = 'NotePadStartAktion';

ExecutablePath = 'C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe';

CommandLineTemplate = 'C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -File C:\Temp\WMITest.ps1 %TIME_CREATED%'

}

$Action = New-CimInstance -Namespace root/subscription -ClassName CommandLineEventConsumer -Property $ActionInfo

Zu beachten ist hier die Notwendigkeit, Variablen in % Zeichen einzuschließen.

Punkt Nr. 2 ist damit auch erledigt. Auch diese neue WMI Instanz dürfen Sie gerne noch einmal mit wbemtest.exe überprüfen.

Nun zum 3. und letzten Punkt, der 1 und 2 miteinander verknüpft:

New-CimInstance -Namespace root/subscription -ClassName __FilterToConsumerBinding -Property @{ Filter = [Ref] $Filter ; Consumer = [Ref] $Action }

Wenn Sie das mit dem Reboot ausprobieren, sollten Sie sich etwas in Geduld üben. Das Initialisieren von WMI dauert etwas. Es kann also sein, dass erstmal gar nichts passiert, wenn Sie es direkt nach der Anmeldung ausprobieren. Warten Sie einfach 5 Minuten und starten Sie dann ein Notepad. Über kurz oder lang sollte dann eine Test.txt in C:\Temp auftauchen.

Wollen Sie das Ganze wieder los werden, müssen Sie „nur“ die 3 Instanzen, die Sie in WMI geschaffen haben, wieder löschen. Das können Sie mittels wbemtest, wie Ihnen sicherlich schon aufgefallen ist, aber natürlich auch über PowerShell.

Den Filter und die Aktion zu löschen ist kein Problem, da wir hier das Name Feld ausgefüllt haben, bei der Verknüpfungsinstanz gibt es aber leider kein Name Attribut (weswegen ich das auch nicht gleich unter Punkt 3 eingefügt habe). Da aber in der Verknüpfungsinstanz der Filter hinterlegt ist, können Sie die Verknüpfungsinstanz wie folgt löschen:

Get-CimInstance -Namespace root/subscription -ClassName __FilterToConsumerBinding | ? { $_.Filter.Name -like "NotepadStartInfo" } | Remove-CimInstance

Die anderen beiden z.B. so:

Get-CimInstance -Namespace root/subscription -ClassName __EventFilter | ? { $_.name -like "NotepadStartInfo" } | Remove-CimInstance

Get-CimInstance -Namespace root/subscription -ClassName CommandLineEventConsumer | ? { $_.name -like "NotepadStartAktion" } | Remove-CimInstance

Wenn Sie nur vorübergehen pausieren wollen, bauen Sie sich in Ihr Script einfach eine entsprechende Logik ein. Erstellen Sie z.B. ein Registry Key und je nachdem ob der gesetzt ist oder nicht führen Sie Ihr Script weiter aus, oder beenden es. Alternativ könnten Sie auch mit Set‑CimInstance Ihren Filter so modifizieren, dass die Bedingung zum Auslösen der Aktion nie erfüllt wird.

Warum der ganze Mist bei Ihnen nicht funktioniert? Spontan fallen mir da nur 2 Dinge ein:

Sie haben es nicht mit erhöhten Rechten ausgeführt (Rechtsklick -> PowerShell als Admin ausführen)

ExecutionPolicy! Dann modifizieren Sie das Template wie folgt:

$CommandLineTemplate = 'C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe ‑ExecutionPolicy Bypass -File C:\Temp\WMITest.ps1 %TIME_CREATED%'

4.2     Windows Verwaltung

In diesem Kapitel finden Sie Tipps und Tricks rund um das Betriebssystem.

4.2.1   Computermanagement

In diesem Abschnitt geht es um PC-Verwaltung.

4.2.1.1   Prüfen welche Computer in einer Domäne online sind

Dieses Skript erstellt eine Liste mit DNS-Namen der Domänenmitglieder, die per Ping erreichbar sind. Das Skript funktioniert nur ab PowerShell 3.0, da es Workflows verwendet (geht schneller – weil parallel). Soll es bereits ab 2.0 (langsamer) funktionieren, dann müssen Sie lediglich Workflow gegen Function tauschen und nach dem foreach das –parallel entfernen.

workflow Get-DomainMemberOnline {

 $ErrorActionPreference="SilentlyContinue"

 foreach -parallel ($Member in Get-ADComputer -filter *) {

  If (Test-Connection $Member.dnshostname -count 1) {$Member.dnshostname}

 }

}

Get-DomainMemberOnline

4.2.1.2   Betriebssystemfunktionen auf einem Windows 7 oder Vista Client verwalten

Auf einem Client steht Ihnen der Servermanager und somit auch nicht das Modul Servermanager zur Verfügung, welches Ihnen anzeigen kann welche Funktionen aktiviert sind, ganz zu schweigen vom hinzufügen und entfernen von Funktionen. Wenn Sie in einem Netzwerk arbeiten, haben Sie sicher irgendwo einen Server mit PowerShell 2.0 herumstehen. Klauen Sie sich doch einfach die Cmdlets von diesem Server. Dazu müssen Sie lediglich das Remoting auf dem Server aktivieren, falls noch nicht geschehen (wie das geht und weitere Hintergründe stehen im Abschnitt Remote Zugriff im Kapitel PowerShell 2.0 des Buchteils Unterschiede zwischen PowerShell Version 1.0, 2.0 und 3.0).

$Sitzung=New-PSSession –Computer EinServerMitPowerShell2

Invoke-Command {Import-Module Servermanager} –Session $Sitzung

Import-PSSession –Session $Sitzung –Module Servermanager

Die erste Zeile erstellt eine Sitzung mit dem Server der über PowerShell 2.0 verfügt. Damit alles klappt, müssen Sie die PowerShell-Konsole mit erhöhten Rechten gestartet haben und daher sowohl auf dem Server, als auch auf Ihrem Client über lokale Administrator Berechtigungen verfügen. Falls Sie auf dem Server einen anderen Benutzer verwenden möchten, als den mit dem Sie gerade am Client angemeldet sind, verwenden Sie den Schalter –Credetial (Get-Credential). Durch Zeile zwei laden Sie auf dem Server das Modul Servermanager in die Sitzung. In Zeile drei importieren Sie die Cmdlets des Servermanager Moduls vom Server in Ihre Client Konsole.

Ab sofort können Sie lokal auf Ihrem Client die Servermanager Cmdlets verwenden:

Get-WindowsFeature

Add-WindowsFeature

Remove-WindowsFeature

Die Ausgabe von Get-WindowsFeature mag etwas seltsam anmuten im Gegensatz zur Ausgabe auf dem Server. Aber ich bin mir sicher, wenn Sie Teil 1 des Buches durchgearbeitet haben, macht Ihnen die Anpassung an Ihre Wünsche keine weiteren Probleme.

4.2.2   Filesystem

Hier finden Sie praktische Tipps, Tricks und Anregungen zum Verwalten des Dateisystems.

4.2.2.1   Allgemeine Filesystem Verwaltung

Grundsätzlich können Sie die Befehle, die Sie von der Bash, oder der CMD.EXE her kennen auch in der PowerShell verwenden. Leider ohne Schalter. Ein ls –l wird genausowenig funktionieren, wie ein dir *.txt /s. In der PowerShell sind einfach Alias Definitionen erstellt, die von den DOS und Linux Befehlen auf die entsprechenden PowerShell Cmdlets deuten. In der PowerShell haben die Cmdlets natürlich auch Schalter, die ähnlich funktionieren wie die unter DOS oder LINUX. Um herauszufinden wie die entsprechenden Schalter heißen, müssen Sie lediglich Get-Help auf das entsprechende DOS bzw. LINUX Kommando ausführen, dann sehen Sie zunächst einmal wie das eigentliche PowerShell Cmdlet dazu heißt und zum anderen die Schalter. Mit dem Schalter ‑Detailed können Sie auch die Erklärung für die Parameter einsehen und mit –Example bekommen Sie einen Haufen guter Beispiele mit Erläuterung, daher spare ich mir an dieser Stelle die Tipparbeit. Hier nur eine kurze Auflistung über die offziellen PowerShell Cmdlets zur Dateiverwaltung:

Inhalt eines Verzeichnises auflisten (dir, ls): Get-ChildItem

Inhalt einer Datei anzeigen (type, cat): Get-Content

Verzeichnis wechseln (cd): Set-Location

Dateien und Verzeichnisse erstellen (mkdir, md): New-Item

Dateien oder Verzeichnisse kopieren (copy, cp): Copy-Item

Dateien oder Verzeichnisse löschen (del, rm): Remove-Item

Dateien oder Verzeichnisse verschieben (move, mv): Move-Item

Ein Laufwerk anlegen (subst, mount): New-PSDrive

4.2.2.2   Eingabeprompt anpassen

Wenn Sie gerne den Eingabe Prompt anpassen möchten geht das am schnellsten mit dem Cmdlet Set-Item:

Set-Item -Path function:prompt -value {“Uhrzeit “+(Get-Date`
-format t)+" auf "+(hostname)+” “+$(Get-Location)+”> “}

Nach dem Schalter –Value können Sie in der geschweiften Klammer beliebigen PowerShell Code angeben, um die Darstellung des Prompts anzupassen.

4.2.2.3   Innerhalb eines Skripts den Pfad zum Skript ermitteln

Wenn Sie für Benutzer Skripte schreiben, legen Benutzer diese irgendwo ab. Oftmals gibt es noch Begleitdateien, die dem Skript zuarbeiten. Vielfach findet man daher in Skripten Zugriffe auf Dateien die mit dem Skript geliefert werden in dieser Form: .\HelferDatei.txt

Das funktioniert gut, solange der Benutzer eine Konsole startet und mittels cd ins Skript-Verzeichnis wechselt. Ruft er es jedoch von einem anderen Verzeichnis mit voller Pfadangabe auf, oder direkt über den Explorer mit Rechtsklick, dann hat man mit der . Angabe in den seltensten Fällen das Verzeichnis des Skripts.

Besser ist auf entsprechende PowerShell Systemvariablen zurück zu greifen. Bis einschließlich PowerShell 2.0 ist das noch etwas mühseelig:

Split-Path -Parent $MyInvocation.MyCommand.Definition

Ab PowerShell 3.0 geht es etwas einfacher:

$PSScriptRoot

Wenn Sie den Pfad inkl. Skriptnamen brauchen…

PowerShell 1.0 und 2.0:

$MyInvocation.MyCommand.Definition

PowerShell 3.0 und höher:

$PSCommandPath

4.2.2.4   Skript um die Festplatte nach bestimmten Dateien und deren Inhalt zu durchsuchen

Wenn Sie von der PowerShell aus nach einer textbasierten Datei (z.B.: *.txt, *.lod, *.bat oder *.ps1) mit einem bestimmten Inhalt suchen, habe ich folgendes kleines Skript für Sie:

10.        # Das Skript verlangt beim Aufruf 3 Parameter: Verzeichnis, Dateityp und Suchbegriff

11.        # Beispiel für den Aufruf, wenn Sie das Skript unter dem Namen Dateisuche.ps1 abspeichern:

12.        # Dateisuche.ps1 C:\ *.ps1 foreach

13.        # Sie ersten drei Zeilen schreiben die übergebenen Parameter in klare Variablennamen

14.        $Verzeichnis=$args[0]

15.        $Filetype=$args[1]

16.        $Begriff=$args[2]

17.        # Funktion um wiederkehrende Elemente nicht mehrfach zu schreiben

18.        Function Sub {

19.         Get-Content $_.Fullname | Select-String $Begriff

20.        }

21.        # Die eigentliche Suche:

22.        Get-ChildItem -Path $Verzeichnis -Include $Filetype -Recurse 2> $null | ForEach {

23.         If (Sub) {

24.          Write-Host $_.FullName"`t" –NoNewLine

25.          (Sub).LineNumber | Write-Host

26.         }

27.        }

Die Parameterübergabe mittels $args dürfte klar sein. Die Funktion überspringen wir auch kurz und gehen gleich zur eigentlichen Suche. Get-ChildItem (in Zeilennr. 22) holt die auf den Dateinamensfilter passenden Dateien aus dem angegebenen Verzeichnis und allen seinen Unterverzeichnissen ab. Wenn als Verzeichnis C:\ angegeben ist, werden Sie einen Haufen Fehlermeldungen bekommen von Verzeichnissen und Dateien auf die Sie keine Berechtigungen haben. Die Fehlermeldungen werden mittels 2> $null unterdrückt.Die Dateiliste wird an eine ForEach-Schleife zur Durchsuchung der einzelnen Dateien übergeben. Innerhalb der Schleife wird mittels If gefragt, ob die Funktion Sub einen Rückgabewert liefert. Nur wenn das der Fall ist, wird der Inhalt der geschweiften Klammer ausgeführt. Doch schauen Sie nun zunächst einmal in die Funktion Sub, was dort passiert. Get-Content liest bekanntlich den Dateiinhalt aus, aber von $_.Fullname?! Was ist in $_? Sie haben eine ForEach-Schleife im Hauptprogramm. Das bedeutet, dass in $_ innerhalb der Foreach-Schleife die jeweilige Datei als Objekt enthalten ist. Würde man an dieser Stelle nur $_ angeben, bekäme man nur den Verzeichnisnamen ohne Pfad. Das würde nicht funktionieren, weil er den Dateinamen in dem Verzeichnis suchen würde in dem das Skript gestartet wurde. In der Eigenschaft Fullname des Dateiobjekts ist der komplette Pfad inkl. des Dateinamens enthalten und so kann Get-Content auch den korrekten Pfad zur Datei finden und diese auslesen. Der Inhalt der Datei wird an Select-String zur Suche nach dem Textschnipsel übergeben. Wird eine Übereinstimmung gefunden gibt es etwas aus, wenn nicht erfolgt auch keine Ausgabe. Ohne Ausgabe hat die Funktion keinen Rückgabewert und somit kann das aufrufende If feststellen, ob der Begriff in der Datei vorkommt. Wenn nein, wird die Datei einfach verworfen. Sollte es zu einer Rückgabe kommen, wird der Inhalt der geschweiften Klammern ausgeführt. Zunächst schreibt ein Write-Host auch wieder den vollständigen Pfad und den Namen auf den Schirm mit einem nachfolgenden Tabstop und der Parameter –NoNewLine sorgt dafür, dass bei einem weiteren Write-Host Befehl in derselben Zeile weiter geschrieben wird und keine neue Zeile beginnt, wie sonst üblich. Dann wird noch einmal die Funktion Sub ausgeführt und vom Rückgabewert die Eigenschaft LineNumber an ein weiteres Write-Host zur Bildschirmausgabe geschickt.

Sicher gibt es auch einfachere Varianten wie z.B.:

Select-String -Path c:\test\* -Pattern foreach

Die haben aber alle Ihre Haken und Ösen. Dieses Skript ist eine Ecke mächtiger und weniger Fehler anfällig. Aber auch das können Sie gerne noch weiter „aufhübschen“.

4.2.2.5   Datei/Ordner mit einem bestimmten Datum als Name anlegen

Um eine Datei oder einen Ordner mit dem Datum oder der Uhrzeit als Datei- bzw. Verzeichnisname zu erstellen kombinieren Sie einfach Get-Date mit New-Item.

New-Item (Get-Date -uformat "%d.%m.%Y") -ItemType Directory

New-Item –ItemType Directory erstellt ein Verzeichnis. Gerne dürfen Sie das auch einfach mkdir abkürzen. Wenn Sie eine leere Textdatei anlegen möchten, geben Sie bei dem Schalter ‑ItemType einfach File ein statt Diectory. Den Ordnernamen liefert das Get-Date Cmdlet in der runden Klammer. Mit dem Schalter –uformat können Sie die Darstellungsweise des Datums oder gerne auch der Uhrzeit anpassen. %d stellt dabei den Tag dar, der Punkt ist einfach nur ein Zeichen. Genausogut kann an Stelle des Punktes Hutzli Butzli stehen ;-). %m=Monat, %Y=Jahr sei noch der Vollständig halber erwähnt. Sie möchten auch wissen, wie Minuten etc.. funktionieren? Get-Help Get-Date –full beantwortet diese Frage.

mkdir ("{0:dddd}" -f ((Get-Date)-(New-Timespan -Day 3)))

Das mkdir Beispiel ist eine etwas verdrehte Variante, aber es tut was es soll, nämlich ein Verzeichnis mit dem Namen des Wochentags vor 3 Tagen zu erstellen. Was mit dem Format-Operator –f so alles anstellen können erfahren Sie im Anhang –f Format-Operatoren. Wie man diese Technik grundsätzlich anwendet, findet Sie im ersten Buchteil Versionsunabhängig, im Kapitel Übersicht der Grundlegenden Befehle und dort im Abschnitt Ausgabebefehle kennenlernen und verstehen. Das Cmdlet New-TimeSpan erstellt Ihnen einen beliebigen Zeitraum. In diesem Falle 3 Tage. Die Zeit bzw. das Datum wird von der PowerShell immer in Ticks gerechnet. Das dabei dann Jahre oder Millisekunden heraus kommen oder angezeigt werden sollen ist eigentlich ziemlich gleich. Sie müssen der PowerShell nur sagen, wie Sie es gerne sehen würden, oder wie die PowerShell Sie interpretieren soll. New-TimeSpan –Second würde der PowerShell klar machen, dass Sie eben keine 3 Tage sondern Sekunden möchten. Sobald Sie von Ihnen den Wert erhalten hat, baut Sie den sowieso erst einmal in Ticks um. Wenn alles dieselbe Einheit hat, können Sie auch besser rechnen ;-). Mehr über New-TimeSpan erfahren Sie natürlich wieder mit:
Get-Help New-TimeSpan ‑full

4.2.2.6   Alte Dateien löschen, archivieren oder was auch immer Sie damit tun möchten

Sicher standen Sie schon einmal vor der Problematik Ihr Dateisystem auszumisten. Mit PowerShell ist das recht einfach im Vergleich zu anderen Skriptsprachen oder gar Programmiersprachen. Hier im Beispiel will ich mich auf das löschen konzentrieren. Um zu archiviren (zippen) brauchen Sie nur das Löschen gegen das Zippen aus dem etwas später folgenden Abschnitt Zip-Archive erstellen auszutauschen.

Zunächst sollten Sie sich der Frage widmen was Sie unter „alt“ verstehen und wie Sie der PowerShell klar machen, was Sie meinen. Die PowerShell stellt Ihnen für Verzeichnisse und Dateien gleichermaßen die Eigenschaften CreationTime, LastAccessTime und LastWriteTime zur Verfügung. In diesen Eigenschaften finden Sie die jeweilge Zeitangabe in Form der Ortszeit. Wenn Sie es lieber in UTC (Universal Time Coordinated) hätten, gibt es dieselben Eigenschaften noch einmal mit den Buchstaben Utc hinten dran. CreationTime ist die Zeit zu der das Verzeichnis/die Datei angelegt wurde. LastAccessTime ist, wann die Datei zuletzt benutzt wurde und LastWriteTime ist, wann die Datei das letzte Mal verändert wurde.

Erstellen Sie sich für Ihre ersten Tests am besten ein neues Verzeichnis und kopieren Sie Dateien aus dem Windows-Verzeichnis hinein. Die Dateien aus dem Windows-Verzeichnis sollten damit in Ihrem Testverzeichnis bereits mit unterschiedlichen LastWriteTime Eigenschaften vorliegen.

ls | ? {$_.LastWriteTime -lt ((Get-Date)-(New-TimeSpan -Days 365))}

Mit dieser Befehlskette sollten nur noch Dateien gelistet werden, die vor einem Jahr das letzte Mal verändert wurden. Ls listet den Verzeichnisinhalt, ? vergleicht die Eigenschaft der letzten Veränderung ($_.LastWriteTime) der jeweiligen Datei mit dem aktuellen Datum (Get-Date) weniger einem Jahr (New-TimeSpan –Days 365). Wenn die Ausgabe passt (vielleicht müssen Sie auf Ihrem System bzw. Ihren Beispieldateien noch etwas an der Zeit, den „Days“ drehen, damit ein Unterschied zum normalen Verzeichnislisting entsteht), können wir die Ausgabe einfach an ein lösch, zip oder sonstiges Kommando übergeben. Um den alten Kram einfach zu löschen könnte das so gehen (Achtung! Weg is weg! Nix Papierkorb oder Strg+Z!):

ls | ? {$_.LastWriteTime -lt ((Get-Date)-(New-TimeSpan -Days 365))} | rm

Linux-like oder

dir | ? {$_.LastWriteTime -lt ((Get-Date)-(New-TimeSpan -Days 365))} | del

DOS-like oder PowerShell-like

Get-ChildItem | ? {$_.LastWriteTime -lt ((Get-Date)-(New-TimeSpan`
-Days 365))} | Remove-Item

Wenn Sie gerne gelöchert werden, ob Sie die Datei auch wirklich löschen möchten, spendieren Sie dem Remove-Item am Schluß doch noch den Schalter aus den CommonParameter –Confirm.

Vielleicht bauen Sie sich ein Skript, oder eine Funktion, der Sie nur noch das Verzeichnis und das Datum oder die Anzahl Tage übergeben?!

4.2.2.7   Zip-Archive erstellen

Mit einem Zip-Archiv können Sie mhrere Dateien in eine einzelne paken und dabei komprimieren. Beispielsweise um die Übertragungsmenge zu reduzieren, wenn Sie die Datei(en) per E-Mail verschicken, oder auf einem Webserver zum Download anbieten möchten. Um ein Zip-Archiv zu erstellen, greift man am besten auf das COM-Objekt-Model zurück. Hier finden Sie in der Objektklasse Shell.Application die Möglichkeit zu zippen. Das nachfolgende Skript fügt eine einzelne Datei einer Zip-Datei hinzu:

$Path="c:\test\test.zip"

Set-Content $path ("PK" + [char]5 + [char]6 + ("$([char]0)" * 18))

$ShellApplication=New-Object -ComObject Shell.Application

$ZipPackage =$ShellApplication.Namespace($Path)

# den nachfolgenden Befehl für jede hinzuzufügende Datei wiederholen

$ZipPackage.CopyHere("c:\test\text.txt", 0)

Die erste Zeile legt den Pfad und den Namen für die Zip-Datei in der Variablen $Path ab. Da eine Zip-Datei mit einer bestimmten Zeichenkette beginnen muss, wird in der 2. Zeile die „magische“ Zeichenkette in die Pfad-Variable eingeflößt. Anhand des Anfangs der Datei wissen entprechende Programme zum entpacken von Zip-Dateien, dass sie hier richtig sind. In der 3. Zeile wird dann das COM-Objekt aus der Klasse Shell.Application erstellt. In Zeile 4. erklären Sie Ihrem frisch gebackenen Objekt, wo das Zip-File auf der Festplatte liegen soll und legen damit die eigenliche Zip-Datei an. Danach folgt eine Kommentarzeile, die erklärt wie weitere Dateien hinzugefügt werden können. In der letzten Zeile haben Sie ein Beispiel, wie Sie über die Methode CopyHere einfach weitere Dateien hinzufügen können.

Selbstverständlich können Sie die Vorbereitungen hintereinander wegschreiben und das hinzufügen von Dateien zum Zip-Archiv in eine Schleife packen. In Kombination mit dem vorangegangenen Abschnitt im Buch könnten Sie so z.B. alte Dateien „wegkomprimieren“ lassen und die alten originale im Nachgang löschen. So haben Sie Platz gespart. Besonders gut eignet sich das für alte Log-Dateien, die Sie noch nicht komplett entsorgen möchten, aber wahrscheinlich auch nicht mehr brauchen. Am besten Sie erstellen dazu einen Job in der Aufgabenplanung die automatisch Ihr PowerShellarchivierungsskript startet. Wie erfahren Sie im nächsten Abschnitt.

4.2.2.8   PowerShell Skripte durch die Aufgabenplanung starten lassen.

Mit der Aufgabenplanung von Windows können Sie Aufgaben unter Windows automatisieren lassen. Ob die Festplatte regelmäßig defragmentiert werden soll, oder ein IP-Adresskonflikt behandelt werden soll ist bereits in der Aufgabenplanung auf aktuellen Windowssystemen hinterlegt. Sie können aber auch selbst Hand anlegen und z. B. Ihre PowerShell Skripte einsetzen, um in bestimmten Situationen ausgeführt zu werden. Ob dies nun regelmäßig geschieht, beim booten oder herunterfahren des PCs, wenn der Rechner Langeweile hat oder sogar wenn ein bestimmter Fehler auftritt können Sie mithilfe der Aufgabenplanung festlegen. Der Vorteil gegenüber dem Start-Job Cmdlet (siehe Buchteil Unterschiede zwischen den PowerShellversionen, Kapitel PowerShell 2.0 im Abschnitt Jobs) in der PowerShell liegt vor allem darin, dass die Aufgabenplanung von Windows sowieso die ganze Zeit läuft, während Jobs der PowerShell nur so lange beachtet werden, wie der PowerShell-Prozess läuft, der den Job gestartet hat.

Die Aufgabenplanung von Windows finden Sie im Startmenü unter
Alle Programme/Zubehör/Systemprogramme:

Damit Sie immer schnell Ihre eigens erstellten Aufgaben finden ist es ratsam, wenn Sie sich zunächst einen eigenen Ordner in der Aufgabenplanungsbibliothek erstellen. Dazu suchen Sie in der linken Spalte die Aufgabenplanungsbibliothek, klicken drauf und dann finden Sie in der rechten Spalte einen Eintrag Neuer Ordner… Legen Sie damit einen Ordner z.B. namens MeineAufgaben an:

Wenn Sie nun Ihren Ordner anklicken können Sie in der rechten Spalte wiederum auf eine Einfache Aufgabe erstellen klicken. Darauf startet ein Assistent, der Ihnen bei der Erstellung einer Aufgabe hilft. Im ersten Dialogfenster geben Sie Ihrer Aufgabe einen Namen um sie leicht identifizieren zu können. Warum nennen Sie es nicht z.B. ErsterSkriptTest? Das Feld Beschreibung müssen Sie nicht ausfüllen, es dient lediglich Kommentarzwecken. Wenn Sie anschließend auf Weiter klicken, können Sie auf der 2. Seite den Auslöser für die Ausführung Ihres Skriptes festlegen. Das kann Zeit abhängig wiederkehrend wie z.B. täglich, monatlich sein, oder einmalig zu einem bestimmten Zeitpunk. Aber Sie können es auch vom anmelden am PC, starten des PCs oder Ereignis abhängig (das geht besser aus der Ereignisanzeige heraus) ausführen. Wenn Sie für den ersten Test Beim Anmelden auswählen und wieder auf Weiter klicken wird das Skript beim nächsten Anmelden ausgeführt. Auf der 3. Seite können Sie nun die Aktion festlegen, was geschehen soll. Das wir ein PowerShell Skript ausführen möchten müssen Sie hier Programm starten auswählen und wiederum auf Weiter klicken. Der Trick ist bei Programm/Skript: eben nicht Ihr PowerShell Skript einzutragen, sondern hier einfach nur powershell einzutippen. Dadurch wird ein PowerShell Prozess gestartet. Wenn Sie mögen können Sie gerne den kompletten Pfad zu PowerShell.exe eintippen, was aber normaler Weise nicht notwendig ist. Nur wenn Sie Sicherheitsfanatiker sind können Sie den kompletten Pfad tippen oder über die Durchsuchen Schaltfläche zu PowerShell.exe navigieren und auswählen. Damit wird ein PowerShell-Prozess gestartet, toll…und? PowerShell können Sie beim Start Parameter mit auf den Weg geben. Welche das alle sind erfahren Sie durch Eingabe von PowerShell -? an der Eingabeaufforderung (z.B. können Sie mit –WindowStyle hidden festlegen, dass man das PowerShellfenster nicht sehen soll. Achtung! Dieser Schalter muss am Anfang stehen, sonst sehen Sie das Fenster trotzdem!). Welche Parameter sind nun wichtig? Zunächst einmal benötigen Sie –file gefolgt vom vollständigen Pfad (Laufwerk, Ordner-Struktur und Skriptname inkl. der Dateinamenerweiterung). Sollte auf dem PC  die Ausführung von PowerShell Skripten nicht zulässig sein (restricted oder so ein Quark wie allsigned) geben Sie einfach den Parameter ‑executionpolicy unrestricted an und Ihr Skript wird trotzdem ablaufen. Nein, das ist kein Hackertrick, das ist ein Admintrick ;-). Sollte unrestricted einmal nicht funktionieren, verwenden Sie bypass, dann läuft Ihr Skript auf jeden Fall auf jedem PC ohne Rückfragen. Schreiben Sie zum testen ein kleines Skript, dass einen Ordner im Dateisystem erstellt (z.B.: mkdir c:\check) und speichern Sie dies ab. In der Aufgabenplanung können Sie dann bei den Argumenten eintragen:

-executionpolicy unrestricted –file c:\Pfad\zu\Ihrem\Skript.ps1

Zu guter letzt ein Klick auf Weiter und anschließend auf Fertigstellen und Ihre erste Aufgabe steht. Nun brauchen Sie sich nur einmal ab- und wieder anzumelden und Sie sollten auf Laufwerk c: einen Ordner namens check vorfinden, oder was auch immer Sie in Ihrem Skript an Anweisungen gegeben haben.

Selbstverständlich können Sie auch aus einem PowerShell Skript heraus auch auf die Aufgabenplanung zugreifen:

schtasks /create /sc minute /tr "powershell -windowstyle hidden `
‑executionpolicy unrestricted -file Pfad\Skript.ps1" /tn MeinSkript

Hierfür gibt es (bis Version 2.0) kein PowerShell Cmdlet, aber Sie können das Programm schtasks.exe aus der PowerShell heraus verwenden. Der Schalter /create legt fest, dass Sie ein neues Aufgabenplanungselement erstellen möchten. /sc minute bewirkt die minütliche Ausführung Ihres Skripts. Mit /tr geben Sie an welches Programm gestartet werden soll. Hier schreiben Sie nun in Anführungszeichen das Programm mit den Parametern. Was also in der grafischen Version getrennt erfolgt, machen Sie hier in einem Aufwasch. Der letzte Schalter /tn gibt Ihrer Aufgabe schließlich noch einen Namen. Auch hier gibt es natürlich wieder einen Haufen Möglichkeiten, die Sie mit schtasks /? ergründen können.

Wenn Sie die Infos aus den letzten drei Abschnitten zusammensetzen, können Sie ein Skript erstellen, das 1 x im Monat all Ihre Log-Dateien in ein Zip-File schreibt und anschliessen die Originale löscht. Am besten natürlich auch gleich das Zip-File, dass älter ist als ein Jahr. Damit pflegt sich Ihre Log-Archivierung z.B. auf Ihrem Webserver von selbst. Ihre Musiksammlung wird nach Liedern durchforstet, die seit einem Jahr nicht mehr angehört wurde und packt diese in ein Archiv. Die Einsatzgebiete sind sehr weitreichend.

4.2.2.9   Der Touch-Befehl der Linux Shell

In der PowerShell haben Sie zwar den New-Item Befehl mit dem sich eine neue leere Textdatei wie mit touch anlegen lässt, jedoch können Sie mit dem Linux touch auch die Zeiten für den Dateizugriff aktualisieren, was mit New-Item nicht geht. Hier ein Skript, dass die Funktionalität des Linux Befehls implementiert, jedoch mit abweichender Syntax. Wenn Sie mögen, können Sie es ja so anpassen, dass man den Unterschied zu Linux gar nicht mehr merkt. Das Skript können Sie unter einem beliebigen Namen abspeichern. Ich empfehle maketouch.ps1. Im Skript wird durch das Global: vor dem Funktionsnamen selbst bei ganz normaler Skriptausführung die Funktion Touch in die PowerShell Konsole in der aktuellen Sitzung eingebaut.

10.        # Die Funktion Touch von Linux

11.        # Vollständige Syntax:

12.        # Touch -FileName $FileName -DateTime $DateTime -Forward $Forward -Reference $Reference -OnlyModification $OnlyModification -OnlyAccess $OnlyAccess

13.        # Kurzform um eine Datei zu erstellen, oder alle Zeiten auf das aktuelle Datum zu setzen:

14.        # Touch Dateiname.Erweiterung

15.        function Global:Touch {

16.        param (

17.         [string]$FileName=$null,         # Dateien die erstellt oder aktualisiert werden sollen.

18.         [DateTime]$DateTime=(Get-Date),  # Falls keine Zeit übergeben wird, nimm die aktuelle.

19.         [int]$Forward=0,                 # Zeitergänzung in Sekunden.

20.         [string]$Reference=$null,        # Zeitstempel aus von dieser Datei übernehmen, statt der aktuellen Zeit.

21.         [bool]$OnlyModification=$false,  # Nur die modification time ändern.

22.         [bool]$OnlyAccess=$false         # Nur die access time ändern.

23.        )

24.        $Touch=$null

25.        if ($FileName) {

26.         $Files=@(ls -Path $FileName -EA SilentlyContinue)

27.          if (!$Files) {

28.           # Wenn die Datei nicht existiert erstelle eine.

29.           # Bei *-Platzhalterzeichen ignorieren.

30.           Set-Content -Path $FileName -value $null

31.           $Files=@(ls -Path $FileName -EA SilentlyContinue)

32.          }

33.          if ($Files) {

34.           if ($Reference) {

35.            $Reffile=ls -Path $Reference -EA SilentlyContinue

36.            if ($Reffile) {

37.             [datetime]$Touch=$Reffile.LastAccessTime.AddSeconds($Forward)

38.            }

39.           } elseif ($DateTime) {

40.            [datetime]$Touch=$DateTime.AddSeconds($Forward)

41.           }

42.           if ($Touch) {

43.            [DateTime]$UTCTime=$Touch.ToUniversalTime()

44.            foreach ($File in $Files) {

45.             if ($OnlyAccess) {

46.              $File.LastAccessTime=$Touch

47.              $File.LastAccessTimeUtc=$UTCTime

48.             } elseif ($OnlyModification) {

49.              $File.LastWriteTime=$Touch

50.              $File.LastWriteTimeUtc=$UTCTime

51.             } else {

52.              $File.CreationTime=$Touch

53.              $File.CreationTimeUtc=$UTCTime

54.              $File.LastAccessTime=$Touch

55.              $File.LastAccessTimeUtc=$UTCTime

56.              $File.LastWriteTime=$Touch

57.              $File.LastWriteTimeUtc=$UTCTime

58.             }

59.             $File | select Name, *time*

60.            }

61.           }

62.          }

63.         }

64.        }

Nach starten des Skripts können Sie beispielsweise eingeben:

Touch Opferdatei.txt

Daraufhin wird die Opferdatei.txt erstellt, falls sie noch nicht vorhanden war. Existierte die Datei bereits werden alle Zeit Eigenschaften der Datei auf diesen Moment wo Sie das Ganze eintippen aktualisiert.

Touch Opferdatei.txt –Forward 600

Setzt die Zeiten der Opferdatei, auf die aktuelle Zeit zuzüglich 600 Sekunden (=10 Minuten).

Touch Opferdatei.txt –Reference Bezugsdatei.txt

Übernimmt die Zeiten der Bezugsdatei in die Zeiten der Opferdatei.

Mit dem Schalter –OnlyModification ändern Sie nur die Eigenschaften LastWriteTime und LastWriteTimeUtc und bei –OnlyAccess ändern Sie nur die LastAccessTime und die LastAccessTimeUtc.

4.2.2.10                Dateiberechtigungen mit PowerShell

Berechtigungen am Dateisystem mit der PowerShell zu vergeben ist kein Spaß. Die PowerShell macht vieles einfacher, aber Berechtigungsvergabe ist mit dem normalen calcs, xcacls oder icacls einfacher und effizienter. Diese externen Tools können Sie natürlich auch von der PowerShell aus benutzen. Das einzige was ganz nett ist, steht auch als Beispiel in der Hilfe zu Set‑ACL: Das Kopieren von Berechtigungen von einer Datei zu einer anderen. Für diejenigen, die Berechtigungen unbedingt mit PowerShell vergeben möchten, hier ein entsprechendes Skript das zwei Cmdlets namens Add- und Remove-Permission implementiert:

10.        # Ein paar sich wiederholdene Aufgaben als Unterfunktion bekannt machen

11.        Function Global:ParaCheck {

12.         $Script:Konto=New-Object system.Security.Principal.NTAccount($User)

13.         try {

14.          $SID=$Konto.Translate([System.Security.Principal.SecurityIdentifier]).value

15.         }

16.         catch {

17.          Write-Host "Benutzerkonto ist unbekannt!" -Foreground Yellow

18.          break

19.         }

20.         Get-Item $Path -ea SilentlyContinue > $null

21.         # Fehlermeldung, wenn der Pfad nicht existiert

22.         if ($? -eq $false) {

23.          Write-Host "Angegebener Pfad: $Path exisitiert nicht!" -Foreground Yellow; break

24.         }

25.        }

26.        Function Global:ShowPerm ([String]$Wann) {

27.         $Script:ACL=Get-Acl $Path

28.         Write-Host "ACL $($Wann):"

29.         $ACL.access | select @{Name="Benutzer oder Gruppe";Expression={$_.Identityreference}},@{Name="Erlauben/Verweigern";Expression={if ($_.accesscontroltype -eq "Allow") {"Erlaubt"} Else {"Verweigert"}}},@{Name="Berechtigungen";Expression={$_.filesystemrights}} | ft -auto

30.        }

31.        # Hinzufügen (nicht ändern !) einer Berechtigung

32.        Function Global:Add-Permission {

33.         Param(

34.          [String]$Path=(Read-Host "Bitte Pfad zur Datei oder dem Verzeichnis angeben"),

35.          [String]$User=(Read-Host "Bitte Benutzer oder Gruppe angeben (Beispiel: Domäne\Benutzer)"),

36.          [String]$Perm="FullControl",

37.          [Switch]$Deny

38.         )

39.         Switch -regex ($Perm) {

40.          "^R" {$Permission="ReadAndExecute";break} # Lesen, Read beginnt mit R

41.          "^M" {$Permission="Modify";break}         # Ändern, Modify beginnt mit M

42.          "^D" {$Permission="Delete";break}         # Löschen, Delete beginnt mit D

43.          "^F" {$Permission="FullControl";break}    # Vollzugriff, Fullcontrol beginnt mit F

44.          default {Write-Host "Falsche Berechtigung! Bitte geben Sie nur Lesen, Ändern, Löschen oder Vollzugriff beim Schalter Permission ein!" -Foreground Yellow; break 1 }

45.         }

46.         # Benutzer Eingaben überprüfen

47.         ParaCheck

48.         # Stand vorher hübsch anzeigen

49.         ShowPerm "vorher"

50.         # Rechteeintrag (ACE) zusammenschrauben

51.         $Rights=[System.Security.AccessControl.FileSystemRights] $Permission

52.         If ($Deny) {

53.          $Access=[System.Security.AccessControl.AccessControlType]::Deny

54.         } Else {

55.          $Access=[System.Security.AccessControl.AccessControlType]::Allow

56.         }

57.         if ((Get-Item $Path).PSIsContainer) {

58.          # Vererbung gibt es nur bei Verzeichnissen...

59.          $Inherit=[System.Security.AccessControl.InheritanceFlags] "ContainerInherit, ObjectInherit"

60.          $Prop=[System.Security.AccessControl.PropagationFlags] "None"

61.          $AccessRule=New-Object System.Security.AccessControl.FileSystemAccessRule ($Konto,$Rights,$Inherit,$Prop,$Access)

62.         } Else {

63.          # ...aber nicht bei Dateien

64.          $AccessRule=new-object System.Security.AccessControl.FileSystemAccessRule ($Konto,$Rights,$Access)

65.         }

66.         try {

67.          # Rechteeintrag (ACE) zur Rechteliste (DACL) hinzufügen

68.          $ACL.AddAccessRule($AccessRule)

69.          # ...und Schuss

70.          Set-Acl -Path $Path -AclObject $ACL -ea stop

71.         }

72.         catch {

73.          Write-Host "Nicht ausreichende Berechtigungen zum Ändern der Rechteliste. Probieren Sie es als Administrator." -Foreground Yellow

74.          break

75.         }

76.         # Zur Kontrolle anzeigen, was wir verbrochen haben

77.         ShowPerm "nachher"

78.        }

79.        # Löschen eines Benutzers aus der Rechteliste

80.        Function Global:Remove-Permission {

81.         Param(

82.          [String]$Path=(Read-Host "Bitte Pfad zur Datei oder dem Verzeichnis angeben"),

83.          [String]$User=(Read-Host "Bitte Benutzer oder Gruppe angeben (Beispiel: Domäne\Benutzer)")

84.         )

85.         # Benutzer Eingaben überprüfen

86.         ParaCheck

87.         # Stand vorher hübsch anzeigen

88.         ShowPerm "vorher"

89.         try {

90.          # Rechteeintrag (ACE) aus Rechteliste (DACL) entfernen

91.          $ACL.PurgeAccessRules($Konto)

92.          # ...und Schuss

93.          Set-Acl -AclObject $ACL -Path $Path -ea stop

94.         }

95.         catch {

96.          Write-Host "Nicht ausreichende Berechtigungen zum Ändern der Rechteliste. Probieren Sie es als Administrator." -Foreground Yellow

97.          break

98.         }

99.         # Zur Kontrolle anzeigen, was wir verbrochen haben

100.        ShowPerm "nachher"

101.       }

Ich glaube, jetzt wissen Sie was ich meine ;-). Überspringen wir zunächst die Erklärung der ersten beiden Funktionen ParaCheck und ShowPerm und starten bei der Funktion Add-Permission ab Zeile 32. Im Parameterblock werden die entgegenzunehmenden Angaben für die Berechtigungsvergabe vorgegeben. Die ersten drei Parameter akzeptieren wegen des vorangestellten [String] nur Texte. Die ersten beiden wiederum sorgen durch die Angaben von (Read-Host „Info…“) dafür, dass die Parameter nachträglich mit einer Erklärung was einzugeben ist abgefragt werden, wenn der Benutzer vergessen hat diese anzugeben. $Perm nimmt die Art der Berechtigung entgegen. Hat der Benutzer auch hier keine Angabe gemacht, wird einfach Fullcontrol (=Vollzugriff) zugewiesen. Der letzte Parameter $Deny ist vom Typ Switch. Das bedeutet entweder gibt der Benutzer den Schalter –Deny an und somit ist $Deny gleich $True, oder wenn er Ihn weglässt ist $Deny gleich $False.

Als nächstes wird mithilfe von Switch die Übergebene Berechtigung ausgewertet. Der Schalter ‑regex sorgt dabei dafür, dass Reguläre Ausdrücke benutzt werden können um den Inhalt von $Perm auszuwerten. “^R“ bedeutet, dass wenn der Inhalt von $Perm mit dem Buchstaben R beginnt, die Anweisung in der darauf folgenden geschweiften Klammer ausgeführt wird. Es spielt also keine Rolle, ob der Benutzer R, r, Re, re, Rea, rea, Read, read oder sogar Radiesschen eintippt, da Sie nicht explizit angegeben haben, dass Groß-/Kleinschreibung berücksichtigt werden soll. Hauptsache es beginnt mit einem kleinen oder großen R. In der geschweiften Klammer danach wird dann der Variablen $Permission der Text ReadAndExecute zugewiesen und die Switch verarbeitung durch break beendet. Entsprechend wird mit den anderen wichtigen Berechtigungen verfahren. Sie könnten das Programm noch erweitern, um die weiteren Berechtigungen von NTFS. Welche das sind verrät Ihnen:

[System.Enum]::Getnames("System.Security.AccessControl.FileSystemRights")

Eine Erklärung zu den einzelnen Berechtigungen finden Sie unter:

http://msdn.microsoft.com/de-de/library/system.security.accesscontrol.filesystemrights.aspx

Sollte $Perm weder mit r, m, d oder f beginnen, greift die geschweifte Klammer hinter default. Hier wird eine Fehlermeldung in Gelb erzeugt und anschliessend mit break 1 die Verarbeitung abgebrochen. Nur break würde nur die Switch-Anweisung verlassen, aber mit dem Skript fortfahren. Da die Funktion Global definiert ist würde ein exit die PowerShell Konsole schliessen. Daher funktioniert an dieser Stelle nur das break mit der 1 wie gewünscht.

Dann erfolgt der Sprung in die Funktion ParaCheck. Also zurück zum Anfang des Skripts, zu Zeile 11, wo die Funktion beschrieben steht. Die Funktion dient der Überprüfung der weiteren übergebenen Parameter. Zunächst wird aus der .NET-Klasse System.Security.Principal.NTAccount ein Benutzerkontenobjekt generiert. Dazu wird der übergebene Benutzername weitergereicht und die Objektinstanz (aus der Klasse abgeleitetetes Objekt) in $Konto hinterlegt. $Konto ist mit dem Scope-Bezeichner Script: ausgestattet, damit in der aufrufenden Funktion $Konto ebenfalls bekannt ist, da es in der Rechteliste verwendet wird. Im try Block wird versucht das Benutzerkonto in eine SID (Security-Identifier) zu übersetzen und diese in $SID zu hinterlegen. Eigentlich hätte hier auch [void] stehen können, da uns die SID nicht wirklich interessiert, sondern nur ob der Benutzername auch wirklich existent ist, bzw. einer eindeutigen Sicherheitskennung (der SID) zu geordnet werden kann. Die Funktion schlägt nämlich fehlt, wenn das nicht geht und lößt damit den catch Block aus. Der weißt den Benutzer darauf hin, dass er den Benutzernamen für die Rechtezuweisung falsch eingegeben hat und beendet das Programm. Sollte der Benutzer zugeordnet werden können, wird als nächstes der Dateiname überprüft. Mit Get-Item prüft das Skript, ob der übergebene Pfad existiert. Die PowerShell Fehlermeldung (durch: –ea SilentlyContinue) sowie die Ausgabe bei Erfolg (durch > $null) wird unterdrückt, dafür fragt das Skript in der nächsten Zeile mittels $?, ob der vorangegangene Befehl erfolgreich war. Wenn nein, gibt es auch wieder eine selbstgestrickte Fehlermeldung und das Skript wird beendet.

Wenn bis hierhin alles gut gelaufen ist, war der Parametercheck erfolgreich und es geht wieder zurück in die vom Benutzer aufgerufene Funktion, wo aber bereits die nächste Unterfunktion mit ShowPerm „vorher“ auf uns wartet.

Die ShowPerm Funktion ab Zeile 26 ist recht kurz. Der Text „vorher“, wird durch die runden Klammern innerhalb der Funktion Showperm in der Variablen $Wann hinterlegt. Die restlichen verwendeten Variablen vererben (z.B. $Path) sich automatisch von der aufrufenden Funktion, aber nicht mehr zurück, was für $ACL aber notwendig ist, da dies von der aufrufenden Funktion weiter verwendet werden soll. Deshalb bekommt $ACL ebenso wie zuvor $Konto den Scope-Bezeichner Script: mit auf den Weg. Zunächst holt sich die Funktion die Sicherheitsbeschreibung von der Datei bzw. dem Verzeichnis und hinterlegt dies in $ACL. Dann wird ein Text auf den Bildschirm gebracht: ACL $Wann. Sie erinnern sich. In $Wann steht vorher. Also erscheint auf dem Bildschirm: „ACL vorher:“. Da das Cmdlet Get-ACL nicht nur die Berechtigungen abholt, sondern auch noch den ganzen Rest der dazugehört, wie z.B. den Besitzer der Datei u.v.a.m. wird in der nächsten Zeile nur das heraus geholt, was für den vorher/nachher-Vergleich von Interesse ist, nämlich das was in der Eigenschaft Access hinterlegt ist. Auch da steht wieder ein Haufen Zeug drin. Hier soll nur der Benutzer oder die Gruppe, angezeigt werden und ob ihnen ein bestimmtes Recht erlaubt oder verweigert wurde. Das ist hier deshalb so ein Roman, weil die Informationen natürlich in Englisch vorliegen und das ganze auf Deutsch umgeschrieben wird. Ein Select IdentityReference, AccessControlType, FilesystemRights hätte es auch getan. Nach der Anzeige des Ist-Zustands geht es wieder zurück in die aufrufende Funktion.

Als erstes, zurück in Zeile 51, wird geprüft, ob die Berechtigungen passen mit

$Rights=[System.Security.AccessControl.FileSystemRights] $Permission

Hier wird die ursprüngliche Berechtigung von $Permission nach der Prüfung durch die .NET-Klasse in $Rights abgelegt.

Danach wird überprüft, ob der Schalter $Deny angegeben wurde. Wenn ja, wird in $Access Deny“ hinterlegt, ansonsten kommt „Allow“ hinein.

Dann wird geprüft ob es sich beim übergebenen Pfad um eine Datei oder ein Verzeichnis handel. Da Dateien die Endpunkte des Dateisystems darstellen, können die auch nichts vererben. Dementsprechend entfallen Angaben zur Vererbung in der Berechtigungsliste.  Geprüft wird das mittels der Eigenschaft PSIsContainer, die von der .NET-Klasse her für Verzeichnisse den Wert True enthält und für Dateien False.

$Inherit und $Prop steuern die Vererbung. In dieser Form wird die Berechtigung auf den Ordner selbst angewand und auf alle im Ordner befindlichen Dateien und Verzeichnisse. Die Verzeichnisse würde die Berechtigung ebenfalls an Ihre Inhalte weitergeben. Welche Werte Sie hier sonst noch zuweisen können, ist unter
 http://msdn.microsoft.com/de-de/library/system.security.accesscontrol.inheritanceflags.aspx
für die InhertianceFlags hinterlegt und unter
http://msdn.microsoft.com/de-de/library/system.security.accesscontrol.propagationflags.aspx
für die PropagationFlags. An dieser Stelle nun komplett den Aufbau vom NTFS-Filesystem zu erklären würde den Rahmen des Buches sprengen. Daher hier nur die Verweise für die alten Haudegen, die es mit den Berechtigungen bzw. der Vererbung ganz genau wissen möchten. Dann wird in $Access aus den ganzen Teilen (Benutzer, Berechtigung, Vererbung, Erlauben/Verweigern) ein Objekt generiert, dass den kompletten Rechteeintrag enthält. Dreht es sich nur um eine Datei statt ein Verzeichnis, können Sie auf die Definition undAngabe der Vererbung verzichten.

Dann kommt wieder ein try-Block, ab Zeile 66, indem versucht wird, den eben zusammengesetzten Berechtigungseintrag der bestehenden Rechteliste ($ACL wurde in der Unterfunktion ShowPerm generiert) hinzuzufügen. Mit Set-ACL wird dann der Datei bzw. dem Verzeichnis die neue Rechteliste eingeimpft. Sollte bei dem Hinzufügen schon etwas schief gehen, führt dies zu einem Abbruchfehler. Das Anwenden auf die Datei würde jedoch normaler Weise keinen Abbruchfehler darstellen. Da auch hier der catch-Block anspringen soll, müssen Sie, um aus dem Nicht-Abbruchfehler einen Abbruchfehler zu machen, bei Set-ACL noch den Eintrag –ea stop hinzufügen. Läuft also bei einem der beiden etwas schief, greift der catch-Block. Hier handelt es sich in der Regel um nicht ausreichende Berechtigungen. Daher wird eine entsprechende Fehlermeldung angezeigt und das Skript beendet.

Sollte alles geklappt haben wird erneut ShowPerm, dieses Mal mit dem Begriff „nachher“ aufgerufen, der nun die Informationen nach der Änderung anzeigt. Damit hätten Sie dann einen weiteren Eintrag zu Berechtigungsliste hinzugefügt.

Die nächste Funktion Remove-Permission ist da etwas einfacher bzw. kürzer. Hier werden nur 2 Parameter entgegen genommen: Der Pfad zur Datei bzw. zum Verzeichnis und der Name des Benutzer bzw. der Gruppe, die eine Berechtigung entzogen bekommen soll. Fehlen die Parameter, werden Sie automatisch vom Benutzer mit dem entsprechenden Hinweis nachgefragt. Auch hier durchläuft das Skript wieder die Funktion ParaCheck zur Überprüfung des Benutzernamens und des Pfads. Auch ShowPerm wird wieder zum Vorher/Nachher Effekt eingesetzt.

$ACL haben Sie wieder aus der Funktion ShowPerm erhalten. Anstatt einen Eintrag hinzuzufügen, wollen Sie ihn nun löschen, also verwenden Sie hier die Methode PurgeAccessRules und übergeben das zu löschende Konto. Auch hier muss die Änderung wieder entsprechend in die Datei mittels Set-Acl geimpft werden. Nur noch ein ShowPerm zur Kontrolle und fertig.

Nachtrag: Inzwischen gibt es das File System Security PowerShell Modul in der Technet Gallery, dass die Rechtevergabe ebenfalls vereinfacht. Infos dazu gibt es in zwei Tutorials https://blogs.technet.microsoft.com/fieldcoding/2014/12/05/ntfssecurity-tutorial-1-getting-adding-and-removing-permissions/ und https://blogs.technet.microsoft.com/fieldcoding/2014/12/05/ntfssecurity-tutorial-2-managing-ntfs-inheritance-and-using-privileges/.

4.2.2.11                In einer Remote Session auf einen Fileshare per UNC zugreifen

Hat man Windows Remoting konfiguriert und Enter-PSSession als auch Invoke-Command klappen problemlos, fängt man an diese tollen Möglichkeiten rege zu nutzen. Ab und an will man dann aber vielleicht auch einmal aus so einer Remote-Session heraus auf einen Netzwerkpfad in Form von \\Servername\Freigabename zugreifen und man bekommt immer so nette Fehlermeldungen wie:

Get-ChildItem : Cannot find path ‘\\Servername\Freigabename’ because it does not exist.
+ CategoryInfo          : ObjectNotFound: (\\Servername\Freigabename:String) [Get-ChildItem], ItemNotFoundException
+ FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.GetChildItemCommand

Doch der Server ist online und wenn Sie am entfernten Computer lokal angemeldet versuchen darauf zuzugreifen ist alles fein. Was läuft hier schief? Das ist wieder einmal so ein “Sicherheitsfeature”.

Der Quick and Dirty Way ist einfach mit:

net use n: \\Servername\Freigabename

eine Netzlaufwerksverbindung herzustellen (so umgeht man einfach das tolle Sicherheitsfeature). New-PSDrive und andere Experimente können Sie sich sparen…habe ich alles schon ausprobiert. N: können Sie dann ganz normal ansprechen.

Nun der offizielle Weg:

Mittels Gruppenrichtlinien müssen Sie unter Computer Configuration\Policies\Administrative Templates\Windows Components\Windows Remote Management (WINRM) bei den Unterpunkten WINRM Client als auch WINRM ServiceAllow CredSSP authenticationaktivieren. Des Weiteren müssen Sie unter Computer Configuration\Policies\Administrative Templates\System\Credentials Delegation die beiden Einträge Allow Delegating Fresh Credentials und noch einmal der Eintrag mit Allow Delegating Fresh Credentials with NTLM-only Server Authentication mit so einem Eintrag schmücken:

wsman/NameDesRechnersAufDenSieSichRemoteDraufSchaltenMöchten

Diese Policy muss an eine OU gehängt werden welche die Computer enthalten von denen aus Sie Enter-PSSession bzw. Invoke-Command ausführen möchten.

Haben Sie keine GPOs (z.B. weil kein AD) dann geht es auch in der PowerShell auf den einzelnen Systemen (aber nicht remote ) mit:

Enable-WSManCredSSP –Role Server

und

Enable-WSManCredSSP –Role Client -DelegateComputer NameDesServers

Um sich dann mit dem Server zu verbinden, müssen Sie die zusätzlichen Schalter -Credential und -Authentication mit angeben. Zuvor sollten Sie sich allerdings die Anmeldeinformationen in einer Variablen hinterlegen, z.B. so:

$c=get-credential

Dann können Sie Ihr eigentliches Enter-PSSession in dieser Form ausführen:

Enter-PSSession Remoteserver - Credential $c -Authentication credssp

4.2.2.12                Datendeduplizierung

Eine tolle Funktion ist die Datendeduplizierung. Sie ermöglicht es im Normalfall identische Blöcke auf der Festplatte zu finden und diese zu einem einzelnen zusammenzufassen, sodass der Speicherplatz nur 1 x effektiv belegt wird. Gerade auf einem Hyper-V-Server, der viele Gastsysteme bereitstellt, die zumindest in den Betriebssystemdateien weitgehend identisch sind kann hier sehr viel Speicherplatz eingespart werden. Leider spinnt die Deduplizierung manchmal und macht genau das Gegenteil von dem erwarteten. So hatte ich neulich 3 TB als belegten Plattenplatz, wobei die Summe aller Dateien lediglich 2 TB ergab. VSS war nicht aktiviert, also blieb nur noch ein Problem bei der Deduplizierung als Ursache. Nach der Bereinigung war nicht nur noch knapp 1 TB belegt. Um einen Datenträger mit aktivierter Deduplizierung von Balast zu befreien tippen Sie lediglich:

Start-DedupJob –Volume D: -Type GarbageCollection –Full

Der Parameter –Volume gibt das Laufwerk an, dass aufgeräumt werden soll, –Type GarbageCollection gibt die nicht genutzten Speicherblöcke frei, -Full sorgt dafür, dass er es auch ordentlich macht. Die standardmäßig eingerichtete Optimization (Type) sowie das Scrubbing bringt bezüglich Speicherfreigabe leider herzlich wenig.

4.2.3   Registry

Hier finden Sie praktische Tipps, Tricks und Anregungen zum Verwalten der Registrierdatenbank.

4.2.3.1   Navigieren in der Registry

Um in die Registry zu gelangen können Sie die vorgefertigten PSDrives HKCU und HKLM verwenden.

cd HKLM:

oder

cd HKCU:

Durch die Standard Befehle wie cd und dir können Sie sich genauso durch die Registry hangeln wie durch den Dateisystembaum. Auf die rot gelisteten Einträge haben Sie selbst als Administrator u.U. keinen Zugriff. Um dies kenntlich zu machen hat man diese rot eingefärbt.

Im HKEY_LOCAL_MACHINE sind alle Computer abhängigen Einstellungen enthalten. Im HKEY_CURRENT_USER allerdings nur der aktuell angemeldete Benutzer. Wäre doch schön, wenn man als Administrator auch den HKEY_USERS zur Verfüngung hätte. Den können Sie sich selbst als zusätzliches PSDrive anlegen:

New-PSDrive HKUsers -PSProvider Registry -Root HKEY_USERS

Nach Eingabe dieses Cmdlet können Sie dann mit cd HKUsers: auch in den Users-Key wechseln, der u. a. auch den Default-User und nicht nur den momentan angemeldeten Benutzer enthält.

Dir ist ja bekannter Maßen ein Alias auf Get-Childitem. Damit werden die Schlüssel angezeigt, jedoch nicht die Einträge und die Inhalte. Dies erfahren Sie mir Get-Itemproperty im entsprechenden Schlüssel. Abkürzen können Sie auch das, aber mit dem weniger geläufigen gp, wie „Get-Property“.

4.2.3.2   Schlüssel und Werte in der Registrierdatenbank anlegen, löschen, verändern

Um einen Schlüssel anzulegen oder zu löschen können Sie ebenfalls, die ganz normalen Kommandos des Dateisystems verwenden

mkdir Key

legt im aktuellen Schlüssel den Unterschlüssel namens Key an. Gerne dürfen Sie auch md statt mkdir verwenden.

del Key

löscht den Schlüssel namens Key im aktuellen Verzeichnis. Ob das funktioniert hat können Sie jederzeit mit dir oder ls überprüfen (natürlich auch mit Get-Childitem, wenn Sie gerne viel tippen – und statt del können Sie natürlich auch gerne rm verwenden).

Das Umbenennen funktioniert ähnlich einfach:

ren Key Vieh

Ren wie Rename kommt natürlich von Rename-Item. Die zwei folgenden Worte sind zunächst der original Name und dann der Name in den der Schlüssel geändert werden soll. Wenn Sie zuvor den Schlüssel namens Key gelöscht haben, müssen Sie sich für den Test zum Umbenennen natürlich erst einmal wieder einen durch md anlegen.

Einen Eintrag anlegen können Sie selbstverständlich auch:

New-ItemProperty -Path HKCU:\ -Name Bezeichnung -Value “Inhalt“ –Type String

Um die Pathangabe kommen Sie leider nicht drum herum. Es nützt also nichts mit cd irgendwo hin zu navigieren, um sich die Pfadangabe zu sparen. Zumindest müssen Sie einen Punkt für das aktuelle Verzeichnis angeben, also z.B. so:

New-ItemProperty . Bezeichnung -type String -value "Inhalt"

Kürzer geht es leider nicht.

Der Schalter –Name gibt den Bezeichner für den Wert an und kann in der richtigen Reihenfolge entfallen. Der Schalter –Value gibt den Wert an, der zugewiesen werden soll.

Der Schalter –Type (bei Set-ItemProperty) oder –PropertyType (bei New‑ItemProperty) gibt die Art des Inhalts an. Je nachdem, was Sie hineinschreiben möchten, müssen Sie einen der folgenden Typen angeben:

String (Reg_SZ)

Gibt eine einfache Zeichenfolge an.

ExpandString (Reg_Expand_SZ)

Wie String, kann aber Umgebungsvariablen enthalten, die entsprechend aufgelöst werden.

Binary (Reg_Binary)

Speichert eine Zahl binär ab.

DWord (Reg_DWord)

Gibt eine 32-Bit-Binärzahl an.

MultiString (Reg_Multi_SZ)

Gibt ein Array von Zeichenfolgen an. Der Text kann im Gegensatz zu String also mehrzeilig sein.

QWord (Reg_QWord)

Gibt eine 64-Bit-Binärzahl an.

Mit Set-ItemProperty verfahren Sie genauso wie mit New-Itemproperty. Im Unterschied zu New-ItemProperty können Sie mit Set-ItemProperty nicht nur Einträge erstellen, sondern auch verändern.

Bei den folgenden Befehlen sollten Sie äußerste Vorsicht walten lassen! Löschen der falschen Bereiche der Registrierdatenbank kann Ihre Betriebssysteminstallation unbrauchbar machen.

Mit Clear-ItemProperty . Bezeichnung können Sie einen zugewiesenen Wert im Eintrag löschen, ohne den Bezeichner selbst zu löschen. Als Alias können Sie cpl verwenden. Wenn Sie mit Get-ItemProperty . nachschauen, stellen Sie fest, dass der Bezeichner weiterhin vorhanden ist, allerdings keinen Wert mehr enthält.

Mit Rename-ItemProperty . AlterName NeuerName können Sie den Bezeichner ändern. Alias: rnp.

Remove-ItemProperty im Gegensatz zu Clear-ItemProperty löscht den Eintrag komplett. Hier ist der Alias rp vordefiniert.

Help Move-ItemProperty –Examples liefert eine perfekte Erklärung um Einträge zu verschieben. Alias ist hier mp.

Um ganze Schlüssel zu kopieren können Sie Copy-Item oder kurz cp verwenden:

Copy-Item HKLM:\Software\ZuKopierenderSchluessel\* HKLM:\Software\Ziel\

Mit diesem Befehl werden die Registrierungsschlüssel und -werte im Registrierungsschlüssel ZuKopierenderSchluessel unter HKLM\Software in den Schlüssel Ziel kopiert. Das Platzhalterzeichen * gibt an, dass der Inhalt des Schlüssels ZuKopierenderSchluessel und nicht der Schlüssel selbst kopiert werden soll.

In derselben Art arbeitet auch Move-Item (Alias: mv). Das ist wohl der gefährlichste Bereich!!! Am besten testen Sie das in einer virtuellen Umgebung und nicht auf Ihrem Arbeitsrechner.

4.2.3.3   Umgebungsvariablen dauerhaft ändern

Um eine Umgebungsvariable dauerhaft zu ändern, kommen Sie um einen Registryzugriff nicht drum herum. Wollen Sie also beispielsweise das Verzeichnis, das Ihre eigenen PowerShell Skripte enthält als benutzerbezogene Umgebungsvariable setzen, geben Sie folgendes ein:

Set-ItemProperty -Path HKCU:\Environment -name MyPSSkripts -value “C:\MyPSSkripts”

Wenn Sie sich nun ab- und wieder anmelden und z.B. eine cmd.exe starten und dort set eintippen, werden Sie in den Umgebungsvariablen Ihre MyPSSkripts Zuordnung entdecken.

Die systembezogenen Umgebungsvariablen, wie z.B. die begehrte Path Variable finden Sie unter HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\Environment.

4.2.4   Internet Explorer kontrollieren

Den Internet Explorer können Sie über die COM-Schnittstelle kontrollieren. Um den IE zu starten brauchen Sie zunächst ein COM-Objekt, das auf der Klasse des IE basiert:

$ie=new-object -com "InternetExplorer.Application"

Die meisten Fenster sind nach Prozessstart erst einmal versteckt. Um diese sichtbar zu machen können Sie die Eigenschaft Visible auf $true setzen:

$ie.visible=$true

Drei Mal dürfen Sie raten, was wohl passiert, wenn Sie wieder $false zuweisen. Im Taskmanager sehen Sie den IE allerdings bereits als Prozess, aber nicht als Anwendung solange Visible auf $false steht. Bei $true taucht der IE dann auch unter dem Anwendungstab auf.

Um eine bestimmte Internetseite ansurfen zu lassen, verwenden Sie die Methode Navigate:

$ie.navigate("http://www.microsoft.com")

Wenn Sie die Adresszeile verstecken möchten, können Sie das mit:

$ie.addressbar=$false

Auf die Standardsuchseite gelangen Sie über die Methode:

$ie.gosearch()

Leider können Sie der Methode keine Parameter übergeben was gesucht werden soll. Der einzige Weg führt hier über den Suchbegriff in der URL mit der Methode Navigate.

Auf die zuvor dargestellte Seite gelangen Sie mit der Methode GoBack.

$ie.goback()

Was GoForward macht ist damit wohl klar. GoHome geht entsprechend auf die Startseite.

Besonders interessant ist die Eigenschaft Document. Diese enthält nämlich die gerade angezeigte Website. Ein bisschen Phantasie und Sie können die Website von PowerShell aus bedienen. Gehen Sie zunächst einmal auf meine Website:

$ie.navigate("http://www.martinlehmann.de/wp")

Hier sehen Sie einen Knopf mit Person beschriftet. Den wollen wir nun von PowerShell aus mal anklicken. Das Gemeine dabei ist, dass Sie bei fremden Websites natürlich nicht wissen, welche ID ein Element auf einer Website hat. Doch die Eigenschaft Document ist natürlich nicht wirklich eine Eigenschaft, sondern ein komplexes Objekt. An diesem Objekt gibt es die Methode GetElementsByTagName und mittels

$ie.document.GetElementsByTagName("*")

können Sie sich alle Elemente einer Website auflisten lassen, also nicht nur Knöpfe! Das dauert natürlich ein Weilchen. Aber wenn Sie einmal wissen, wie die Site aufgebaut ist, können Sie ein Element auch gezielt über GetElementByID("NameDerID") oder GetElementsByName("NameDesElements") abrufen, was dann natürlich entsprechend schneller geht. Sicher wollen Sie das nicht alles durchlesen, was dabei ausgeworfen wird, daher lassen Sie am besten nach der Beschriftung suchen:

$ie.document.getelementsbytagname("*") | ? {$_.textcontent`
 -eq "Person"} | select UniqueID

Bei dynamischen Websites können die IDs von mal zu mal auch variieren. Dann bleibt nur die langsame Methode:

$Button=$ie.document.getelementsbytagname("*") | ? {$_.textcontent`
 -eq "Person"}

Ja, nun haben Sie den Knopf in Form von $Button auch schon in den Fingern. Dann müssen Sie jetzt eigentlich nur noch die Methode Click() daran ausführen:

$Button.Click()

Was Sie noch alles mit dem Knopf machen könnten verrät Ihnen wie üblich ein:

$Button | Get-Member

Ähnlich funktioniert das Ganze mit Eingabefeldern, etc…

Beenden können Sie den IE mit dieser Methode:

$ie.quit()

Weiter experimentieren können Sie natürlich auch gerne mit:

$ie | gm

4.2.5   32 oder 64 Bit?

Manchmal kann es wichtig sein, zu wissen, ob die aktuelle PowerShell 32 oder 64 bittig ausgeführt wird. Z.B., wenn man über COM 32-bittige Datenbanktreiber ansprechen möchte, klappt dies nicht aus einer 64 bit PowerShell. Wenn Sie [System.IntPtr]::Size aufrufen und eine 4 zurück gemeldet bekommen, läuft Ihre PowerShell gerade mit 32 Bit. Ist der Wert hingegen eine 8, arbeiten Sie unter 64 Bit.

Die 64 bit Version der PowerShell und des ISE finden Sie unter: C:\Windows\System32\WindowsPowerShell\v1.0

Die 32 bit Version der PowerShell und des ISE finden Sie unter: C:\Windows\SysWOW64\WindowsPowerShell\v1.0

Wenn Sie einfach nur PowerShell eintippen startet standardmäßig die 64 Bit Version.

4.3     Microsoft-Office

Hier finden Sie nützliche Tipps rund ums Microsoft Office Packet.

4.3.1   Allgemein

Auch hier greifen Sie über die COM-Schnittstelle zu. Zunächst benötigen Sie natürlich wieder die fragliche Objektklasse:

Word.Application

Excel.Application

Excel.ChartApplication

Outlook.Application

PowerPoint.Application

Access.Application

Publisher.Application

MSGraph.Application

OneNote.Application

InfoPath.Application

Um also beispielsweise Excel zu starten benötigen Sie folgendes:

$excel=new-object -com excel.application

$excel.visible=$true

$workbook=$excel.workbooks.add()

$worksheet=$excel.worksheets.add()

Damit ist Excel nicht nur gestartet, sondern auch gleich mit einer leeren Arbeitsmappe, sowie einem Tabellenblatt ausgestattet.

Achtung! Sollten Sie beim Aufruf der Method workbooks.add() diese Fehlermeldung bekommen:

Dann sind Sie über einen Bug gestolpert. Dies tritt auf bei unterschiedlich gesetzten Culture Einstellungen:


 

Innerhalb eines Skriptes können Sie mit dieser Zeile:

[System.Threading.Thread]::CurrentThread.CurrentCulture = "en-US"

 

vor dem Methodenaufruf workbooks.add(), oder einfach zu Beginn des Skriptes, das Problem umschiffen. Leider funktioniert es nicht auf der Konsole direkt, sondern nur in einem Skript.

 

Um eine bestimmte Arbeitsmappe zu laden, tauschen Sie einfach die letzten beiden Zeilen gegen diese hier:

$filepath="C:\OrdnermitExcelDateien\Mappe1.xls"

$workbook=$excel.workbooks.open($filepath)

Ähnlich können Si emit anderen Office Anwendungen verfahren. Sie müssen natürlich etwas überlegen, aber Sie kommen sicher mit Get-Member schnell selbst darauf, dass es bei Word z.B. so heißen muss:

$word=new-object -com word.application

$filepath="C:\OrdnermitWordDateien\Word.doc"

$document=$word.documents.open($filepath)

Das Interessante dabei ist, dass workbooks eine Eigenschaft ist, wie man mit Get-Member herausfindet. Versucht man nun ein Get-Member auf $excel.workbooks bekommt man allerdings eine Fehlermeldung. Dabei sieht man allerdings auch unter Definition die Klasse der Eigenschaft Microsoft.Office.Interop.Excel.Workbooks. Danach lässt sich aber wieder prima googeln und bei MSDN findet man die Information, dass hier die Methoden open und add unterstütz werden. So können Sie die gefühlten 10 Millionen Möglichkeiten der Bearbeitung von Office Dokumenten speziell nach Ihren Bedürfnissen ergründen und z.B. auch herausfinden, dass

$excel.quit()

Excel beendet oder

$workbook.close()

die Arbeitsmappe schliesst.

4.3.2   Word

Der Abschnitt für das Erstellen und Beschreiben von Word-Dokumenten ist Winword spezifisch. Der Dateikonverter kann aber mit geringem Aufwand auch auf andere Office-Formate angepasst werden.

4.3.2.1   Dokumente erstellen und ausdrucken

Das, was wie im vorangegangenen Beispiel bei Excel das workbooks ist, ist bei Winword das documents. Um also Word zu starten und ein leeres Dokument einzulegen geben Sie ein:

$word=new-object -com word.application

$word.visible=$true

$doc=$word.documents.add()

Sie wollen etwas schreiben? Kein Problem:

$text=$doc.paragraphs.add()

$text.range.style="Titel"

$text.range.text="Überschrift"

$text.range.insertparagraphafter()

Mit der ersten Zeile teilen Sie mit, dass Sie einen Absatz mit Text (Paragraph) Ihrem Dokument hinzufügen möchten. In der 2. Zeile bestimmen Sie die zu verwendende Formatvorlage. Dazu müssen Sie wissen wie die Formatvorlagen in Ihrer Wordversion lauten. Bei Word 2010 z.B. brauchen Sie nur mit der Maus über die bestehenden Formatvorlagen (im Menüband Start) zu gehen und dann werden deren Bezeichnungen eingeblendet. In Zeile 3 wird der eigentliche Text übergeben und in der letzten Zeile wo das Ganze eingefügt werden soll. Fertig!

Zum Ausdrucken auf dem Standarddrucker geben Sie ein:

$doc.printout()

4.3.2.2   Word-Dokumente konvertieren

Mit dem nachfolgenden Skript können Word-Dokumente von *.doc nach *.docx konvertiert werden:

# Konvertiert im Ordner C:\konverter liegende *.doc in *.docx Dateien

[ref]$SaveFormat="microsoft.office.interop.word.WdSaveFormat" -as [type]

$Word=New-Object -Com word.application

Get-ChildItem -Path "c:\konverter\*" -Include “*.doc” | Foreach {

"Konvertiere: $_"

 $Path=($_.Fullname).Substring(0,($_.Fullname).LastIndexOf("."))

 $Doc=$Word.Documents.Open($_.Fullname)

 $Doc.SaveAs([ref] $path, [ref]$SaveFormat::wdFormatDocumentDefault)

 $Doc.Close()

}

$Word.Quit()

Remove-Variable Word

[gc]::collect()

[gc]::WaitForPendingFinalizers()

Die zweite Zeile hinterlegt die verfügbaren Abspeicherformate in der Variablen $SaveFormat. Die dritte Zeile erstellt das Word-Objekt und startet somit den Prozess. Die vierte Zeile liest alle *.doc-Dateien aus dem angegebenen Verzeichnis und stratet eine Schleife über alle enthaltenen Dateien. Zeile 5 dient nur zu Informationszwecken. In Zeile 6 finden wir in der Eigenschaft Fullname den kompletten Pfad. Den benötigen Sie zum abspeichern, aber da sich die Dateinamenerweiterung von doc in docx ändert müssen Sie die Dateinamenerweiterung abschneiden. LastIndexOf liefert die Position des letzten Punktes im Dateinamen. Substring schneidet von 0 bis zur von LastIndexOf gelieferten Position aus und übergibt dies in die Variable $Path. Die Zeile 7 öffnet das Word-Dokument im alten Format. In Zeile 8 wird der Inhalt der Variablen „by Reference“ an die Speichern unter (SaveAs) Methode übergeben und somit im neuen Docx-Format (default) abgespeichert. Die neue Dateinamenerweiterung ergibt sich durch die Dokumententypvorgabe. Die letzte Zeile in der Schleife schließt das konvertierte Word-Dokument, bevor es ans nächste geht. Sind alle Dokumente umgewandelt, folgen noch die Aufräumarbeiten. Zunächst das schließen des Word-Prozesses und löschen der Variable $Word. Die letzten beiden Zeilen starten den Garbage Collector, zu Deutsch Müllsammler, der die freigegebenen Speicherbereiche (der beendete Winword-Prozess) nun wirklich nicht nur als leer markiert, sondern tatsächlich dem Betriebssystem zurückgibt.

Wenn Sie anders herum oder in andere unterstütze Formate konvertieren möchten, müssen Sie natürlich herausbekommen, welche Formate Ihnen angeboten werden. Na, eine Idee wie Sie dahinter kommen? Stimmt, da war doch was…

[System.Enum]::getnames("microsoft.office.interop.word.wdsaveformat“)

Listet Ihnen die von Word unterstützen Formate.

Einen ähnlichen Konverter können Sie natürlich auch für die anderen Office-Formate basteln. Jetzt wissen Sie ja, wie das grundsätzlich funktioniert.

4.3.3   Excel

Wie Sie Excel öffnen und eine Arbeitmappe mit einer Tabelle anlegen haben Sie bereits im allgemeinen Teil erfahren. Hier finden Sie nun ein paar Bearbeitungsmöglichkeiten.

4.3.3.1   Zellen auslesen und bearbeiten

Zunächst einmal schreiben Sie doch etwas in eine Zelle. Von Excel sind Sie Positionsangaben wie beim Schiffeversenken gewöhnt. A1…Treffer und versenkt. A gibt dabei die Spaltenposition und die 1 die Zeilenposition an. Von der PowerShell aus sind aber beide Werte numerisch anzugeben und zwar erst die Zeile und dann die Spalte. Um also nun etwas in Zelle C1 zu schreiben, müssen Sie sich erst einmal bewusst sein, dass C=3 ist und die 1 (die Zeile) zuerst angegeben werden muss:

$worksheet.Cells.Item(1,3).value2="Puh!"

Alternativ können Sie aber auch im Excelformat Zellkoordinaten angeben und dabei gleich ganze Flächen füllen:

$worksheet.Range("D2:F2").FormulaR1C1="3"

$worksheet.Range("D3:F3").FormulaR1C1="4"

Wenn Sie die Zelle auslesen möchten, geben Sie einfach ein:

$worksheet.Cells.Item(1,3)

Interessant nicht wahr? Das lädt doch förmlich zum Spielen ein. Über die Eigenschaft HasFormular können Sie z.B. herausfinden, ob eine Formel in die Zelle eingegeben wurde. Wenn ja, dann finden Sie in der Eigenschaft Formular natürlich die Formel. Wenn Sie selbst eine Formel eintragen möchten geht das so:

$worksheet.Range("D4:F4").Formula="=Summe(D2:F3)"

Natürlich können Sie alle von Excel bekannten Formeln verwenden.

Formatieren können Sie selbstverständlich auch:

$worksheet.Range("D2:F2").Numberformat="00,00" # Nummernformat

$worksheet.Range("D2:F2").Interior.ColorIndex=10 # Die Farbe als Indexnummer

$worksheet.Range("D3:F3").Interior.Color=0x000000FF # RGB Werte in Hexadezimal

$worksheet.Range("D3:F3").Font.Size=15 # Schriftgröße

$worksheet.Range("D2:F2").Font.Bold=$true # direkt Fett

$worksheet.Range("D4:F4").Font.Fontstyle="Fett" #oder die Deutschen Bezeichnungen aus Excel

$worksheet.Range("C1").Font.Strikethrough=$true # Durchgestrichen

$worksheet.Range("C1").Orientation=30 # Ein bisschen drehen?

Erstellen Sie bitte Spalten- und Zeilenüberschriften, z. B. so:

4.3.3.2   Diagramme erstellen

Um ein Diagramm einzufügen müssen Sie zunächst die Daten markieren, die für das Diagramm verwendet werden sollen (dazu sollten Sie im Bereich C1:F4 ein paar Daten mit Spalten und Zeilenbeschriftung einfügen – gerne per PowerShell):

$worksheet.Range("C1:F4").Select()

$excel.Selection.CurrentRegion.Select()

Dann das Diagramm hinzufügen:

$workbook.Charts.Add()

Und wenn Sie es auf der aktuellen Tabelle haben möchten:

$workbook.ActiveChart.Location(2,$($worksheet.name))

Auf all die Objekte können Sie selbstverständlich Get-Member ausführen. Ich schätze einmal da gibt es alles, was Sie auch mit der Maus im Excel machen können. Viel Spaß beim experimentieren!

4.3.4   Outlook

Gerade Outlook ist für PowerShell Skripting besonders interessant. Zum einen um E-Mails von PowerShell aus zu verschicken und zum anderen um Outlook aufzuräumen oder einen selbst gebastelten Spamfilter zu integrieren.

4.3.4.1   E-Mails von Outlook oder direkt per SMTP verschicken

Um per Outlook E-Mails zu verschicken benötigen Sie folgendes kleines Skript:

$ol=New-Object -comObject Outlook.Application

$mail=$ol.CreateItem(0)

$mail.Recipients.Add("NamedesEmpfängers@domäne.de")

$mail.Subject="Betreff: Info für den Empfänger"

$mail.Body="Hier können Sie Ihren Nachrichtentext übergeben. Gerne auch in Form einer Variablen. Ihre Informationen, sollten dann aber als Objekt-Typ STRING vorliegen."

$Mail.Send()

In der ersten Zeile wird erst einmal wieder ein Outlook-Objekt gebastelt. Sehen müssen Sie das natürlich nicht, also ersparen Sie sich hier die Eigenschaft Visible auf $True zu setzen. In Zeile 2 wird mit der Methode CreateItem das E-Mail-Objekt erstellt. Zeile 3 gibt die E-Mail-Adresse des Empfängers an. In Zeile 4 wird der Betreff festgelegt.In der 4. Zeile steht schließlich der Nachrichtentext und über die letzte Zeile wird die E-Mail verschickt. Raten Sie doch einmal, was die Eigenschaft Attachments macht ;-). Mit Get-Member auf $mail können Sie natürlich wieder erforschen, was sonst noch so geht, wie z.B. BCC u.v.a.m….

Nicht immer hat man einen E-Mail-Client wie Outlook greifbar, dann möchte man die E-Mail vielleicht gerne direkt per SMTP verschicken. Das klappt mit diesem Skript:

10.        $Dateiname="Pfad zum Dateianhang, falls Sie einen mit versenden möchten"   # z.B. c:\zipdatei.zip

11.        $SmtpServer=new-object system.net.mail.smtpClient

12.        $SmtpServer.Host="hostname.domäne.de"     # FQDN des SMTP-Servers

13.        $MailMessage=New-Object system.net.mail.mailmessage

14.        $Anhang=new-object Net.Mail.Attachment($Dateiname)

15.        $MailMessage.from="Absenderadresse"

16.        $MailMessage.To.add("Empfängeradresse")

17.        $MailMessage.Subject=“Betreff: Info fuer den Empfaenger”

18.        $MailMessage.IsBodyHtml=$true     # je nachdem ob Sie html ($true) oder txt ($false) Mails schicken möchten

19.        $MailMessage.Body="Hier können Sie Ihren Nachrichtentext übergeben. Gerne auch in Form einer Variablen. Ihre Informationen, sollten dann aber als Objekt-Typ STRING vorliegen."

20.        $MailMessage.Attachments.Add($Anhang)

21.        $SmtpServer.Send($MailMessage)

Das Skript ist in weiten Bereichen selbst erklärend. In Zeile 3 das FQDN bedeutet: Full-Qualified-Domain-Name und ist einfach nur der MX Eintrag im DNS des SMTP-Servers, also z.B. smtp.strato.de. Die Eigenschaft IsBodyHtml legt fest, ob Sie die Nachricht als einfachen Text oder im HTML-Format verschicken möchten. Wenn Sie $true wählen, können Sie in der Body Eigenschaft den Text mit HTML-Tags formatieren (z.B.: <B>Dieser Text wird FETT dargestellt!</B>). Für die HTML-Formatierungsmöglichkeiten würde ich Ihnen gerne die Internetseite http://selfhtml.org empfehlen.

4.3.4.2   Zugriff auf verschiedene Standardordner von Outlook

Zunächst einmal müssen Sie wissen, wie Sie an einzelne Ordner von Outlook dran kommen. Wenn Sie zunächst wieder ein Outlook-Objekt bauen, wie beim E-Mail versenden in der ersten Zeile steht, haben Sie schon einmal den Anfang und somit das gesamte Outlook in Ihren Fängen. Nun müssen Sie zunächst Ihre Aktionen auf einen bestimmten Standardordner von Outlook eingrenzen. Dazu müssen Sie die „geheime“ Nummer kennen, die dem jeweiligen Ordner entspricht:

3 Gelöschte Elemente

4 Postausgang

5 Gesendete Elemente

6 Posteingang

9 Kalender

10 Kontakte

11 Journal

12 Notizen

13 Aufgaben

16 Entwürfe

Um nun wieder ein Objekt vom jeweiligen Ordner zu erstellen, geben Sie für Gelöschte Elemente z.B. folgendes ein:

$deleted=$ol.Session.GetDefaultFolder(3)

Ein Get-Member auf $deleted verrät, dass es eine Eigenschaft Items gibt, welche die gelöschen Elemente enthält. Wenn Sie nun $deleted.Items eingeben, sehen Sie die gelöschten Elemente am Bildschirm. Sollte Ihr Ordner mit den gelöschten Elementen leer sein, ist das hier natürlich recht langweilig. Schauen Sie doch einfach in der gleichen Weise einmal in die Gesendeten Elemente.

4.3.4.3   Gezielt E-Mails aus dem Posteingang löschen

Natürlich können Sie sich nicht nur in den Ordnern umsehen, sondern auch automatisiert Aufgaben erledigen lassen. Zunächst holen Sie sich doch bitte einmal den Posteingang in eine Variable:

$in=$ol.Session.GetDefaultFolder(6)

Auch hier finden Sie natürlich die Eigenschaft Items, welche die Mails enthält. Mit einem Get‑Member lässt sich auch wieder herausfinden welche Eigenschaften und Methoden die Items (Mails) des Posteingangs haben. So können Sie mit dem Where-Object Cmdlet nach alle diesen Eigenschaften in Ihren E-Mails suchen bzw. filtern lassen. Möchten Sie also alle E-Mails eines bestimmten Absenders und einem bestimmten Betreff könnte das ungefähr so aussehen:

$in.Items | ? {$_.senderemailaddress –eq “absender@firma.de” –and $_.subject –like “*Suchbegriff im Betreff*”}

Wenn Sie nicht mit allen Details zugeschüttet werden möchten, können Sie natürlich noch ein Select‑Object hinten anstellen:

$in.Items | ? {$_.senderemailaddress –eq “absender@firma.de” –and $_.subject –like “*Suchbegriff im Betreff*”} | select senderemailaddress,sent,subject,body

Wenn Sie auf diese Weise sichergestellt haben, dass Sie eine bestimme Mail löschen möchten, können Sie die Methode delete auf die E-Mails anwenden:

$in.Items | ? {$_.senderemailaddress –eq “absender@firma.de” –and $_.subject –like “*Suchbegriff im Betreff*”} | foreach {"E-Mail mit dem Betreff: $($_.subject) wird gelöscht.";$_.delete()}

Achtung: Die Methode delete ist, wenn sie auf einen Select folgt, natürlich nicht mehr vorhanden. Daher muss das Select, wie im Beispiel wieder entfernt werden. Wenn Sie sich erst noch informieren möchten was gerade passiert, muss das natürlich auch vor dem Löschvorgang erfolgen ;-).

Auf diese Weise kann man Dubletten automatisch entfernen lassen, einen eigens angepassten Spamfilter bauen u.v.a.m.

4.3.4.4   Vorgang E-Mails senden/empfangen auslösen

Möchten Sie E-Mails vom Provider abrufen müssen Sie sich zunächst anmelden:

$ol.session.logon("Profilname")

Wenn Sie nur ein Standardprofil haben ist der Name in der Regel Outlook. Sie müssten also im vorangegangenen Beispiel Profilname durch Outlook ersetzen. Wenn Sie nur eine leere Klammer übergeben, erscheint eine Dialogbox, die Ihnen ermöglicht grafisch, das Profil auszuwählen.

Um E-Mail nun zu versenden und zu empfangen geben Sie ein:

$ol.session.sendandreceive($true)

Bei $true in der Klammer wird wiederum grafisch der Verlauf angezeigt, bei $false kann der Status nicht verfolgt werden. Um sich wieder abzumelden, verwenden Sie die Eigenschaft logoff statt logon.

Mit $ol.quit() können Sie Outlook ordnungsgemäß beenden.

4.4     Datenbankzugriffe

Hier finden Sie praktische Tipps, Tricks und Anregungen zum verwalten von Datenbanken.

4.4.1   Datenbankzugriffe auf  Access

In diesem Abschnitt steht beschrieben, wie Sie mit Access Datenbanken arbeiten können. Dazu muss natürlich Access auch auf Ihrem Computer installiert sein.

4.4.1.1   Access-Datei (accdb) anlegen

Um erst mal eine entsprechende Datei anzulegen verwenden Sie das COM-Objekt mit der Methode NewCurrentDatabase(). Zum Beispiel:

$path="C:\DB.accdb"

$application=New-Object -ComObject Access.Application

$application.NewCurrentDataBase($path,10)

$application.CloseCurrentDataBase()

$application.Quit()

Dadurch wird auf Laufwerk C: eine Datenbankdatei namens DB.accdb erstellt.

4.4.1.2   Verbindung mit einer Access-Datenbank herstellen

Um nun von PowerShell aus auf die Datenbank zuzugreifen, muss zunächst eine Verbindung hergestellt werden:

$connection=new-object -comobject ADODB.Connection

$connection.Open("Driver={Microsoft Access Driver (*.mdb, *.accdb)};DBQ=$path")

Eine Verbindung muss vor jeglichen Zugriffen hergestellt werden.

Um die Verbindung wieder zu trennen geben Sie:

$connection.Close()

ein. Achtung! Bei den nachfolgenden Tätigkeiten müssen Sie die Verbindung immer erst wieder aufbauen, wenn Sie sie zuvor geschlossen haben.

4.4.1.3   Tabelle in einer Access-Datenbank anlegen

Um eine Tabelle zu erstellen müssen Sie, im Gegensatz zu Excel, zunächst definieren wie die Daten strukturiert sind. Achtung! Nicht vergessen zuerst die Verbindung zur Datenbankdatei herzustellen.

$felder = "NamederSpalte1 Counter, NamederSpalte2 Date, NamederSpalte3 Integer, NamederSpalte4 Text"

$command = "Create Table GewünschterNamederTabelle ($felder)"

$connection.Execute($command)

Die Variable $felder enthält die Bezeichnungen (Überschriften) der Spalten, gefolgt von den dafür vorgesehenen Datentypen. Für NamederSpalte1 ist der Datentyp Counter vorgesehen. Dies sorgt für eine automatische fortlaufende Nummerierung (Index). Für NamederSpalte2 ist der Datentyp Date vorgesehen. In diesem Feld kann also Datum hinterlegt werden. Für NamederSpalte3 ist der Datentyp Integer vorgesehen. In diesem Feld können Sie ganze Zahlen (keine Kommazahlen, dafür bräuchten Sie Double) hinterlegt werden. Für NamederSpalte4 ist der Datentyp Text vorgesehen. In diesem Feld können beliebige Texte hinterlegt werden. Im Anhang finden Sie eine Übersicht zu den von der jeweiligen SQL Anwendung unterstützten Datentypen.

In der Variablen $command ist das gewünschte Datenbankkommando hinterlegt. In diesem Falle soll eine Tabelle erstellt werden und der Befehl dazu lautet Create Table. Die Tabelle muss natürlich auch einen Namen bekommen, denn in einer Access-Datei können mehrere Tabellen hinterlegt werden. Dieser folgt direkt auf den Befehl Create Table. Logischer Weise muss der Name der Tabelle dann auch eindeutig sein. Auf den Namen wiederum folgt in runden Klammern die Definition der Struktur, der Tabelle, die Sie zuvor in $felder hinterlegt haben.

Nach der ganzen harten Arbeit wird es nun Zeit für das „Simsalabim“ in Form von $connection.Execute($command).

Nun können Sie gerne einmal die Access-Datei mit Access öffnen und feststellen, dass nun tatsächlich eine entsprechende Tabelle erstellt wurde. Danach schliessen Sie Access wieder.

4.4.1.4   Datensatz in Tabelle anlegen

Jetzt wollen Sie sicher auch mal etwas hineinschreiben. Dazu müssen Sie lediglich den Inhalt von $command anpassen und wieder mit einem anschließenden $connection.Execute($command) abschicken.

$command = "Insert into GewünschterNamederTabelle (NamederSpalte2, NamederSpalte3, NamederSpalte4) values ('23.11.1969','46','Martin Lehmann')"

Das hierfür zu verwendende Datenbank Kommando lautet Insert into. Darauf folgt der eindeutige Name der Tabelle (GewünschterNamederTabelle), die mit dem Datensatz gefüllt werden soll. In der ersten runden Klammer geben Sie die Namen der Spalten an die gefüllt werden sollen. Interessant hierbei ist, dass NamederSpalte1 ausgelassen wurde und wenn Sie hinterher kontrollieren, trotzdem was drin steht. Dies ist dem Datentyp Counter zu schulden, der automatisch hoch zählt, wenn neue Datensätze eingefügt werden. Das Wörtchen values leitet die zweite runde Klammer ein. In dieser werden dann für die zuvor benanneten Spalten in der gleichen Reihnfolge die Werte übergeben. In diesem Falle also mein Geburtsdatum, mein aktuelles Alter, gefolgt von meinem Namen. Wenn Sie möchten, schicken Sie doch gleich noch mal die zwei Befehlszeilen mit Ihren Werten ab.

4.4.1.5   Komplette Tabelle auslesen

Diese Abfrage dürfte Ihnen vielleicht etwas vertraut vorkommen:

$command = "Select * from GewünschterNamederTabelle"

Auch hier muss natürlich wieder ein $connection.Execute($command)hinterher geschickt werden. Die Ausgabe ist zugegebener Maßen ziemlich eklig. Aber um eine schicke Tabelle zu bekommen hilft das hier weiter:

$Chaos=$connection.Execute($command)

$Chaos.MoveFirst()

$Table=@()

do {

    $Obj=New-Object pscustomobject

    $Obj | Add-Member -MemberType NoteProperty -Name NamederSpalte1 -Value $Chaos.Fields.Item("NamederSpalte1").Value;

    $Obj | Add-Member -MemberType NoteProperty -Name NamederSpalte2 -Value $Chaos.Fields.Item("NamederSpalte2").Value;

    $Obj | Add-Member -MemberType NoteProperty -Name NamederSpalte3 -Value $Chaos.Fields.Item("NamederSpalte3").Value;

    $Obj | Add-Member -MemberType NoteProperty -Name NamederSpalte4 -Value $Chaos.Fields.Item("NamederSpalte4").Value

    # Werte aus Datensatz in Array bringen

    $Table+=$Obj

    $Chaos.MoveNext()

} until ($Chaos.EOF -eq $True)

$Table

4.4.1.6   Gezielt Daten auslesen

Das Ganze läuft so ähnlich wie man auch eine gesamte Tabelle ausliest:

$command = "Select NamederSpalte4 from GewünschterNamederTabelle where NamederSpalte1 = 2 order by NamederSpalte3"

Auch hier darf das anschliessende Simsalabim in Form von $connection.Execute($command) natürlich nicht fehlen.  Hier habe ich nun definiert, dass ich in der Ausgabe nicht den kompletten Datensatz haben möchte (* = alle Spalten), sondern nur  den Inhalt aus NamederSpalte4. Weiterhin möchte ich gerne nur die Einträge aus der Tabelle die in der Spalte NamederSpalte1 den Wert 2 stehen haben. Schlußendlich soll die Ausgabe noch nach dem Inhalt der Spalte NamederSpalte3 sortiert werden. Das Sortieren fällt natürlich nur dann auf, wenn Sie noch einige Datensätze hinzugefügt haben. Um absteigend zu sortieren müssen Sie hinter dem jeweiligen Spaltenbezeichner das Wörtchen DESC (für descending = absteigend) schreiben.

Grundsätzlich können Sie natürlich alles an Abfragen definieren, was SQL hergibt. Sollten Sie sich damit noch nicht so auskennen kann ich Ihnen die Lektüre der Seite http://www.sql-und-xml.de/sql-tutorial/ empfehlen.

4.4.1.7   Werte gezielt ändern

Auch hier hilft uns wieder SQL weiter:

$command = "Update GewünschterNamederTabelle Set NamederSpalte4 = ‘Anderer Namewhere NamederSpalte1 = 2"

Das SQL Statement nennt sich Update. Darauf folgt der Name der Tabelle in der die Änderung stattfinden soll. Mit Set können Sie festlegen in welcher Spalte (NamederSpalte4) der nachfolgend angegebene gewünschte Wert (Anderer Name) geschrieben werden soll. Mit where legen Sie wiederum die Zeile fest. Selbstverständlich darf auch hier das abschließende $connection.Execute($command) nicht fehlen.

4.4.1.8   Datensatz löschen

Einen einzelnen Datensatz können Sie über das SQL-Statement Delete löschen:

$command = "Delete from GewünschterNamederTabelle where NamederSpalte1 = 2"

Nach einem $connection.Execute($command) wird hier der Datensatz in Zeile 2 entfernt bzw. der, dessen Autonummerierung aus NamederSpalte1 den Wert 2 hat.

4.4.2   Datenbankzugriffe auf  SQL

Grundsätzlich können Sie natürlich alle SQL Kommandos von Access (vorangegangenes Kapitel) auch für SQL Server nutzen. Das einzige, was Sie anpassen müssen ist das Verbindungsobjekt.

Dazu brauchen Sie zunächst eine entsprechende Objektinstanz:

$connection=New-Object System.Data.SqlClient.SqlConnection

Den sogenannten ConnectionString (enthält Angaben, wie man sich mit der Datenbank verbinden möchte) erfragen Sie bei Ihrem Datenbank-Administrator. Im einfachsten Fall könnte der so aussehen:

$connection.connectionstring="Data Source=NamesdesServers\NamederSQLInstanz;Integrated Security=True;DataBase=NameDerDatenbankInstanz"

Mit $connection.open() öffnen Sie die Datenbank. $command kann jetzt leider nicht einfach nur ein String (Text) sein, sondern hier müssen wir mit speziellen Objekten arbeiten. Also zunächst mal ein Objekt aus der zugrundeliegenden Klasse ableiten:

$command=New-Object System.Data.SqlClient.SqlCommand

$felder habe ich hier aus dem vorangegangenen Abschnitt von Access entlehnt. Im Anhang finden Sie eine Übersicht zu den von der jeweiligen SQL Anwendung unterstützten Datentypen.

Das Kommando zum anlegen einer Tabelle sieht nun wieder ähnlich aus, wie im Abschnitt über Access:

$command.commandtext = "Create Table GewünschterNamederTabelle ($felder)"

Der Unterschied liegt hier in der kleinen Erweiterung vor dem = in Form von .commandtext und im Aufruf zur Ausführung:

$command.executenonquery()

Die restlichen Anordnungen laufen also entsprechnd ähnlich den im Abschnitt über Access beschriebenen SQL Anweisungen ab mit den hier erwähnten kleinen Modifikationen. Wenn Sie mit Ihren Aktionen fertig sind, vergessen Sie nicht die Verbindung wieder ordentlich abzubauen mit:

$connection.close()

4.5     GUI – Grafische Oberflächen mit PowerShell

Hier finden Sie eine Schritt für Schritt Anleitung, wie man mit Hilfe des .NET-Frameworks grafische Oberflächen gestaltet. Dies beginnt zunächst mit einer MessageBox, wie Sie sie vielleicht von Visual Basic her kennen, über einem einfachen leeren Fensterrahmen und das Hinzufügen von Knöpfen bis hin zu komplexen Ansichten wie der eines Dateimanagers.

4.5.1   Simple Messagebox

Eine einfache MessageBox, wie man Sie von VBS her kennt geht natürlich auch mit PowerShell. Dies wäre dann die Quick&Dirty Variante. Auch hier müssen Sie zunächst die System.Windows.Forms Klasse laden (siehe Abschnitt Einfaches Fenster). Allerdings ist die Klasse Messagebox etwas anders (einfacher) zu handhaben, als der Rest der hier beschrieben steht. Statt erst ein Fensterrahmen zu erstellen und dann alles entsprechend auszuschmücken rufen Sie die Klasse direkt auf. Um die Positionierung, Farbgestaltung etc. brauchen (und können Sie auch nicht) Sie sich keine Gedanken zu machen. Der Aufruf ist dementsprechend auch recht primitiv:

$fb=[System.Windows.Forms.MessageBox]::Show("Darzustellender Text" , "Überschrift", Knöpfe, "Icon")

Für den Platzhalter Knöpfe geben Sie ein der u.a. Zahlen Zahlen an. Je nachdem welche Knopfkombination Sie haben möchten.

0:

OK

1:

OK Cancel

2:

Abort Retry Ignore

3:

Yes No Cancel

4:

Yes No

5:

Retry Cancel

 

Wenn Sie ein Icon haben möchten, stehen Ihnen folgende zur Verfügung:


Membername

Beschreibung

Asterisk

Das Meldungsfeld enthält ein Symbol, das aus dem Kleinbuchstaben i in einem Kreis besteht.

Error

Das Meldungsfeld enthält ein Symbol, das aus einem weißen X in einem Kreis mit rotem Hintergrund besteht.

Exclamation

Das Meldungsfeld enthält ein Symbol, das aus einem Ausrufezeichen in einem Dreieck mit gelbem Hintergrund besteht.

Hand

Das Meldungsfeld enthält ein Symbol, das aus einem weißen X in einem Kreis mit rotem Hintergrund besteht.

Information

Das Meldungsfeld enthält ein Symbol, das aus dem Kleinbuchstaben i in einem Kreis besteht.

None

Das Meldungsfeld enthält keine Symbole.

Question

Das Meldungsfeld enthält ein Symbol, das aus einem Fragezeichen in einem Kreis besteht. Es wird empfohlen, das Fragezeichensymbol nicht mehr für Meldungen zu verwenden, da auf diese Weise kein eindeutiger Meldungstyp angegeben wird und die Formulierung einer Meldung als Frage für jeden Meldungstyp möglich wäre. Außerdem können Benutzer das Fragezeichensymbol für Meldungen mit Hilfeinformationen verwechseln. Verwenden Sie daher in Meldungsfeldern kein entsprechendes Fragezeichensymbol mehr. Die Verwendung des Fragezeichensymbols wird vom System nur noch aus Gründen der Abwärtskompatibilität unterstützt.

Stop

Das Meldungsfeld enthält ein Symbol, das aus einem weißen X in einem Kreis mit rotem Hintergrund besteht.

Warning

Das Meldungsfeld enthält ein Symbol, das aus einem Ausrufezeichen in einem Dreieck mit gelbem Hintergrund besteht.

 

Quelle: http://msdn.microsoft.com/de-de/library/system.windows.forms.messageboxicon(v=vs.110).aspx

Ein Beispiel:

$fb=[System.Windows.Forms.MessageBox]::Show("Was nun" , "Weiter", 2, “Question)

$fb

In der Variablen $fb finden Sie dann, welcher Knopf geklickt wurde.

4.5.2   Vom einfachen Fenster zu aufwendigen Gestaltungsdetails

Um ein einfaches Fenster zu erstellen, benötigen Sie folgendes Grundgerüst:

[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Drawing")

[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")

$Fenster=New-Object System.Windows.Forms.Form

[void] $Fenster.ShowDialog()

Zugegeben, dass ist nun wirklich nicht gerade weltbewegend, aber es soll ja auch nur einmal den Fensterrahmen anzeigen.

Die ersten beiden Zeilen laden die .NET-Erweiterungen für die grafische Oberflächengestaltung. Die wichtigere von beiden ist System.Windows.Forms, denn die zweite System.Drawing brauchen Sie hier eigentlich noch gar nicht. Die wird erst im nächsten Abschnitt, bei Positionsangaben interessant. Aber gewöhnen Sie sich am besten gleich an beide zu laden. In der 3. Zeile erstellen Sie ein neues Objekt für den Fensterrahmen basierend auf der Klasse System.Windows.Forms.Form und legen es in der Variablen $Fenster ab. Dieser Befehl würde ohne die vorangegangene 2. Zeile fehlschlagen, da er ohne diese Zeile die Klasse nicht kennen würde. Nun haben Sie ein Fenster, aber wo ist es? Die meisten GUI Elemente sind immer erst einmal unsichtbar. Um das Fenster, dass Sie zusammengeschraubt haben nun sichtbar zu machen dient die 4. Zeile. Hier rufen Sie am Fenster-Objekt die Methode ShowDialog() auf. Das macht das Fenster letztendlich sichtbar und schaltet es „scharf“. Die [void] Angaben sorgen dabei dafür, dass Sie .NET nicht mit Statusmeldungen „zuquatscht“. Wenn Sie das Fenster wieder weg haben möchten, klicken Sie einfach rechts oben auf das schliessen Kreuzchen.

4.5.2.1   Fenster Größe und Position festlegen

Selbstverständlich dürfen Sie $Fenster auch mit Get‑Member untersuchen. Dabei werden Sie eine Eigenschaft namens StartPosition finden. Gerne dürfen Sie auch deren Inhalt einmal mit $Fenster.StartPosition abfragen. Ein

[System.Enum]::GetNames("System.Windows.Forms.FormStartPosition")

verrät Ihnen auch gleich, was Sie da alles reinschreiben könnten. Mit CenterScreen bekommen Sie das Fenster immer in der Bildschirmmitte zentriert. Das ist zwar schön, aber nicht immer gewünscht. Wenn es nun in der linken, oberen Ecke stehen soll müssen Sie manuell positionieren:

$Fenster.StartPosition="Manual"

Manuell ist aber nicht gleich links, oben! Vielleicht haben Sie auch schon die Eigenschaft Location entdeckt. Mit der können Sie die Position festlegen. Allerdings können Sie hier nicht einfach $Fenster.Location=10,10 reinschreiben. Get-Member zeigt in der Splate Definition, dass Location ein Objekt von Typ Point der Klasse System.Drawing und nicht von System.Windows.Forms ist! Daher brauchen Sie spätestens jetzt System.Drawing, wie im vorangegangenen Abschnitt bereits erwähnt. Um nun die Position links, oben auszurichten geben Sie

$Fenster.Location=New-Object System.Drawing.Point(0,0)

ein. Nun können Sie mit

$Fenster.ShowDialog()

Das Fenster wieder anzeigen lassen und nun sollte es links, oben auftauchen. Die erste Zahl in der Klammer gibt an wieviele Pixel vom linken Rand das Fenster dargestellt werden soll und die zweite Zahl wie weit vom oberen Rand. Wenn Sie das Fenster schließen und nun

$Fenster.Location=New-Object System.Drawing.Point(50,300)

eingeben und erneut $Fenster.ShowDialog()aufrufen werden Sie feststellen, das Ihr Fenster nun entsprechend anders positioniert wurde. Schließen Sie wieder das Fenster und geben Sie nun

$Fenster.Size=New-Object System.Drawing.Size(100,50)

ein. Noch einmal

$Fenster.ShowDialog()

und Sie werden feststellen, dass Sie mit der Eigenschaft Size soeben die Breite und Höhe des Fensters festgelegt haben.

Weiterhin hat der aufmerksame Leser festgestellt, dass bei Location System.Drawing.Point verwendet wurde und bei Size System.Drawing.Size. Beide sind per Definition 2 x 32Bit-Integer vom Aufbau her. Ob Sie nun Ihre Positionen basierend auf System.Drawing.Point oder System.Drawing.Size schreiben bleibt im Endeffekt gleich.

4.5.2.2   Fenster Farben und Hintergrundbild

Die Eigenschaft ForeColor bezieht sich auf die Farbe des im Fenster darzustellenden Textes. Da wir im Moment noch keinen Text haben, macht es an dieser Stelle wenig Sinn damit herumzuprobieren. Es funktioniert aber im Prinzip ganz genauso wie bei der Hintergrundfarbe.

Um die Hintergrundfarbe einzustellen, verwenden Sie die Eigenschaft Backcolor. Die ist wieder aus der Klasse System.Drawing. Bei System.Drawing.Color gibt es bereits vordefnierte Farben, aber Sie können die Farbwerte auch ganz genau einstellen. Um die Hintergrundfarbe auf einen vordefinierten Wert einzustellen geben Sie z.B. folgendes ein:

$Fenster.BackColor= [System.Drawing.Color]::Red

Ein $Fenster.Showdialog() zeigt das Ergebnis entsprechend mit rotem Hintergrund.

$Fenster.BackColor= [System.Drawing.Color]::FromArgb(255,0,0,255)

Der vorangegangene Befehl stellt durch die Methode FromArgb der Klasse System.Drawing.Color blau ein. Der Methode FromArgb müssen 4 Werte übergeben werden. Der 1. Wert gibt die Transparenz an. Da dies an der Form Klasse durch die Eigenschaft Opacity geregelt wird, muss beim Fenster hier immer 255 als erster Wert übergeben werden. Aus den nachfolgenden 3 Werten kann man dann die gewünschte Farbe aus Ihrem Rot, Grün und Blau Anteil zusammen mischen. Da in diesem Beispiel Rot und Grün auf 0 stehen und Blau auf dem höchsten Wert 255, kommt entsprechend ein sattes Blau heraus.

Wenn Sie wissen möchten welche weiteren Methoden und Eigenschaften (z.B. welche vordefinierten Farben es gibt) an der System.Drawing.Color Klasse hängen geben Sie einfach den Namen der Klasse in eine Suchmaschine ein und klicken Sie den erst besten Link mit msdn.microsoft.com beginnend an. Dort finden Sie die komplette .NET Dokumentation zu dieser Klasse.

Um ein Hintergrundbild einzubauen verwenden Sie die Klasse System.Drawing.Image und die Methode FromFile um Werte in die Eigenschaft BackgroundImage Ihres Fensters zu schreiben:

$Fenster.BackgroundImage=[System.Drawing.Image]::FromFile("Pfad\zu\Grafikdatein.jpg”)

…ShowDialog (haben Sie ja schon oft genug gemacht).

Mit der Eigenschaft BackgroundImageLayout können Sie festlegen, ob Sie das Bild gekachelt, eingepasst, oder sonst wie dargestellt haben möchten. Um es einzupassen können Sie z.B. das hier angeben:

$Fenster.BackgroundImageLayout="Zoom"

Welche Anpassungen Sie machen können verrät:

[System.Enum]::GetNames("System.Windows.Forms.ImageLayout")

4.5.2.3   Fenster und/oder deren Elemente vertecken bzw. abblenden

Das Fenster Objekt, als auch die im nachfolgenden Abschnitt beschriebenen Controls haben die beiden Eigenschaften Visible und Enabled.

Statt ein Fenster beim Zusammenbau jedes Mal über die Methode Showdialog() zu aktivieren, wieder zu schliessen, nachbessern (z.B. bei der Positionierung oder Farbgestaltung), wieder aktiveren, machen Sie es doch einfach während des Zusammenbaus sichtbar:

$Fenster.Visible=$true

Dann können Sie mit den anderen Eigenschaften spielen und zusehen, wie sich das Fenster „live“ anpasst. Wenn Sie $false zuweisen, machen Sie es wieder unsichtbar. Das ist aber nicht immer gewünscht, sondern vielleicht, das es nur inaktiv (grau) aussieht und nicht verändert werden kann. Dies können Sie mit der Eigenschaft Enabled erreichen:

$Fenster.Enabled=$false

um es auszugrauen, bzw.

$Fenster.Enabled=$true

um es wieder zu aktivieren.

Achtung Haken! Wenn Sie ein Fenster Visible=$true geschaltet haben, können Sie nicht mehr die Methode Showdialog() aufrufen. Dazu müssen Sie Visible erst wieder auf $false umschreiben.

4.5.3   Was sind Controls?

Controls sind Objekte, die Sie in Ihren Fenstern verbauen können. Also Knöpfe, Karteikartenreiter, Auswahlfelder, Texte, etc…

Wenn Sie sich ein $Fenster | Get-Member anschauen werden Sie auch schon die „Eigenschaft“ Controls sehen. Diese ist vom Datentyp: System.Windows.Forms.Control+ControlCollection Controls. Mit Select oder Get-Member, werden Sie hier aber nicht weit kommen, da erst einmal kein Element eingebaut ist. Wenn Sie das nun weiter untersuchen wollen, schauen Sie am besten im Internet im MSDN nach dieser Klasse: System.Windows.Forms.Control.ControlCollection. Da werden Sie z.B. eine Methode Add finden, die Ihnen erlaubt nun verschiedene Objekte dem Fenster hinzuzufügen. Diese Eigenschaft kann man sich also am besten als einzelnes Objekt vorstellen, dass in diese Eigenschaft hinterlegt wird, oder gleich als Array von verschiedenen Elementen, die Sie ins Fenster hängen möchten.

4.5.4   Fensterrahmen und Texte

In den folgenden 4 Unterpunkten werden verschiedene Aspekte der Textgestaltung bei Fenstern erklärt.

4.5.4.1   Titelleiste beschriften

Ganz einfach! In der Variable $Fenster finden Sie die Eigenschaft Text. Damit lässt sich die Überschrift festlegen.

Den grauen Teil benötigen Sie nur, wenn Sie die Bibliotheken noch nicht geladen und auch noch kein Fenster Objekt gebaut haben.

[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Drawing")

[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")

$Fenster=New-Object System.Windows.Forms.Form

$Fenster.Text="Beschriftung der Titelleiste"

[void] $Fenster.ShowDialog()

4.5.4.2   Text in Statusleiste

Um am unteren Fensterrand etwas in die Statuszeile zu schreiben, können Sie auf die Klasse System.Windows.Forms.StatusBar zurückgreifen:

Den grauen Teil benötigen Sie nur, wenn Sie die Bibliotheken noch nicht geladen und auch noch kein Fenster Objekt gebaut haben.

[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Drawing")

[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")

$Fenster=New-Object System.Windows.Forms.Form

$StatusLeiste=New-Object System.Windows.Forms.StatusBar

$StatusLeiste.Text="Auswahl: Noch nicht getroffen"

$StatusLeiste.Name="StatusLeiste"

$Fenster.Controls.Add($StatusLeiste)

[void] $Fenster.ShowDialog()

Das dürfte dann so aussehen:

4.5.4.3   Text innerhalb des Fensters frei positionieren

Um Texte in einem Fenster zu positionieren benötigen Sie die Klasse System.Windows.Forms.Label.

Die Eigenschaft Text enthält den anzuzeigenden Text, während die Eigenschaft Name, für Sie als Referenz dient, wenn Sie später auf das Element zugreifen möchten. Die letzte Zeile verknüpft dann Ihr Text Objekt mit Ihrem Fensterrahmen Objekt. Das geschieht mit der Methode Add() der Unterklasse Controls des Fenster-Objektes.

Den grauen Teil benötigen Sie nur, wenn Sie die Bibliotheken noch nicht geladen und auch noch kein Fenster Objekt gebaut haben.

[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Drawing")

[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")

$Fenster=New-Object System.Windows.Forms.Form

$Beschriftung=New-Object System.Windows.Forms.Label

$Beschriftung.Location=New-Object System.Drawing.Point(10,50)

$Beschriftung.Size=New-Object System.Drawing.Size(100,20)

$Beschriftung.Text="Text im Fenster"

$Beschriftung.Name="FensterBeschriftung"

$Fenster.Controls.Add($Beschriftung)

[void] $Fenster.ShowDialog()

Wenn Sie nun mehrfach die Methode Controls.Add() verwenden möchten, achten Sie darauf, dass Objekte, die Sie „ins Fenster hängen möchten“ sich in der Eigenschaft Name und Location unterscheiden. Der erste Wert der Eigenschaft Location eines Text Objektes sollte natürlich kleiner sein, als die gesamt Fensterbreite und der 2 kleiner als die gesamt Fensterhöhe ;-).

4.5.4.4   Schreibstil (Font) festlegen

Die Schriftart können Sie an allen Objekten, vom Fensterrahmen über Knöpfe, Text bis hin zu Eingabefeldern festlegen. Wenn Sie es am Fensterrahmen angeben, vererbt es sich an alle daran gebundenen Objekte. Zumindest solange nicht bei einem Objekt wieder etwas anderes festgelegt wird. Bleiben wir zunächst einmal beim Fensterrahmen.

$Fenster.Font

Zeigt Ihnen den aktuell eingestellten Schriftstil. Nun dürfen Sie nur einen Fehler nicht machen: Denken! ;-)

So viel Sie schon gelernt haben, schauen Sie nun gleich wieder mit Get-Member was da so alles geht und stellen fest, dass es eine Eigenschaft FontFamily gibt und versuchen diese nun nach allen Regeln der Kunst umzustellen. Leider ohne Erfolg.

Um den Font umzustellen, müssen Sie sich direkt an $Fenster.Font wenden, statt dem Unterpunkt FontFamily:

$Fenster.Font="WingDings"

…macht die Umstellung der Schrift wohl recht gut deutlich. Welche Schriften Ihnen auf dem jeweiligen System zur Verfügung stehen finden Sie so heraus:

[System.Drawing.FontFamily]::families

Wenn Sie mit Get-Member wieder das Font Objekt untersuchen, stellen Sie fest, dass es bei den Eigenschaften Height, Size oder Italic (Kursiv) nur „Getter“ gibt. D.h. Sie können nur nach der Schriftgröße fragen, aber Sie nicht einstellen. Hier müssen Sie wieder ein komplett neues Schriftobjekt bauen und dann in die „Font-Eigenschaft“ des Objektes zuweisen:

$Fenster.Font=New-Object System.Drawing.Font("arial",18,[System.Drawing.FontStyle]::Bold)

4.5.5   Knöpfe

Ein paar Knöpfe im Fenster wären schick?

Auch ganz einfach mit der Klasse: System.Windows.Forms.Button

Den grauen Teil benötigen Sie nur, wenn Sie die Bibliotheken noch nicht geladen und auch noch kein Fenster Objekt gebaut haben.

[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Drawing")

[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")

$Fenster=New-Object System.Windows.Forms.Form

$Knopf=New-Object System.Windows.Forms.Button

$Knopf.Location=New-Object System.Drawing.Size(75,120)

$Knopf.Size=New-Object System.Drawing.Size(75,23)

$Knopf.Text="OK"

$Knopf.Name="OK"

$Knopf.DialogResult="OK"

$Knopf.Add_Click({$Fenster.Close()})

$Fenster.Controls.Add($Knopf)

[void] $Fenster.ShowDialog()

Erklärungsbedürftig sind hier die Eigenschaft DialogResult und das Ereignis Add_Click().

Zunächst einmal das Click Ereignis Add_Click(): Wie komme ich denn eigentlich darauf? Wenn Sie ein Get-Member auf Ihren $Knopf absetzen, finden Sie ganz am Anfang einen Satz voll sogenannter Events (Ereignisse), auf die Sie reagieren können. Darunter werden Sie auch einen Click finden. Selbstverständlich können Sie auch auf die ganzen anderen Ereignisse dort einfach mit vorangestelltem Add_IhrGewünschtesEreignisAusDerListeDerEreignisse() bestimmen was dann passieren soll. Bleiben wir beim Mausklick. Klickt jemand mit der Maus auf den Knopf, dann macht PowerShell das, was noch einmal in den geschweiften Klammern eingeschlossen steht. In diesem Beispiel also am Objekt $Fenster die Methode close() aufrufen. 3 Mal dürfen Sie raten was passiert…

In dem Moment wenn Sie nun das Fenster durch Klick auf OK schliessen und somit die ShowDialog() Methode von $Fenster beenden, geschieht etwas Interessantes. Das, was Sie der Eigenschaft DialogResult von $Knopf zugewiesen haben, wird in diesem Moment in die Eigenschaft DialogResult von $Fenster übernehmen. Leider können Sie nur bestimme Werte dort hinein geben: None, OK, Cancel, Abort, Retry, Ignore, Yes, No. Somit können Sie also in $Fenster.DialogResult immer ablesen, welcher Knopf zuletzt gedrückt wurde, wenn Sie bei mehreren Knöpfen diese mit unterschiedlichen DialogResult Werten vorbelegen.

4.5.6   Tool-Tips (Hilfetexte)

Sie kennen das sicher, wenn man mit der Maus eine kurze Zeit über einer bestimmten Stelle steht wird plötzlich so ein kleines Fensterchen mit Infos angezeigt:

Das geht bestimmt mit der Methode MouseOver oder Mousehover…nein, viel einfacher:

Den grauen Teil benötigen Sie nur, wenn Sie die Bibliotheken noch nicht geladen und auch noch kein Fenster Objekt gebaut haben.

[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Drawing")

[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")

$Fenster=New-Object System.Windows.Forms.Form

$Knopf=New-Object System.Windows.Forms.Button

$Knopf.Location=New-Object System.Drawing.Size(75,120)

$Knopf.Size=New-Object System.Drawing.Size(75,23)

$Knopf.Text="OK"

$Knopf.Name="OK"

$Knopf.DialogResult="OK"

$Knopf.Add_Click({$Fenster.Close()})

$Fenster.Controls.Add($Knopf)

$ToolTip = New-Object System.Windows.Forms.ToolTip

$ToolTip.BackColor = LightGoldenrodYellow

$ToolTip.IsBalloon = $true

$ToolTip.SetToolTip($Knopf,"Anmerkung")

[void] $Fenster.ShowDialog()

Hier können Sie auf das Objekt System.Windows.Forms.ToolTip  setzen. Was die Eigenschaft BackColor macht, ist wohl klar. Zusätzlich habe ich mal die Eigenschaft IsBalloon auf $true gesetzt, weil es einfach ein bischen schöner aussieht. Notwendig ist dies aber nicht. Ein $ToolTip | gm zeigt Ihnen, dass Sie natürlich noch viel mehr einstellen können, wie z.B. die Einblendverzögerung. Doch nun zur letzten spannenden (neuen) Zeile:

$ToolTip.SetToolTip($Knopf,"Anmerkung")

Hier wird die Methode SetToolTip() am Tooltip Objekt aufgerufen. Darüber können Sie die Verbindug zu einem beliebigen anderen Objekt herstellen. Bislang haben Sie immer von einem bestimmten (Eltern) Objekt ein anderes (Kind) Objekt hinzugefügt. Hier gehen Sie den Weg nun quasi anders herum. Vom Kind Objekt $ToolTip aus sagen Sie in der Methode SetToolTip an welches (Eltern) Objekt ($Knopf) Sie den durch ein Komma getrennten Text "Anmerkung" anhängen möchten.

4.5.7   Ladebalken

Nutzen wir doch gleich ein bisschen Ihre Kenntnisse über Knöpfe bei der Erstellung eines Fortschrittsbalkens, bzw. Ladeanzeige, oder wie auch immer Sie dies titulieren möchten:

Das nachfolgende Skript nutzt den „Weiter“ Knopf um den Ladebalken weiter zu füllen. Die Klasse System.Windows.Forms.ProgressBar bringt Ihnen das Objekt für die Ladestandanzeige und hinterlegt dies in der Variable $LadeBalken. Dann wird der Eigenschaft Value der Wert 0 zugewiesen und schließlich ans Fenster „geklebt“. Im Klick-Ereignis des Knopfes wird der Wert bei jedem Klick dann um 10 erhöht, solange der Wert weniger als 100 ist. Bei 100 wird er dann wieder auf 0 zurückgesetzt.

Der graue Teil sollte Ihnen wieder aus den vorangegangnen Kapiteln bekannt sein.

[reflection.assembly]::loadwithpartialname("System.Windows.Forms") | Out-Null

[reflection.assembly]::loadwithpartialname("System.Drawing") | Out-Null

$Fenster=New-Object System.Windows.Forms.Form

$Fenster.Name="Ladebalken"

$LadeBalken=New-Object System.Windows.Forms.ProgressBar

$Ladebalken.Value=0

$Fenster.controls.add($Ladebalken)

$Knopf=New-Object System.Windows.Forms.Button

$Knopf.Location=New-Object System.Drawing.Size(75,120)

$Knopf.Size=New-Object System.Drawing.Size(75,23)

$Knopf.Text="Weiter"

$Knopf.Name="Weiter"

$Knopf.Add_Click({

 if ($Ladebalken.Value -lt 100) { $Ladebalken.Value += 10 } else {$Ladebalken.Value=0 }

})

$Fenster.Controls.Add($Knopf)

$Fenster.showdialog()

4.5.8   Eingabefelder

Möchten Sie Texte oder Zahlen vom Benutzer abfragen, können Sie das natürlich auch über benutzerfreundliche Fenster tun.

Alles was Sie dazu brauchen ist folgender Code in dem o.a. Grundgerüst:

Den grauen Teil benötigen Sie nur, wenn Sie die Bibliotheken noch nicht geladen und auch noch kein Fenster Objekt gebaut haben.

[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Drawing")

[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")

$Fenster=New-Object System.Windows.Forms.Form

$EingabeFeld=New-Object System.Windows.Forms.TextBox

$Fenster.Controls.Add($EingabeFeld)

[void] $Fenster.ShowDialog()

Wenn Sie mögen, können Sie den Text schon vorbelegen:

$EingabeFeld.Text="Vorgabe"

4.5.9   Aktives Element eines Fensters festlegen

Vielleicht möchten Sie gerne festlegen, bei welchem der Eingabefelder der Text eingegeben wird, wenn Sie direkt los tippen, ohne vorher mit der Maus in ein bestimmtes EingabeFeld zu klicken. Das aktive Element eines Fensters, können Sie über die Eigenschaft ActiveControl von $Fenster steuern:

$Fenster.ActiveControl=$EingabeFeld

oder

$Fenster.ActiveControl=$Fenster.Controls | ? {$_.Name –like "KnopfA"}

Nun wissen Sie auch, für was die Eigenschaft Name an einem Control gut ist.

4.5.10                     Listen Auswahl und DropDown Felder

Um eine Liste mit auswählbaren Objekten zu erstellen können Sie folgenden Code nutzen:

Den grauen Teil benötigen Sie nur, wenn Sie die Bibliotheken noch nicht geladen und auch noch kein Fenster Objekt gebaut haben.

[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Drawing")

[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")

$Fenster=New-Object System.Windows.Forms.Form

$Auswahlfeld=New-Object System.Windows.Forms.Listbox

$Auswahlfeld.SelectionMode="MultiExtended"

[void] $Auswahlfeld.Items.Add("1. Element")

[void] $Auswahlfeld.Items.Add("2. Element")

[void] $Auswahlfeld.Items.Add("3. Element")

[void] $Auswahlfeld.Items.Add("4. Element")

[void] $Auswahlfeld.Items.Add("5. Element")

$Auswahlfeld.Height=90

$Fenster.Controls.Add($Auswahlfeld)

[void] $Fenster.ShowDialog()

Das Ganze könnte dann so aussehen:

Hier sind wieder zwei Besonderheiten zu beachten: SelectionMode und Items.Add.

Mit SelectionMode legen Sie fest, ob und wie mehrere Elemente aus der Liste ausgewählt werden können:

Beim Wert MultiExtended können bei bei gedrückter Shift oder Strg Taste mehrere Elemente auf einmal ausgewählt werden (wie von anderen Mehrfachauswahlen her bekannt).

Bei Zuweisung von MultiSimple, entfällt die Notwendigkeit Shift oder Strg zu drücken. Zum aus- oder abwählen is einfach nur das jeweilige Element anzuklicken.

Wenn Sie One zuweisen, kann nur ein einzelnes Element ausgewählt werden.

Bei None sollten Sie vielleicht eher System.Windows.Forms.Label verwenden. Da gibt’s nichts auszuwählen.

Über die Methode Add() des Unterobjekts Items Ihres Auswahlfeldes, können Sie dann die gewünschten Listenelemente hinzufügen. Wie Sie sehen, können Sie dieses Hinzufügen beliebig oft wiederholen, in einer Schleife verpackt ausführen, oder auch gleich einen Array übergeben. Selbstverständlich können Sie auch jederzeit auf die Eigenschaft Items zugreifen und Ihren Wünschen entsprechend nachträglich verändern.

Jetzt wollen Sie natürlich gerne wissen, was ein Benutzer ausgewählt hat, wenn die Methode ShowDialog() des $Fenster beendet ist. Je nachdem, ob Sie nur ein Element über SelectionMode zur Auswahl zugelassen haben oder mehrere, finden Sie das Ergebnis in den Eigenschaften SelectedItem bzw. SelectedItems in Form der Beschriftung. Wenn Sie lieber positional das Ergebnis haben möchten können Sie auch die Eigenschaften SelectedIndex bzw. SelectedIndices abfragen.

DropDown Felder oder auch DropDown-Boxen genannt funktionieren eigentlich genauso, bis auf einen kleinen Unterschied. Erstellen Sie statt:

$Auswahlfeld=New-Object System.Windows.Forms.Listbox

einfach eine Combobox, statt einer Listbox:

$Auswahlfeld=New-Object System.Windows.Forms.Combobox

Entfernen Sie bitte auch die Zeile mit SelectionMode, da die Combobox immer nur eine einzelne Auswahl zulässt und es daher die Eigenschaft SelectionMode bei der Combobox nicht gibt.

Weiterhin kann man statt der Methode Items.Add() auch einfach über die Eigenschaft Datasource auf einen Array deuten, um die Elemente im Dropdownfeld hinzuzufügen:

$Auswahlfeld.Datasource=$IhrArrayAusStrings

4.5.11                     Kalender

Haben Sie jemals versucht von einem Benutzer eine Datumseingabe zu bekommen? Selbst wenn man eine Anweisung dazu schreibt, wie das Datum einzugeben ist, kommt meistens Müll dabei heraus. Ganz zu schweigen von der Logik, die Sie für die Fehlerbehandlung einbauen müssten. Das grafische Kalender Objekt wirkt hier wahre Wunder!

http://www.martinlehmann.de/wp/wp-content/uploads/2011/08/8.jpg

So, jetzt wird’s kompliziert:

Den grauen Teil benötigen Sie nur, wenn Sie die Bibliotheken noch nicht geladen und auch noch kein Fenster Objekt gebaut haben.

[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Drawing")

[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")

$Fenster=New-Object System.Windows.Forms.Form

$Kalender=New-Object System.Windows.Forms.MonthCalendar

$Fenster.Controls.Add($Kalender)

[void] $Fenster.ShowDialog()

In dieser super einfachen Form, können Sie das ausgewählte Datum entweder in der Eigenschaft SelectionStart oder SelectionEnd direkt als Systemdatum ablesen. Aber warum gibt es da ein Start und ein End? Weil der Benutzer bei gedrückter Shift Taste auch einen Zeitraum auswählen kann! Na, ausprobiert? Dann kommt jetzt die nächste Frage…“Warum kann ich nur 7 Tage am Stück markieren?“. Welche Zeitspanne in Tagen zulässig ist können Sie mit der Eigenschaft MaxSelectionCount steuern. Ein:

$Kalender.MaxSelectionCount=30

setzt also den möglichen Zeitraum auf 30 Tage. Ein

$Kalender | Get-Member

eröffnet natürlich wieder ganze Welten (Zeitraum, den der Kalender zur Auswahl anbietet, Wochennumern, u.v.a.m.). Ein bisschen gemein ist den ersten Tag der Woche z.B. auf Montag zu legen:

$Kalender.FirstDayOfWeek=[System.Windows.Forms.Day]::Monday

Wer mag, kann auch gerne einmal mit der sehr ähnlichen Klasse

$dateTimePicker=New-Object System.Windows.Forms.DateTimePicker

statt System.Windows.Forms.MonthCalendar experimentieren:

4.5.12                     Karteikartenreiter

Grunsätzlich wissen Sie an dieser Stelle bereits wie Karteikarten funktionieren um so etwas in der Art zu erstellen:

Das einzige was Ihnen fehlt sind die Namen der Klassen System.Windows.Forms.TabControl und System.Windows.Forms.TabPag um die Karteikarten Reiter Ansicht zu erstellen. Hier das Skript dazu:

Der graue Teil sollte Ihnen wieder aus den vorangegangnen Kapiteln bekannt sein.

[reflection.assembly]::loadwithpartialname("System.Windows.Forms") | Out-Null

[reflection.assembly]::loadwithpartialname("System.Drawing") | Out-Null

$Fenster=New-Object System.Windows.Forms.Form

$Fenster.Name="Karteikarten Reiter"

$Kartei=New-Object System.Windows.Forms.TabControl

$Kartei.Name="Karten Sammlung"

$Kartei.SelectedIndex=0

$Fenster.Controls.Add($Kartei)

$Reiter1=New-Object System.Windows.Forms.TabPage

$Reiter1.TabIndex=0

$Reiter1.Text="Karte Eins"

$Reiter1.Name="Eins"

$Kartei.Controls.Add($Reiter1)

$Text1=New-Object System.Windows.Forms.Label

$Text1.Name="Seite1"

$Text1.Text="Seite Eins"

$Reiter1.Controls.Add($Text1)

$Reiter2=New-Object System.Windows.Forms.TabPage

$Reiter2.TabIndex=1

$Reiter2.Text="Karte Zwei"

$Reiter2.Name="Zwei"

$Kartei.Controls.Add($Reiter2)

$Text2=New-Object System.Windows.Forms.Label

$Text2.Name="Seite2"

$Text2.Text="Seite Zwei"

$Reiter2.Controls.Add($Text2)

$Fenster.showdialog()

Die Klasse System.Windows.Forms.TabControl ist das Element das die einzelnen Reiter aufnimmt. Über die Eigenschaft SelectedIndex wird festgelegt welcher Reiter gerade aktiv ist. In diesem Beispiel werden 2 Reiter eingefügt. Der Erste ist 0, der Zweite ist 1, usw. Auch das System.Windows.Forms.TabControl, welches im Beispielskript in der Variable $Kartei hinterlegt ist, müssen Sie wie jedes andere Control dem Fenster hinzufügen. Die einzelnen Reiter erzeugen Sie mithilfe der Klasse System.Windows.Forms.TabPage. Diese fügen Sie nun nicht dem Fenster hinzu, sondern dem $Kartei Objekt. Wichtig ist hierbei, dass sich Ihre einzelnen Reiter über die Eigenschaft TabIndex unterscheiden (wie z.B. in der Zeile: $Reiter1.TabIndex=0).

4.5.13                     Gruppieren von Radiobuttons und Checkboxen

Radiobuttons und Checkboxen unterscheiden sich dadurch, dass bei einer Sammlung von RadioButtons immer nur einer ausgewält werden kann, während bei Checkboxen eine Mehrfach auswahl möglich ist. Um innerhalb eines Fensters zu bestimmen welche Knöpfe zusammen gehören (von denen jeweils nur einer ausgewählt werden darf) kann man auf die Klasse System.Windows.Forms.GroupBox zurückgreifen. Ohne diesen Zwischenschritt der Gruppierung gilt das komplette Fenster als Sammlung von der nur ein Knopf ausgewählt werden kann. Im nachfolgenden Beispiel finden Sie gleich beide Varianten dargestellt. Die Checkboxen sind direkt an das Fenster gehängt während die Radiobuttons über eine Gruppierung ins Fenster gehängt werden:

Der graue Teil sollte Ihnen wieder aus den vorangegangnen Kapiteln bekannt sein.

10.        [void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")

11.         [void] [System.Reflection.Assembly]::LoadWithPartialName("System.Drawing")

12.        $Fenster=New-Object System.Windows.Forms.Form

13.        $Fenster.Name=$Fenster.Text="CheckBoxen"

14.        $EGruppe=New-Object System.Windows.Forms.GroupBox

15.        $EGruppe.Name="EGruppe"

16.        $EGruppe.Text="Entweder/Oder"

17.        $EGruppe.Location=New-Object System.Drawing.Point(10,10)

18.        $EGruppe.Size =New-Object System.Drawing.Size(150,90)

19.        $Fenster.Controls.Add($EGruppe)

20.        $EinzelKnopf1=New-Object System.Windows.Forms.RadioButton

21.        $EinzelKnopf2=New-Object System.Windows.Forms.RadioButton

22.        $EinzelKnopf1.Name="Einzel1"

23.        $EinzelKnopf2.Name="Einzel2"

24.        $EinzelKnopf1.Text="Einzelknopf A"

25.        $EinzelKnopf2.Text="Einzelknopf B"

26.        $EinzelKnopf1.TabIndex=0

27.        $EinzelKnopf2.TabIndex=1

28.        $EinzelKnopf1.Location=New-Object System.Drawing.Point(10,20)

29.        $EinzelKnopf2.Location=New-Object System.Drawing.Point(10,50)

30.        $EinzelKnopf1.Size=$EinzelKnopf2.Size=New-Object System.Drawing.Size(115,30)

31.        $EinzelKnopf1,$EinzelKnopf2 | foreach {$EGruppe.Controls.Add($_)}

32.        $MehrfachKnopf1=New-Object System.Windows.Forms.CheckBox

33.        $MehrfachKnopf2=New-Object System.Windows.Forms.CheckBox

34.        $MehrfachKnopf1.Name="Mehrfach 1"

35.        $MehrfachKnopf2.Name="Mehrfach 2"

36.        $MehrfachKnopf1.Text="Mehrfach A"

37.        $MehrfachKnopf2.Text="Mehrfach B"

38.        $MehrfachKnopf1.TabIndex=0

39.        $MehrfachKnopf2.TabIndex=1

40.        $MehrfachKnopf1.Location=New-Object System.Drawing.Point(10,120)

41.        $MehrfachKnopf2.Location=New-Object System.Drawing.Point(10,150)

42.        $MehrfachKnopf1.Size=$MehrfachKnopf2.Size=New-Object System.Drawing.Size(100,20)

43.        $MehrfachKnopf1,$MehrfachKnopf2 | foreach {$Fenster.Controls.Add($_)}

44.        [void] $Fenster.ShowDialog()

45.        $Fenster.controls | ? {$_.gettype() -eq [System.Windows.Forms.CheckBox]} | select Name,Checked

46.        ($Fenster.controls | ? {$_.gettype() -eq [System.Windows.Forms.Groupbox]}).controls | select Name,Checked

Auch hier möchte ich mich bei der Erklärung wieder auf das Wesentliche beschränken. In den Zeilen 14-19 wird zunächst ähnlich den vorangegangenen Abschnitten das Gruppenobjekt erstellt und an das Fenster geklebt. In den Zeilen 20-30 werden zwei Einzelauswahlfelder (Radiobuttons) erzeugt. Der etwas andere Aufbau der Zeile 31 ist meiner Faulheit geschuldet. Statt die Knöpfe in je einer einzelnen Zeile der Gruppe hinzuzufügen (nicht dem Fenster!) hinzuzufügen, habe ich einfach beide Knopf Objekte per Pipe an eine Foreach Schleife übergeben. In $_ haben Sie dann also den jeweiligen Knopf innerhalb der Schleife, um Ihn der Gruppe hinzuzufügen. In den Zeilen 32-42 werden dann die Mehrfachauswahlfelder (Checkboxen) erstellt um dann in Zeile 43 direkt ins Fenster (statt an eine weitere Gruppe) gehängt. 44 stellt Ihr Fenster wie gewohnt dar. Die letzten beiden Zeilen sind der Auswertung gewidmet. In Zeil 45 kümmern Sie sich um die Auswertung der Mehrfachauswahl. $Fenster.controls enthält alle Objekte die Sie dem Fenster hinzugefügt haben, also auch das Gruppenobjekt. Durch den nach der Pipe folgenden Where-Object-Filter, der nur noch Objekte vom Typ System.Windows.Forms.CheckBox zuläßt, sind nach der 2. Pipe nur noch die Checkbox Objekte enthalten. Der Select sorgt schließlich dafür, von den Checkbox Objekten nur noch den Namen und den Status zu liefern. Zeile 46 ist ähnlich, doch etwas trickreicher. Die Radiobuttons sind ja keine Controls des Fensters, sonder der Gruppe, die wiederum selbst ein Control des Fensters ist. Ausgangsbasis sind also auch erst einmal wieder sämtliche Controls des Fensters, die durch einen Where-Object-Filter dieses Mal nach Gruppen Objekten sucht. Sie haben nur eins, also haben Sie darmit Ihr Gruppen Objekt. Um das Gruppenobjekt als solches anzusprechen ist der eben beschriebene Teil in runde Klammern verpackt. Von diesem Gruppen Objekt greifen Sie durch .controls entsprechend auf die an das Gruppen Objekt gehängten Controls (Ihre RadioButtons) zu. Die werfen Sie mittels der Pipe über den Zaun zu Select. Dann wird nach die Eigenschaft Name und Checked gefilter und schon haben Sie eine Info welcher Radiobutton gewählt wurde. Wenn Sie nur den Namen des ausgewählten Radiobuttons haben möchten (es kann ja immer nur einer aus der Gruppe angeklickt sein), dann können Sie die letzte Zeile auch so formulieren:

($Fenster.controls | ? {$_.gettype() -eq [System.Windows.Forms.Groupbox]}).controls | ? {$_.Checked} | select Name

4.5.14                     Dateien öffnen und speichern Dialoge

Statt sich die Mühe zu machen einen „Datei Öffnen“-Dialog in mühevoller Kleinarbeit selbst zusammen zu bauen, können Sie auch gerne auf die entsprechende .NET-Klasse zurückgreifen, die Ihnen einen entsprechenden Dialog genau wie in Excel oder Word zusammenstellt. Dazu brauchen Sie nicht einmal einen Fensterrahmen, denn den bringt die Klasse System.Windows.Forms.OpenFileDialog bereits mit. Es wird also auf jeden Fall ein weiteres Fenster geöffnet, genauso wie in Word oder Notepad auch. Dieses Skript stellt Ihnen den Dialog zur Verfügung:

Der graue Teil sollte Ihnen wieder aus den vorangegangnen Kapiteln bekannt sein.

[reflection.assembly]::loadwithpartialname("System.Windows.Forms") | Out-Null

$openFileDialog = New-Object System.Windows.Forms.OpenFileDialog

$openFileDialog.Title = "Eine Datei öffnen"

$openFileDialog.InitialDirectory = "c:"

[void] $openFileDialog.ShowDialog()

$openFileDialog.FileName

Ja, das ist auch schon alles, was unbedingt nötig ist. Mit der Eigenschaft Titel legen Sie die Überschrift fest und mit InitialDirectory, welches Verzeichnis als Ausgangspunkt angezeigt werden soll. Nach beenden des Dialogs finden Sie in der Eigenschaft Filename den kompletten Pfad zu Datei. Wenn Ihnen der Dateiname (ohne Pfad) reicht, können Sie auch die Eigenschaft SafeFileName verwenden (auch wenn es sich um den Datei öffnen Dialog handelt!).

Nachdem Sie den Pfad haben, können Sie die Datei mit Get-Content, Import-CSV oder auf eine andere gewünschte Art dann tatsächlich laden.

Wenn Sie die Eigenschaft Multiselect auf $true setzen, können auch mehrere Dateien auf einmal ausgewählt werden. Dann finden Sie die ausgewählten Dateien bzw. deren Pfade in Form eines Arrays in den Eigenschaften Filenames bzw. SafeFileNames.

Mit $openFileDialog.ShowHelp = $True können Sie die Hilfe verfügbar machen.

Selbstverständlich können Sie auch Filter auf die Anzeige nur bestimmter Dateitypen setzen:

$openFileDialog.Filter="Komma separierte Dateien|*.csv|Alle Arten|*.*"

Das würde dann so aussehen:

Es muss also immer abwechselnd durch | getrennt „Anzuzeigender Text|Filter|nächster Text|für nächsten Filter“ aufgebaut werden. Welcher Filter vorgegeben wird, legen Sie mit der Eigenschaft FilterIndex fest.

Da der Dialog einfach nur einen Namen und den Pfad festlegt und das Laden, bzw. Speichern durch Ihren Programmcode erfolgen muss, ist es eigenlich egal ob Sie die Klasse System.Windows.Forms.OpenFileDialog oder System.Windows.Forms.SaveFileDialog verwenden. Der kleine aber feine Unterschied liegt lediglich darin, ob als Knopf Öffnen (im Screenshot Open) oder Speichern angezeigt wird. Beim Speichern sind dann vielleicht noch die beiden zusätzlichen Eigenschaften CheckFileExists und CheckPathExists interessant. Diese überprüfen automatisch, ob der Pfad bzw. der Name exisistieren, wie die Benamung der Eigenschaften auch schon vermuten läßt.

4.5.15                     Explorer ähnliche Ansichten

Das ist wohl eines der komplexesten Themen in der GUI programmierung. Als Beispiel soll hier einmal eine Auflistung aller OUs Ihres Active Directory stattfinden. Selbstverständlich kann das Beispiel genauso gut für Dateibäume, Navigation in der Registry, oder beliebige andere hierarchische Strukturen angepasst werden.

Dazu sind folgende Zeilen nötig:

10.        Import-Module ActiveDirectory

11.        $Domain=Get-ADDomain

12.        $LDAPDomainName=$Domain | select -expandproperty DistinguishedName

13.        $PDCEmulator=$Domain | select -expandproperty PDCEmulator

14.        $DNs=Get-ADOrganizationalUnit -filter * -server $PDCEmulator | select @{n="DistinguishedName";e={$_.DistinguishedName.tostring()}} | select @{n="Level";e={$_.DistinguishedName.split(",").count}},DistinguishedName | sort Level

15.        [reflection.assembly]::loadwithpartialname("System.Windows.Forms") | Out-Null

16.        [reflection.assembly]::loadwithpartialname("System.Drawing") | Out-Null

17.        $Fenster=New-Object System.Windows.Forms.Form

18.        $Fenster.Size=new-object System.Drawing.Size (420,445)

19.        $Fenster.Text="OU-Viewer"

20.        $Fenster.Name="OUView"

21.        $Fenster.AutoScroll=$true

22.        $Wurzel=New-Object System.Windows.Forms.TreeView

23.        $Wurzel.Size=new-object System.Drawing.Size (400,400)

24.        $Wurzel.Name="Wurzel"

25.        $Stamm=New-Object System.Windows.Forms.TreeNode

26.        $Stamm.Text=$LDAPDomainName

27.        $Stamm.Name=$LDAPDomainName

28.        [void] $Wurzel.Nodes.Add($Stamm)

29.        $Gesamtbild=[System.Windows.Forms.TreeNodeCollection] $Wurzel.Nodes

30.        foreach ($DN in $DNs) {

31.         $Zweig=New-Object System.Windows.Forms.TreeNode

32.         $Zweig.Text=$DN.DistinguishedName.split(",")[0]

33.         $Zweig.Name=$DN.DistinguishedName

34.         $ParentName=$DN.DistinguishedName.substring($DN.DistinguishedName.indexof(",")+1)

35.         $ParentObject=$Gesamtbild.find($ParentName,$True)[0]

36.         [void] $ParentObject.Nodes.Add($Zweig)

37.        }

38.        $Fenster.Controls.Add($Wurzel)

39.        [void] $Fenster.ShowDialog()

40.        $Wurzel.SelectedNode

Zeile Nr. 10 besorgt Ihnen die Active Directory Cmdlets. In den nächsten 3 Zeilen stellt das Script den LDAP-Pfad Ihrer Domäne fest und welcher Domänen Controller die Rolle des PDC-Emulators hat.

In Zeile 14 werden in der Variablen $DNs, die LDAP-Pfade aller OUs Ihrer Domäne hinterlegt und die Ebene (Level) auf der Sie liegen. Um die Struktur entsprechend von der Domänen Basis aus aufbauen zu können wurden die Objekte dabei gleich nach der entsprechenden Ebene sortiert.

Die Zeilen 15 und 16 laden wieder die Assemblies für die Fenstergestaltung.

Die Zeilen 17-21 erstellen den Fensterrahmen.

Spannend wird es ab Zeile 22. Hier wird nun das Wurzel Objekt mit Hilfe der Klasse System.Windows.Forms.TreeView erstellt und in der Variablen $Wurzel hinterlegt. Die Zeilen 23-24 bedürfen wohl keiner Erläuterung.

Die oberste Stelle in der Hierarchie wird über das Stamm Objekt, das in Zeile 25 mit Hilfe der Klasse System.Windows.Forms.TreeNode erstellt wird, definiert und in der Variablen $Stamm hinterlegt. In den folgenden beiden Zeilen wird in den Eigenschaften Text und Name jeweils der LDAP-Name der Domäne hinterlegt.

In Zeile 28 wird der Stamm auf die Wurzel aufgepfropft.

Leider kennt die Klasse System.Windows.Forms.TreeNode keine Möglichkeit in untergeordneten Knoten zu suchen. Um den kompletten Baum, von der Wurzel ab, komplett durchsuchen zu können, brauchen Sie ein zusätzliches Objekt der Klasse System.Windows.Forms.TreeNodeCollection. Dies wird in Zeile 29 erstellt.

Um die ganzen OUs als Unterpunkte (bzw. Knoten = Nodes) in den Wurzel einzubinden eignet sich eine Schleife über alle OU-Objeke aus der Variable $DNs. Die Schleife beginnt in Zeile 30 und endet in Zeile 37.Innerhalb der Schleife werden die einzelnen OUs jeweils unter dem Namen $DN geführt

In Zeile 31 wird zunächst ein weiteres Knoten Objekt erstellt und in der Variablen $Zweig hinterlegt.

$DN besteht aus 2 Eigenschaften: DistinguishedName und Level. In der Eigenschaft Text von $Zweig soll nur der Name der OU angezeigt werden. Daher wird mittels der Methode split(",")[0] der Name der OU vom Rest des LDAP-Pfades abgetrennt. In der Eigenschaft Name des Zweig Objektes soll jedoch der komplette LDAP-Pfad der OU hinterlegt sein. Dies passiert in den Zeilen 32 und 33.

In Zeile 34 wird aus dem kompletten LDAP-Pfad des aktuellen Objekts der Name der OU selbst abgeschnitten. Damit haben Sie dann den kompletten LDAP-Pfad der übergeordneten OU in $ParentName hinterlegt.

Nun können Sie in Zeile 35 das benötigte Elternobjekt dynamisch erstellen indem Sie den Stamm von der Wurzel aus nach dem LDAP-Pfad, den Sie der Eigenschaft Name zugewiesen haben, durchsuchen. Die Methode find($ParentName,$True)[0] hat, glaube ich, noch ein paar zusätzliche Erklärungen nötig. $ParentName ist klar. $True sagt aus, dass der komplette Stamm durchsucht wird. Übergeben Sie hier $False wird nur in der aktuellen Ebene nach Unterobjekten gesucht. Also nur was direkt am Stamm angebunden ist. Die Anweisung [0] ist ebenfalls nötig, um ein einzelnes Objekt zu bekommen. Das Gemeine ist nämlich, dass Sie immer ein Objekt vom Typ Array erhalten, auch wenn es nur ein einzelnes Objekt ist und ein Array hat nun einmal keine Methode Nodes.Add(). Die brauchen Sie aber in der nachfolgenden Zeile 36. Dort wird dann nämlich dem dynamisch erzeugten Elternobjekt das neue Zweig Objekt als Unterpunkt hinzugefügt.

Ihr fertiger „Baum“ kann nun auch in Zeile 38 ins Fenster gehängt werden. Zeile 39 ist wohl klar und in Zeile 40 wird die Eigenschaft SelectedNode von $Wurzel abgefragt, damit Sie wissen welcher Knoten angeklickt wurde, bevor das Fenster geschlossen wurde.

4.5.15.1                Icons einbauen

Vielleicht hätten Sie auch gerne noch ein paar Symbole für unterschiedliche AD-Objekte, wenn Sie nicht nur OUs, sondern auch Computer, Benutzer und Gruppen anzeigen lassen möchten. Dazu benötigen Sie die Klasse System.Windows.Forms.ImageList.

Zunächst einmal müssen Sie sich Icon-Dateien besorgen. Diese haben die Endung *.ico. Dr. Google wird Ihnen dabei ganz schnell weiter helfen.

Hinweis: Durch die Angabe filetype:ico bei Google bekommen Sie nur Seiten angezeigt, die auch *.ico Dateien zum Download anbieten. Genausogut könnten Sie mit filetyp:iso auch nach DVD-Images suchen, oder mit filetyp:exe nach ausführbaren Programmen.

Sobald Sie ein paar Icon-Dateien heruntergeladen haben kann es weiter gehen. Diesen Code Abschnitt

$ImageList=New-Object System.Windows.Forms.ImageList

$Image = [System.Drawing.Image]::FromFile("C:\Icons\group.ico")

$ImageList.Images.Add("Group",$Image)

$Image = [System.Drawing.Image]::FromFile("C:\Icons\folder.ico")

$ImageList.Images.Add("Folder",$Image)

$Wurzel.ImageList=$ImageList

Diese Zeilen sollten Sie im vorangegangenen Skript zwischen Zeile 22 und 23 einfügen. Wichtig dabei ist, dass die den Pfad zu Ihren heruntergeladenen *.ico Dateien vollständig angeben, selbst wenn das Skript im selben Verzeichnis liegt wie die *.ico-Dateien.

Die Bilder, können über eine Indexnummer angegeben werden. Das erste Bild, das Sie der $ImageList Variablen hinzugefügt haben, hat die Nummer 0, das zweite Bild Nummer 1, usw.

In Zeile 31 im vorangegangnen Skript wurde die Variable $Zweig definiert. Danach können Sie mit

$Zweig.ImageIndex=0

das Gruppensymbol zuordnen, oder mit dem Wert 1 entsprechend das Ordnersymbol. Selbstverständlich, können Sie das auch mit if oder switch Anweisung entsprechend zuordnen lassen:

switch ($DN.ObjectClass) {

    "organizationalUnit" {$Zweig.ImageIndex=0}

    "user" {$Zweig.ImageIndex=1}

    "group" {$Zweig.ImageIndex=2}

    "computer" {$Zweig.ImageIndex=3}

    "printer" {$Zweig.ImageIndex=4}

    default {$Zweig.ImageIndex=6}

}

4.5.16                     Durch Events andere Controls im Fenster beeinflussen

In diesem Abschnitt ist anhand eines DropDown- und eines Textfeldes erläutert wie sich Controls gegenseitig beeinflussen können.

Der graue Teil sollte Ihnen wieder aus den vorangegangnen Kapiteln bekannt sein.

10.        [void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")

11.        [void] [System.Reflection.Assembly]::LoadWithPartialName("System.Drawing")

12.        $Fenster = New-Object System.Windows.Forms.Form

13.        $Fenster.Size = New-Object System.Drawing.Size(200,100)

14.        $Fenster.Text = "Interaktion"

15.        $Text = New-Object System.Windows.Forms.Label

16.        $Text.Location = New-Object System.Drawing.Size(100,10)

17.        $Text.Size = New-Object System.Drawing.Size(50,20)

18.        $Text.Text = "Nichts ausgewählt"

19.        $Fenster.Controls.Add($Text)

20.        $Wahl = New-Object System.Windows.Forms.Combobox

21.        $Wahl.Location = New-Object System.Drawing.Size(10,10)

22.        $Wahl.Size = New-Object System.Drawing.Size(70,20)

23.        "Eins","Zwei","Drei" | foreach {

24.         [void] $Wahl.Items.Add("$_")

25.        }

26.        $Wahl.Add_SelectedValueChanged({

27.         $Text.Text=$Wahl.SelectedItem

28.        })

29.        $Fenster.Controls.Add($Wahl)

30.        $Fenster.ShowDialog()

In den Zeilen 23-25 wird eine Schleife erzeugt, um die Werte „Eins“, „Zwei“ und „Drei“, als auszuwählenden Text, dem Dropdown-Feld (enthalten in $Wahl) in Zeile 24 hinzuzufügen. Der Clou liegt nun in Zeile 26. Wenn Sie auf $Wahl ein Get-Member absetzen, werden Sie das Event SelectedValueChanged finden. Natürlich können Sie auch bei den ganzen anderen Ereignissen etwas auslösen lassen. Durch ein vorangestelltes Add_ vor dem Eventnamen können Sie Ihr Control, auf Auslösen des Events hin, überwachen lassen. Trifft das Ereignis ein, wird der Code in den ({IhrCode}) Klammern ausgeführt. Im Code der Zeile 27 wird der Eigenschaft Text des Label-Objektes $Text das ausgewähle Item der Dropdownbox (in der Eigenschaft $Wahl.SelectedItem hinterlegt) zugewiesen. Wie die jeweiligen Eigenschaften und Events bei anderen GUI-Elementen heißen, lässt sich über Get-Member leicht herausfinden.

4.5.17                     Fensterinhalt mittels Timer aktualisieren

Die meisten Aktionen geschehen, wenn irgendetwas angeklickt wird. Aber vielleicht wollen Sie einige Angaben im Fenster automatisch aktualisieren lassen, ohne dass etwas angeklickt wird, wie z.B. die aktuelle Uhrzeit, oder eine Prozessliste ähnlich wie im Taskmanager. Dazu können Sie auf die System.Windows.Forms.Timer Klasse zurückgreifen:

Der graue Teil sollte Ihnen wieder aus den vorangegangnen Kapiteln bekannt sein.

[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")

$Fenster=New-Object System.Windows.Forms.Form

$Fenster.Text="Uhr"

$Beschriftung=New-Object System.Windows.Forms.Label

$Beschriftung.Text=$Beschriftung.Name="Zeitanzeige"

$Fenster.Controls.Add($Beschriftung)

$timer = New-Object System.Windows.Forms.Timer

$timer.Interval = 1000

$timer.add_tick({

 ($Fenster.Controls | ? {$_.name -like "Zeitanzeige"}).Text="{0:HH:mm:ss}" -f (get-date)

})

$timer.Enabled = $true

$Fenster.ShowDialog()

Zunächst generieren Sie, wie üblich ein Objekt. Hier mit der Klasse System.Windows.Forms.Timer. Über die Eigenschaft Interval des Timers legen Sie fest wie oft/schnell der Timer ticken soll. Die Angabe erfolgt dabei in Millisekunden (1000 Millisekunden=1 Sekunde). Durch hinzufügen des Tick Ereignisses add_tick({}) zum Timer Objekt, können Sie innnerhalb der Klammern festlegen was geschehen soll. Hier wird von den Controls des Fenster Objektes das Control mit der Namenseigenschaft Zeitanzeige angerufen und die Eigenschaft Text mit der aktuellen Uhrzeit belegt. Letztlich dürfen Sie nicht vergessen, den Timer auch über die Eigenschaft Enabled scharf zu schalten und natürlich in der letzten Zeile das Fenster auch darstellen zu lassen. Das Interessante dabei ist, dass Sie das Timer Objekt nicht als Control ans Fenster nageln müssen.

Je mehr und je aufwändigere Aktionen in das Tick Ereignis hineinschreiben, umso länger sollten Sie das Zeitinterval setzen. Vergessen Sie nicht, dass Ihr PowerShell Skript nicht kompiliert ist, sondern interpretiert wird. Sie werden es auf jeden Fall merken, wenn es zu viel ist. Dann wird Ihr Fenster nämlich nach relativ kurzer Zeit sehr komisch aussehen, oder gar nicht mehr auf Änderungen reagieren, weil dann die nächste Aktulisierung bereits gestartet wird, wenn die erste noch gar nicht zu Ende ist.

4.5.18                     Mit XAML erstellte GUIs einbetten

Vielleicht kennen Sie Tools wie z.B. Visual Studio, mit denen Sie grafische Oberflächen „malen“ können, statt jedes Element einzeln zu skripten. Zunächst ein kleines Beispielskript für die Grundlagen:

10.        Add-Type -AssemblyName presentationframework

11.        [xml]$XAML=@'

12.         <Window

13.          xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

14.          xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

15.          Title="MainWindow" Height="100" Width="20">

16.          <StackPanel>

17.           <TextBox Name="text"/>

18.           <Button Name="buttonShowText" Content="Zeige Text"/>

19.          </StackPanel>

20.         </Window>

21.        '@

22.        $XAMLLeser=(New-Object System.Xml.XmlNodeReader $xaml)

23.        $Fenster=[Windows.Markup.XamlReader]::Load( $XAMLLeser )

24.        $textbox=$Fenster.FindName("text")

25.        $button=$Fenster.FindName("buttonShowText")

26.        $button.add_Click({ $textbox.Text="Text"})

27.        $Fenster.ShowDialog() | out-null

In Zeile 10 fügen Sie die Klasse zur Unterstützung des Presentationframework hinzu. In den Zeilen 11-21 folgt der XAML Inhalt, der die Fenstergestaltung beschreibt. Dies kann natürlich auch über ein Get-Content in einer separaten Datei stehen. Dazu später mehr. In Zeile 22 erstellen Sie dann das Objekt, dass die XAML Fensterbeschreibung aufnehmen soll. In Zeile 23 wird dann das eigentlich Fenster-Objekt, wie Sie es aus den vorangegagnenen Abschnitten kennen aus dem XAML-Code erstellt. Um das Fenster mit etwas Leben zu erfüllen, müssen Sie nun eine Verknüpfung zwischen den XAML Elementen und Powershell-Variablen herstellen. Dies geschieht in den Zeilen 24 und 25. Im XAML-Code steht in Zeile 17 die Benamung für ein Textelement text. Über die Methode FindName in Zeile 24 suchen Sie nach einem Element im XAML Code, dass den Namen text trägt und weisen es der Variablen $textbox zu. Somit können Sie ab dieser Zeile, auf das Textelement mittels $textbox zugreifen und es beeinflussen (vergessen Sie nicht die wunderbare Welt von Get-Member !). In Zeile 25 geschieht ähnliches mit den Knopfelement buttonShowText. Dementsprechend können Sie in Zeile 26 dem Knopf auch in Form von $button das Click Event hinzufügen. Beim Click Ereignis/Event wird dann die Textbox mit einem Inhalt ausgestattet. Zeile 27 muss ich sicher nicht mehr erläutern.

Wenn Sie mit Visual Studio eine XAML-Datei erstellen, können Sie diese auch direkt in PowerShell einlesen, statt den XAML-Code per Cut&Paste direkt ins PowerShell Skript einzufügen. Dazu verwenden Sie einfach das Get-Content Cmdlet:

Add-Type -AssemblyName presentationframework

[xml]$XAML=Get-Content MainWindow.xaml

$reader=(New-Object System.Xml.XmlNodeReader $xaml)

$window=[Windows.Markup.XamlReader]::Load( $reader )

$window.ShowDialog() | out-null

Die Datei MainWindow.xaml könnte von Visual Studio generiert so aussehen:

<Window x:Class="WpfApplication1.MainWindow"

        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

        Title="MainWindow" Height="350" Width="525">

    <Grid>

        <CheckBox Content="CheckBox" HorizontalAlignment="Left" Margin="57,43,0,0" VerticalAlignment="Top"/>

        <Button Content="Button" HorizontalAlignment="Left" Margin="254,188,0,0" VerticalAlignment="Top" Width="75"/>

    </Grid>

</Window>

Wenn Sie nun das PowerShellSkript starten, werden Sie allerdings eine Fehlermeldung bekommen. Die Lösung ist recht einfach: Löschen Sie die in fett gedruckte Angabe:

x:Class="WpfApplication1.MainWindow"

Danach sollten Sie das Programm ohne Fehlermeldung starten können und ein Fenster mit einer Checkbox und einem Knopf erhalten.

4.5.19                     GUIHelper-Modul

Das GUIHelper-Modul soll hier als Beispiel für die Erstellung eigener Module dienen, weniger dazu GUI-Elemente zu erklären. Dann darüber haben Sie in den vorangegangenen Kapiteln schon weitaus mehr gelert. Das GUIHelper Modul setzt sich aus mehreren einzelnen Skripten zusammen:

Installationsanweisung.txt, guidemo.ps1, GuiHelper.psm1, NewWindow.ps1, ShowWindow.ps1, CloseWindow.ps1, GetDropDown.ps1, GetInputbox.ps1, NewButton.ps1, NewCalendar.ps1, NewDropDown.ps1, NewInputBox.ps1, NewTextBox.ps1, SetTextBox.ps1

Die Installationsanweisung.txt gibt Auskunft was zu tun ist um das Modul in der PowerShell bereit zu stellen.

Guidemo.ps1 ist eine in PowerShell ausführbare Datei, die als Beispiel dienen soll, was man mit dem Modul so alles anstellen kann.

Beide eben genannten Dateien sind nicht im GUIHelper Modul Ordner notwendig und können auch gelöscht werden.

GUIHelper.psm1 ist die eigentliche Modul Datei. Diese dient in erster Linie dazu alle in der Auflistung oben nachfolgend genannten *.ps1 Dateien in das Modul einzubinden. In den nachfolgenden Abschnitten werden die Modul-Datei, als auch die einzelnen eingebundenen *.ps1 Skripte näher erläutert.

Die jeweils aktuellste Version des GUIHelper Moduls finden Sie auch meiner Website am Ende des Artikels unter: http://www.martinlehmann.de/wp/download/powershell-gui-programmierung-fur-dummies-step-by-step/

4.5.19.1                GuiHelper.psm1

Die Modul Datei ist schnell erklärt. Die Kommentar Zeilen mit führendem # Zeichen lasse ich an dieser fortgeschrittenen Stelle gleich unter den Tisch fallen - ich will Sie ja nicht langweilen. Die Zeilen 13 und 14 laden die GUI-Unterstützung zentral für das gesamte Modul und somit alle in das Modul eingebundenen *.ps1 Dateien. Diese benötigen alle gemeinsam die System.Drawing und die System.Windows.Forms Bibliotheken. Zeile 18 holt sich aus der vom System vorgegebenen Variable $Myinvocation, den Pfad zu Module Datei GuiHelper.psm1, denn es könnte ja sein, dass jemand das Modul auch im Benutzerprofil, statt im System Ordner für PowerShell ablegt. Um die anderen Dateien nachzuladen, die im selben Verzeichnis liegen, scheiden Sie in Zeile 19 einfach den Dateinamen vom kompletten Pfad ab und hinterlegen somit in $ModulePath den Pfad zu allen anderen benötigten *.ps1 Dateien. Zeile 20 listet dann vom Modulverzeichnis alle *.ps1 Dateien und übergibt es mit der Pipe an einen weiteren Where-Object Filter (?), der dafür sorgt, das nicht ausversehen, die Demo Datei mit eingebunden wird. Dann wird mit der Foreach-Schleife jede der gefundenen *.ps1 Dateien an dieser Stelle mittels Dot-Sourcing (Aufruf durch . $_.Fullname) ins psm1-Skript eingebunden. Das hat den netten Vorteil, dass das Modul schnell um zusätzliche *.ps1 Skripte mit weiteren Funktionen erweitert werden kann. Der Rest von Zeil 24 bis 37 enthält einfach nur Text, der beim Laden des Moduls als Info, dank der Pipe an oh, auf dem Bildschirm ausgegeben wird. Auf eine Manifest Datei wurde der Übersicht halber verzichtet.

10.        # Powershell GUIHelper Version 0.94 von Martin Lehmann

11.        # .NET GUI-Unterstützung einbinden

12.         

13.        [void] [System.Reflection.Assembly]::LoadWithPartialName("System.Drawing")

14.        [void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")

15.         

16.        # Dann die einzelnen Funktionen aus den beliebig erweiterbaren GUI-Scripten einbauen

17.         

18.        $ModuleFile=$Myinvocation.MyCommand.Definition

19.        $ModulePath=$ModuleFile.substring(0,$ModuleFile.lastindexof("\"))

20.        Get-ChildItem $ModulePath -filter *.ps1 | ? {$_.name -notlike "guidemo.ps1"} | Foreach {. $_.FullName}

21.         

22.        # Infotext ausgeben

23.         

24.        'GUIHelper Modul von Martin Lehmann erfolgreich geladen!

25.        Folgende Befehle werden unterstützt:

26.        New-GUIWindow, New-GUIButton, New-GUIText, New-GUIInputBox, New-GUICalendar, Show-GUIWindow

27.        Selbstverständlich können Sie die Befehle auch mittels help Befehlname näher erforschen.

28.         

29.        Ein Beispiel:

30.         $Fenster=New-GUIWindow Test

31.         $ChangeButtonAction={Set-GUITextBox $Fenster "Textfeld2" (Get-GUIInputbox $Fenster "Inputbox1").content 10 70 280 20}

32.         New-GUIButton $Fenster "Change" "Change" 200 200 75 23 -Action $ChangeButtonAction

33.         New-GUIButton $Fenster "Close" "Close" 275 200 75 23 -Action {Close-GUIWindow $Fenster}

34.         New-GUITextBox $Fenster "Textfeld1" "Bitte geben Sie hier den Text ein:" 10 20 280 20

35.         New-GUIInputBox $Fenster "Inputbox1" "Vorgabe1" 10 40 260 20

36.         New-GUITextBox $Fenster "Textfeld2" "Ausgabe" 10 70 280 20

37.         Show-GUIWindow $Fenster' | oh

4.5.19.2                NewWindow.ps1

Dieses Skript stellt das New-GUIWindow Cmdlet zur Verfügung. Dies erzeugt das eigentliche Fenster Objekt, dessen Rückgabewert für alle weiteren Aktionen benötigt wird. Die Zeilen 10 bis 34 enthalten die Informationen für das Get-Help Cmdlet. Details dazu finden Sie im Abschnitt Module selbst entwickeln. Zeile 35 und 50 enthalten die Begrenzer und den Namen der Funktion bzw. des Cmdlets. Die Zeilen 36 bis 43 definieren die Schalter und die möglichen Parameterübergaben an das New‑GUIWindow Cmdlet. Sollten irgendwelche Parameter fehlen, sind schon Standardwerte vordefiniert. In Zeile 44 wird das Fenster Objekt erzeugt. Hier wird nun zur Erzeugung des Objekts auf die in der GUIHelper.psm1 Datei geladene Objektklasse System.Windows.Forms.Form (aus der Bibliothek System.Windows.Forms) verwiesen. Das Objekt wir dann in der Variablen $Window hinterlegt. Achtung! Die Variable $Windows ist ausserhalb der Funktion unbekannt. Daher wird diese in Zeile 49 an das aufrufende Programm übergeben. Damit erklärt sich dann auch warum beim Aufruf des New-GUIWindow Cmdlet immer ein $Variable= vorangestellt wird. Darin wird dann nämlich das übergebene Fenster Objekt geführt. Die Eigenschaft Text in Zeile 45 definiert (je nach übergebenem Parameter) die Inschrift der Titelleiste. Die in der nächsten Zeile folgende Eigenschaft Name ist interessant, um in größeren FensterSammlungen, die miteinander in Beziehung stehen, dieses Objekt mit einer Suche nach dem Namen wieder ausfindig zu machen, denn die Variablen funktionieren ja leider nicht so ohne weiteres funktionsübergreifend. Die Eigenschaft Location in Zeile 47 bestimmt wo die linke (x) obere (y) Ecke des Fensters auf dem Bildschirm positioniert werden soll. In Zeile 48 wird, mittels der Eigenschaft Size, von dem eben genannten Punkt aus, die Fensterbreite (Width) und –höhe (Height) definiert.

10.        <#

11.        .SYNOPSIS

12.         Legt ein Fenster an.

13.        .DESCRIPTION

14.         Dieses Cmdlet erzeugt ein Fenster-Objekt. Dieses kann mithilfe des Cmdlets Show-GUI angezeigt werden.

15.        .PARAMETER Name

16.         Der Parameter Name muss für interne Identifikation übergeben werden.

17.        .PARAMETER Title

18.         Im Parameter Title können Sie den Text für die Titelleiste festlegen. Wird dieser Parameter nicht angegeben wird die Titelleiste mit den Informationen aus dem Parameter Name beschriftet.

19.        .PARAMETER X

20.         Der Parameter X gibt an wieviele Pixel der Knopf vom linken Fensterrand aus positioniert werden soll.

21.        .PARAMETER Y

22.         Der Parameter Y gibt an wieviele Pixel der Knopf vom oberen Fensterrand aus positioniert werden soll.

23.        .PARAMETER Width

24.         Width gibt an wieviele Pixel breit (von X aus gezählt) der Knopf sein soll.

25.        .PARAMETER Height

26.         Width gibt an wieviele Pixel hoch (von Y aus gezählt) der Knopf sein soll.

27.        .EXAMPLE

28.         $Fenster=New-Window -Name Fenster1 -X 100 -Y 200 -W 400 -H 300

29.         Show-GUIWindow $Fenster

30.         Erstellt ein Fenster-Objekt namens Fenster1 mit einer Breite von 400 Pixel und einer Höhe von 300 Pixel und legt dies in der Variablen $Fenster ab.

31.         Durch das nachfolgende Cmdlet GUI-Show $Fenster, wird dieses Fenster-Objekt sichtbar gemacht.

32.        .LINK

33.         http://www.martinlehmann.de/wp/windows/powershell-gui-programmierung-fur-dummies-step-by-step/

34.        #>

35.        function New-GUIWindow {

36.         param (

37.          [string]$Name="Fenster",

38.          [int]$X=100,

39.          [int]$Y=200,

40.          [int]$Width=400,

41.          [int]$Height=300,

42.          [string]$Title=$Name

43.         )

44.         $Window = New-Object System.Windows.Forms.Form

45.         $Window.Text = $Title

46.         $Window.Name = $Name

47.         $Window.Location = New-Object System.Drawing.Point($X,$Y)

48.         $Window.Size = New-Object System.Drawing.Size($Width,$Height)

49.         return $Window

50.        }

4.5.19.3                ShowWindow.ps1

Show-GUIWindow ist für die Anzeige des fertig zusammen gesetzten Fensters verantwortlich. Die Zeilen 10 bis 24 sind wieder die Kommentare für das Get-Help Cmdlet. Als einzigen Parameter nimmt dieses Cmdlet in Zeile 27 die Variable des Fenster-Objektes entgegen. Zeile 29 zeigt letztlich das Fenster an, und wartet bis das Fenster (durch eines der anderen GUI-Cmdlets oder druch Klicken auf das Fensterkreuz, rechtsoben) geschlossen wird. Zeile 30 definiert einen Array in der Variablen $Feedback. In diesen Array werden die Rückgabewerte des Fensters nachfolgend eingelesen. Zeile 31 ruft alle Controls (Knöpfe, Eingabefelder, DropDown-Boxen, etc.) des Fensters ab und startet eine Schleife und alle Controls auszuwerten. In Zeile 32 wird ein Objekt zur Aufnahme der Werte erstellt und in der Variablen $Control abgelegt. In Zeile 33 wird dann der Name des aktuellen Controls der Schleife in die Eigenschaft Name des neu geschaffenen Objekts $Control übertragen. In Zeile 34 wird geprüft, ob es sich beim aktuell in der Schleife bearbeiteten Control, um ein Eingabefeld handelt. Achtung!!! Die Eingabefelder sind im .NET als Textbox benannt. Trifft das zu, wird in Zeile 35 der Eigenschaft ControlType des in der aktuellen $Control Variable erstellten Objekts der Wert Inputbox zugewiesen. In Zeile 36 wird geprüft, ob es sich um einen anzuzeigenden Text handelt. Im .NET wird dies als Label bezeichnet. Wenn dem so ist, wird der Eigenschaft ControlType der Wert Textbox zugewiesen. Wenn es sich in Zeile 38 um eine ComboBox (.NET-Bezeichnung) handelt, tituliere ich es hier in Zeile 39 als DropDown. In allen anderen Fällen (Zeile 40) wird einfach in Zeile 41 die tatsächliche .NET-Bezeichnung in die Eigenschaft ControlType übertragen. In Zeil 43 wird geprüft, ob die Eigenschaft ControlType des $Control Objekts den Wert Button enthält. Wenn ja, wird in Zeile 44 in der Eigenschaft Content des $Control Objekts die Eigenschaft Tag des Knopfes übertragen. Bei den Werten TextBox, InputBox oder Label der Eigenschaft ControlType in Zeile 45 wird in Zeile 46 die Eigenschaft Text des Controls in die Eigenschaft Content des $Control Objekts geschrieben. Handel es sich um ein MonthCalendar Control in Zeil 47 wird in Zeil 48 die Eigenschaft SelectionStart in die Eigenschaft Content übernommen. Bei 49-50 wird bei einer DropDown die Eigenschaft SelectedItem übernommen. In Zeile 51-52 wird in allen anderen Fällen der Text Unsupported Control zurück geliefert. Vielleicht erinnern Sie sich noch an den in Zeile 30 definierten Array $Feedback? Dieser wird nun in Zeile 54 um das soeben zusammengestellte $Control Objekt ergänzt. Sind zu allen Controls die entsprechenden $Control Objekte erstellt und in den $Feedback Array eingetragen, ist die Schleife über alle Controls des Fensters in Zeile 55 beendet. Somit kann dann die Funktion bzw. das Show-GUIWindow Cmdlet den Array $Feedback als Rückgabewert des Fensters an das aufrufende Programm/Skript in Zeile 56 übergeben.

10.        <#

11.        .SYNOPSIS

12.         Zeigt ein Fenster-Objekt an.

13.        .DESCRIPTION

14.         Zeigt ein zuvor erstelltes Fenster-Objekt an. Wird das Fenster geschlossen, liefert Show-GUIWindow Rückgabewerte aus dem Fenster.

15.        .PARAMETER Window

16.         Dem Parameter Window muss die Variable des Fenster-Objektes übergeben werden.

17.        .EXAMPLE

18.         $Fenster=New-GUIWindow -Name Fenster1 -X 100 -Y 200 -W 400 -H 300

19.         Show-GUIWindow $Fenster

20.         Erstellt ein Fenster-Objekt namens Fenster1 mit einer Breite von 400 Pixel und einer Höhe von 300 Pixel und legt dies in der Variablen $Fenster ab.

21.         Durch das nachfolgende Cmdlet Show-GUIWindow $Fenster, wird dieses Fenster-Objekt sichtbar gemacht. Enthält das Fenster keine weiteren Elemente, kann das Fenster mit dem Kreuzsymbol in der rechten, oberen Ecke geschlossen werden. Danach gibt das Cmdlet die Inhalte des Fensters zurück und die Skriptverarbeitung wird vorgesetzt.

22.        .LINK

23.         http://www.martinlehmann.de/wp/windows/powershell-gui-programmierung-fur-dummies-step-by-step/

24.        #>

25.        function Show-GUIWindow {

26.         param (

27.          [System.Windows.Forms.Form]$Window

28.         )

29.         [void] $Window.ShowDialog()

30.         $Feedback=@()

31.         $Window.Controls | foreach {

32.          $Control=New-Object PSCustomObject

33.          Add-Member -InputObject $Control -MemberType NoteProperty -Name Name -Value $_.Name

34.          if (($_.gettype().name) -eq "TextBox") {

35.           Add-Member -InputObject $Control -MemberType NoteProperty -Name ControlType -Value "InputBox"

36.          } elseif (($_.gettype().name) -eq "Label") {

37.           Add-Member -InputObject $Control -MemberType NoteProperty -Name ControlType -Value "TextBox"

38.          } elseif (($_.gettype().name) -eq "ComboBox") {

39.           Add-Member -InputObject $Control -MemberType NoteProperty -Name ControlType -Value "DropDown"

40.          } else {

41.           Add-Member -InputObject $Control -MemberType NoteProperty -Name ControlType -Value $_.gettype().name

42.          }

43.          if ($Control.Controltype -eq "Button") {

44.           Add-Member -InputObject $Control -MemberType NoteProperty -Name Content -Value $_.Tag

45.          } elseif ($Control.Controltype -eq "TextBox" -or $Control.Controltype -eq "InputBox" -or $Control.Controltype -eq "Label") {

46.           Add-Member -InputObject $Control -MemberType NoteProperty -Name Content -Value $_.Text

47.          } elseif ($Control.Controltype -eq "MonthCalendar") {

48.           Add-Member -InputObject $Control -MemberType NoteProperty -Name Content -Value $_.SelectionStart

49.          } elseif ($Control.Controltype -eq "DropDown") {

50.           Add-Member -InputObject $Control -MemberType NoteProperty -Name Content -Value $_.SelectedItem

51.          } else {

52.           Add-Member -InputObject $Control -MemberType NoteProperty -Name Content -Value "Unsupported Control"

53.          }

54.          $Feedback+=$Control

55.         }

56.         $Feedback

57.        }

4.5.19.4                CloseWindow.ps1

Mit dem Close-GUIWindow Cmdlet, lässt sich das erstellte Fenster schließen. Dieses Cmdlet alleine macht nicht viel Sinn, wohl aber in Kombination z.B. mit dem New-GUIButton Cmdlet, wenn man möchte das bei Klick auf einen bestimmten Knopf das Fenster geschloßen wird. Die Zeilen 10-24 dienen wieder rein dem Get-Help Cmdlet. Die restlichen Zeilen sind ebenfalls nur Funktionsrumpf und Parameterübergabe. Die einzige „Magie“ steckt in Zeile 29. Durch das im Parameterblock empfangene Fenster-Objekt, trägt die $Window Variable das entsprechende Objekt mit der angebundenen Close()-Methode mit sich. Die wird in Zeile 29 einfach aufgerufen und schließt somit das übergeben Fenster Objekt. Dies sorgt nicht nur dafür, dass das Fenster geschlossen wird, sondern auch dafür, dass das Skript des (im vorangegangenen Abschnitt erklärt) Show‑GUIWindow Cmdlet, nach dem Halt in Zeile 29, fortgesetzt wird.

10.        <#

11.        .SYNOPSIS

12.         Schließt ein Fenster.

13.        .DESCRIPTION

14.         Schließt ein zuvor erstelltes Fenster-Objekt. Da ein PowerShellSkript bei Show-GUIWindow stehen bleibt, bis das Fenster auf irgendeine Aktion hin geschlossen wird, wird dieses Cmdlet in der Regel als Aktion bei anderen Cmdlets angegeben. Close-GUIWindow im Skript nach Show-GUIWindow auszuführen macht keinen Sinn, da das Skript nicht zu der Zeile kommen wird, wenn das Fenster nicht zuvor geschlossen wird.

15.        .PARAMETER Window

16.         Dem Parameter Window muss die Variable des Fenster-Objektes übergeben werden.

17.        .EXAMPLE

18.         $Fenster=New-GUIWindow "Fenster"

19.         New-GUIButton $Fenster "Schließen" "OK" 75 120 75 23 -Action {Close-GUIWindow $Fenster}

20.         Show-GUIWindow $Fenster

21.         Legt einen Knopf an, der beim Anklicken die Aktion des Schließens des mit $Fenster angegebenen Fensters ausführt.

22.        .LINK

23.         http://www.martinlehmann.de/wp/windows/powershell-gui-programmierung-fur-dummies-step-by-step/

24.        #>

25.        function Close-GUIWindow {

26.         param (

27.          [System.Windows.Forms.Form]$Window

28.         )

29.         [void] $Window.close()

30.        }

4.5.19.5                GetDropDown.ps1

Mit diesem Cmdlet können Sie während das Fenster angezeigt wird den Status der aktuellen Auswahl abrufen. Das Cmdlet funktioniert nur, wenn durch ein New-GUIDropDown Cmdlet dem Fenster-Objekt auch ein DropDown-Feld hinzugefügt wurde. Die ersten Zeilen 10-37 sind wieder für die Hilfetexte des Cmdlet. Das Get-GUIDropDown Cmdlet erwartet zwei Eingabe Parameter. Das Fenster-Objekt und den Namen des DropDown-Feldes von dem Sie den aktuellen Wert auslesen möchten. Es könnte ja sein, das mehr als nur ein DropDown-Feld im Fenster eingebaut ist. Deswegen muss zur Identifikation der dem DropDown-Feld zugewiesene Name mit übergeben werden. Dementsprechend sieht der Parameter Block in den Zeilen 39-42 aus. In Zeile 43 wird wird in der Variablen $DropDown das entsprechende Control hinterlegt. Dies geschieht mit Hilfe einer namesbasierten Suche auf alle Controls des Fenster-Objektes. Danach wird in Zeile 44 wieder ein Objekt $Control für die Rückgabewerte erstellt. In Zeile 45 wird dann die Eigenschaft Content dem $Control Objekt hinzugefügt. Die Information wird aus der .NET Eigenschaft SelectedItem des DropDown-Feld gewonnen. In den Zeilen 46-49 werden die Positionswerte (wo sich das DropDown-Feld innerhalb des Fensters befindet), sowie Höhe und Breite ebenfalls dem $Control Objekt entsprechend hinzugefügt. In Zeile 50 erfolgt dann die Rückgabe des $Control Objekts an das aufrufende Skript.

10.        <#

11.        .SYNOPSIS

12.         Fragt die aktuellen Werte einer Inputbox ab.

13.        .DESCRIPTION

14.         Mit diesem Cmdlet kann man während das Fenster dargestellt wird, Inhalte des DropDown-Feldes auslesen. Rückgabewerte sind der enthaltene Text, sowie die Positionsparameter des DropDown Feldes.

15.        .PARAMETER Window

16.         Dem Parameter Window muss die Variable des Fenster-Objektes übergeben werden.

17.        .PARAMETER Name

18.         Der Parameter Name muss zur Bestimmung des DropDown-Feldes übergeben werden.

19.        .EXAMPLE

20.         $Fenster=New-GUIWindow Test

21.         $DropdownAction={Set-GUITextBox $Fenster "Textfeld2" (Get-GUIDropDown $Fenster "DropDownFeld1").content 10 70 280 20}

22.         New-GUIButton $Fenster "Close" "Close" 275 200 75 23 -Action {Close-GUIWindow $Fenster}

23.         New-GUIDropDown $Fenster "DropDownFeld1" "Eins","Zwei","Drei" 10 20 280 20 -Action $DropDownAction

24.         New-GUITextBox $Fenster "Textfeld2" "Ausgabe" 10 70 280 20

25.         Show-GUIWindow $Fenster

26.         In der ersten Zeile wird ein Fenster-Objekt angelegt und in der Variablen $Fenster hinterlegt.

27.         Die Variable $DropDownAction wird die Aktion zugewiesen, die beim der Auswahl im DropDownFeld ausgeführt werden soll.

28.         Set-GUITextbox stellt den aktuellen Text, der mithilfe der Anweisung in der runden Klammer abgefragt wird, in "Textfeld2", das weiter unten angelegt wird.

29.         Die Anweisung Get-GUIDropDown holt aus dem Fenster-Objekt $Fenster die Werte aus "DropDownFeld1" (wird weiter unten erstellt) ab.

30.         Allerdings wird nur die Eigenschaft Content (der Text) zurückgegeben. Da dieses Kommand rund geklammert steht, wird der abgeholte Text direkt zur Eingabe des Set-GUITextBox Cmdlets.

31.         New-GUIButton legt einen Knopf zum schließen des Fensters an.

32.         New-GUIDropDown Erstellt das DropDownFeld mit 3 Werten zur Auswahl. Über den Parameter -Action wird festgelegt, was bei der Auswahl eines Feldes passieren soll.

33.         New-GUITextBox dient zur Anzeige der Dropdownauswahl.

34.         In der letzten Zeile wird das zusammengesetzte Fenster letztendlich angezeigt.

35.        .LINK

36.         http://www.martinlehmann.de/wp/windows/powershell-gui-programmierung-fur-dummies-step-by-step/

37.        #>

38.        function Get-GUIDropDown {

39.         param (

40.          [System.Windows.Forms.Form]$Window,

41.          [string]$Name

42.         )

43.         $DropDown = $Window.controls | ? {$_.Name -like $Name}

44.         $Control=New-Object PSCustomObject

45.         Add-Member -InputObject $Control -MemberType NoteProperty -Name Content -Value $DropDown.SelectedItem

46.         Add-Member -InputObject $Control -MemberType NoteProperty -Name X -Value $DropDown.Left

47.         Add-Member -InputObject $Control -MemberType NoteProperty -Name Y -Value $DropDown.Top

48.         Add-Member -InputObject $Control -MemberType NoteProperty -Name Width -Value $DropDown.Width

49.         Add-Member -InputObject $Control -MemberType NoteProperty -Name Height -Value $DropDown.Height

50.         $Control

51.        }

4.5.19.6                GetInputbox.ps1

Diese Cmdlet liefert während das Fenster angezeigt wird, Rückgabewerte aus einem Texteingabefeld. Ähnlich wie es das vorangegangene Get-GUIDropDown Cmdlet für DropDown-Felder macht. Falls Sie Verständnisprobleme haben, bitte einfach die zuvor angegebe Erläuterung zu Get‑GUIDropdown lesen.

10.        <#

11.        .SYNOPSIS

12.         Fragt die aktuellen Werte einer Inputbox ab.

13.        .DESCRIPTION

14.         Mit diesem Cmdlet kann man während das Fenster dargestellt wird, Inhalte des DropDown-Feldes auslesen. Rückgabewerte sind der enthaltene Text, sowie die Positionsparameter des DropDown Feldes.

15.        .PARAMETER Window

16.         Dem Parameter Window muss die Variable des Fenster-Objektes übergeben werden.

17.        .PARAMETER Name

18.         Der Parameter Name muss zur Bestimmung des DropDown-Feldes übergeben werden.

19.        .EXAMPLE

20.         $Fenster=New-GUIWindow Test

21.         $DropdownAction={Set-GUITextBox $Fenster "Textfeld2" (Get-GUIDropDown $Fenster "DropDownFeld1").content 10 70 280 20}

22.         New-GUIButton $Fenster "Close" "Close" 275 200 75 23 -Action {Close-GUIWindow $Fenster}

23.         New-GUIDropDown $Fenster "DropDownFeld1" "Eins","Zwei","Drei" 10 20 280 20 -Action $DropDownAction

24.         New-GUITextBox $Fenster "Textfeld2" "Ausgabe" 10 70 280 20

25.         Show-GUIWindow $Fenster

26.         In der ersten Zeile wird ein Fenster-Objekt angelegt und in der Variablen $Fenster hinterlegt.

27.         Die Variable $DropDownAction wird die Aktion zugewiesen, die beim der Auswahl im DropDownFeld ausgeführt werden soll.

28.         Set-GUITextbox stellt den aktuellen Text, der mithilfe der Anweisung in der runden Klammer abgefragt wird, in "Textfeld2", das weiter unten angelegt wird.

29.         Die Anweisung Get-GUIDropDown holt aus dem Fenster-Objekt $Fenster die Werte aus "DropDownFeld1" (wird weiter unten erstellt) ab.

30.         Allerdings wird nur die Eigenschaft Content (der Text) zurückgegeben. Da dieses Kommand rund geklammert steht, wird der abgeholte Text direkt zur Eingabe des Set-GUITextBox Cmdlets.

31.         New-GUIButton legt einen Knopf zum schließen des Fensters an.

32.         New-GUIDropDown Erstellt das DropDownFeld mit 3 Werten zur Auswahl. Über den Parameter -Action wird festgelegt, was bei der Auswahl eines Feldes passieren soll.

33.         New-GUITextBox dient zur Anzeige der Dropdownauswahl.

34.         In der letzten Zeile wird das zusammengesetzte Fenster letztendlich angezeigt.

35.        .LINK

36.         http://www.martinlehmann.de/wp/windows/powershell-gui-programmierung-fur-dummies-step-by-step/

37.        #>

38.        function Get-GUIDropDown {

39.         param (

40.          [System.Windows.Forms.Form]$Window,

41.          [string]$Name

42.         )

43.         $DropDown = $Window.controls | ? {$_.Name -like $Name}

44.         $Control=New-Object PSCustomObject

45.         Add-Member -InputObject $Control -MemberType NoteProperty -Name Content -Value $DropDown.SelectedItem

46.         Add-Member -InputObject $Control -MemberType NoteProperty -Name X -Value $DropDown.Left

47.         Add-Member -InputObject $Control -MemberType NoteProperty -Name Y -Value $DropDown.Top

48.         Add-Member -InputObject $Control -MemberType NoteProperty -Name Width -Value $DropDown.Width

49.         Add-Member -InputObject $Control -MemberType NoteProperty -Name Height -Value $DropDown.Height

50.         $Control

51.        }

4.5.19.7                NewButton.ps1

Diese Cmdlet erstellt einen Knopf und baut ihn ins angegebene Fenster-Objekt ein. Weiterhin kann ein Knopf auch mit Aktionen belegt werden, die beim Anklicken erfolgen sollen. Bis einschließlich Zeile 48 ist alles wieder nur für das Get-Help Cmdlet. Zeile 49 startet den Funktionsrumpf. 50-59 stellt den Parameterblock dar, der die Eingaben an die Funktion entgegen nimmt. In Zeile 60 wird das Knopf-Objekt aus der Klasse System.Windows.Forms.Button  erstellt und in der Variable $Button  hinterlegt. Zeile 60 und 61 übernehmen die Positionierung und Größe des Knopfes innerhalb des Fensters, ähnlich wie im Abschnitt NewWindow.ps1 bereits beschrieben. In Zeile 63 wird dem Knopf der Name aus dem übergebenen Parameterblock zur eindeutigen Identifikation innerhalb des Fenster-Objektes zugewiesen. Manche Fenster sollen ja vielleicht mehr als nur einen Knopf enthalten. In Zeile 64 wird die Beschriftung des Knopfes aus dem übergebenen Parameterblock festgelegt. In Zeile 65 wird festgestellt, ob auch eine Aktion stattfinden soll bzw. ob der Schalter –Action beim Aufruf verwendet wurde. Wenn dem so ist wird dieser Code in Zeile 66 mit dem Knopf verbunden. In der Klasse System.Windows.Forms.Button  ist der Event (das Ereignis) Click definiert. Durch $Button.Add_Click($Action) wird also der Code der mit $Action aus dem Parameterblock übernommen wurde, bei Anklicken des Knopfes ausgeführt. Dies kann beliebiger PowerShell-Code sein. Wichtig ist nur, dass dieser in der Form –Action {Ihr Code} übergeben wird, wie im Beispiel der Hilfe in Zeile 35. Dort wird das Close-GUIWindow Cmdlet ausgeführt, was einfach nur für das schliessen des Fensters sorgt. In Zeile 68 wird eine weitere Aktion für das Anklicken des Knopfes definiert. Und zwar wird hier die Eigenschaft Tag des Knopf-Objektes selbst ($this ist der Knopf selbst) mit dem Text Clicked beschriftet. Dieser Text wird dann vom Show-GUIWindow Cmdlet auch als Rückgabewert in der Eigenschaft Content eines Knopfes geliefert. In Zeile 69 wird der Knopf dann dem, mittels Parameterblock übergebenen, Fenster-Objekt angeheftet.

10.        <#

11.        .SYNOPSIS

12.         Erstellt einen Knopf zum Anklicken in einem zuvor angelegten Fenster.

13.        .DESCRIPTION

14.         Bevor man den Befehl verwendet muss ein Fenster durch $Fenster=New-GUIWindow Name angelegt worden sein.

15.         Dem Befehl müssen in der folgenden Reihenfolge Parameter übergeben werden:

16.         Variable vom New-GUIWindow Cmdlet, Name für den Knopf, Beschriftung, Links, Oben, Breite, Höhe

17.        .PARAMETER Window

18.         Dem Parameter Window muss die Variable des Fenster-Objektes übergeben werden.

19.        .PARAMETER Name

20.         Der Parameter Name muss für interne Identifikation übergeben werden.

21.        .PARAMETER Content

22.         Im Parameter Content können Sie den Text für die sichtbare Beschriftung des Knopfes festlegen.

23.        .PARAMETER X

24.         Der Parameter X gibt an wieviele Pixel der Knopf vom linken Fensterrand aus positioniert werden soll.

25.        .PARAMETER Y

26.         Der Parameter Y gibt an wieviele Pixel der Knopf vom oberen Fensterrand aus positioniert werden soll.

27.        .PARAMETER Width

28.         Width gibt an wieviele Pixel breit (von X aus gezählt) der Knopf sein soll.

29.        .PARAMETER Height

30.         Width gibt an wieviele Pixel hoch (von Y aus gezählt) der Knopf sein soll.

31.        .PARAMETER Action

32.         Hier können Sie in geschweiften Klammern beliebigen PowerShell-Code einbauen, der beim anklicken des Knopfes ausgeführt wird. Zum Schließen des Fensters bietet sich z.B. das Cmdlet Close-GUIWindow an.

33.        .EXAMPLE

34.         $Fenster=New-GUIWindow "Fenster"

35.         New-GUIButton $Fenster "OKKnopf" "OK" 75 120 75 23 -Action {Close-GUIWindow $Fenster}

36.         Show-GUIWindow $Fenster

37.         Baut einen Knopf in das Fenster, dass durch den Befehl $Fenster=New-GUIWindow "Fenster" angelegt wurde ein.

38.         $Fenster ist die Variable die auf das Fenster verweist in das der Knopf eingebaut werden soll.

39.         "OKKnopf" ist der Name des Knopfes und nur für interne Zwecke wichtig

40.         "OK" ist die Beschriftung des Knopfes, die im Fenster sichtbar ist.

41.         Die erste 75 gibt an, dass der Knopf 75 Pixel vom linken Fensterrand aus eingebaut werden soll.

42.         120 gibt an, dass der Knopf 120 Pixel vom oberen Fensterrand aus eingebaut werden soll.

43.         Die zweite 75 gibt an, dass der Knopf 75 Pixel breit sein soll.

44.         23 gibt an, dass der Knopf 23 Pixel hoch sein soll.

45.         Der Schalter -Action und der Code innerhalb der geschweiften Klammern gibt an, dass das Fenster in dem sich der Knopf befindet beim Anklicken des Knopfes geschlossen werden soll.

46.        .LINK

47.         http://www.martinlehmann.de/wp/windows/powershell-gui-programmierung-fur-dummies-step-by-step/

48.        #>

49.        function New-GUIButton {

50.         param (

51.          [System.Windows.Forms.Form]$Window,

52.          [string]$Name="Knopf",

53.          [string]$Content="Knopf",

54.          [int]$X=300,

55.          [int]$Y=200,

56.          [int]$Width=75,

57.          [int]$Height=23,

58.          [Scriptblock]$Action

59.         )

60.         $Button = New-Object System.Windows.Forms.Button

61.         $Button.Location = New-Object System.Drawing.Size($X,$Y)

62.         $Button.Size = New-Object System.Drawing.Size($Width,$Height)

63.         $Button.Name = $Name

64.         $Button.Text = $Content

65.         if ($Action) {

66.          $Button.Add_Click($Action)

67.         }

68.         $Button.Add_Click({$this.Tag="Clicked"})

69.         $Window.Controls.Add($Button)

70.        }

4.5.19.8                NewCalendar.ps1

Von einem Benutzer ein Datum z.B. über ein Texteingabefeld entgegen zu nehmen ist die Hölle. Zum einen bekämen Sie es als Text und müssten es erst ind Datumsformat umwandeln und zum anderen brauchen Sie etlich Plausibilitätsüberprüfungen, ob die Eingabe die der Benutzer gemacht hat, überhaupt ein Datum ist wie Sie es gerne weiter verarbeiten möchten. Ganz zu schweigen vom 31.Februar oder Schaltjahren. Dieses Cmdlet macht es Ihnen als Programmierer und dem Benutzer einfach sich zu verstehen. Sie schreiben einfach New-GUICalendar $Fenster "Kalender1" und der Benutzer klick an welchen Tag er meint – fertig! Den Rückgabewert können Sie gemeinsam mit den anderen Rückgabewerten von Show-GUIWindow wie gewohnt in der Eigenschaft Content des Kalenders auslesen und zwar als Datumswert! Aufgrund der vorangegangenen Beschreibungen dürfte der Inhalt weitestgehen klar sein. Allerdings hat der Kalender nur Positions- und keine Breiten- oder Höhenangaben, da er eine feste Größe (Zeile 45) hat. Zeile 47 sorgt dafür, dass der heutige Tag nicht extra rot eingekreist wird. Zeile 48 sorgt dafür, das nur ein Datum übergeben wird und keine Zeitspanne.

10.        <#

11.        .SYNOPSIS

12.         Erstellt einen Kalender zur Datumsauswahl in einem zuvor angelegten Fenster.

13.        .DESCRIPTION

14.         Bevor man den Befehl verwendet muss ein Fenster durch $variable=New-GUIWindow Name Breite Höhe angelebt worden sein.

15.         Dem Befehl müssen in der folgenden Reihenfolge Parameter übergeben werden:

16.         Variable vom New-GUIWindow Cmdlet,Name für den Kalender,Links,Oben

17.         Im Gegensatz zu den anderen Fensterelementen ist die Größe des Kalender fest, daher ist keine Höhen (=190) oder Breitenangabe (=200) erforderlich

18.        .PARAMETER Name

19.         Der Parameter Name muss für interne Identifikation übergeben werden.

20.        .PARAMETER X

21.         Der Parameter X gibt an wieviele Pixel der Knopf vom linken Fensterrand aus positioniert werden soll.

22.        .PARAMETER Y

23.         Der Parameter Y gibt an wieviele Pixel der Knopf vom oberen Fensterrand aus positioniert werden soll.

24.        .EXAMPLE

25.         $Fenster=New-GUIWindow "Fenster"

26.         New-GUICalendar $Fenster "Kalender1" 300 20

27.         Show-GUIWindow $Fenster

28.         Baut einen Kalender in das Fenster, dass durch den Befehl $Fenster=New-GUIWindow "Fenster" angelegt wurde ein.

29.         $Fenster ist die Variable die auf das Fenster verweist in das der Knopf eingebaut werden soll.

30.         "Kalender1" ist der Name des Kalenders und nur für interne Zwecke wichtig.

31.         Die 300 gibt an, dass das Eingabefeld 300 Pixel vom linken Fensterrand aus eingebaut werden soll.

32.         Die 20 gibt an, dass der Eingabefeld 20 Pixel vom oberen Fensterrand aus eingebaut werden soll.

33.        .LINK

34.         http://www.martinlehmann.de/wp/windows/powershell-gui-programmierung-fur-dummies-step-by-step/

35.        #>

36.        function New-GUICalendar {

37.         param (

38.          [System.Windows.Forms.Form]$Window,

39.          [string]$Name="Kalender",

40.          [int]$X=300,

41.          [int]$Y=20

42.         )

43.         $Calendar = New-Object System.Windows.Forms.MonthCalendar

44.         $Calendar.Location = New-Object System.Drawing.Size($x,$y)

45.         $Calendar.Size = New-Object System.Drawing.Size(190,200)

46.         $Calendar.Name = $Name

47.         $Calendar.ShowTodayCircle = $False

48.         $Calendar.MaxSelectionCount = 1

49.         $Window.Controls.Add($Calendar)

50.        }

4.5.19.9                NewDropDown.ps1

Mit diesem Cmdlet können Sie DropDown-Felder in Ihr Fenster einbauen. Für ein DropDown-Feld greifen Sie hier auf die .NET Klasse System.Windows.Forms.Combobox  zurück (Zeile 61). In Zeil 54 des Parameterblocks nehmen Sie den Wert (macht wenig Sinn) bzw. eher die Werte in der Variablen $Content entgegen, die im DropDown-Feld angezeigt werden sollen. Da dies in der Regel mehrere zur Auswahl stehende Werte sind, wird eine Schleife über alle Elemente von $Content (Zeile 65-67) erstellt. Damit in Zeile 66 nicht bei jedem hinzugefügten Element eine Rückmeldung erfolgt steht der Ausdruck [Void] zu Beginn. Weitere Details zu DropDown-Feldern finden Sie im Abschnitt Listen Auswahl und DropDown Felder.

10.        <#

11.        .SYNOPSIS

12.         Erstellt einen DropDown-Feld in einem zuvor angelegten Fenster.

13.        .DESCRIPTION

14.         Bevor man das Cmdlet verwendet muss ein Fenster durch $Fenster=New-GUIWindow Name Breite Höhe angelegt worden sein.

15.         Dem Cmdlet müssen in der folgenden Reihenfolge Parameter übergeben werden:

16.         Variable vom New-GUIWindow Befehl,Name für das DropDownFeld, Beschriftung,Links,Oben,Breite,Höhe

17.        .PARAMETER Name

18.         Der Parameter Name muss für interne Identifikation übergeben werden.

19.        .PARAMETER Content

20.         Im Parameter Content können Sie die darzustellenden Elemente in Form eines Arrays übergeben.

21.        .PARAMETER X

22.         Der Parameter X gibt an wieviele Pixel der Knopf vom linken Fensterrand aus positioniert werden soll.

23.        .PARAMETER Y

24.         Der Parameter Y gibt an wieviele Pixel der Knopf vom oberen Fensterrand aus positioniert werden soll.

25.        .PARAMETER Width

26.         Width gibt an wieviele Pixel breit (von X aus gezählt) der Knopf sein soll.

27.        .PARAMETER Height

28.         Width gibt an wieviele Pixel hoch (von Y aus gezählt) der Knopf sein soll.

29.        .PARAMETER Action

30.         Hier können Sie in geschweiften Klammern beliebigen PowerShell-Code einbauen, der beim der Auswahl ausgeführt wird. Zum Schließen des Fensters bietet sich z.B. das Cmdlet Close-GUIWindow an.

31.        .EXAMPLE

32.         $Fenster=New-GUIWindow Test

33.         $DropdownAction={Set-GUITextBox $Fenster "Textfeld2" (Get-GUIDropDown $Fenster "DropDownFeld1").content 10 70 280 20}

34.         New-GUIButton $Fenster "Close" "Close" 275 200 75 23 -Action {Close-GUIWindow $Fenster}

35.         New-GUIDropDown $Fenster "DropDownFeld1" "Eins","Zwei","Drei" 10 20 280 20 -Action $DropDownAction

36.         New-GUITextBox $Fenster "Textfeld2" "Ausgabe" 10 70 280 20

37.         Show-GUIWindow $Fenster

38.         In der ersten Zeile wird ein Fenster-Objekt angelegt und in der Variablen $Fenster hinterlegt.

39.         Die Variable $DropDownAction wird die Aktion zugewiesen, die beim der Auswahl im DropDownFeld ausgeführt werden soll.

40.         Set-GUITextbox stellt den aktuellen Text, der mithilfe der Anweisung in der runden Klammer abgefragt wird, in "Textfeld2", das weiter unten angelegt wird.

41.         Die Anweisung Get-GUIDropDown holt aus dem Fenster-Objekt $Fenster die Werte aus "DropDownFeld1" (wird weiter unten erstellt) ab.

42.         Allerdings wird nur die Eigenschaft Content (der Text) zurückgegeben. Da dieses Kommand rund geklammert steht, wird der abgeholte Text direkt zur Eingabe des Set-GUITextBox Cmdlets.

43.         New-GUIButton legt einen Knopf zum schließen des Fensters an.

44.         New-GUIDropDown Erstellt das DropDownFeld mit 3 Werten zur Auswahl. Über den Parameter -Action wird festgelegt, was bei der Auswahl eines Feldes passieren soll.

45.         New-GUITextBox dient zur Anzeige der Dropdownauswahl.

46.         In der letzten Zeile wird das zusammengesetzte Fenster letztendlich angezeigt.

47.        .LINK

48.         http://www.martinlehmann.de/wp/windows/powershell-gui-programmierung-fur-dummies-step-by-step/

49.        #>

50.        function New-GUIDropDown {

51.         param (

52.          [System.Windows.Forms.Form]$Window,

53.          [string]$Name="DropDown",

54.          [string[]]$Content=@("Eins","Zwei","Drei"),

55.          [int]$X=10,

56.          [int]$Y=20,

57.          [int]$Width=280,

58.          [int]$Height=20,

59.          [Scriptblock]$Action

60.         )

61.         $DropDown=New-Object System.Windows.Forms.Combobox

62.         $DropDown.Name=$Name

63.         $DropDown.Location = New-Object System.Drawing.Point($X,$Y)

64.         $DropDown.Size = New-Object System.Drawing.Size($Width,$Height)

65.         $Content | foreach {

66.          [void] $DropDown.Items.Add($_)

67.         }

68.         if ($Action) {

69.          $DropDown.Add_SelectedValueChanged($Action)

70.         }

71.         $Window.Controls.Add($DropDown)

72.        }

4.5.19.10           NewInputBox.ps1

Mit New-GUIInputBox können Texte entgegen genommen werden. Das einzig erklärungsbedürftige hier dürfte die Zeile 58 sein. In dieser Zeile kann der Text des Textfeldes mit der durch den Parameterblock übergebenen Variable $Content vorbelegt werden.

10.        <#

11.        .SYNOPSIS

12.         Erstellt ein Texteingabefeld in einem zuvor angelegten Fenster.

13.        .DESCRIPTION

14.         Bevor man den Befehl verwendet muss ein Fenster durch $variable=New-GUIWindow Name Breite Höhe angelegt worden sein.

15.         Dem Befehl müssen in der folgenden Reihenfolge Parameter übergeben werden:

16.         Variable vom New-GUIWindow Cmdlet,Name für den Textbereich,Textvorgabe,Links,Oben,Breite,Höhe

17.        .PARAMETER Name

18.         Der Parameter Name muss für interne Identifikation übergeben werden.

19.        .PARAMETER Content

20.         Im Parameter Content können Sie einen Text vorgeben lassen.

21.        .PARAMETER X

22.         Der Parameter X gibt an wieviele Pixel der Knopf vom linken Fensterrand aus positioniert werden soll.

23.        .PARAMETER Y

24.         Der Parameter Y gibt an wieviele Pixel der Knopf vom oberen Fensterrand aus positioniert werden soll.

25.        .PARAMETER Width

26.         Width gibt an wieviele Pixel breit (von X aus gezählt) der Knopf sein soll.

27.        .PARAMETER Height

28.         Width gibt an wieviele Pixel hoch (von Y aus gezählt) der Knopf sein soll.

29.        .EXAMPLE

30.         $Fenster=New-GUIWindow "Fenster"

31.         New-GUIInputBox $Fenster "Inputbox1" "Vorgabe1" 10 40 260 20

32.         Show-GUIWindow $Fenster

33.         Baut ein Textfeld in das Fenster, dass durch den Befehl $Fenster=New-GUIWindow "Fenster" angelegt wurde ein.

34.         $Fenster ist die Variable die auf das Fenster verweist in das der Knopf eingebaut werden soll.

35.         "Inputbox1" ist der Name des Textfeldes und nur für interne Zwecke wichtig.

36.         "Vorgabe1" ist ein Text, der als Vorgabe angezeigt werden soll. Ein "" an dieser Stelle hätte ein leeres Feld als Vorgabe.

37.         Die 10 gibt an, dass das Eingabefeld 10 Pixel vom linken Fensterrand aus eingebaut werden soll.

38.         Die 40 gibt an, dass der Eingabefeld 40 Pixel vom oberen Fensterrand aus eingebaut werden soll.

39.         Die 260 gibt an, dass das Eingabefeld 260 Pixel breit sein soll.

40.         Die 20 gibt an, dass das Eingabefeld 20 Pixel hoch sein soll.

41.        .LINK

42.         http://www.martinlehmann.de/wp/windows/powershell-gui-programmierung-fur-dummies-step-by-step/

43.        #>

44.        function New-GUIInputBox {

45.         param (

46.          [System.Windows.Forms.Form]$Window,

47.          [string]$Name="Inputbox",

48.          [string]$Content="",

49.          [int]$X=10,

50.          [int]$Y=40,

51.          [int]$Width=260,

52.          [int]$Height=20

53.         )

54.         $InputBox = New-Object System.Windows.Forms.TextBox

55.         $InputBox.Location = New-Object System.Drawing.Size($X,$Y)

56.         $InputBox.Size = New-Object System.Drawing.Size($Width,$Height)

57.         $InputBox.Name = $Name

58.         $InputBox.Text = $Content

59.         $Window.Controls.Add($InputBox)

60.        }

4.5.19.11           NewTextBox.ps1

Dieses Cmdlet, bedarf wohl an dieser Stelle gar keiner Erklärung mehr. Es stellt einen Text im Fenster dar.

10.        <#

11.        .SYNOPSIS

12.         Erstellt einen Text in einem zuvor angelegten Fenster.

13.        .DESCRIPTION

14.         Bevor man das Cmdlet verwendet muss ein Fenster durch $Fenster=New-GUIWindow Name Breite Höhe angelegt worden sein.

15.         Dem Cmdlet müssen in der folgenden Reihenfolge Parameter übergeben werden:

16.         Variable vom New-GUIWindow Befehl,Name für den Textbereich, Beschriftung, Links, Oben, Breite, Höhe

17.        .PARAMETER Name

18.         Der Parameter Name muss für interne Identifikation übergeben werden.

19.        .PARAMETER Content

20.         Im Parameter Content können Sie den darzustellenden Text festlegen.

21.        .PARAMETER X

22.         Der Parameter X gibt an wieviele Pixel der Knopf vom linken Fensterrand aus positioniert werden soll.

23.        .PARAMETER Y

24.         Der Parameter Y gibt an wieviele Pixel der Knopf vom oberen Fensterrand aus positioniert werden soll.

25.        .PARAMETER Width

26.         Width gibt an wieviele Pixel breit (von X aus gezählt) der Knopf sein soll.

27.        .PARAMETER Height

28.         Width gibt an wieviele Pixel hoch (von Y aus gezählt) der Knopf sein soll.

29.        .EXAMPLE

30.         $Fenster=New-GUIWindow "Fenster"

31.         New-GUITextBox $Fenster "Textfeld1" "Hello World!" 10 20 280 20

32.         Show-GUIWindow $Fenster

33.         Baut ein Textfeld in das Fenster, dass durch den Befehl $Fenster=New-GUIWindow "Fenster" angelegt wurde ein.

34.         $Fenster ist die Variable die auf das Fenster verweist in das das Textfeld eingebaut werden soll.

35.         "Textfeld1" ist der Name des Textfeldes und nur für interne Zwecke wichtig.

36.         "Hello World!" Ist der Text, der im Fenster angezeigt werden soll.

37.         Die 10 gibt an, dass der Text 10 Pixel vom linken Fensterrand aus eingebaut werden soll.

38.         Die erste 20 gibt an, dass der Text 20 Pixel vom oberen Fensterrand aus eingebaut werden soll.

39.         Die 280 gibt an, dass das Textfeld 280 Pixel breit sein soll.

40.         Die zweite 20 gibt an, dass das Textfeld 20 Pixel hoch sein soll.

41.        .LINK

42.         http://www.martinlehmann.de/wp/windows/powershell-gui-programmierung-fur-dummies-step-by-step/

43.        #>

44.        function New-GUITextBox {

45.         param (

46.          [System.Windows.Forms.Form]$Window,

47.          [string]$Name="Textfeld",

48.          [string]$Content="Textfeld",

49.          [int]$X=10,

50.          [int]$Y=20,

51.          [int]$Width=280,

52.          [int]$Height=20

53.         )

54.         $Text = New-Object System.Windows.Forms.Label

55.         $Text.Location = New-Object System.Drawing.Point($X,$Y)

56.         $Text.Size = New-Object System.Drawing.Size($Width,$Height)

57.         $Text.Name = $Name

58.         $Text.Text = $Content

59.         $Window.Controls.Add($Text)

60.        }

4.5.19.12           SetTextBox.ps1

Mit diesem Cmdlet kann während das Fenster angezeigt wird, der Text einer Textbox beliebig angepasst werden. Sowohl der Textinhalt, als auch die Position und Größe der Textbox. Auch hier findet die Auswahl der Textbox wieder anhand des vergebenen, eindeutigen Namens statt (Zeile 58). Werden keine Parameter übergeben, müssen die alten Werte der entsprechenden Textbox erhalten bleiben. Diese Aufgabe übernehmen die Zeilen 59-63.

10.        <#

11.        .SYNOPSIS

12.         Ändert einen Text oder die Position in einem zuvor angelegten Textfeld.

13.        .DESCRIPTION

14.         Bevor man das Cmdlet verwendet muss bereits ein Textfeld in einem Fenster exisitieren.

15.         Dem Cmdlet müssen in der folgenden Reihenfolge Parameter übergeben werden:

16.         Variable vom New-GUIWindow Cmdlet, Name der TextBox, Neue Beschriftung,Links,Oben,Breite,Höhe

17.        .PARAMETER Name

18.         Der Parameter Name muss für interne Identifikation übergeben werden.

19.        .PARAMETER Content

20.         Im Parameter Content können Sie den Text für die sichtbare Beschriftung des Knopfes festlegen.

21.        .PARAMETER X

22.         Der Parameter X gibt an wieviele Pixel der Knopf vom linken Fensterrand aus positioniert werden soll.

23.        .PARAMETER Y

24.         Der Parameter Y gibt an wieviele Pixel der Knopf vom oberen Fensterrand aus positioniert werden soll.

25.        .PARAMETER Width

26.         Width gibt an wieviele Pixel breit (von X aus gezählt) der Knopf sein soll.

27.        .PARAMETER Height

28.         Width gibt an wieviele Pixel hoch (von Y aus gezählt) der Knopf sein soll.

29.        .EXAMPLE

30.         $Fenster=New-GUIWindow "Fenster"

31.         New-GUITextBox $Fenster "Textfeld1" "Hello World!" 10 20 280 20

32.         Change-GUITextBox $Fenster "Textfeld1" "Bye World!"

33.         Show-GUIWindow $Fenster

34.         Ändert den Text in der angegebenen TextBox des Fensters.

35.         $Fenster ist die Variable die auf das Fenster verweist und Textfeld1 der Name der zu ändernden Textbox.

36.         "Bye World!" ist der neue Text, der im Textfeld1 angezeigt werden soll.

37.        .EXAMPLE

38.         $Fenster=New-GUIWindow "Fenster"

39.         New-GUITextBox $Fenster "Textfeld1" "Hello World!" 10 20 280 20

40.         New-GUIButton $Fenster "Change" "Change" 75 120 75 23 -Action {Set-GUITextBox $Fenster "Textfeld1" "Bye World!" 50 50}

41.         Show-GUIWindow $Fenster

42.         Ändert den Text und die Position in der angegebenen TextBox des Fensters, wenn der Knopf Angeklickt wird.

43.         $Fenster ist die Variable die auf das Fenster verweist und Textfeld1 der Name der zu ändernden Textbox.

44.         "Bye World!" ist der neue Text, der im Textfeld1 angezeigt werden soll und 50 50 die Angabe der neuen Position (x,y).

45.        .LINK

46.         http://www.martinlehmann.de/wp/windows/powershell-gui-programmierung-fur-dummies-step-by-step/

47.        #>

48.        function Set-GUITextbox {

49.         param (

50.          [System.Windows.Forms.Form]$Window,

51.          [string]$Name,

52.          [string]$Content,

53.          [int]$X,

54.          [int]$Y,

55.          [int]$Width,

56.          [int]$Height

57.         )

58.         $Text = $Window.controls | ? {$_.Name -like $Name}

59.         if (!$Content) {$Content=$Text.Text}

60.         if (!$X) {$X=$Text.Top}

61.         if (!$Y) {$Y=$Text.Left}

62.         if (!$Width) {$Width=$Text.Width}

63.         if (!$Height) {$Height=$Text.Height}

64.         $Text.Location = New-Object System.Drawing.Point($X,$Y)

65.         $Text.Size = New-Object System.Drawing.Size($Width,$Height)

66.         $Text.Text = $Content

67. }

4.5.20                     Auf Tastendruck reagieren

Gerade beim OK, oder Abbruch Knopf möchten Sie wahrscheinlich ganz gerne, dass der Benutzer alternativ zum Klick auf OK auch einfach Return bzw. Enter drücken kann. Auch das ist möglich mithilfe des Keydown Events. Dazu müssen Sie am Fenster-Objekt zunächst die Eigenschaft KeyPreview auf $true setzen:

$Fenster.KeyPreview=$true

Weiterhin müssen Sie auf das Ereignis „Keydown“ einen Auslöser mit einer Funktion definieren:

 

$Fenster.Add_KeyDown({Write-Host $_.keycode})

Damit erfahren Sie was beim Drücken einer bestimmten Taste geliefert wird. Wollen Sie auf ein Enter bzw. ein Return mit dem schließen des Fensters reagieren, müsste Ihr Ereignisauslöser wie folgt aussehen:

 

$Fenster.Add_KeyDown({if ($_.KeyCode -eq "Enter") {$Fenster.Close()}})

4.5.21                     Online Dokumentation zu allen GUI-Elementen

Alle möglichen Gestaltungsmöglichkeiten hier zu erwähnen würde den Rahmen des Buches sprengen. Mit dem hier beschriebenen haben Sie allerdings eine sehr gute Grundlage alles Weitere selbst herauszufinden. Eine komplette Beschreibung der Forms Klassenbibliothek finden Sie unter:
http://msdn.microsoft.com/de-de/library/System.Windows.Forms(v=vs.110).aspx

4.6     Internet-Information-Server (IIS)

An dieser Stelle geht es um die praktische IIS Verwaltung natürlich mit Hilfe der PowerShell. Zunächst werden allerdings einige Grundlagen und Hintergründe zu IIS erläutert, die man ohne Vorkenntnisse nur schwer in Erfahrung bringt.

4.6.1   Grundlagen zu IIS

IIS ist der Webserver von Microsoft um html Dokumente (Internetseiten wie z.B. http://www.martinlehmann.de ) im Internet oder Intranet zu veröffentlichen. Darüber hinaus kann man ihn natürlich auch als ftp oder smtp Server einsetzen. IIS Administratoren dürfen gerne diesen Abschnitt über die Grundlagen überspringen und gleich zum nächsten Abschnitt den technischen Details wechseln. Dieser Abschnitt widmet sich Anwendern, die so etwas bislang noch nie gemacht haben.

Tippt jemand im Webbrowser www.microsoft.com ein, dann muss zunächst einmal die IP-Adresse (so eine Art Telefonnummer) zu diesem eindeutigen Namen ausfindig gemacht werden. Das geschieht im Normalfall mit Hilfe von DNS (Domain-Name-Service). Im Internet stehen die sogenannten DNS-Server welche diese Namen-Nummern-Zuordnung liefern. Hängt man mit seinem DSL-Router (z.B. der Fritzbox) am DSL-Anschluß seines Anbieters (z.B. Telekom), so informiert der Anbieter die Fritzbox über deren DNS-Server, der den Rest der Welt kennt. Die Fritzbox bekommt also die IP-Adresse des DNS-Servers des Anbieters, um dort nach denen im Browser eingetippen Namen aus aller Welt zu fragen. Dabei bekommt die Fritzbox überigens auch selbst eine weltweit eindeutige IP-Adresse zugeordnet und ist damit auch von überall aus unter dieser Nummer erreichbar. Die Fritzbox gibt den zu Hause angeschlossenen PCs dann auch wiederum solche IP-Adressen, die aber dem Rest der Welt gegenüber unbekannt sind. Den PCs bei Ihnen zu Hause wird für DNS-Anfragen die Fritzbox als Ansprechpartner genannt. Auf diese Art und Weise können dann Ihre PCs mit dem Rest der Welt kommunizieren. Das Problem, wenn Sie nun selbst einen Webserver betreiben möchten ist, dass Sie keinen offiziellen DNS-Namen haben, der auf Ihre IP-Adresse deutet, die sich womöglich auch noch täglich, bei der sogenannten Zwangstrennung, ändert. Im Internet gibt es dafür sowohl kostenpflichtige, als auch kostenfreie Lösungen. Sie können sich bei verschiedenen Internetanbietern einen DNS-Host-Eintrag oder gar eine komplette Domäne für ein paar Euro im Monat mieten. Für private Zwecke soll es aber am besten kostenfrei sein. Hier würde ich Ihnen no-ip.com empfehlen. Falls Sie des Englischen nicht mächtigen sind, fragen Sie jemand Ihres Vertrauens der Ihnen bei der Anmeldung hilft. Dort können Sie dann einen Namen wie IhrName.no-ip.com registrieren und Ihrem Internet-Router (in den meisten Fällen wohl die Fritzbox) die Anmeldedaten mitteilen. Falls Ihr Router das nicht kann, bietet Ihnen no-ip.com auch eine kostenlose Software zum Download an, die Sie dann auf Ihrem PC installieren können. Änderunngen an Ihrer offiziellen IP-Adresse werden dann sofort von der Soaftware an den Dienst gemeldet. Damit können Sie dann von überall auf der Welt Ihren PC unter dem Namen IhrName.no-ip.com erreichen. Ihren Internet-Router müssten Sie dann zur Durchleitung von Port 80 auf Ihren Webserver überreden. Auch hier sollten Sie einen Bekannten fragen, ob er Ihnen dabei helfen kann, falls Sie damit nicht klar kommen. Die kompletten Hintergründe zu erklären, würde hier den Rahmen des Buches sprengen und auch Screenshots würden hier nicht viel bringen, da jeder Router andere Menüstrukturen aufweist. Danach steht den Betrieb Ihres Webservers nichts mehr im Weg. Sie brauchen nur ein Windows Vista/7/8 oder einen Windows Server 2008 (R2) oder 2012. Sowohl der Client, als auch der Server bieten beide den IIS an. Beim Client finden Sie die Möglichkeit zur Installation in der Systemsteuerung unter Programme. Dort gibt es den Eintrag „Windows-Funktionen aktivieren oder deaktivieren“.

Bei einem Server finden Sie eine ähnliche Ansicht im Servermanager. Dort müssen Sie entsprechend die Rolle (kein Feature) für IIS hinzufügen. Natürlich dürfen Sie dazu auch die PowerShell benutzen, wie im Kapitel „Betriebssystemfunktionen auf einem Windows 7 oder Vista Client verwalten“ im Praxisteil dieses Buches beschrieben verwenden.

4.6.2   Technische Details

Administratoren die sich für Sicherheitsfragen interessieren, sollten diesen Abschnitt unbedingt lesen. Anwender können diesen Abschnitt gerne überspringen. Daher setze ich hier in der Beschreibung auch entsprechende Fachkenntnis voraus und erkläre nicht jede Kleinigkeit wie sonst in diesem Buch üblich. Zum Verständnis des darauffolgenden Abschnittes ist dieser nicht notwendig.

Bei IIS 7.5 unter Windows Server 2008 R2 und auch bei IIS 7 ist es gar nicht so leicht zu durchblicken wann welcher Benutzer zur Authentifizierung und Autorisierung zum Einsatz kommt. Daher habe ich einmal ein Schaubild gezeichnet auf dem die Zusammenhänge der verschiedenen Einstellungen deutlich werden.

http://www.martinlehmann.de/wp/wp-content/uploads/2011/10/IIS7IdentityManagement.png

Der gelb hinterlegte Bereich bezieht sich auf die Basic Settings der Website:

Der Lila Bereich wird über die Authentifizierung erreicht. Windows, Standard und Digest authentifizieren der Besucher mittels lokaler oder Active Directory Datenbank und mappen auf die entsprechende SID des Benutzerkontos. Die Anonyme Anmeldung läßt sich aber auch noch einmal konfigurieren:

Hat man hier Application Pool Identity ausgewählt, hängt es wiederum von den Einstellungen beim ApplicationPool ab als wer man versucht zuzugreifen:

LocalService ist mit ähnlichen Rechten wie ein normaler lokaler Benutzer ausgestattet, kann also auf die meisten Bereiche lesend zugreifen, aber nur auf eigene Dateien schreibend zugreifen.

NetworkService kann nicht nur auf den eigenen Rechner zugreifen, sondern wie ein Domänenbenutzer auch auf andere PCs im Netzwerk.

Wer LocalSystem verlangt, wird kurzer Hand erschlagen! LocalSystem bedeutet Sie machen Ihre Internetbesucher zu lokalen Administratoren. Absolutes NO-GO!!!

Die ApplicationPoolIdentity (gibt es nur ab Server 2008 R2 bzw. IIS 7.5 und ist dort die Standardvorgabe für einen ApplicationPool) wird bei jedem Neustart eines Workerprozesses (w3wp.exe) erzeugt. Diese ist also weder in der lokalen noch der Active Directory-, noch in der lokalen Benutzerdatenbank bekannt. Da wird es schwer Berechtigungen zuzuweisen, doch es geht! Diese Identitäten werden erst beim Starten des jeweiligen Applicationpools von WAS (Windows Process Activation Service) erzeugt und dies wiederum passiert nur, wenn ein Benutzer auf die Website/Anwendung zugreift. Um der ApplicationPoolIdentity Berechtigungen am Dateisystem zu geben müssen Sie zunächst wie gewohnt vorgehen, wenn Sie dann den Benutzer/Gruppe auswählen möchten müssen Sie unter Pfad den lokalen Computer angeben (nicht AD) und dann “IIS APPPOOL\NameDesGewünschtenAppPools” eintippen, da die Benutzer in der grafischen Auswahl nicht angezeigt werden. Je nachdem um welchen ApplicationPool es sich handelt müssen Sie natürlich “NameDesGewünschtenAppPools” durch den Namen Ihres gewünschten Applicationpools austauschen.

4.6.3   Navigieren im IIS PS-Drive

Nachdem Sie mit Import-Module Webadministration die Cmdlets für IIS geladen haben erhalten Sie auch das PSDrive IIS: für den IIS Zugriff. Um also in den IIS zu wechseln tippen Sie lediglich:

cd IIS:

Hier finden Sie die ApplicationPools, Sites und SSLBindungs, in die Sie ebenfalls mit cd hineinwechseln können, um sich dort wiederum mit ls Details anzeigen zu lassen. Haben Sie hier mehrere Websites, wollen aber nur eine sehen, können Sie z.B. die Standardwebsitedetails wie folgt auslesen:

Get-ItemProperty „Default Web Site“

Darauf eine Pipe mit Select * oder Get-Member lädt natürlich auch wieder zum Spielen ein.

4.6.4   Websites und ApplicationPools verwalten

Hier erfahren Sie alles rund um die Webserververwaltung. Anlegen, verändern und löschen von Websites, Applicationpools und deren Einstellungen.

4.6.4.1   Website erstellen

Um selbst eine Website mit der PowerShell anzulegen ist es wie üblich hilfreich einmal die Hilfe zum entsprechenden Cmdlet anzuschauen:

Help New-Website

Damit erfahren Sie welche Parameter direkt beim Erstellen angeben werden können. Leider ist die Hilfe insbesondere die Beispiele zu den Webserver Cmdlets eher Bescheiden. Um eine Website anzulegen, müssen mindestens die folgenden Parameter angegeben werden:

New-Website –Name NameIhrerWebsite –hostheader „www.ihreinternetadresse.de“ –physicalpath “c:\inetpub\IhrOrdner“

Statt –hostheader können Sie auch –Port oder –IPAddress angeben. Aber einen dieser 3 Parameter müssen Sie unbedingt angeben, um sich von der „Default Website“ zu unterscheiden. Alle anderen Angaben sind dann optional.

4.6.4.2   Änderungen an den Website Einstellungen

Wenn Sie nun im Nachhinein etwas an den Bindungen ändern möchten, geht dies mit Set‑WebBinding. Um z.B. den Port auf 40000 zu setzen geben Sie ein:

Set-WebBinding –Name NameIhrerWebsite –BindingInformation `
“*:80:www.ihreinternetadresse.de“ –Propertyname Port –Value 40000

Den Ablageort der Dateien zu ändern ist einfacher:

Set-ItemProperty IIS:\Sites\NameIhrerWebsite –Name `
PhysicalPath ‑Value “C:\AndererOrdner”

Auf dieselbe Art und Weise lassen sich auch die anderen Eigenschaften wie Benutzername,Kennwort oder der Name der Website ändern.

4.6.4.3   Status der Website abfragen

Ob die Website läuft, können Sie mit Get-Websitestate feststellen:

Get-WebsiteState NameIhrerWebsite

4.6.4.4   Website starten oder stoppen

Drei Mal dürfen Sie raten!

Start-Website NameIhrerWebsite

Stop-Website NameIhrerWebsite

4.6.4.5   Löschen einer Website

Eine Website wieder zu löschen steht wohl spätest jetzt außer Frage:

Remove-Website NameIhrer Website

4.6.4.6   Anlegen eines ApplicationPools

Einfach:

New-WebAppPool NameIhresAppPools

4.6.4.7   Änderungen an ApplicationPools und Websites durchführen

So, hier wird es jetzt „haarig“. Dazu müssen Sie nämlich die jeweiligen XML-Inhalte der applicationHost.config (im Verzeichnis C:\Windows\System32\inetsrv) kennen. Am besten Sie klicken sich das zu Testzwecken auf einem Server zusammen und suchen nach den entsprechenden Einstellungen in der applicationHost.config. Das Internet verrät recht wenig über die gegebenen Möglichkeiten. Nehmen wir uns als Beispiel die Einstellungen um 2:00 Uhr Nachts ein AppPoolRecycling durchzuführen. Wenn Sie in die applicationHost.config schauen, sieht einen AppPool ohne jegliche Einstellungen so aus:

   <system.applicationHost>

        <applicationPools>

            <add name="NameIhresAppPools">

            </add>

….

        </applicationPools>

Um nun um 2:00 Uhr Nacht einen AppPoolRecycle einzubauen lautet der Befehl:

Add-Webconfiguration "/system.applicationHost/applicationPools/add[@name='NameIhresAppPools']/recycling/periodicRestart/schedule" -value "02:00:00"

Achtung! Tippfehler geben keine Fehlermeldung zurück!!!! Wenn Sie also schreiben:

Add-Webconfiguration  „/system.applicationHost/ add[@name='NameIhresAppPools']/HutzliButzli” –value “So ein Quatsch”

schluckt er den Befehl genauso, als wäre alles fein. Nur schreibt er den „Müll“ wenigstens nicht in die applicationHost.config. Wenn die Änderung dort also nicht auftaucht, suchen Sie erst einmal nach Tippfehlern!

Ich habe einmal farbig hervorgehoben welcher Teil aus der applicationHost.config welchen Teilen im PowerShell Cmdlet entspricht, sodass Sie sich einen Reim darauf machen können, wie sich das Ganze zusammensetzt. Nach dem der o.a. farbige Befehl Add-Webconfiguration erfolgreich ausgeführt wurde solle Ihre applicationHost.config so aussehen:

   <system.applicationHost>

        <applicationPools>

            <add name="NameIhresAppPools">

                <recycling>

                    <periodicRestart>

                        <schedule>

                            <add value="02:00:00" />

                        </schedule>

                    </periodicRestart>

                </recycling>

            </add>

        </applicationPools>

Wenn Sie mehrere Zeiten hinzufügen möchten, wiederholen Sie einfach das Cmdlet mit weiteren Zeiten. Um einen Wert an einer bestimmten Stelle hinzuzufügen, können Sie optional noch den Schalter –AtIndex hinzufügen. Insbesondere bei der Default-Document Einstellung einer Website (nicht eines ApplicationPools) kann das interessant sein:

Add-WebConfiguration //defaultDocument/files  "IIS:\sites\Default Web Site" -atIndex 0 -Value @{value="mystart.htm”}

Das würde nun nicht in die applicationHost.config geschrieben werden, sondern in die Web.config (die liegt im Verzeichnis wo auch Ihre html Dokumente liegen) der Default Website. Hier haben Sie am Anfang mit // die Positionsangabe in der Web.config und welche Web.config verwendet werden soll folgt innerhalb der Anführungszeichen. –atIndex 0 bewirkt, dass dies der oberste Eintrag wird und alle anderen (vielleicht bereits vorhandnen) Einträge eine Position nach unten rutschen. Er würde also zu aller erst nach mystart.htm schauen und nur wenn die nicht da ist, andere htm oder asp Seiten aufrufen die hier ebenfalls noch gelistet sind.

Um einen Eintrag zu ändern, können Sie auf Set-WebConfiguration zurückgreifen. Wollen Sie also Ihre ganzen Zeiten durch eine neue, z.B. 5:00 Uhr, ersetzten geben Sie ein:

Set-WebConfiguration "/system.applicationHost/applicationPools/add[@name='NameIhresAppPools']/recycling/periodicRestart/schedule" -value (@{value="05:00:00"})

Dies löscht alle Recycling Uhrzeiten und setzt dafür 5:00 Uhr ein.

Wenn Sie einzelne Abschnitte entfernen möchten geht das mit Clear-Webconfiguration. Machen Sie doch einmal Frühjahrsputz:

Clear-WebConfiguration "/system.applicationHost/applicationPools/add[@name='NameIhresAppPools']/recycling/periodicRestart/schedule"

Das löscht Ihnen zwar den Schedule Tag (Tag=Kenn-/Auszeichnung), läßt aber den periodic Restart-Tag einfach leer stehen, was natürlich nicht gerade übersichtlich ist. Ebenso haben Sie auch noch den recycling-Tag um den periodic Restart gewickelt, der sonst auch keine Bedeutung mehr hat.

            <add name="NameIhresAppPools">

                <recycling>

                    <periodicRestart>

                    </periodicRestart>

                </recycling>

            </add>

Nun müssen Sie glücklicher Weise nicht für jeden Abschnitt einzeln ein Clear‑Webconfiguration absetzen, sondern geben einfach die höchste zu löschenden Ebene an und alles was darin kreucht und fleucht ist weg. Um den gelben Bereich zu löschen geben Sie ein:

Clear-WebConfiguration "/system.applicationHost/applicationPools/add[@name='NameIhresAppPools']/recycling"

4.6.4.8   .NET artiger Zugriff auf IIS

Das mit den Konfig-Files kann natürlich schon ziemlich häßlich sein. Wer lieber im .NET-Stil zugreifen mag, kann das natürlich auch. Ich persönlich finde es wesentlich einfacher. Hier ein Beispiel für die ApplicationPool Konfiguration:

$DefaultAppPool=Get-Item IIS:\AppPools\DefaultAppPool

Damit holen Sie sich die Konfiguration des DefaulApplicationPools in die Objektvariable $DefaultAppPool.

$DefaultAppPool | select *

Eine Pipe an Select zeigt Ihnen mal wieder die Eigenschaften an.

Hier finden Sie nun z.B. die Eigenschaft queueLength. Diese können Sie nun einfach durch eine Zuweisung ändern:

$DefaultAppPool.queueLength=2000

Aktuell ist $DefaultAppPool aber kein „Life“-Objekt. Um die Änderungen nun tatsächlich in die IIS-Konfiguration zu übernehmen, bedarf es noch eines:

$DefaultAppPool | Set-Item

Selbstverständlich gibt es auch Methoden, die Sie nutzen können. Welche verrät Ihnen, wie üblich:

$DefaultAppPool | gm

ApplicationPool beenden geht dann wie gewöhnlich:

$DefaultAppPool.stop()

Zum starten oder recyceln muss ich wohl nicht schreiben?!

4.6.4.9   ApplicationPool Status abfragen

Wenn Sie wissen möchten ob ein bestimmter ApplicationPool läuft geben Sie:

Get-WebAppPoolState NameIhresAppPools

ein.

4.6.4.10                ApplicationPool beenden und wieder starten

Das ist nun schon wieder fast zu einfach:

Stop-WebAppPool NameIhresAppPools

Start-WebAppPool NameIhresAppPools

4.6.4.11                Löschen eines ApplicationPools

Und hier ist es auch wieder ein Einfaches:

Remove-WebAppPool NameIhresAppPools

4.7     Hyper-V

Hier finden Sie praktische Beispiele wie Sie Hyper-V mit Hilfe der PowerShell verwalten können. Allgemeine Cmdlets wie Start-VM, New-VHD oder  Remove-VM erspare ich uns. Wie die funktionieren, finden Sie nach Lektüre vom Rest des Buches ganz schnell selbst heraus.

4.7.1   Allgemeine Informationen über die Gastsysteme

Um sich einen Überblick zu verschaffen oder Statistiken zu schreiben eignet sich get-vm. Durch get-vm * erhalten Sie einen Überblick über alle auf dem System eingerichteten virtuellen Computer. Mit dem Schalter –ClusterObject oder –Computername können Sie den Hyper-V-Cluster oder –Host angeben, von dem Sie die Informationen haben möchten. Natürlich können Sie die ausgegebenen Informationen wieder durch  | Select * erweitern.

4.7.1.1   Auflistung der IP-Adressen aller eingeschalteten Gastsysteme

Oft interessant ist, welche Gäste gerade genutzt werden und welche IP-Adressen diese belegen:

Get-VM | ? {$_.state –eq „running“} | Get-VMNetworkAdapter | select ` VMName,Switchname,IPAddresses | ft –auto

4.7.1.2   Beanspruchten Plattenplatz berechnen

Bei dynamischen Datenträgern möchten Sie vielleicht ganz gerne wissen, welche virtuelle Festplatte gerade wieviel Plattenkapäzität belegt:

Get-VMHardDiskDrive * | select path | ls | ft name,@{n=“Größen in GB“;e={$_.length/1GB};f=“F“} -auto

4.7.1.3   Leistungsdaten der Gastsysteme erfassen und auswerten

Um Performance Daten zu erhalten, müssen Sie zunächst die Leistungsüberwachung für die jeweilige VM (Virtual Machine = Gastsystem) mittels Enable-VMResourceMetering Gastname aktivieren. Danach können Sie dann mit Measure-VM Gastname die Leistungsauswertung der Gastmaschine abfragen.

4.7.1.4   Nicht genutzten Speicherplatz virtueller dynamischer Datenträger freigeben

Diese kleine Kombi gibt ungenutzte Kapazitäten auf allen virtuellen dynamischen Datenträgern frei:

Get-VMHardDiskDrive * | Optimize-VHD

Wenn innerhalb der Gastsysteme größere Datenmengen gelöscht wurden, kann dies auch größere Speicherfreigaben auf der Festplatte des Hostsystems bewirken. Dieser Vorgang dauert natürlich ein Weilchen. Ggf. könnnen Sie daher noch den Schalter –AsJob verwenden. Sind die Festplatten aktuell mit Schreibzugriff in eingeklinkt, kann diese Befehlskette nicht Ihre volle Wirkung entfalten. Daher bietet es sich an, dies bei geplanten Wartungsintervallen, wenn die Gastsysteme heruntergefahren sind, durchzuführen. Überprüfen lassen sich die Ergebnisse sehr schön mit der bereits unter 4.7.1.2 vorgestellten Befehlskette.

4.7.2   Snapshot von mehreren VMs gleichzeitig erstellen

Das leidige Thema Updates kann schon richtig in Arbeit ausarten. Erst Snapshots erstellen, dann die Systeme aktualisieren und die Snapshots ggf. nach erfolgreichem Update wieder löschen. Sie können mehrere Maschinen automatisch mit Snapshots versehen, indem Sie entweder mehrere Gastnamen angeben, oder einfach mit * Platzhalter arbeiten um gleich alle Gäste eines Hyper-V-Hosts zu sichern:

Get-VM * | Checkpoint-VM –Snapshotname "VorUpdate"

Um die Snapshots nach erfolgreichem Update wieder zu entfernen geben Sie folgendes ein:

Get-VMSnapshot –VMName * –Name "VorUpdate" | Remove-VMSnapshot

Leider ist die Parameterbenennung nicht so konsistent und daher einleuchtend, wie bei anderen Cmdlets.

4.7.3   Komplettes Backup von VMs erstellen (VHDs, Config, Snapshots)

Um ein komplettes Backup von VMs zu erstellen eignet sich das Cmdlet Export-VM. Damit können Sie die dazugehörigen VHDs (VirtualHardDisk = virtuelle Festplatten vom Gastsystem), die Konfiguration sowie die erstellten Snapshots in einem Ordner Ihrer Wahl abspeichern:

Export-VM –Name NamedesGastSystems –Path C: \BackupOrdner –AsJob

-AsJob empfiehlt sich, weil das natürlich ein Weilchen dauern kann bis der Vorgang abgeschlossen ist - muss aber nicht sein. Selbstverständlich dürfen Sie auch hier wieder mit * Platzhalter arbeiten, wenn Sie alle VMs eines Hosts sichern möchten.

Die Sache hat allerdings einen kleinen Haken: Der Export selbst ist nur lokal (nicht auf ein Netzlaufwerk) möglich. Nach dem Export kann man sie aber natürlich wo anders hin verschieben. Lt. Microsoft soll die Sicherung auf ein Netzlaufwerk ab 2012 R2 funktionieren, was ich aber leider nicht bestätigen kann. Unter 2012 (ohne R2) muss die VM vorher auch noch heruntergefahren werden.

Um das Backup wiederherzustellen, verwenden Sie das Cmdlet Import-VM:

Import-VM –Path C:\BackupOrdner\NameDerVM\Virtual Machines\GUID.xml –Copy

Der Pfad muss auf die XML-Konfigurationsdatei (statt einfach nur dem Sicherungsverzeichnis) verweisen. Der Schalter –Copy sorgt dafür, dass die vhd-Datei wieder an den Standardablageort des Hosts für vhds kopiert wird. Ohne diesen Schalter, verwendet Hyper-V die Datei direkt aus der Sicherung.

4.8     Lokale clientseitige Benutzerwerwaltung

Das nachfolgende Kapitel über Active Directory erläutert die zentrale Benutzerverwaltung. Doch egal, ob Sie nun mit Ihrem PC in einer Domäne oder in einer Arbeitgruppe arbeiten, gibt es doch viele Dinge die man gerne auf einem Mitgliedsserver oder einer Arbeitsstation über Benutzer und Gruppen wissen oder verändern möchte.

4.8.1   Wer bin ich, oder WhoAmI

Um mehr, z. B. innerhalb eines Skriptes, über den Benutzer mit dessen Rechten und Berechtigungen das Skript läuft herauszufinden können Sie:

$user=[Security.Principal.WindowsIdentity]::GetCurrent()

befragen. Das Ergebnis wird in der Variablen $user abgelegt. In der Eigenschaft AuthenticationType finden Sie Informationen, ob der Benutzer mittles NTLM (steht auch bei einer lokalen Anmeldung) oder Kerberos angemeldet wurde. In Name ist der Anmeldenamen in Form von Domäne\Benutzer bzw. Computer\Benutzer bei einer Anmeldung mit einem lokalen Benutzerkonto hinterlegt. Bei User ist die SID des Benutzers hinterlegt und bei Groups finden Sie die SIDs der Gruppen in denen der Benutzer Mitglied ist.

4.8.2   Benutzer Konten Steuerung UAC (User Account Control)

Admin ist nicht gleich Admin! Obwohl Sie Mitglied im Club der lokalen Administratoren Gruppe sind, kann es sein, dass Sie nicht wirklich über alle Berechtigungen verfügen. Seit Windows Vista bzw. Server 2008 gibt es die Benutzerkontensteuerung. Wenn Sie einmal eine einfache Komandozeile aufmachen (dazu tippen Sie in das Suchfeld des Startmenüs einfach cmd und drücken Enter) und dort whoami /groups eintippen und mit Enter bestätigen, werden Sie im Normalfall folgendes sehen:

Der hell markierte Bereich zeigt „nur“ die Mittlere Verbindlichkeitsstufe und die SID S-1-16-8192 an. Wenn Sie die Konsole wieder schließen und erneut öffnen und dieses Mal aber beim Enter drücken bitte noch die Shift Taste (die zum Großbuchstaben schreiben) festhalten, werden Sie feststellen, dass die UAC anspringt und Sie ggf. nach Administrator Anmeldeinformationen fragt. Wenn Sie nun erneut whoami /groups ausführen werden Sie nun feststellen, dass in der letzten Zeile eine Hohe Verbindlichkeitsstufe mit der SID S-1-16-12288 haben. Jetzt sind Sie vollwertiger Admin und können tun und lassen was Sie möchten. Auch alle von hier aus gestarteten Programme erben diese göttlichen Mächte. Z. B. nervt der Explorer nicht mehr, wenn Sie Dateien im C:\Windows\System32 Verzeichnis bearbeiten. Whoami /groups ist ja ganz nett, aber nicht Objekt orientiert.

Wollen Sie in der PowerShell wissen, ob Sie gerade als heiliger Mann unterwegs sind holen Sie sich, wie bereits im vorangegangenen Abschnitt Wer bin ich beschrieben, erst den aktuellen Benutzer in die Variable $user und dann geben Sie folgendes ein:

(New-Object Security.Principal.WindowsPrincipal`
$user).IsInRole([Security.Principal.WindowsBuiltinRole]::`
Administrator)

Wenn Sie ein False zurückbekommen sind Sie gerade als normal Sterblicher unterwegs. Kommt ein True heraus…viel Spaß beim randalieren.

Da fällt mir eine nette Funktion ein:

Function HolyMan {

 $user=[Security.Principal.WindowsIdentity]::GetCurrent()

 (New-Object Security.Principal.WindowsPrincipal`
 $user).IsInRole([Security.Principal.WindowsBuiltinRole]::`
 Administrator)

}

Wenn Sie das in Skript, Profile-Skript oder Modul einbauen, können Sie jederzeit eine Abfrage machen wie:

If (Holyman) {

 “Seht, ich habe Feuer gemacht!“

 # Tun Sie was Böses, z.B. Festplatten formatieren ;-)

} else {

 “Looser!“

 # Schreiben Sie ein paar belanglose Texte

}

Nun können Sie hergehen und Ihr Script z.B. mit automatischer UAC ausstatten. Sprich, wird das Script nicht priviligiert gestartet geht automatischer die UAC-Box auf und fragt nach erhöhung der Rechte/Berechtigungen. Kopieren Sie dazu dies hier an den Anfang Ihres Scriptes:

$id=[System.Security.Principal.WindowsIdentity]::GetCurrent()

$principal=New-Object System.Security.Principal.WindowsPrincipal($id)

if(!$principal.IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator)) {

 $powershell=[System.Diagnostics.Process]::GetCurrentProcess()

 $psi=New-Object System.Diagnostics.ProcessStartInfo $powershell.Path

 $script=$MyInvocation.MyCommand.Path

 $prm=$script

 foreach($a in $args) {

  $prm+=' '+$a

 }

 $psi.Arguments=$prm

 $psi.Verb="runas"

 [System.Diagnostics.Process]::Start($psi) | Out-Null

 return

}

Ihr Script wird dann automatisch das Script noch einmal in einem weiteren PowerShell-Prozess mit erhöhten Rechten starten.

4.9     Active Directory

Die Active-Directory-Verwaltung ist womöglich neben der GUI-Programmierung eine der Königsdisziplinen der PowerShell. Hier werden Sie mit der PowerShell nicht nur einfach Benutzer anlegen, sondern werden auch einen Weg finden die PowerShell als grafisches Element in die Managementkonsole (mmc) von Windows zu integrieren und somit ein echte „Killerapplikation“ für die AD-Verwaltung zu schreiben, die perfekt auf Ihr Unternehmen abgestimmt ist. Stellen Sie sich vor ein Administrator klickt auf „Neuen Benutzer anlegen“ und daraufhin werden Sie gefragt, ob das ein Mitarbeiter im Verkauf oder der Produktion ist. Je nachdem was der Admin angeklickt hat, werden automatisch virtuelle Maschinen angelegt, Homeverzeichnisse erstellt, per FTP auf IIS bereit gestellt und vielleicht in Sharepoint eingebunden…der Phantasie sind keine Grenzen mehr gesetzt.

Voraussetzung für die nachfolgenden Beispiele ist immer das importierte Modul ActiveDirectory. Da dies ein Modul ist und Module in Version 1.0 noch nicht unterstützt werden, rate ich dringend auf eine aktuelle Version der PowerShell zu aktualisieren. Auf Domänencontrollern steht Ihnen dies automatisch zur Verfügung. Sollten Sie noch PowerShell 2.0 verwenden müssen Sie die Unterstützung durch das Modul erst mittels Import-Module ActiveDirectory aktivieren. Auf Client Computern müssen Sie zusätzlich zunächst RSAT installieren und dann das Feature Remote Server Verwaltungs Tools\Rollenverwaltungstools\AD DS- und AD LDS-Tools\Active Directory- Modul für Windows PowerShell aktivieren.

4.9.1   Navigieren im AD PS-Drive

Damit Sie im PS-Drive AD: navigieren können, muss auf jeden Fall vorher das Modul geladen sein (auch bei PowerShell 3.0 oder höher – cd AD: lädt leider nicht automatisch das Modul, sondern nur ein Cmdlet wie z.B. Get-ADUser). Damit wissen Sie nun schon einmal wie das PS-Drive für den Active-Directory-Zugriff lautet und wie Sie dort hin wechseln.

Hier angekommen können Sie sich mit Get-ChildItem einen Überblick verschaffen, was Ihnen hier alles geboten wird. In Regel finden Sie hier eine Auflistung der bekannten Active-Directory Partitionen. Allen voran natürlich die aktuelle Domänenpartition (mit den Benutzern, Gruppen und Computer, etc…), aber auch die Konfigurations-Partition (mit den Angaben zum Aufbau der Domänenstrukturen), die Schemapartition (enthält Angaben wie die Objekte beschaffen sind, also z.B. ob es am Benutzerobjekt eine Telefonnummer und/oder ein Kostenstellenattribut gibt) sowie die Forest- und Domain-DNS-Zones (mit den DNS Einträgen der Active-Directory integrierten Zonen).

Wenn Sie nun versuchen z.B. in die Domänen Partition zu wechseln, so wie Sie es vom Dateisystem auf der Festplatte gewohnt sind, werden Sie mit einem Fehler konfrontiert:

Zugegen, ein bisschen blöd, aber ist es dennoch möglich in die Domänenpartition mittel LDAP‑Pfad‑Angabe zu wechseln:

cd "DC=ads,DC=martinlehmann,DC=home"

Wie Sie sicher schon bemerkt haben finden Sie die Information zum LDAP-Pfad in der Spalte DistinguishedName, wenn Sie das Get-ChildItem im AD-Drive aufgerufen haben. Die Anführungszeichen dürfen Sie auch nicht vergessen!

Wenn Sie nun z.B. in der Domänenpartition angekommen sind, können Sie sich natürlich wieder mit Get-ChildItem (dir oder ls funktionieren natürlich genausogut) erst einmal umschauen.

Hier finden Sie nun z.B. die Organisations-Einheit Domain Controllers. Ein einfaches cd "Domain Controllers" funktioniert leider wieder nicht, aber immerhin müssen Sie nicht den kompletten LDAP-Pfad wie er wieder unter DistinguishedName angezeigt wird eintippen, sondern ein cd "OU=Domain Controllers" als relative Pfadangabe reicht aus. Ebenso kommen Sie mit cd .. auch wieder eine Ebene nach oben.

Eine neue Organisations-Einheit an der aktuellen Position können Sie am kürzesten auf diese Weise anlegen:

md "OU=Test"

md ist, wie Sie vielleicht bereits vermutet haben, wieder mal ein Alias und zwar auf: New-Item.

Also eigentlich können Sie hier ähnlich wie im Filesystem agieren, dürfen dabei aber nie den LDAP‑Bezeichner des Objektes vergessen:

DC=Domain Component=Domänenbestandteil
OU=
Organizational Unit=Organisations Einheit
CN=Common Name=Allgemeiner Name

Damit erschöpft sich auch schon Microsofts LDAP-Vorrat. Ein O oder C z.B. gibt’s hier nicht).

Zum praktischen Arbeiten auf die Schnelle, ist wohl dsa.msc oder adsiedit.msc etwas sinnvoller und zum skripten der Zugriff auf die ActiveDirectory-Cmdlets ohne AD: PS-Drive. Nettes Feature um die Leute zum Staunen zu bringen, aber eben leider auch nicht mehr.

4.9.2   AD-Objekt-Verwaltung

Seit PowerShell 2.0 stehen Ihnen eine ganze Menge spezieller, nur für die Active-Directory Verwaltung gedachter, Cmdlets zur Verfügung. Falls Sie noch PowerShell 2.0 einsetzen müssen Sie zunächst das Modul dafür laden:

Import-Module ActiveDirectory

Eine komplette Auflistung aller AD-Befehle erhalten Sie mit:

Get-Command -module ActiveDirectory

Alle hier einzeln aufzulisten, um Seiten zu schinden, erspare ich uns, denn auch hier funktioniert Get‑Help.

Allerdings möchte ich den Unerfahrenen einen einfachen Einstieg bieten und die Cracks auf ein paar interessante oder auch problematische Cmdlets hinweisen.

4.9.2.1   Get-ADUser

Mit dem Cmdlet Get-ADUser können Sie, wie der Name schon vermuten lässt, Benutzer aus dem Active Directory abfragen. Aber schon dieses „einfache“ Cmdlet hat seine Tücken.

In der simpelsten Variante geben Sie einfach ein:

Get-ADUser NameDesBenutzers

Der NameDesBenutzers kann dabei der SAMAccountname, DistinguishedName, die GUID oder die SID sein. Hierbei bezieht sich die Angabe auf den Parameter –Identity (die mittlere Variante der unter Syntax aufgelisteten Möglichkeiten, wenn Sie Get-Help bemühen).

Dies liefert Ihnen die folgenden Attribute des Benutzers:

·        DistinguishedName

·        Enabled

·        GivenName

·        Name

·        ObjectClass

·        ObjectGUID

·        SamAccountName

·        SID

·        Surname

·        UserPrincipalName

Als aufmerksamer Leser kenn Sie ja bereits den Trick mit:

Get-ADUser NameDesBenutzers | Select *

oder

Get-ADUser NameDesBenutzers | Get-Member

…um an die wertvollen versteckten Details zu kommen. Aber, hey, PowerShell kann auch ganz schön gemein sein, indem Sie einfach nicht alles liefert was man sich so erwünscht. Es wird nämlich durch diesen Trick kein einziges weiteres Attribut geliefert. In Active Directory Benutzer und Computer finden Sie da doch schon viel mehr. Also wo ist der Rest?

Get-ADUser verfügt wie die meisten Get-Cmdlets für Active Directory einen Parameter ‑Properties. Probieren Sie doch einmal:

Get-ADUser NameDesBenutzers -Properties *

Da brauchen Sie gar keinen Select mehr ;-). Alles da, was das Herz begehrt. Stellt sich nur die Frage: Warum macht Microsoft so gemeine Sachen? Stellen Sie sich mal eine Benutzerverwaltung eines internationalen Konzerns vor, statt Ihrer Domäne zu Hause. Sagen wir mal, wir haben in einer Domäne 80000 Benutzer. Da macht es schon einen kleinen Unterschied in dem verursachten Netzwerkverkehr, wenn man nur 10 Attribute aller Benutzer abfragt, oder alle Attribute. Deswegen, werden nur diese 10 wichtigsten immer mit geliefert und alle anderen gewünschten, müssen Sie in einer Komma-separierten Liste hinter den Parameter -Properties schreiben. Ein * stellt hier natürlich die brachialste Lösung dar. Bitte denken Sie an die Kollegen aus der Netzwerkabteilung, bevor Sie ein * an dieser Stelle benutzen.

Mit Get-ADUser –Identity können Sie immer nur einen einzelnen Benutzer aus dem Active‑Directory holen. Wie eben erwähnt gibt es aber noch zwei weitere Varianten Benutzer aus dem Active‑Directory abzufragen:

Get-ADUser –Filter

Get-ADUser –LDAPFilter

Zunächst Get-ADUser –Filter: Mit dem Schalter –Filter oder einfach kurz –f können Sie einen Filter definieren, um festzulegen welche Benutzer aus dem Active‑Directory zurück geliefert werden sollen. Die Beschreibung im Get-Help hierzu ist zugegeben etwas unübersichtlich, aber gar nicht so schwierig wie es in Get-Help aussieht, denn es funktioniert ähnlich wie bei einem Where‑Object. Nur, dass Sie hier nicht die Variable $_. benutzen dürfen. Hier mal ein Vergleich:

Get-ADUser –f * | Where-Object {$_.enabled –eq $false}

Get-ADUser –f {enabled –eq $false}

Gut die erste Syntax kenn‘ ich - die benutze ich in Zukunft. Schlechtes Juju! Denn, bei der ersten Variante werden erst alle Benutzer aus der Domäne auf Ihren Rechner gesaugt und dann im RAM-Speicher ausgewertet. Bei der 2. Variante haben Sie nicht nur weniger Tipparbeit, sondern den weitaus größeren Vorteil, dass die Frage von Ihrem Client auch so formuliert an den Domänen Controller geht und dieser nur noch die Benutzer liefert, die dem Filter entsprechen. Also wieder einen Haufen Netzwerkverkehr gespart. Im Gegensatz zu Where-Object gibt es noch ein paar Einschränkungen:

-match und alle Varianten (z.B. -cnotmatch) sind nicht erlaubt.

–like unterstützt nur den * Platzhalten, nicht aber das ?.

Möchten Sie alle Benutzer die in einem bestimmen Attribut (z.B. Mail) irgendeinen Wert haben:

Get-ADUser -Filter {mail -like "*"}

Möchten Sie alle Benutzer die in einem bestimmen Attribut (z.B. Mail) keinen Wert haben:

Get-ADUser -Filter {mail -notlike "*"}

Achtung!

Get-ADUser -Filter {mail -like ""}

Funktioniert nicht!

Leider funktioniert Filter auch nicht mit allen Attributen gleichermaßen:

Die Lösung zu diesem speziellen problem finden Sie unter: Active Directory Objekte de-/aktivieren.

Für Leute aus der Linux Ecke ist die Syntax des Parameter –LDAPFilter vielleicht bereits in Fleisch und Blut. Hier auch noch ein paar Beispiele:

Get-ADUser -LDAPFilter "(SAMAccountName=Mar*)"

Liefert alle Benutzer deren SAMAccountName mit Mar beginnt.

Get-ADUser -LDAPFilter "(&(SAMAccountName=Mar*)(mail=*))"

Liefert alle Benutzer deren SAMAccountName mit Mar beginnt und die eine E-Mail-Addresse eingetragen haben.

Get-ADUser -LDAPFilter "(|(SAMAccountName=Mar*)(mail=*))"

Liefert alle Benutzer deren SAMAccountName mit Mar beginnt oder die eine E-Mail-Addresse eingetragen haben.

Mehr Details zu den Filtermöglichkeiten finden Sie mit:

help about_activedirectory_filter

Für Multi-Domänen-Umgebungen, oder Domänen mit RODC ist der Schalter –Server ggf. in Zusammenhang mit –Credential auch sehr interessant. Mit –Server können Sie die abzufragende Domäne, oder gar den Domänencontroller festlegen. Ist immer blöd, wenn man z.B. mit New-ADUser zufällig auf einem RODC landet  und versucht Benutzer anzulegen ;-).

Die restlichen Parameter sind, denke ich,  im Get-Help zur Genüge erklärt.

4.9.2.2   Get-ADGroup

Mit Get-ADGroup können Sie die Gruppen aus dem Active-Directory beziehen. Dieser Befehl funktioniert im Großen und Ganzen ähnlich Get-ADUser.

Hier sind besonders interessant, die Eigenschaften GroupCategory und GroupScope.

Die GroupCategory gibt an, ob es sich um eine Security-Group (Sicherheitsgruppe) oder Distribution-Group (Verteilergruppe) handelt. Im normal Fall werden DistributionGroups gar nicht oder kaum genutzt. In Exchange-Landschaften, kann es vereinzelt zu DistributionGroups kommen. Wenn man die nicht mit dabei haben will, formuliert man einen entsprechenden Filter. Beispielsweise:

Get-ADGroup –f {GroupCategory –like "Security"}

Den Gruppentyp (DomainLocal, Global, Universal) können Sie über die Eigenschaft GroupScope filtern:

Get-ADGroup –f {GroupScope –like "Global"}

Listet nur Globale Gruppen.

4.9.2.3   Gruppenmitgliedschaften ändern

Mit Add-ADGroupMember können Sie Gruppen neue Mitglieder hinzuügen. Beispiel:

Add-ADGroupMember GruppeDerDieMitgliederHinzugefügtWerden Benutzer,GruppeA,GruppB

Remove-ADGroupMember GruppeDerDieMitgliederHinzugefügtWerden Benutzer,GruppeA,GruppB

Nimmt die angegebenen Benutzer und Gruppen wieder aus einer Gruppe heraus.

4.9.2.4   Get-ADComputer

Get-ADComputer ist ebenfalls eines der erwähnenswerten Standard Cmdlets die zur Abholung von Computer Objekten aus dem Active Directory dient. Funktionsweise ist wieder ähnlich Get‑ADGroup oder Get-ADUser. Hier sind in den erweiterten Attributen (-Properties) diese Angaben recht interessant:

OperatingSystem                           : Windows Server 2008 R2 Enterprise
OperatingSystemServicePack     : Service Pack 1
OperatingSystemVersion            : 6.1 (7601)

4.9.2.5   Get-ADObject und Restore-ADObject

Funktioniert wie die bereits erwähnten CMDlets, aber der Vor- und gleichzeit auch Nachteil daran ist, dass Sie damit auf jedes X-beliebige Objekt im Active-Directory zugreifen können. D.h. hier sollten Sie in Ihrem Filter gleich noch mit einbauen, welchen ObjektTyp = objectclass Sie eigentlich im Visir haben. Ansonsten bekommen Sie vielleicht auch Drucker und hatten eigentlich nur an Computer gedacht. Um also Ihre Druckerwarteschlangen im Active-Directory zu finden, probieren Sie das hier:

Get-ADObject -f {objectclass -like "printQueue"}

Weiterhin gibt es hier noch einen interessanten Schalter, der den anderen CMDlets fehlt: ‑IncludeDeletedObjects

Damit können Sie sich auch bereits gelöschte Objekte ausgeben lassen und mit etwas Glück an das CMDlet Restore-ADObject übergeben um es wieder herzustellen. Siehe hierzu die Abschnitte über Eigene AD - Papierkorb Cmdlets und GUI für AD Papierkorb.

4.9.2.6   Active Directory Objekte de-/aktivieren

Um alle gesperrten Benutzerkonten zu aktivieren, können Sie auf die folgende Kombination setzen:

Search-ADAccount –LockedOut| Enable-ADAccount

Mit Get-ADUser können Sie ja leider nicht direkt mit dem Filter {lockedout –eq $true} arbeiten wie in dem Abschnitt über Get-ADUser bereits hingewiesen. Achtung die Kombination hat einen Haken! In der Regel haben Sie da natürlich dann auch das eingebaute Gast- und Administrator Konto aktiviert, was Sie aber in der Regel nicht wollen. Also die sollten vielleicht erst noch anderweitig (z.B. mit eine Where-Object) ausgefiltert werden.

Wo es ein Enable-ADAccount gibt, ist ein Disable-ADAccount natürlich nicht weit.

Disable-ADAccount NameZumBeispielDesComputers

Deaktiviert das entsprechende Computerkonto. Die beiden CMDlets sind also nich wie Get-ADUser oder Get-ADComputer bereits auf einen bestimmten Objekttyp fixiert, sondern lassen sich auf alle AD-Objekte anwenden, die ein Attribut Enabled besitzen.

4.9.2.7   Active Directory Objekte anlegen

Wohl das komplexeste New-AD.. CMDlet ist New-ADUser. Dies soll hier als Beispiel stellvertretend für die anderen dienen. Zu fast jedem Attribut verfügt das New-ADUser CMDlet über einen eigenen Schalter mit entsprechnd die Angaben gemacht werden können. Es gibt allerdings auch Attribute, wie z.B. networkAddress, die man ebenfalls gerne ausfüllen möchte, wofür es aber offensichtlich keinen Schalter gibt. Nun, den gibt’s schon, doch ist der zwischen den anderen „gefühlten 500“ nur schwer zu finden. Das Zauberwort ist –OtherAttributes. Die Angaben welche Eigenschaft mit welchem Inhalt gefüllt werden soll muss in Form einer Hashtable übergeben werden. Um nun also die networkAddress bei der Neuanlage eines Benutzers zu füllen gehen Sie so vor:

New-ADUser NameDesBenutzers -OtherAttributes @{‘networkAddress’=’1.2.3.4’}

Ein Benutzer kann nur gleich aktiviert (durch den Schalter –Enabled) werden, wenn er auch ein Kennwort (durch den Schalter –AccountPassword) hat, dass den Kennwortrichtlinien der Domäne entspricht. Der Schalter -AccountPassword akzeptiert allerdings nur verschlüsselte Kennwort-Angaben. Lösung:

$Password=ConvertTo-SecureString -string "4$Geheimer" -asplaintext -Force

New-ADUser Name –Enabled -AccountPassword $Password

4.9.2.8   Directory Objekte ändern

Hier kommen die Set-AD… CMDlets ins Spiel. Auch hier ist PowerShell im Großen und Ganzen wieder mit Get-Help selbsterklärend. Gibt es zu einer Eigenschaft keinen entsprechenden Schalter (für die Postleitzahl beim User gibt es z.B. –PostalCode, nicht aber für die Telefonnummern hinter der Andere Schaltfläche in Active Directory Benutzer und Computer beim Benutzerobjekt – die sind in der Eigenschaft othertelephone versteckt), sind die Schalter –Add, -Remove oder –Replace interessant. Um die Eigenschaft othertelephone mit einem weiteren Wert zu versehen können Sie z.B.:

Set-ADUser NameDesBenutzers –Add @{othertelephone=08154711,081524}

verwenden.

Um nur die Nummer 08154711 aus der Liste zu ersetzen, ist entsprechend der PowerShell-Dokumentation so vorzugehen:

Set-ADUser NameDesBenutzers –Replace @{othertelephone=08154711,047110815}

Das stimmt aber nicht so ganz. Denn statt die 08154711 durch die Nummer 047110815 zu ersetzen, wird die komplette Liste an Nummern durch die 047110815 ausgetauscht. Allerdings läßt sich eine bestimmte Telefonnummer gezielt entfernen:

Set-ADUser NameDesBenutzers –Remove @{othertelephone=047110815}

Um tatsächlich nur die angegebebene Nummer auszutauschen, können Sie auf diesen kleinen Workaround zurückgreifen:

Set-ADUser NameDesBenutzers –Add @{othertelephone=047110815} `
–Remove @{othertelephone=08154711}

Den Backtick habe ich nur wegen dem Zeilenumbruch hier im Buch eingefügt. Natürlich dürfen Sie das auch in einer Zeile schreiben.

4.9.2.9   Active Directory Objekte verschieben

Mittels Move-ADObject können Sie unabhängig vom Objekttyp, das Objekt an eine andere Stelle innerhalb Ihrer Active Directory Domäne verschieben. Über Domänen Grenzen hinweg brauchen Sie entsprechende Migrationtools.

Move-ADObject DistinguishedNameOderGuid –TargetPath "OU=NeuesZuhause,DC=Name,DC=Land"

Da hier der SAMAccountName nicht erlaubt ist, wohl aber ein komplettes Active-Directory-Objekt, können Sie natürlich gerne ein wenig Kreativität an den Tag legen:

Get-ADUser SAMAccountName | Move-ADObject–TargetPath "OU=NeuesZuhause,DC=Name,DC=Land"

4.9.2.10                Active Directory Objekte löschen

Hier möche ich gar nicht viel schreiben, außer, dass es natürlich zu den meisten New-CMDlets auch entsprechend Gegenspieler mit Remove- als Verb gibt.

4.9.2.11                Active Directory Gruppenrichtlinien

Auch hier müssen Sie zunächst wieder bei PowerShell 2.0 das Modul für Gruppenrichtlinienunterstützung laden, da diese Cmdlets nicht im ActiveDirectory Module enthalten sind:

Import-Module GroupPolicy

Zu den Cmdlets selbst gibt es wohl nicht viel zu erklären, was Get-Help nicht auch ordentlich erklären würde. Für die Cracks, die auch mal Gruppenrichtlinien migrieren, oder mit WMI-Filtern hantieren müssen, empfehle ich das GPOMigration Module: https://gallery.technet.microsoft.com/Migrate-Group-Policy-2b5067d8

4.9.2.12                Computer identifizieren von denen sich mit falschen Passwort angemeldet wurde

Wenn ein Benutzer sich eine bestimmte Anzahl (gesteuert durch AD-Gruppenrichtlinien) mal falsch angemeldet hat wird sein Benutzerkonto gesperrt. Den Benutzer zu finden ist recht einfach, der wird bestimmt schnell bei der Hotline anrufen. Aber die Maschine von der aus versucht wurde sich einzuloggen zu finden ist mühseelig. Daher hier ein kleines Skript, dass hilft ggf. die „bösen Jungs“ zu finden:

10.        # Skript um die Station mit fehlerhaften Logins ausfindig machen, die zur Sperrung des Benutzeraccounts führte.

11.        param(

12.         $User=$Env:Username,

13.         $Domain=$Env:Userdomain

14.        )

15.        # Schreibbare Domänencontroller identifizieren

16.        $DomainControllers = Get-ADDomainController -Filter {ISReadOnly -eq $false} -Server $Domain

17.        # PDCEmulator identifizieren

18.        $PDCEmulator=($DomainControllers | ? {$_.OperationMasterRoles -contains "PDCEmulator"})

19.        $LockOutInfo=@()

20.        # Die Details zur Sperrung auf den einzelnen DCs abrufen

21.        Foreach($DC in $DomainControllers) {

22.         Try {

23.          $UserInfo=Get-ADUser $User -Server $DC.Hostname -Properties AccountLockoutTime,LastBadPasswordAttempt,BadPwdCount,LockedOut -ErrorAction Stop

24.         }

25.         Catch {

26.          Write-Warning $_

27.          Continue

28.         }

29.         If ($UserInfo.LastBadPasswordAttempt) {

30.          $LockOutInfo += New-Object -TypeName PSObject -Property @{

31.           Name=$UserInfo.SamAccountName

32.           SID=$UserInfo.SID.Value

33.           LockedOut=$UserInfo.LockedOut

34.           BadPwdCount=$UserInfo.BadPwdCount

35.           BadPasswordTime=$UserInfo.BadPasswordTime

36.           DomainController=$DC.Hostname

37.           AccountLockoutTime=$UserInfo.AccountLockoutTime

38.           LastBadPasswordAttempt=($UserInfo.LastBadPasswordAttempt).ToLocalTime()

39.          }

40.         }

41.        }

42.        $LockOutInfo | Format-Table -Property Name,LockedOut,DomainController,BadPwdCount,AccountLockoutTime,LastBadPasswordAttempt -AutoSize

43.        Try {

44.         $LockOutEvents = Get-WinEvent -ComputerName $PDCEmulator.HostName -FilterHashtable @{LogName='Security';Id=4740} -ErrorAction Stop | Sort-Object -Property TimeCreated -Descending

45.        }

46.        Catch {          

47.         Write-Warning $_

48.         Continue

49.        }

50.        Foreach($Event in $LockOutEvents) {            

51.         If($Event | ? {$_.Properties[2].value -match $UserInfo.SID.Value}) { 

52.          $Event | Select @{N='User';E={$_.Properties[0].Value}},@{N='DomainController';E={$_.MachineName}},@{N='EventId';E={$_.Id}},@{N='LockedOutTimeStamp';E={$_.TimeCreated}},@{N='Message';E={$_.Message -split "`r" | Select -First 1}},@{N='LockedOutLocation';E={$_.Properties[1].Value}}

53.         }

54.        }

Der Aufruf ist simpel:

./Identifywronglogon.ps1 –User NameDesGesperrtenBenutzers`
–Domain NameDerDomäneDesBenutzers

Die letzte Zeile der Ausgabe zeigt dann die Station an von der aus die fehlerhaften Anmeldungen statt gefunden haben.

Zunächst werden alle Domänencontroller nach ungültigen Anmeldeversuchen des Benutzers untersucht (Bis Zeile 41). Die Ergebnisse werden im Objekt $LockOutInfo hinterlegt und in Zeile 42 präsentiert. In Zeile 44 wird die Ereignisanzeige des PDCEmulators auf die entsprechenden Anmeldeereignisse hin untersucht. In Zeile 52 wird dann der PC von dem die Sperrung ausgelöst wurde herausgefiltert.

4.9.2.13                Name zu ForeignSecuriptyPrincipal finden

Manchmal ist es wichtig zu Benutzer und Gruppen aus anderen Gesamtstrukturen (Forests) den Namen herauszufinden. In der Active Directory Benutzer und Computer Konsole passiert das automatisch, aber bei Abfragen mit der PowerShell finden Sie in der Regel von Benutzern/Gruppen aus anderen Gesamtstrukturen nur die SID. Diese sind in der Domäne im Container ForeignSecurityPrincipals abgelegt. Aber auch dort finden Sie leider nur die SIDs und keine Namen. Bei Microsoft finden Sie unter diesem Link: https://gallery.technet.microsoft.com/scriptcenter/Convert-Foreign-Security-1fdbaf46

die Function Convert-FspToUsername die das für Sie erledigt.

4.9.2.14                Leere OrganizationalUnits (OUs) finden

Vielleicht möchten Sie gerne wissen, welche OUs Sie eigentlich entsorgen könnten, da Sie keine Objekte beinhalten. Das Problem dabei ist, dass an der OU keine Eigenschaft existiert, die Aufschluß darüber gibt, ob Sie weitere Objekte beinhaltet. Und so ist das PS-Drive AD: schließlich doch zu etwas nutze ;-).

Get-ADOrganizationalUnit –Filter * | Foreach {
 if (!(ls "AD:\$($_.Distinguishedname)")) {$_.Distinguishedname}
}

4.9.3   AD, Powershell und mmc in einer Oberfläche kombinieren

Die MMC (Microsoft-Management-Console) wird oft verteufelt, aber auch unterschätzt! Zugegeben, sie ist manchmal etwas gemütlich, aber dafür sehr anpassungsfähig. So hat man die Möglichkeit sich eine komplett angepasste Verwaltungsoberfläche aus verschiedenen Teilen zusammenzubauen:

4.9.3.1   MMC erstellen und an eigene Bedürfnisse anpassen

Um das Console Root mit Elementen zu bestücken starten Sie zunächst eine leere mmc indem Sie diese 3 Buchstaben einfach im Suchbereich der Startmenüs, von der PowerShell oder der herkömmlichen cmd.exe eintippen und ausführen. In der Menüleiste klicken Sie auf Datei Snap-In hinzufügen/entfernen:

Hier können Sie nun auswählen welche mmc Snap-Ins in Ihrer Super-Konsole zusammengestellt werden sollen. Auf der linken Seite wählen Sie aus, was mit einem Klick auf Hinzufügen auf der rechten Seite landet und somit Bestandteil Ihrer Konsole wird:

Die Erklärung was die Knöpfe ganz rechts (Entfernen, Nach oben/unten) bewirken erspare ich mir. Probieren Sie es einfach aus. Mit einem Klick auf Erweitert können Sie mit Hilfe des Snap-Ins (auf der linken Seite verfügbar) Ordner Strukturen wie auf einer Festplatte anlegen und bestimmen in welchem Unterordner das jeweilige Snap-In landet.

Hoffentlich wird Ihre Konsole nie so gigantisch ;-). Umbenennen können Sie die Ordner Elemente nach einem Klick auf OK, per Rechtsklick auf den Ordner in der Konsole.

Selbst Webseiten können hier mit Hilfe des Snap-Ins Link auf Website eingebaut werden. Es erscheint ein Assistent, bei dem Sie lediglich die URL und eine Bezeichnung für den Knoten angeben müssen:

Selbstverständlich können Sie später jederzeit nicht mehr benötigte Snap-Ins entfernen oder neue hinzufügen. Doch um diese Zusammenstellung nun dauerhaft verfügbar zu machen müssen Sie Ihre Auswahl erst mittels Datei/speichern unter irgendwo unter einem sinnvollen Namen abspeichern. Als Ort würde ich Ihnen entweder das Desktop (C:\Users\Ihr Benutzername\Desktop) oder das Startmenü (Bei Windows 7 z.B.: C:\Users\Ihr Benutzername\AppData\Roaming\Microsoft\Windows\Start Menu\Programs – Falls Sie den Pfad nicht finden, schalten Sie vorher im Explorer mit Alt,Extras,Ordneroptionen,Ansicht die geschützten Systemdateien und Ausgeblendetet Dateien,Ordner und Laufwerke ein) empfehlen. Wie?! Den Namen soll ich Ihnen auch noch auf den Bauch binden? Wie wär’s mit KillerKonsle.msc? ;-) Nun können Sie die Konsole schliessen und jederzeit wieder aufrufen und benutzen, oder weiter daran feilen.

Sollten Sie die Konsole für andere Administratoren oder gar Benutzer zur Verfügung stellen wollen können Sie Ihre Konsole wie eine normale von Windows mitgelieferte Konsole wie z. B. Active Directory Benutzer und Computer oder DNS aussehen lassen. Dazu können Sie in der Menüleiste unter Ansicht Elemente aus der Konsole entfernen und unter Datei/Optionen den Konsolenmodus auf Benutzermodus – beschränkter Zugriff, Einzelfenster setzen bevor Sie speichern. Aber Achtung! Danach wird es schwer die Konsole selbst wieder für Anpassungen zugänglich zu machen. Sie sollten die eingeschränkte Fassung daher immer in einer separaten Datei abspeichern. Mehr Details zu den Einschränkungen will ich an dieser Stelle nicht preisgeben, da dies ja ein Buch über PowerShell und nicht über die msc ist. Die wichtigsten Informationen haben Sie, um effizient damit arbeiten zu können.

Eine Kleinigkeit soll aber noch zur Vorbereitung verraten werden. Die Favoriten. Ähnlich dem InternetExplorer können Sie einzelne Knoten über den Menüeintrag Favoriten für den Schnellzugriff hinzufügen. Diese Favoriten tauchen dann zunächst im Favoriten menü als weiterer Punkt zum anklicken auf:

4.9.3.2   Mit der Aufgabenblockansicht zusätzliche Funktionalität nachrüsten

Wenn Sie nun Ihre Konsole zumindest einmal mindestens mit Active Directory Benutzer und Computer bestückt haben können Sie sich an das Aufpeppen der Konsole mit Hilfe der Aufgabenblockansicht machen. Um die Aufgabenblockansicht einzurichten, klicken Sie zunächst im linken Navigationsbereich mit der rechten Maustaste und es erscheint ein Kontextmenü:

Hier wählen Sie Neue Aufgabenblockansicht aus. Auf der ersten Willkommen-Seite können Sie gleich auf Weiter klicken. Auf der AufgabenblockStil- Seite können Sie die grafische Darstellung Ihrer Aufgabenblockansich anpassen. Auch hier können Sie auf Weiter klicken, wenn Sie mit dieser Darstellung einverstanden sind. Aber die Seite Aufgabenblockwiederverwendung ist wichtig. Hier können Sie auswählen, ob diese Aufgabenblockansicht für alle Elemente desselben Typs gilt, oder nur für das aktuell ausgewählte Element. Haben Sie vor Klick auf Neue Aufgabenblockansicht eine Organisationseinheit (kurz OU) ausgwählt, können Sie hier entscheiden, ob sich die Ansicht nur auf die angeklicke OU, oder für alle OUs (AlleStrukturelemente, die desselben Typ…) gelten soll. Des Weiteren gibt es noch ein Häkchen, das die Aufgabenblockansicht zur Standardansicht macht. Wieder eine Seite weiter (Name und Beschreibung) vergeben Sie Ihrer Ansicht einen Namen. Das wird dann interessant, wenn Sie mehrere Aufgabenblockansichten erstellen, um diese besser voneinander unterscheiden zu können. Im finalen Schritt (Fertigstellen des Assistenten) gibt es noch ein Häkchen, das Sie gleich zur Erstellung einer Aufgabe leitet. Wenn Sie das entfernen, bevor Sie auf Fertig stellen klicken, können Sie erst einmal sehen, wie sich die Darstellung verändert hat. OK, das ist nicht gerade spektakulär was dabei heraus kommt, aber Sie stehen ja hier noch ganz am Anfang.

4.9.3.3   Favoriten als Navigations Icons einbinden

Wenn Sie zuvor Favoriten definiert haben, können Sie diese gleich in die Aufgabenblockansicht einbauen, ansonsten sollten Sie erst wie im Abschnitt MMC erstellen und an eigene Bedürfnisse anpassen beschrieben ein paar Favoriten anlegen.

Wenn Sie für einen Knoten bereits eine Aufgabenblockansicht wie im vorangegeangenen Abschnitt beschrieben erstellt haben, können Sie diese nun per Rechtsklick weiter bearbeiten:

Klicken Sie auf den Reiter Aufgaben:

Mit dem Knopf Neu können Sie nun auch Ihre Aufgabenblockansicht um viele Funktionen erweitern, doch starten Sie zunächst mit einem der vorbereiteten Favoriten Einträgen. Nachdem Sie auf Neu geklickt haben, erscheint wieder der nervige Willkommen-Dialog. Klicken Sie auf Weiter und wählen Sie bei Befehlstyp Navigation und klicken Sie wiederum auf Weiter:

Auf der Navigationsseite können Sie dann einen Ihrer zuvor definierten Favoriten auswählen. Danach vergeben Sie im Dialogfeld Name und Beschreibung eben genau dies für Ihren Navigationsbefehl:

Dann wählen Sie noch ein hübsches Bildchen aus:

Wenn Sie Benutzerdefiniertes Symbol auswählen können Sie eine Bilder DLL oder EXE Dateien mit Bildern angeben die weitere, teils hübschere Bildchen bereitstellen. Zu empfehlen ist hier unter C:\Windows\System32 die Datei Shell32.dll (nicht Shell.dll – die hat keine Bildchen!). Auf älteren Windows Systemen finden Sie vielleicht auch noch eine moricons.dll, die aber bei weitem nicht so hübsche Bildchen enthält. Klicken Sie auf Fertig stellen und Sie haben damit Ihre erste Aufgaben blockansicht Erweiterung erstellt. War nicht so schwer, oder? Nun können Sie direkt aus der Ansicht heraus auf das Symbol, welches Sie der Navigation zugeordnet haben klicken und laden somit direkt beim Zielknoten. Ist auf dem Zielknoten keine Aufgabenblockansicht mit Navigations Elementen erstellt werden Sie den weg zu anderen Favoriten nur wieder über die Menüleiste finden. Aber zur Übung können Sie auch gleich am Zielknoten eine Aufgabenblockansicht erstellen, die den Weg zurück oder zu anderen interessanten Orten in Ihrer Konsole anbietet.

Gerne können Sie auch Gespeicherte Abfragen aus Active Directory Benutzer und Computer als Favoritenziele festlegen. Ein Rechtsklick auf den Knoten Gespeicherte Abfragen mit nachfolgenden Klick auf Neu/Abfrage erstellt eine neue Suchanforderung für Objekte aus den Active Directory.

Geben Sie einen sprechenden Namen ein und ggf. eine Beschreibung. Über den Abfragestamm können Sie festlegen ab wo im Active Directory nach den entsprechenden Objekten gesucht werden soll und mit dem Häkchen ob nur in diesem Container, oder auch in Untercontainern gesucht werden soll.

Mit der Schaltfläche Festlegen, können Sie letztendlich Ihre Suchabfrage formulieren. Mit Allgemeinen Abfragen im Dropdownfeld Suchen können Sie infach und schnell nach Benutzer, Computern oder Gruppen suchen. Dabei können Sie zusätzliche Angaben machen, ob z. B. nur deaktivierte Benutzerkonten aufgelistet werden sollen.

Über die einfachen Abfragen hinaus können Sie auch gezielt nach bestimmten Eigenschaften z. B. von Computern suchen:

Wem das immer noch nicht reicht, greift selbst zur LDAP-Abfrage:

Damit läßt sich dann wohl so ziemlich alles abfackeln. Der große Clou dabei; ist die Abfrage einmal definiert und Sie haben Ihre MMC gespeichert, klicken Sie in Zukunft nur noch drauf und drücken ggf. einmal F5. Die komplexesten Abfragen müssen also nur ein Mal definieren und dann können Sie diese immer wieder nutzen.

4.9.3.4   Menübefehle einbauen

Im vorangegangenen Abschnitt sind Ihnen im Dialogfeld Befehlstyp sicher auch die beiden Punkte Menübefehl und Shellbefehl aufgefallen und bestimmt haben Sie sich auch gleich gefragt, was man damit so alles anstellen kann. Lassen Sie mich zunächst auf die Möglichkeiten eines Menübefehls eingehen.

Wenn Sie wie im vorangegangenen Abschnitt beim Anlegen einer neuen Aufgabe bis zum Dialogfeld Befehlstyp vorgedrungen sind, wählen Sie dieses Mal bitte den obersten Punkt Menübefehl aus. Daraufhin erscheint das Dialogfeld Kontextmenübefehl. Hier können Sie nun auf der linken Seite ein im Knoten liegendes Objekt auswählen und rechts davon Aktionen die auf dieses Objekt angewendet werden können:

Wenn Sie bei Befehlsquelle Knoten in der Struktur auswählen, können Sie auch Befehle von anderen Knoten Ihrer MMC-Konsole hier zur Verfügung stellen (z.B. eine neue LDAP-Suchabfrage erstellen):

Der Rest des Assistenten verläuft wieder genauso, wie bereit bei den Favoriten im vorangegangenen Abschnitt beschrieben: Name, Bildchen….fertig.

Vielleicht sind Ihnen in der Übersicht der Aufgaben auch schon die Knöpfchen Nach oben/unten aufgefallen. Damit können Sie die Sortierung Ihrer Aufgaben ändern. Mit Entfernen können Sie eine einzelne Aufgabe löschen, ohne die gesamte Aufgabenblockansicht dabei zu löschen (die können Sie per Rechtsklick auf den Knoten/Aufgabenblockansicht löschen entfernen). Mit Ändern können Ihre Aufgaben angeasst werden, was besonders im nachfolgenden Abschnitt interessant werden dürfte.

4.9.3.5   Externe Funktionalität hinzufügen

Ab und zu braucht man mal den Taschenrechner, den Explorer, oder einen TerminalserverClient (RemoteDesktopClient oder wie auch immer der aktuelle Name sein mag. Sie wissen schon was ich meine. Keine Ahnung warum Microsoft alle Nase lang bekannt Namen neu erfinden muss. Wahrscheinlich damit man in den Prüfungen wieder etwas fragen kann. Innovative neue Funktionen scheint es ja keine mehr zu geben). Auch beliebige Windows Programme können als Icon in die Konsole zum direkten Aufruf eingebaut werden. Dafür greifen Sie auf die Aufgabe Shellbefehl zurück.

Die Einleitung einer neuen Aufgabe kennen Sie nun ja schon zur Genüge. Lassen Sie uns hier also auch gleich wieder beim Dialogfeld nach Auswahl des Eintrags Shellbefehl beim Dialogfeld Befehle und Parameter weiter machen.

4.9.3.5.1    Taschenrechner in die MMC einbauen

Im Falle des Taschenrechners ist das ganz einfach. Tippen Sie einfach calc in der Zeile unter Befehl ein und klicken Sie auf Weiter. Den Rest kennen Sie auch schon…Name, Bildchen…Fertig stellen. Das war’s auch schon.

Wenn Sie den Namen eines externen Programmes nicht genau kennen, können Sie auch gerne die Durchsuchen Schaltfläche verwenden um im Dateisystem danach zu suchen. Gerne können Sie auch manuell den kompletten Pfad mit eintragen.

4.9.3.5.2    Explorer mit bestimmten Verzeichnis starten

Wie man den Explorer startet, oder den TerminalserverClient dürfte Ihnen damit auch schon klar sein, aber ich kann da noch einen drauf setzen. Wie wäre es, nicht einfach nur den Explorer zu starten, sondern gleich ein bestimmtes Verzeichnis anzusteuern? Füllen Sie dazu einfach die Zeile Parameter mit dem gewünschten Zielverzeichnis aus:

Noch cooler? Nun, wie wäre es, wenn Sie im Active Directory einfach einen Computer anklicken und danach auf ein Icon namens Laufwerk C: und sie sehen direkt auf dem C: Laufwerk des Zeilcomputers oder vielleicht in einer bestimmten anderen Freigabe des Computers? Auch nicht weiter schwer:

 

Wie Sie wissen, können Sie mit einem UNC-Namen in Form von \\Computername\Freigabename auf einen freigegebenen Ordner zugreifen. C$ ist die verstecke Administrative Standardfreigabe (um darauf zugreifen zu können, müssen Sie natürlich auch dem Zielcomputer zur Gruppe der lokalen Administratoren gehören). Die Magie steckt allerdings in $COL<0>. $COL<0> ist eine Variable, die immer den Namen des zuvor angeklickten Objekts enthält. Also den Namen des ausgewählen Computerobjektes. Mehrfachauswahl (die Geschichte mit der Strg oder Shift Taste) ist dabei aber leider nicht möglich. Daher bekommt der Explorer den Netzwerkpfad zum Zielcomputer übergeben. Statt $COL<0> können auch andere Informationen übergeben werden. Was das alles ist, verrät Ihnen das Play-Knöpfchen rechts neben dem Eingabefeld für die Parameter. Leider funktionieren nicht alle Felder. Man muss von Fall zu Fall ausprobieren, welche Felder tatsächlich Informationen übermitteln. $COL<0> funktioniert aber eigentlich immer. Zumindest mir ist es bislang noch nicht untergekommen, das $COL<0> keine Information liefert.

4.9.3.5.3    Remotedesktop Verbindung und Remote Computermanagement per Mausklick aus der MMC heraus

Sie wollen eine Remotedesktopsitzung mit eiem Computer aus dem Active Directory? Erstellen Sie einfach eine weitere Aufgabe mit einem Shellbefehl und tragen Sie bei Befehl mstsc ein und bei den Parameter /v:$COL<0>

.

Um die Computerverwaltung remote aufzurufen geben Sie bei Befehl compmgmt.msc ein und bei Parameter /Computer=$COL<0>.

4.9.3.5.4    Hosts Datei auf einem ausgewählten Rechner per Mausklick bearbeiten

Sie möchten gerne per Mausklick das Hosts-File auf einem PC ansehen und bearbeiten? Befehl ist notepad und Parameter \\$COL<0>\C$\Windows\System32\Drivers\etc\hosts.

Coole Sache, was? Wer hätte gedacht, dass es so einfach sein kann, ein so angepasste und mächtige Konsole zusammen zu bauen und dabei sind wir noch nicht einmal bei den PowerShell-Schweinerein angelangt. Das alles (abgesehen von den Gespeicherten Abfragen, die erst seit 2003) geht seit Windows 2000!

4.9.3.6   MMC mit PowerShell Skripten erweitern

In den nachfolgenden Abschnitten erfahren Sie anhand einiger Praxisbeispiele wie Sie diese Macht noch mit PowerShell erweitern können um die absolute KillerApplikation zusammen zu schrauben. Fangen wir klein an…

4.9.3.6.1    PowerShell Konsolen Icon zum Starten aus der Management Konsole einbauen

Hier wählen Sie im Assistenten der Aufgabenblockansicht auch wieder den Shellbefehl aus. Wenn Sie sich mit der Standardhintergrundfarbe (Schwarz!) der PowerShell anfreunden können, dürfen Sie als Befehl gerne einfach nur powershell eintragen. Wenn Sie aber lieber blau möchten, so wie sich die Konsole normaler Weise öffnet ist der einfachste Weg den Desktop-Link als Ziel einzutragen: C:\Users\All Users\Microsoft\Windows\Start Menu\Programs\Accessories\Windows PowerShell\Windows PowerShell.lnk

Bei den Parametern ist der Phantasie wieder einmal keine Grenze gesetzt. So können Sie Beispielsweise mit dem Schalter –ExecutionPolicy unrestricted ein PowerShell Icon anlegen, dass Ihnen trotz auf dem System gesetzter ExecutionPolicy=Restricted, eine Konsole öffnet von der aus Sie PS1-Skripte laufen lassen können. –NoExit sorgt dafür, dass die Konsole geöffnet bleibt und nicht gleich wieder, z.B. nach Ausführung eines Skriptes, verschwindet. Sie können auf der Konsole gleich noch ein paar Befehle ausführen lassen:

-Noexit -command "& {cd C:\WindowsPowerShell\Scripts}"

Mit –command “& {Ihre PowerShell Befehle}” können Sie wie im angegebenen Beispiel also gleich in ein Verzeichnis wechseln lassen, in dem Ihre ganzen Skripte liegen. Halt! Nicht das Buch zu schlagen! Nein, Sie müssen Ihre Skripte nicht alle von Hand starten. Das wäre nicht cool. Aber nicht für jedes Skript will man auch ein Knöpfchen bauen. Also lesen Sie weiter…

4.9.3.6.2    Computer per Mausklick mittels PowerShell rebooten lassen

Um diese Funktion nutzen zu können, muss auf dem Zielcomputer das Windows Remote Management aktiviert sein. Wie das geht steht im Kapitel PowerShell 2.0 im Abschnitt Remote Zugriff. Eine Lösung ohne WinRM finden Sie aber auch gleich noch im Anschluß. Darüber hinaus muss der Benutzer, der das Knöpfchen nutzen möchte  auf dem Zielsystem natürlich zur Gruppe der lokalen Administratoren gehören.

Auch hier verwenden Sie wieder im Assistenten den Shellbefehl. Bei Befehl tragen Sie einfach nur powershell ein und bei Parameter geben Sie -command "& {restart-computer $COL<0>}“ an:

Wenn Sie nicht auf jedem Zielcomputer PowerShell mit aktiviertem WINRM zur Verfügung haben ersetzen Sie einfach bei Befehl powershell durch shutdown und bei Paramter schreiben Sie:

/r /m \\$COL<0> /t 0

4.9.3.6.3    Die 10 Speicher intensivsten Prozesse eines Computers anzeigen lassen

Dann wollen wir doch einmal ein ganzes PowerShell Skript laufen lassen. Denken Sie bitte dabei daran, dass die ExecutionPolicy Skriptausführung zulassen muss. Das folgende Skript kann auch in einer ganz normalen PowerShell Sitzung gestratet werden. Es erwartet einen Parameter in Form eines Computernamens. Wird dieser nicht übergeben, verwendet das Skript einfach localhost. Das wird in der Zeile die mit params beginnt festgelegt. Das gesamte Skript ist in eine Endlosschleife while ($true) {} eingeschlossen und muss daher mit Strg+C abgebrochen werden. $a enthält die Rückgabewerte eines invoke-command. Auf dem Zielsystem muss also auch wieder WINRM aktiviert sein. Alles was in der geschweiften Klammer des Invoke-Command ausgeführt wird passiert also auf dem durch -Computername übergebenen Computer. Zunächst wird eine Prozessliste abgerufen und danach Idle- und Systemprozess entfernt. Dann wird nach Speicher sortiert und schliesslich davon nur von den ersten 10 die Werte Name,ID,CPU,VM und Handler zurück geliefert und in den Array $a verfüttert. CLS löscht den Bildschirm, bevor die neue Tabelle ausgegeben wird. Dann schreibt das Skript den Computernamen auf den Schirm. Direkt darauf folgt die Zeile mit den Werte Überschriften. Dann kommt eine Schleife über den Array, sodass innerhalb der Schleife $i genau einen Prozess mit den übermittelten Werten enthält. $i wiederum ist ein Hash aus den Eigenschaften und den Werten des jeweiligen Prozesses. Wenn der Speicherplatz des Prozesses zwischen 200 und 300 MB liegt, wird die Schriftfarbe auf Gelb gestellt, über 300 MB auf Rot und alle anderen Werte (< 200MB) entsprechend Weiß. Dann erfolgt die formatierte Ausgabe der Werte mit Hilfe des –f Operators. Sind alle 10 Prozesse abgearbeitet wird die Schriftarbe wieder auf Weiß gesetzt. Es erfolgt ein Hinweis, dass das Skript mit Strg+C beendet werden kann. Die Anzeige bleibt so 2 Sekunden stehen, bevor die nächste Messung vorgenommen wird.

10.        # Zeigt die 10 Speicher-lastigsten Prozesse von Remotesystemen

11.         param ([String]$Computername)

12.         while (1 -eq 1) {

13.          $a=invoke-command {

14.           Get-Process | ? {($_.name -ne "Idle") -and ($_.name -ne "System")} | sort VM -desc | select Name,ID,CPU,VM,Handles -first 10

15.          } -Computername $Computername

16.          cls

17.          "Computer: $Computername"

18.          "      Name        ID   CPU Speicher(MB) Handles"

19.          foreach ($i in $a) {

20.          if ($i.vm -gt 200MB -and $i.vm -lt 300MB) {

21.           $host.ui.rawui.foregroundcolor="Yellow"

22.          } elseif ($i.vm -gt 300MB) {

23.           $host.ui.rawui.foregroundcolor="Red"

24.          } else {

25.           $host.ui.rawui.foregroundcolor="White"

26.          }

27.          "{0,15} {1,5} {2,5:f0} {3,12:f} {4,7:f}" -f ($i.name),($i.id),($i.cpu/10000),($i.vm/1MB),($i.Handles)

28.         }

29.         $host.ui.rawui.foregroundcolor="White"

30.         "Abbrechen mit Strg+C"

31.         sleep 2

32.        }

Nennen Sie das Skript doch einfach MemCheck.ps1 und probieren Sie es aus. Ja, ich weiß, man könnte ja auch die CPU, die Festplatte…und, und, und. Viel Spaß beim experimentieren! Oder bauen Sie das Skript so um, das es alle Prozesse die mehr als X MB haben automatisch killt. Doch nun, wenn das Skript so funktioniert, bauen Sie es doch bitte zunächst einmal in Ihre MMC ein.

Ja, auch hier nehmen Sie bitte wieder den Shellbefehl. Der Befehl ist nach wie vor Powershell bei Parameter geben Sie folgendes ein:

-Command "& {~\Documents\WindowsPowerShell\MemCheck.ps1 $COL<0>}"

Anstatt der PowerShell Befehle steht hier nun einfach der Pfad zu Ihrem PowerShell Skript. Statt einem absoluten Pfad wie C:\...\Skript.ps1 habe ich hier einen relativen verwendet. Dieser beginnt mit ~. Das ~-Symbol gibt angepasst auf den jeweilig angemeldeten Benutzer den Pfad zum Benutzerprofil, ähnlich eine Variablen. Mein Benutzer nennt sich Martin. Die ~ entspricht somit auf meinem System C:\Users\Martin. Der Rest steht dann wieder in der Parameterzeile \Documents\etc…. Alles zusammen ergibt dann automatisch C:\Users\Martin\Documents\WindowsPowerShell\MemCheck.ps1. Das bedeutet natülich, wenn sich ein anderer Benutzer anmeldet, dass auch bei diesem Benutzer das Skript in seinem Documents Verzeichnis liegen muss. Vielleicht ist es also an dieser Stelle sinnvoller die Skripte absolut anzugeben und auf eine Netzwerkfreigabe zu deuten. Damit alle Benutzer der Konsole an einem Zentralen Ort Zugriff darauf haben. Des Weiteren dürfen Sie nicht vergessen, wieder $COL<0> folgen zu lassen. Damit vom angeklickten Objekt der Name an das PowerShell Skript als Parameter übergeben wird.

Wenn Sie Skripte starten, können Sie alternativ zu der –Command Version auch gleich den Schalter ‑File angeben, dass macht das Ganze dann wesentlich kürzer:

-File ~\Documents\WindowsPowerShell\MemCheck.ps1 $COL<0>

Weiterhin könnten Sie in Kombination mit GUI-Programmierung im Skript etwas Ärger bekommen. Nutzten Sie z.B. die Methode Showdialog() um eine GUI anzuzeigen und haben in dieser einen „Datei speichern“-Knopf eingebaut, der seinerseits noch ein eigenes Fenster öffnet, geht dies zwar auf, wenn Sie das Skript direkt starten, nicht aber wenn Sie über die mmc versuchen. Hat mich ein Weilchen gekostet, das Problem in den Griff zu bekommen, aber hier die Lösung: Geben Sie zusätzlich zu –File bzw. –Command noch zusätzlich den Parameter –Sta an. Also z.B. so:

-Sta -File ~\Documents\WindowsPowerShell\MemCheck.ps1 $COL<0>

Dies sorgt dafür das Ihr PowerShell-Skript in einem eigenen Thread unbehebligt vor sich hin werkeln kann.

4.9.3.6.4    Online Check in die MMC einbauen

Wenn Sie gerne wissen möchten ob ein bestimmter PC Online ist, weil Sie Ihn z.B. gerade über Ihr neues Icon rebooted haben fehlt Ihnen natürlich noch eine Anzeige, wann der PC wieder erreichbar ist. Das würde aber in einer Konsole ziemlich häßlich aussehen. Also bauen wir gleich eine kleine GUI dazu.

Das nachfolgende Skript beginnt auch wieder mit dem Param Statement in Zeile 11 um einen Computernamen entgegen zu nehmen und in einer Variablen abzulegen. Dann folgt der grundlegende Aufbau eines Fensters mit Hilfe der Windows.Forms Klasse. Details zur GUI-Programmierung finden Sie im Abschnitt GUI – Grafische Oberflächen mit PowerShell. Die eigentliche ICMP-Prüfung steht im Abschnitt der mit Aktualisierungs-Timer hinzufügen kommentiert ist. In Zeile 38 wird zunächst aus der Timer Klasse ein Objekt erstellt und in der Variablen $timer hinterlegt. $timer_OnTick (Zeile 39-42) enthält den Code der bei jedem Tick ausgeführt wird. $Label.Text wurde ja bereits zu Beginn mit dem Text Starting versehen. Dies wird so lange angezeigt, bis es zu einem Tick kommt. Bei einem Tick wird $Label.Text neu mit dem Text Online oder Offline belegt, je nachdem ob der Befehl test-connection –count 1 erfolgreich war (sprich der Zielcomputer in Form von $Computername per ICMP erreichbar ist). Die nachfolgende Zeile ist eine kleine Spielerei, die je nachdem welchen Text inzwischen $Lable.Text enthält auch $Label.ForeColor anpasst. Der Text Starting wird in der Standardfarbe Schwarz ausgegeben. Beim ersten Tick ändert sich die Farbe von $Label zu grün, wenn das Zielsystem erreichbar ist und rot in allen anderen Fällen, sprich wenn er noch nicht erreichbar ist. Wann ein Tick und somit eine Verfügbarkeitsprüfung erfolgt regelt die nachfolgende Zeile 43 $timer.Interval=5000. Die 5000 entsprechen 5000stel Sekunden. Daher findet ein Tick alle 5 Sekunden statt. Dann wird die in $timer_OnTick definierte Programmfunktion dem $timer Objekt bekannt gemacht und dort hineingeklinkt. $timer.Enabled=$true in Zeil 45 sorgt dafür, dass der Timer auch „tickt“. Nur noch auf den Schirm damit und fertig ist Ihr grafischer Ping check. Auch dieses Skript können Sie gerne erst wieder manuell testen, indem Sie nach dem Skriptnamen den zu testenden Computernamen hinten dran schreiben.

10.        # Ping check

11.        param ([String]$Computername="Localhost")

12.        # Klassen laden

13.        [void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")

14.        [void] [System.Reflection.Assembly]::LoadWithPartialName("System.Drawing")

15.        # Fensterrahmen gestalten

16.        $objForm=New-Object System.Windows.Forms.Form

17.        $objForm.Text="$Computername ist:"

18.        $objForm.Size=New-Object System.Drawing.Size(175,75)

19.        $objForm.StartPosition="CenterScreen"

20.        # Auf Taste reagieren

21.        $objForm.KeyPreview=$True

22.        $objForm.Add_KeyDown({if ($_.KeyCode -eq "Escape") {$objForm.Close()}})

23.        # Knopf einbauen

24.        $ExitButton=New-Object System.Windows.Forms.Button

25.        $ExitButton.Location=New-Object System.Drawing.Size(90,10)

26.        $ExitButton.Size=New-Object System.Drawing.Size(50,23)

27.        $ExitButton.Text="Exit"

28.        $ExitButton.Add_Click({$objForm.Close()})

29.        $objForm.Controls.Add($ExitButton)

30.        # Textfeld einbauen

31.        $Label=New-Object System.Windows.Forms.Label

32.        $Label.DataBindings.DefaultDataSourceUpdateMode=0

33.        $Label.Location=New-Object System.Drawing.Size(15,15)

34.        $Label.Size=New-Object System.Drawing.Size(60,23)

35.        $Label.text="Starting"

36.        $objForm.Controls.Add($Label)

37.        # Aktualisierungs-Timer hinzufügen

38.        $timer=New-Object System.Windows.Forms.Timer

39.        $timer_OnTick={

40.         $Label.Text=if (test-connection $Computername -count 1) {"Online"} else {"Offline"}

41.         if ($Label.Text -eq "Online") {$Label.ForeColor="Green"} else {$Label.ForeColor="Red"}

42.        }

43.        $timer.Interval=5000 # Alle 5 Sekunden

44.        $timer.add_tick($timer_OnTick)

45.        $timer.Enabled=$true

46.        $objForm.ShowDialog()

Auch das möchten Sie sicher in Ihrer MMC haben. Wie üblich wählen Sie bitte auch hier wieder einen Shellbefehl aus. Befehl ist auch wieder PowerShell bei Parameter würde ich folgende Variante vorschlagen:

-windowstyle hidden -Command "& {Pfad zu Ihrem Skript\onlinechk.ps1 $COL<0>}"

Das Besondere hier liegt im Schalter –windowstyle hidden, den Rest kennen Sie bereits. Dieser Schalter sorgt dafür, dass die PowerShell Konsole selbst versteckt ausgeführt wird und nur noch das GUI zu sehen ist.

4.9.3.6.5    Schneller Eventlog Check

Der Eventlogcheck zeigt vom ausgwählten Computer die 20 aktuellsten Errors und Warnings an. Der Aufruf erfolgt wieder ähnlich mit zusätzlicher Angabe, ob Sie das System, oder Application Log sehen möchten mit:

-windowstyle hidden -Command "& {Pfad zu Ihrem Skript\eventinfo.ps1 $COL<0> System}"

bzw.

-windowstyle hidden -Command "& {Pfad zu Ihrem Skript\eventinfo.ps1 $COL<0> Application}"

Den ganzen GUI-Teil zu erklären spare ich mir an dieser Stelle. Der ganze Witz steckt eigentlich in Zeile 104. Dort wird das Eventlog entsprechend der übergebenen Parameter abgefragt. Fest definiert sind hier nur das die 20 aktuellsten Ereignisse (-newest 20)  von Typ Warning und Error (-entrytype error,warning) gelistet werden.

100.       # Ruft die aktuelsten 20 Errors und/oder Warnings aus dem System- oder Application-Log eines Computers ab

101.       # Als Parameter wird zuerst der Computername und als Zweites der Logname (Application oder System) erwartet

102.       param ([String]$Computername,[String]$Log)

103.       # $fb bekommt die Einträge aus dem entsprechenden Log

104.       $fb=Get-EventLog  -Logname $log -computer $Computername -entrytype error,warning -newest 20

105.       # Laden der System.Windows.Forms Klasse für die grafische Oberfläche

106.       [void][reflection.assembly]::LoadWithPartialName(„System.Windows.Forms“)

107.       # Aus der Klasse System.Windows.Forms.Form ein Fenster-Objekt erstellen

108.       $Fenster = new-object Windows.Forms.Form

109.       $Fenster.Text = „Die letzten 20 Errors und Warnings aus $Log-Log von $Computername“

110.       $Fenster.Size = New-Object System.Drawing.Size(800,660)

111.       $Fenster.StartPosition = "CenterScreen"

112.       # Aus der Klasse System.Windows.Forms.Button ein Knopf-Objekt erstellen

113.       $ExitKnopf = new-object Windows.Forms.Button

114.       $ExitKnopf.Text = „Exit“

115.       $ExitKnopf.Left = 10

116.       $ExitKnopf.Top = 10

117.       $ExitKnopf.Width = 430

118.       $ExitKnopf.Height = 30

119.       # Auf Klick mit schliessen des Fensters reagieren

120.       $ExitKnopf.Add_Click({$Fenster.close()})

121.       # Knopf auf's Fenster kleben

122.       $Fenster.Controls.Add($ExitKnopf)

123.       # Aus der Klasse System.Windows.Forms.Label ein Text-Objekt erstellen

124.       $Beschriftung = New-Object System.Windows.Forms.Label

125.       $Beschriftung.Location = New-Object System.Drawing.Size(450,10)

126.       $Beschriftung.Size = New-Object System.Drawing.Size(325,600)

127.       $Beschriftung.Text = "Eintrag auswählen!"

128.       # Text auf's Fenster kleben

129.       $Fenster.Controls.Add($Beschriftung)

130.       # Schleife um die 20 Events als einzelne Elemente anzusprechen

131.       $Count=0

132.       foreach ($line in $fb) {

133.        # Aus der Klasse System.Windows.Forms.Button ein Knopf-Objekt erstellen und mit einigen Event-Infos beschriften

134.        $button=new-object Windows.Forms.Button

135.        $button.Text = ("Zeit: $($line.timegenerated),`tEventID: $($line.eventid), Msg: $($line.message.replace("`n"," "))").substring(0,79)

136.        if ($line.entrytype -eq "Warning") {

137.         # Warnings in Brown, weil DarkYellow in Schwarz angezeigt wird und Yellow zu hell ist um es gut lesen zu können

138.         $button.Forecolor="Brown"

139.        } else {

140.         # Errors in Rot

141.         $button.Forecolor="Red"

142.        }

143.        $button.Textalign="MiddleLeft"

144.        $button.name=$count

145.        $button.Left = 10

146.        $button.Top = 50+$count*29

147.        $button.Width = 430

148.        $button.Height = 25

149.        # Bei Klick auf den jeweiligen Knopf im rechten Textbereich die Fehlermeldung anzeigen

150.        $button.Add_Click({

151.         $Beschriftung.Text = $fb[$($this.name)] | select -expandprop message

152.        })

153.        # Knopf ins Fenster kleben

154.        $Fenster.Controls.Add($button)

155.        $count++

156.       }

157.       # Fenster anzeigen

158.       $Fenster.ShowDialog()

159.                       # $line | select -expandprop message

4.9.3.6.6    Dienste eines Windows Systems zentral per Mausklick verwalten

Um Dienststatus einzusehen und bestimmte Dienste zu starten oder zu beenden wie in diesen Screenshot zu sehen:

Empfiehlt sich dieses Skript in die MMC einzubauen:

100.       param ([String]$Computername="Localhost")

101.       # get basics

102.       $LOS=get-service -Computer $Computername

103.       $feedback=@()

104.       # functions

105.       function doservicechange ($do,$Requested) {

106.        # Consistency Check

107.        foreach ($SR in $Requested) {

108.         $status=$SR.substring(0,7)

109.         if ((($do -eq "Start") -and ($status -eq "Running")) -or (($do -eq "Stop") -and ($status -eq "Stopped"))) {

110.          Throw “You can start a running service, nor stop a stopped service.”

111.         }

112.        }

113.        foreach ($SR in $Requested) {

114.         $svc=$SR.substring(10)

115.         if ($do -eq "Start") {

116.          invoke-command {

117.           start-service $args

118.          } -Computer $Computername -Argumentlist $svc

119.         }

120.         if ($do -eq "Stop") {

121.          invoke-command {

122.           stop-service $args

123.          } -Computer $Computername -Argumentlist $svc

124.         }

125.        }

126.       }

127.       # gui

128.       [void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")

129.       [void] [System.Reflection.Assembly]::LoadWithPartialName("System.Drawing")

130.       $objForm=New-Object System.Windows.Forms.Form

131.       $objForm.Text="Service Controller auf $Computername"

132.       $objForm.Size=New-Object System.Drawing.Size(380,700)

133.       $objForm.StartPosition="CenterScreen"

134.       $objForm.KeyPreview=$True

135.       $objForm.Add_KeyDown({if ($_.KeyCode -eq "Escape") {$objForm.Close()}})

136.       $StartButton=New-Object System.Windows.Forms.Button

137.       $StartButton.Location=New-Object System.Drawing.Size(10,640)

138.       $StartButton.Size=New-Object System.Drawing.Size(75,23)

139.       $StartButton.Text="Start"

140.       $StartButton.Add_Click({

141.        foreach ($objItem in $objListbox.SelectedItems){

142.         $feedback += $objItem;$bresult="Start"

143.        }

144.        $objForm.Close()

145.       })

146.       $objForm.Controls.Add($StartButton)

147.       $StopButton=New-Object System.Windows.Forms.Button

148.       $StopButton.Location=New-Object System.Drawing.Size(90,640)

149.       $StopButton.Size=New-Object System.Drawing.Size(75,23)

150.       $StopButton.Text="Stop"

151.       $StopButton.Add_Click({

152.       foreach ($objItem in $objListbox.SelectedItems){

153.        $feedback += $objItem;$bresult="Stop"}

154.        $objForm.Close()

155.       })

156.       $objForm.Controls.Add($StopButton)

157.       $RestartButton=New-Object System.Windows.Forms.Button

158.       $RestartButton.Location=New-Object System.Drawing.Size(170,640)

159.       $RestartButton.Size=New-Object System.Drawing.Size(75,23)

160.       $RestartButton.Text="Restart"

161.       $RestartButton.Add_Click({

162.        foreach ($objItem in $objListbox.SelectedItems){

163.         $feedback += $objItem;$bresult="Restart"

164.        }

165.        $objForm.Close()

166.       })

167.       $objForm.Controls.Add($RestartButton)

168.       $CancelButton=New-Object System.Windows.Forms.Button

169.       $CancelButton.Location=New-Object System.Drawing.Size(270,640)

170.       $CancelButton.Size=New-Object System.Drawing.Size(75,23)

171.       $CancelButton.Text="Cancel"

172.       $CancelButton.Add_Click({$objForm.Close()})

173.       $objForm.Controls.Add($CancelButton)

174.       $objLabel=New-Object System.Windows.Forms.Label

175.       $objLabel.Location=New-Object System.Drawing.Size(10,20)

176.       $objLabel.Size=New-Object System.Drawing.Size(280,20)

177.       $objLabel.Text="Wähle die Dienste ggf. mit Strg und die Aktion:"

178.       $objForm.Controls.Add($objLabel)

179.       $objListbox=New-Object System.Windows.Forms.Listbox

180.       $objListbox.Location=New-Object System.Drawing.Size(10,40)

181.       $objListbox.Size=New-Object System.Drawing.Size(330,600)

182.       $objListbox.SelectionMode="MultiExtended"

183.       foreach ($svc in $los) {

184.        $item="$($svc.status) | $($svc.name)"

185.        [void] $objListbox.Items.Add($item)

186.       }

187.       $objForm.Controls.Add($objListbox)

188.       $objForm.Topmost=$True

189.       $objForm.Add_Shown({$objForm.Activate()})

190.       [void] $objForm.ShowDialog()

191.       if ($feedback) {

192.        if ($bresult -eq "Start") {

193.         doservicechange "Start" $feedback

194.        } elseif ($bresult -eq "Stop") {

195.         doservicechange "Stop" $feedback

196.        } elseif ($bresult -eq "Restart") {

197.         doservicechange "Stop" $feedback

198.         do {

199.          sleep 2

200.          $weiter=$true

201.          $feedback | foreach {

202.           $svc=$_.substring(10)

203.           if ((get-service $svc -computer $Computername).status -eq "Running") {$weiter=$false}

204.           write-host "." -nonewline

205.          }

206.         } while ($weiter -eq $false)

207.         $change=@()

208.         $feedback | foreach {

209.          $change+=$_.replace("Running","Stopped")

210.         }

211.         $feedback=$change

212.         doservicechange "Start" $feedback

213.        }

214.       }

Der Aufruf geschieht wieder mit dem Kommando PowerShell und als Parameter übergibt man:

-windowstyle hidden -Command "&{~\PfadzumSkript\Servicemgr.ps1 $COL<0>}"

4.9.3.6.7    Benutzer-Objekte übergeben

Leider kann man den Benutzeranmeldenamen ($COL<22>) nicht übergeben. Nur $COL<0> enthält nur den ersten Teil bis zum Leerzeichen eines Anzeigenamens. Daher können Sie einen Benutzer im AD nicht eindeutig übergeben. Man kann aber versuchen es so eindeutig wie möglich zu machen und für den Rest vielleicht mit einem Dropdownfeld, oder einem Out-Gridview –passthru weiter durch den Benutzer eingrenzen zu lassen.

Sie können an Ihr Script die Werte $COL<0> $NAME<0> $NAME<1> übergeben. Damit haben Sie dann entweder 3 Einzelteile des Anzeigenamens oder auch Teile der OU-Struktur im Skript.

Wenn Sie nun im Script folgenden Param-Block verwenden:

Param($Eins,$Zwei,$Drei)

Können Sie mit diesem Baustein den Benutzer möglicher Weise identifizieren und bearbeiten.

$User=Get-ADUser –LDAPFilter “(name=$Eins*$Zwei*$Drei*)”

If (!$User) {$User=Get-ADUser –LDAPFilter “(name=$Eins*$Zwei*)”}

If (!$User) {$User=Get-ADUser –LDAPFilter “(name=$Eins*)”}

If (!$User) {“User nicht gefunden”; exit}

If ($User –is [Array]) {“Mehrere Benutzer entsprechen der Auswahl:”; $User}

4.9.4   Eigene AD – Papierkorb Cmdlets

Die mitgelieferten Cmdlets um auf den Active-Directory Papierkorb zuzugreifen oder Ihn zu aktivieren, sind nicht ganz so intuitiv. Hier eine Lösung mit 3 Cmdlets bzw. Funktionen (Start‑RecycleBin, Show-DeletedADObjects, Restore-DeletedADObjects), die dies vereinfachen sollen:

100.       # Einfache Powershellbefehle für den AD-Papierkorb

101.       # von Martin Lehmann

102.        

103.       # In diesem Abschnitt versucht das Skript die Voraussetzungen zu schaffen und nicht nur einfach Fehlermeldungen zu generieren, wenn etwas fehlt. Die einfache Variante wäre natürlich über die #Requires Anweisung gewesen

104.        

105.       if (($host).version -lt "2.0") {Throw "Bitte erst einmal Powershell 2.0 installieren. Ohne geht dieses Script nicht!"}

106.       $admodule=$false

107.       foreach ($i in get-module -list) {if ($i.Name -eq "ActiveDirectory") {$admodule=$true}}

108.       if (!$admodule) {

109.        "Das PowershellModul für ActiveDirectory ist auf diesem Rechner nicht vorhanden. Ich versuche es nach zu installieren."

110.        $srvmodule=$false

111.        foreach ($i in get-module -list) {if ($i.Name -eq "ServerManager") {$srvmodule=$true}}

112.        if (!$srvmodule) {

113.         Throw "Leider hat es nicht geklappt, da das Powershellmodul für den Servermanager nicht verfügbar ist."

114.        } else {

115.         import-module ServerManager

116.         $adpwrshell=add-windowsfeature RSAT-AD-PowerShell 2>$nul

117.         if ($adpwrshell.Success -eq $false) {

118.           Throw "Leider konnte das Powershellmodul für Active Directory nicht installiert werden."

119.         }

120.        }

121.       }

122.       $admodule=$false

123.       foreach ($i in get-module) {if ($i.Name -eq "activedirectory") {$admodule=$true}}

124.       if (!$admodule) {import-module activedirectory}

125.        

126.       "`nSie haben soeben für die aktuelle Powershell Konsole die folgenden Befehle erhalten:`n"

127.       "Start-RecycleBin            aktiviert den Papierkorb und informiert über den Status."

128.       "Show-DeletedADObjects       zeigt Objekte aus dem Active Directory Papierkorb an."

129.       "Restore-DeletedADObjects    stellt Objekte aus dem Active Directory Papierkorb nach einer Rückfrage wieder her."

130.       "`nUm mehr über die Befehle zu erfahren geben Sie vor dem Befehl das Wort help ein. Beispiel: help Start-RecycleBin"

131.       # Um den Papierkorb mittels "Start-RecycleBin" anzulegen müssen Sie Organisations-Admin sein!

132.        

133.       function Global:Start-RecycleBin {

134.       <#

135.       .SYNOPSIS

136.       Aktiviert den Active Directory Papierkorb und zeigt den Status an.

137.       .DESCRIPTION

138.       Der Befehl unterstützt keine Parameter, da der Papierkorb nur für die Gesamstruktur (Forest) aktiviert werden kann. Sie benötigen daher als ausführender Benutzer Organistionsadministratoren Berechtigung. Die Aktivierung kann nicht Rückgängig gemacht werden, da dies von Microsoft nicht vorgesehen ist!

139.       .EXAMPLE

140.       PS C:\Start-RecycleBin

141.       Aktiviert den Active Directory Papierkorb.

142.       .LINK

143.       http://www.martinlehmann.de/wp/customizing/einfache-powershell-befehle-fur-den-active-directory-papierkorb/

144.       .LINK

145.       http://www.martinlehmann.de/wp/

146.       #>

147.        $forest=(get-addomain).Forest

148.        try {

149.         enable-adoptionalfeature "Recycle Bin Feature" -scope ForestOrConfigurationSet -Target $forest

150.        }

151.        catch {

152.         write-host "`nSie haben wahrscheinlich ein Replicationsproblem im Active Directory!`nFür die Aktivierung des Papierkorbs ist dies aber nicht weiter tragisch.`nWeitere Infos liefert das Kommando: dcdiag.`n" -foregroundcolor yellow

153.        }

154.        if ((Get-ADOptionalFeature "Recycle Bin Feature").enabledscopes) {

155.         "Active Directory Papierkorb ist für die Gesamtstruktur $forest verfügbar."

156.        } else {

157.         "Active Directory Papierkorb ist für die Gesamtstruktur $forest leider nicht aktiviert."

158.         "Bitte prüfen Sie ob Ihr Active Directory Forest auf 2008 R2 Level hochgestuft wurde."

159.        }

160.       }

161.        

162.       # "Restore-DeletedADObjects Namensteil" versucht das gelöschte Objekt zu finden und wiederherzustellen

163.        

164.       function Global:Restore-DeletedADObjects {

165.       <#

166.       .SYNOPSIS

167.       Stellt gelöschte Active Directory Objekte mit Hilfe eines Suchbegriffs wieder her.

168.       .DESCRIPTION

169.       Stellt gelöschte Active Directory Objekte mit Hilfe eines Suchbegriffs wieder her. Für den Suchbegriff müssen keine * als Platzhalter verwendet werden. Dies passiert im Script automatisch.

170.       .PARAMETER Namensteil

171.       Geben Sie einen Teil des Names des wiederherzustellenden Objektes ein.

172.       .EXAMPLE

173.       PS C:\Restore-DeletedADObjects Ben

174.       Haben Sie beispielsweise die Benutzer mit den Namen Benutzer1,Benutzer2,Benutzer3,Benutzer4,Benutzer5,User1,User2 gelöscht und Sie würden den o.a. Befehl eingeben. Würden Sie anschliessend für jeden der ersten 5 Benutzer gefragt werden, ob Sie diesen Benutzer wiederherstellen möchten. User1 und 2 fallen nicht in das Suchmuster und würden nicht zur Wiederherstellung angeboten werden.

175.       .LINK

176.       http://www.martinlehmann.de/wp/customizing/einfache-powershell-befehle-fur-den-active-directory-papierkorb/

177.       .LINK

178.       http://www.martinlehmann.de/wp/

179.       #>

180.        if (!(Get-ADOptionalFeature "Recycle Bin Feature").enabledscopes) {

181.         Throw "Active Directory Papierkorb ist für die Gesamtstruktur $forest nicht aktiviert. Bitte aktivieren Sie den Papierkorb zunächst mit Start-RecycleBin."

182.        }

183.        if ($args[0]) {

184.         $doc=$(get-addomain).DeletedObjectsContainer

185.         $todel=get-adobject -ldapfilter "(objectClass=*)" -searchbase $doc -includedeletedobjects | ? {$_.Name -like "*$args[0]*" -and $_.DistinguishedName -ne $doc}

186.         $todel

187.         "Wollen Sie die aufgelisteten Objekte wieder herstellen?"

188.         $antwort=read-host -prompt "Ja/Nein "

189.         if ($antwort -like "j*") {

190.          $todel | restore-adobject

191.          "Objekte sind ab sofort wieder im Active Directory verfügbar!"

192.         } else {

193.          "Objekte wurden nicht wieder hergestellt!"

194.         }

195.        } else {

196.         "Bitte geben Sie nach dem Befehl wenigstens einen Teil (sollte eindeutig sein) des Namens des gelöschten Objektes an."

197.        }

198.       }

199.        

200.       # "Show-DeletedADObjects" zeigt alle gelöschten AD Objekte an

201.        

202.       function Global:Show-DeletedADObjects {

203.       <#

204.       .SYNOPSIS

205.       Zeigt alle gelöschten Active Directory Objekte an.

206.       .DESCRIPTION

207.       Der Befehl unterstützt keine Parameter. Alle im Papierkorb befindlichen Objekte werden ausgegeben.

208.       .EXAMPLE

209.       PS C:\Show-DeletedADObjects

210.       Listet alle im Active Directory befindlichen Objekte.

211.       .LINK

212.       http://www.martinlehmann.de/wp/customizing/einfache-powershell-befehle-fur-den-active-directory-papierkorb/

213.       .LINK

214.       http://www.martinlehmann.de/wp/

215.       #>

216.        if (!(Get-ADOptionalFeature "Recycle Bin Feature").enabledscopes) {

217.         Throw "Active Directory Papierkorb ist für die Gesamtstruktur $forest nicht aktiviert. Bitte aktivieren Sie den Papierkorb zunächst mit Start-RecycleBin."

218.        }

219.        $doc=$(get-addomain).DeletedObjectsContainer

220.        $objects=get-adobject -ldapfilter "(objectClass=*)" -searchbase $doc -includedeletedobjects | ? {$_.DistinguishedName -ne $doc}

221.        if ($objects) {

222.         $objects

223.        } else {

224.         "Es sind keine gelöschten Objekte vorhanden!"

225.         "Möglicher Weise liegt das Löschdatum zu weit in der Vergangenheit, oder der AD-Papierkorb wurde erst nach der Löschung aktiviert."

226.        }

227.       }

4.9.5   GUI für AD Papierkorb

Dieses Skript stellt eine grafische Oberfläche zur Verwaltung des Active-Directory Papierkorbs dar.

Damit lassen sich Dateien aud dem papierkorb nicht nur wieder herstellen, sondern auch dauerhaft aus dem Papierkorb entfernen.

Das Werkzeug ist Multi-Domain tauglich und erkennt selbständig den kompletten Forest (Gesamtstruktur). Zu nächst ist die Domäne auszuwählen und das Datum, bis wann in die Vergangenheit gesucht werden soll. Darauf hin läuft das Tool los um die Domäne nach gelöschten Objekten abzusuchen. Das Ergebnis wird in einer Baumstruktur grafisch dargestellt.

Die grün dargestellten Objekte sind nach wie vor im AD verfügbar, die schwarz dargestellten und mit einem Totenkopf versehenen hingegen sind gelöschte Objekte. Diese kann man entsprechend anhaken und dann entweder wiederherstellen, oder dauerhaft löschen. Das Skript benötigt für die Symbole entsprechende *.ico Dateien. Hier das Skript (Anmerkungen im Skript):

100.       # recyclebin Version 2.1 von Martin Lehmann

101.       # Benutzung auf eigene Verantwortung!

102.        

103.       # Unterstützung für PowerShell-GUI-Programmierung besorgen

104.       [void] [System.Reflection.Assembly]::LoadWithPartialName("System.Drawing")

105.       [void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")

106.        

107.       # Erst mal gucken ob AD-Modul für Powershell irgendwie zu beschaffen ist

108.        

109.       if (($host.version.major -lt 2)) {throw "Bitte erst einmal Powershell 2.0 oder höher installieren."}

110.       $admodule=$false

111.       foreach ($i in get-module -list) {if ($i.Name -eq "ActiveDirectory") {$admodule=$true}}

112.       if (!$admodule) {

113.        "Das PowershellModul für ActiveDirectory ist auf diesem Rechner nicht vorhanden. Ich versuche es nach zu installieren."

114.        $srvmodule=$false

115.        foreach ($i in get-module -list) {if ($i.Name -eq "ServerManager") {$srvmodule=$true}}

116.        if (!$srvmodule) {

117.         throw "Leider hat es nicht geklappt, da das Powershellmodul für den Servermanager nicht verfügbar ist."

118.        } else {

119.         import-module ServerManager

120.         $adpwrshell=add-windowsfeature RSAT-AD-PowerShell 2>$nul

121.         if ($adpwrshell.Success -eq $false) {

122.           throw "Leider konnte das Powershellmodul für Active Directory nicht installiert werden."

123.         }

124.        }

125.       }

126.       $admodule=$false

127.       foreach ($i in get-module) {if ($i.Name -eq "activedirectory") {$admodule=$true}}

128.       if (!$admodule) {import-module activedirectory}

129.        

130.       # Ist der Papierkorb überhaupt aktiviert?

131.        

132.       $forest=$(get-addomain).Forest

133.       if ((Get-ADOptionalFeature "Recycle bin feature").enabledscopes) {

134.        write-host "Active Directory Papierkorb ist für $forest verfügbar." -ForegroundColor Green

135.       } else {

136.        try {

137.         enable-adoptionalfeature "Recycle Bin Feature" -scope ForestOrConfigurationSet -Target $forest

138.         

139.         if ($?) {throw "Active-Directory Papierkorb wurde eben erst eingeschaltet, da er deaktiviert war. Erst ab jetzt können gelöschte Objekte können wieder hergestellt werden."}

140.        }

141.        catch {

142.         throw "AD-Papierkorb konnte in Ihrer Umgebung nicht aktiviert werden. Voraussichtlich is der Forestlevel nicht min. 2008, oder Sie haben keine Enterprise-Admin Berechtigungen."

143.        }

144.       }

145.        

146.       # Alle im Forest gefundenen Domänen abrufen

147.       $AvailableDomains=(get-adforest).domains

148.       # Wenn keine Domäne gefunden wird abbrechen

149.       if ($AvailableDomains.count -lt 1) {throw "Keine Domäne zur Verwaltung gefunden"}

150.       else {

151.        # Fenster erstellen

152.        $Fenster=New-Object System.Windows.Forms.Form

153.        $Fenster.Text="Active-Directory Papierkorbmanager"

154.        $Fenster.Location=New-Object System.Drawing.Size(50,10)

155.        $Fenster.Size=New-Object System.Drawing.Size(600,250)

156.        # Infotexte einbauen

157.        $Count=0

158.        "Bitte Domäne auswählen","Bis zu welchem Datum in der Vergangenheit wurden die Objekte gelöscht?" | foreach {

159.         $Beschriftung=New-Object System.Windows.Forms.Label

160.         $Beschriftung.Location=New-Object System.Drawing.Point((30+$Count*250),20)

161.         $Beschriftung.Size=New-Object System.Drawing.Size(250,30)

162.         $Beschriftung.Text=$_

163.         $Beschriftung.Name="Info"

164.         $Fenster.Controls.Add($Beschriftung)

165.         $Count++

166.        }

167.        # Dropdown für die Domänenauswahl einbauen

168.        $Auswahlfeld=New-Object System.Windows.Forms.Combobox

169.        $Auswahlfeld.Location=New-Object System.Drawing.Size(40,50)

170.        $AvailableDomains | foreach {[void] $Auswahlfeld.Items.Add("$_")}

171.        $Auswahlfeld.Width=200

172.        $Fenster.Controls.Add($Auswahlfeld)

173.        # Kalender einbauen

174.        $Kalender=New-Object System.Windows.Forms.MonthCalendar

175.        $Kalender.FirstDayOfWeek=[System.Windows.Forms.Day]::Monday

176.        $Kalender.Location=New-Object System.Drawing.Size(300,60)

177.        $Kalender.SelectionStart=$Kalender.SelectionStart=(get-date)-(New-TimeSpan -day 1)

178.        $Fenster.Controls.Add($Kalender)

179.        # OK- und Cancel-Knopf einbauen

180.        $Count=0

181.        "OK","Cancel" | foreach {

182.         $Knopf=New-Object System.Windows.Forms.Button

183.         $Knopf.Location=New-Object System.Drawing.Size((50+$Count*100),100)

184.         $Knopf.Size=New-Object System.Drawing.Size(75,23)

185.         $Knopf.Text=$_

186.         $Knopf.Name=$_

187.         $Knopf.DialogResult=$_

188.         $Knopf.Add_Click({$Fenster.Close()})

189.         $Fenster.Controls.Add($Knopf)

190.         $Count++

191.        }

192.        # 1. Fenster mit Datum und Domänenauswahl anzeigen

193.        [void] $Fenster.ShowDialog()

194.        # Ergebnis in sprechende Variablen Namen überführen

195.        $WorkingDomain=$Auswahlfeld.SelectedItem

196.        $RestoreDate=$Kalender.SelectionStart

197.        if ($Fenster.DialogResult -ne "OK" -or (!$WorkingDomain)) {throw "Abbruch durch den Benutzer, oder keine Domäne ausgewählt."}

198.       }

199.        

200.       #[string]$WorkingDomain=$WorkingDomain

201.       "Jetzt muss ich erst mal grübeln:"

202.        

203.       # LDAP Domänen Pfad aus DNS bilden

204.       $LDAPDomainName="DC=$($WorkingDomain.replace(".",",DC="))"

205.        

206.       # Liste gelöschter Objekte

207.       $DOC=$(get-addomain $Workingdomain -server $WorkingDomain).DeletedObjectsContainer

208.       try {

209.        $AllDeletedObjects=get-adobject -filter {(isdeleted -eq $true) -and (Modified -gt $RestoreDate) -and (distinguishedname -notlike $DOC)} -searchbase $DOC -includedeletedobjects -properties * -resultpagesize 40000 -server $WorkingDomain

210.       }

211.       catch {

212.        throw "Sie müssen Mitglied der Domänen-Admins der ausgewählten Domäne sein. Die domänenlokale Administratoren Gruppe auf dem DC ist nicht ausreichend!"

213.       }

214.       if (!$AllDeletedObjects) {throw "Im angegebenen Zeitraum wurden keine gelöschten Objekte gefunden. Entweder war der Papierkorb nicht frühzeitig genug aktiviert, oder Sie müssen ein Datum weiter in der Vergangenheit wählen."}

215.        

216.       # Fehlende Objekt-Pfade rekonstruieren

217.       $ObjectsWithNoFQDN=$AllDeletedObjects | ? {$_.LastKnownParent -like "*\0ADEL:*"}

218.       $BadObjectswithPath=@()

219.       foreach ($BadObject in $ObjectsWithNoFQDN) {

220.        write-host "." -NoNewline

221.        $CheckObject=$BadObject

222.        $MissingPath=$CheckObject.distinguishedname.split("\")[0]

223.        [String]$ResolvedGUID=$CheckObject.ObjectGUID

224.        do {

225.         $GUID=$CheckObject.LastKnownParent.split(":")[1].split(",")[0]

226.         try {

227.          $Parent=get-adobject $Guid -IncludeDeletedObjects -Properties * -server $WorkingDomain

228.         }

229.         catch {

230.          # Hier konnte das angeblich darüber liegende Objekt nicht mehr gefunden werden

231.          break

232.         }

233.         $MissingPath+=",$($Parent.distinguishedname.split("\")[0])"

234.         $ResolvedGUID+=",$($Parent.ObjectGUID.tostring())"

235.         $CheckObject=$Parent

236.        } while ($CheckObject.LastKnownParent -like "*\0ADEL:*")

237.        if ($CheckObject.LastKnownParent -like $LDAPDomainName){

238.         $MissingPath+=",$($LDAPDomainname)"

239.        }

240.        if ($CheckObject.LastKnownParent -notlike $LDAPDomainName -and $CheckObject.LastKnownParent -notlike "*\0ADEL:*") {

241.         $MissingPath+=",$($CheckObject.LastKnownParent)"

242.         $Parent=get-adobject $CheckObject.LastKnownParent -IncludeDeletedObjects -Properties * -server $WorkingDomain

243.         $ResolvedGUID+=",$($Parent.ObjectGUID.tostring())"

244.         $Marker=$true

245.        } else {

246.         $Marker=$true

247.        }

248.        $BadObjectswithPath+=$BadObject | select *,@{N="MissingPath";E={$Missingpath}},@{N="ResolvedGUIDPath";E={$ResolvedGUID}},@{N="Marker";E={$Marker}}

249.       }

250.        

251.       # Objekte mit und ohne Pfad in einer Liste zusammenführen

252.       $WorkingObjectsList=($AllDeletedObjects | ? {$_.LastKnownParent -notlike "*\0ADEL:*"})+$BadObjectswithPath | sort USNchanged

253.        

254.       # 2. Fenster mit Baumansicht aufbauen

255.       $Fenster2=New-Object System.Windows.Forms.Form

256.       $Fenster2.Size=new-object System.Drawing.Size (840,700)

257.       $Fenster2.Text="Bitte wählen Sie aus"

258.       $Fenster2.Name="TreeView"

259.       $Fenster2.AutoScroll=$true

260.        

261.       # Beschriftung

262.       $Beschriftung=New-Object System.Windows.Forms.Label

263.       $Beschriftung.Location=New-Object System.Drawing.Size(10,40)

264.       $Beschriftung.Size=New-Object System.Drawing.Size(450,23)

265.       $Beschriftung.Text="Einfach alle dauerhaft zu löschenden bzw. wiederherzustellenden Objekte auswählen"

266.       $Beschriftung.Name="Anweisung"

267.       $Beschriftung.Add_Click({$Fenster2.Close()})

268.       $Fenster2.Controls.Add($Beschriftung)

269.       $Count++

270.        

271.       # Knöpfe erstellen

272.       $Count=0

273.       "Wiederherstellen","Dauerhaft löschen","Abbruch","Ausklappen","Einklappen" | foreach {

274.        $Knopf=New-Object System.Windows.Forms.Button

275.        $Knopf.Location=New-Object System.Drawing.Size((10+$Count*150),10)

276.        $Knopf.Size=New-Object System.Drawing.Size(125,23)

277.        $Knopf.Text=$_

278.        $Knopf.Name=$_

279.        if ($_ -eq "Ausklappen") {

280.         $Knopf.Add_Click({

281.          ($Fenster2.Controls | ? {$_.name -like "Wurzel"}).expandall()

282.         })

283.        } elseif ($_ -eq "Einklappen") {

284.         $Knopf.Add_Click({

285.          ($Fenster2.Controls | ? {$_.name -like "Wurzel"}).collapseall()

286.         })

287.        } else {

288.         $Knopf.Add_Click({$this.Tag="Pressed";$Fenster2.Close()})

289.        }

290.        $Fenster2.Controls.Add($Knopf)

291.        $Count++

292.       }

293.        

294.       # Baumansicht aus den gefundenen LDAP-Objekten generieren

295.       # GUI Objekt für die Baumansicht definieren

296.       $Wurzel=New-Object System.Windows.Forms.TreeView

297.       $Wurzel.Size=new-object System.Drawing.Size (800,600)

298.       $Wurzel.Location=new-object System.Drawing.Point (10,65)

299.       $Wurzel.Name="Wurzel"

300.       $Gesamtbild=[System.Windows.Forms.TreeNodeCollection] $Wurzel.Nodes

301.       $Wurzel.CheckBoxes=$true

302.       $Wurzel.Add_AfterCheck({

303.        if ($_.node.checked) {

304.         if (!$_.node.parent.checked -and $_.node.parent) {$_.node.parent.checked=$true}

305.        } else {

306.         if ($_.node.nodes) {$_.node.nodes | foreach {$_.checked=$false}}

307.        }

308.       })

309.       # Den Stamm angelegen

310.       $Stamm=New-Object System.Windows.Forms.TreeNode

311.       $Stamm.Text=$LDAPDomainName

312.       $Stamm.ForeColor="Green"

313.       $Stamm.Name=(Get-ADObject $LDAPDomainName -server $WorkingDomain).ObjectGUID

314.       [void] $Wurzel.Nodes.Add($Stamm)

315.        

316.       # Icons, sprich die Bildchen beschaffen

317.       $ImageList=New-Object System.Windows.Forms.ImageList

318.       $Count=0

319.       $Scriptpath=$Myinvocation.MyCommand.Definition.substring(0,$Myinvocation.MyCommand.Definition.lastindexof("\"))

320.       "folder","user","group","computer","printer","dead","other" | foreach {

321.        $Image = [System.Drawing.Image]::FromFile("$Scriptpath\$($_).ico")

322.        $ImageList.Images.Add($_,$Image)

323.       }

324.       # und dem Baumobjekt bekannt machen

325.       $Wurzel.ImageList=$ImageList

326.        

327.       # Aufbau der Pfade, die zu gelöschten Objekten führen

328.       $BuildPaths=@()

329.       $BuildPaths+=$WorkingObjectsList | ? {$_.Marker} | select MissingPath | foreach {$_.Missingpath.tostring()} # Objekte mit rekonstruiertem Pfad

330.       $BuildPaths+=$WorkingObjectsList | ? {!$_.Marker} | select Distinguishedname,Lastknownparent | foreach {"$($_.distinguishedname.split("\")[0].tostring()),$($_.lastknownparent.tostring())"} # Objekte mit bekanntem Pfad

331.        

332.       foreach ($FullPath in $BuildPaths) {

333.        Write-Host "." -NoNewline

334.        $CreateInReverseOrder=@()

335.        $CurrentObject=$FullPath

336.        $Striplist=$FullPath.replace(",$LDAPDomainName","").split(",")

337.        $ParentOU=$Stamm.Name

338.        for ($Count=0;$Count -lt $Striplist.count;$Count++) {

339.         $CreateInReverseOrder+=$CurrentObject

340.         $CurrentObject=$CurrentObject.replace("$($Striplist[$Count]),","")

341.        }

342.        for ($Count=$CreateInReverseOrder.count-1;$Count -gt 0;$Count--) {

343.         try {

344.          $DN=Get-ADObject ($CreateInReverseOrder[$Count]) -Properties ObjectGUID,ObjectClass -server $WorkingDomain

345.          if ($Gesamtbild.find($DN.ObjectGUID,$True)[0]) {$ParentOU=$DN.ObjectGUID;continue}

346.          # Knoten in die Baumstruktur für das jeweilige LDAP-Objekt einbauen

347.          $Zweig=New-Object System.Windows.Forms.TreeNode

348.          $Zweig.Text=$CreateInReverseOrder[$Count].split(",")[0]

349.          $Zweig.Name=$DN.ObjectGUID

350.          $Zweig.ForeColor="Green"

351.          # Je nach LDAP-Objekt, dass passende Bildchen dran

352.          switch ($DN.ObjectClass) {

353.           "organizationalUnit" {$Zweig.ImageIndex=0}

354.           "user" {$Zweig.ImageIndex=1}

355.           "group" {$Zweig.ImageIndex=2}

356.           "computer" {$Zweig.ImageIndex=3}

357.           "printer" {$Zweig.ImageIndex=4}

358.           default {$Zweig.ImageIndex=6}

359.          }

360.          # Eltern Objekt identifizieren

361.          $ParentObject=$Gesamtbild.find($ParentOU,$True)[0]

362.          # Knoten als Unterobjekt zum Eltern Objekt hinzufügen

363.          [void] $ParentObject.Nodes.Add($Zweig)

364.          $ParentOU=$DN.ObjectGUID

365.         }

366.         catch {

367.          # Das Eltern-Objekt selbst wurde gelöscht, kann aber zugeordnet werden. Wird bei "Paste Deleted Objects" verarbeitet.

368.         }

369.        }

370.       }

371.        

372.       # Gelöschte Objekte einsetzen

373.       $Script:ClickedObjects=@()

374.       $WorkingObjectsList=$WorkingObjectsList | sort -Property uSNChanged -Descending

375.       foreach ($DN in $WorkingObjectsList) {

376.        $Zweig=New-Object System.Windows.Forms.TreeNode

377.        if ($DN.MissingPath) {

378.         $Zweig.Text=$DN.MissingPath.split(",")[0]

379.         $ParentName=$DN.ResolvedGUIDPath.split(",")[1]

380.        } else {

381.         $Zweig.Text=$DN.DistinguishedName.split("\")[0]

382.         $ParentName=(get-adobject "$($DN.Lastknownparent)" -Properties ObjectGUID -server $WorkingDomain).objectguid

383.        }

384.        $Zweig.ImageIndex=5

385.        $Zweig.Name=$DN.ObjectGUID

386.        $ParentObject=$Gesamtbild.find($ParentName,$True)[0]

387.        try {

388.         [void] $ParentObject.Nodes.Add($Zweig)

389.        }

390.        catch {

391.         # Aufgrund von nicht mehr restaurierbaren Objekten kann es zu Fehlern in der Struktur kommen.

392.         # Da diese Objekte sowieso nicht wiederhergestellt werden können, stehen hier nur diese Kommentarzeilen.

393.         # Steht in Zusammenhang mit dieser Zeile:    # Hier konnte das angeblich darüber liegende Object nicht mehr gefunden werden

394.        }

395.       }

396.       $Fenster2.Controls.Add($Wurzel)

397.       # 2. Fenster anzeigen

398.       [void] $Fenster2.ShowDialog()

399.        

400.       # Rekursive (sich selbst aufrufende) Funktion um die vom Benutzer/Admin markierten Objekte zu identifizieren und gesammelt in den Array $fb zu schreiben

401.       function get-checked {

402.        param ($Object)

403.        if ($Object.checked) {

404.         $fb=New-Object PSCustomObject

405.         $fb | Add-Member -membertype NoteProperty -name "ObjectGUID" -value $Object.name

406.         $fb | Add-Member -membertype NoteProperty -name "Path" -value $Object.fullpath

407.         $fb

408.        }

409.        if ($Object.nodes) {$Object.nodes | foreach {get-checked $_}}

410.       }

411.        

412.       # Die Namen aller Objekte, die mit Pressed markiert sind in $Clicked hinterlegen.

413.       $Clicked=$Fenster2.controls | ? {$_.Tag -eq "Pressed"} | select -ExpandProperty Name

414.       if ($Clicked -eq "Abbruch") {"Abbruch durch den Benutzer";exit}

415.        

416.       # Aus den geklickten Objekten mit der o.a. Funktion get-checked, die ObjectGUID und den Pfad besorgen

417.       $SelectedNodes=get-checked ($Fenster2.controls | ? {$_.name -like "Wurzel"}).nodes

418.       $ObjectGUIDsToWorkOn=Compare-Object $WorkingObjectsList $SelectedNodes -property ObjectGUID -IncludeEqual -ExcludeDifferent -passthru

419.        

420.       if (!$ObjectGUIDsToWorkOn) {"Keine Objekte ausgewählt";exit}

421.        

422.       # Alle markierten Objekte wiederherstellen

423.       if ($Clicked -eq "Wiederherstellen") {

424.        $ObjectGUIDsToWorkOn | % {restore-adobject -Identity $_.ObjectGUID -confirm:$false -Server $WorkingDomain}

425.       }

426.       # Alle markierten Objekte dauerhaft löschen

427.       if ($Clicked -eq "Dauerhaft löschen") {

428.        $Objects=$ObjectGUIDsToWorkOn.count

429.        if (!$Objects) {$Objects=1}

430.        # Bevor die Objekte für immer verschwinden noch mal eine Sicherheitsabfrage

431.        if ([System.Windows.Forms.MessageBox]::Show("Sicher, dass alle angehakten, schwarzen Objekte ($Objects Stück) aus dem Papierkorb dauerhaft gelöscht werden sollen?" , "Objekte dauerhaft löschen", 1, "Question") -eq "OK") {

432.         $ObjectGUIDsToWorkOn | % {remove-adobject -Identity $_.ObjectGUID -IncludeDeletedObjects -confirm:$false -Server $WorkingDomain}

433.        }

434.       }

435.        

436.       "Fertig!"

4.9.6   Gruppenrichtlinien

Dieser Bereich geht auf Active-Directory Gruppenrichtlinien ein. Die Cmdlets zur Gruppenrichtlininenverwaltung sind nicht Bestandteil des ActiveDirectory-Moduls, sondern müssen zusätzlich über:

Import-Module GroupPolicy

geladen werden. Dies kann auch unabhängig vom ActiveDirectory-Modul geschehen.

Leider ist hier nicht ganz so viel Energie in die Entwicklung gesteckt worden, wie in die ActiveDirectory Cmdlets. Das merken Sie spätestens dann, wenn Sie feststellen, dass es zwar ein Set‑GPLink, aber kein Get-GPLink gibt. Weiterhin problematisch ist, dass Sie nicht objektorientiert auf die Einstellungen in den Gruppenrichtlinien zugreifen können. Die einzige Möglichkeit besteht darin mit dem Cmdlet Get-GPOReport, mit dem Sie sich die Einstellungen als HTML- oder XML-Report ausgeben lassen können.

4.9.6.1   Gruppenrichtlinien analysieren

Das nachfolgende Skript analysiert Gruppenrichtlinien und erstellt einen Bericht darüber. SID-Filter werden dabei berücksichtigt, nicht aber WMI-Filter, da hierfür die Computer eingeschaltet sein müssten. Das Skript zieht sich alle benötigten Informationen selbständig. Es müssen und können auch keine Parameter übergeben werden.

100.       # Dieses Skript erstellt einen Bericht über Fehler in der GPO-Konfiguration. Nur WMI-Filter werden dabei nicht berücksichtigt.

101.       # Gruppenrichtlinien- und ActiveDirectory-Module laden

102.       Import-Module GroupPolicy,ActiveDirectory

103.       # LogPfade festlegen

104.       $Logfile="C:\ADGPOPowerShellDocuDetailed.txt"

105.       $DetailedLogFile="C:\ADGPOPowerShellDocu.txt"

106.       # Die nachfolgenden Einträge können in Multi-Domänen-Umgebungen natürlich gerne angepasst werden

107.       # Feststellen in welcher Domäne wir "zuhause" sind

108.       $Domain=Get-ADDomain

109.       # LDAP-Pfad definieren

110.       $LDAPDomain=$Domain.Distinguishedname

111.       # Netbios Name festlegen

112.       $NetBIOSDomain=$Domain.NetBIOSName

113.       # Canonischen Namen aus LDAP-Pfad generieren

114.       $CanonicalDomain=$LDAPDomain.replace("DC=","").replace(",",".")

115.       # Standardbereichtungen festelegen. Achtung! Ist auf Deutsche Sprache festgelegt. Dies muss für Englisch entsprechend angepasst werden. Für SIDs war ich zu faul ;-)

116.       $DefaultTrustees="NT-AUTORITÄT\SYSTEM","$NetBIOSDomain\Organisations-Admins","NT-AUTORITÄT\DOMÄNENCONTROLLER DER ORGANISATION","NT-AUTORITÄT\Authentifizierte Benutzer","$NetBIOSDomain\Domänen-Admins"

117.       # Logfiles anlegen. Ein einfaches und ein detailiertes...

118.       "Dokumentation PowerShell-Script Datum: $(get-date)","" > "$Logfile"

119.       "Detailiert Dokumentation PowerShell-Script Datum: $(get-date)","" > "$DetailedLogFile"

120.        

121.       # Wichtigste Funktion überhaupt

122.       # Diese baut aus einem XML-Report und einigem mehr in PowerShell verwertbare Objekte.

123.       function Get-GPODetailedInfo {

124.        [CmdletBinding()]

125.        param (

126.         [Parameter(Mandatory=$true,ValueFromPipeline=$true)]

127.         [Alias('DisplayName')]

128.         [string[]]$Name

129.        )

130.        foreach ($n in $Name) {           

131.         $problem=$false

132.         try {

133.          Write-Verbose -Message "Versuche einen XML-Report für GPO: $n zu ziehen."

134.          [xml]$report=Get-GPOReport -Name $n -ReportType Xml -ErrorAction Stop

135.         }

136.         catch {

137.          $problem=$true

138.          Write-Warning -Message "Fehler beim Versuch von GPO: $n einen Report zu ziehen."

139.         }

140.         if (-not($problem)) {

141.          Write-Verbose -Message "Baue PowerShell-Objekt für GPO: $n"

142.          $GPOInfo=New-Object PSCustomObject

143.          # Name der GPO

144.          $GPOInfo | Add-Member -MemberType NoteProperty -name 'GPOName' -value $report.GPO.Name

145.          # Verlinkungsinformationen zusammensetzen

146.          $Links=@()

147.          foreach ($Info in $report.GPO.LinksTo) {

148.           $Link=New-Object PSCustomObject

149.           # Pfad der Verlinkung

150.           $Link | Add-Member -MemberType NoteProperty -name 'SOMPath' -value $Info.SOMPath

151.           # Link aktiv?

152.           $Link | Add-Member -MemberType NoteProperty -name 'Enabled' -value $(if ($Info.Enabled -eq "true") {$true} else {$false})

153.           # Erzwungen ?

154.           $Link | Add-Member -MemberType NoteProperty -name 'NoOverride' -value $(if ($Info.NoOverride -eq "true") {$true} else {$false})

155.           $Links+=$Link

156.          }

157.          # Verlinkungsinformationen ins Objekt kleben

158.          $GPOInfo | Add-Member -MemberType NoteProperty -name 'LinksTo' -value $Links

159.          foreach ($UC in "Computer","User") {

160.           if ($report.GPO."$($UC)".ExtensionData) {

161.            # Wenn die GPO Computer- und/oder Benutzereinstellungen hinterlegt hat...

162.            $GPOInfo | Add-Member -MemberType NoteProperty -name "Has$($UC)Settings" -value $true

163.            $Settings=@()

164.            foreach ($Extension in $report.GPO."$($UC)".ExtensionData) {

165.             $Item=New-Object PSCustomObject

166.             # Top-Level-Einstellungen aus XML in PowerShell-Objekt überführen

167.             $Item | Add-Member -MemberType NoteProperty -name $Extension.name -value $Extension.Extension.innertext

168.             $Settings+=$Item

169.            }

170.            # Falls feststellbar, Einstellungen ins Objekt kleben

171.            $GPOInfo | Add-Member -MemberType NoteProperty -name "$($UC)Settings" -Value $Settings

172.           } else {

173.             # Wenn die GPO keine Computer- und/oder Benutzereinstellungen hinterlegt hat...

174.            $GPOInfo | Add-Member -MemberType NoteProperty -name "Has$($UC)Settings" -value $false

175.           }

176.          }

177.          # Berechtigungen einsetzen

178.          $GPOInfo | Add-Member -MemberType NoteProperty -name 'Trustees' -value $($report.GPO.SecurityDescriptor.Permissions.TrusteePermissions | % {$_.Trustee.Name.InnerText})

179.          # Erstellungsdatum einsetzen

180.          $GPOInfo | Add-Member -MemberType NoteProperty -name 'CreatedDate' -value ([datetime]$report.GPO.CreatedTime).ToShortDateString()

181.          # Änderungsdatum einsetzen

182.          $GPOInfo | Add-Member -MemberType NoteProperty -name 'ModifiedDate' -value ([datetime]$report.GPO.ModifiedTime).ToShortDateString()

183.          # ZUsammengeschraubtes Objekt mit Gruppenrichtlinieninfo zurückgeben

184.          $GPOInfo

185.         }

186.        }

187.       }

188.        

189.       # Funktion die Infos in Blau auf den Bildschirm schreibt und in beide Logs dokumentiert

190.       function Document {

191.        $args | Write-Host -ForegroundColor Blue

192.        $args >> "$DetailedLogFile"

193.        $args >> "$Logfile"

194.       }

195.        

196.       # Funktion die Infos in der Standardfarbe auf den Bildschirm schreibt und nur ins Detail-Log schreibt

197.       function LogDetail {

198.        $args | Write-Host

199.        $args >> "$Logfile"

200.       }

201.        

202.       # Alle GPOs der Domäne auslesen (dafür sollten Sie natürlich Domain-Admin sein)

203.       $AllGPOs=get-gpo -all | sort Displayname

204.       # Die Infos zu den GPOs aus der Funktion zusammenbauen lassen

205.       $GPOLinkInfos=foreach ($Gpo in $AllGPOs) {Get-GPODetailedInfo $GPO.Displayname}

206.       # Feststellen auf welchen OUs die Vererbung von Gruppenrichtlinien deaktiviert ist

207.       $DisabledInheritance=Get-ADOrganizationalUnit -f {gPOptions -eq 1} | select -ExpandProperty Distinguishedname

208.        

209.       # Hier beginnt die Auswertung der einzelnen GPOs

210.       Foreach ($GPO in $AllGPOs) {

211.        Document "","Analyzing $($GPO.Displayname)"

212.        # Linkinfo aus dem PowerShellGPOObjekt extrahieren

213.        $CorrespondingGPOLinkInfo=$GPOLinkInfos | ? {$_.GPOName -eq $GPO.Displayname}

214.        # Feststellen, ob überhaupt Benutzer- oder Computereinstellungen in der GPO vorliegen

215.        if (-not $GPO.User.enabled -and -not $GPO.Computer.enabled) {

216.         # Wenn Nein, entsprechend dokumentieren

217.         Document "","GPO $($GPO.Displayname) hat Benutzer- und ComputerSettings Deaktiviert: GPO wird nicht angewendet. Kann gelöscht werden."

218.        } else {

219.         # Wenn Ja, feststellen welche nicht standisierten SID-Filter gesetzt wurden

220.         $TrusteeException=$CorrespondingGPOLinkInfo.Trustees | ? {$DefaultTrustees -notcontains $_}

221.         # Erst den User-, dann den Computerabschnitt auswerten

222.         Foreach ($UC in "User","Computer") {

223.          # Wenn Einstellungen aktiv und Inhalt vorhanden...

224.          if ($GPO.$UC.enabled -and $CorrespondingGPOLinkInfo."Has$($UC)Settings") {

225.           Document "","$($UC)-Abschnitt: Aktiv und enthält $UC-Einstellungen"

226.           # Jeden Link einzeln untersuchen

227.           Foreach ($Link in $CorrespondingGPOLinkInfo.LinksTo) {

228.            # Wenn der Link deaktiviert ist dokumentieren und gleich mit dem nächsten weiter machen

229.            if (!$Link.enabled) {

230.             Document "Link ist auf OU $($Link.SOMPath) deaktiviert: GPO wird nicht angewendet - Link kann entfernt werden"

231.             continue

232.            }

233.            # Canonischen Namen aus der Linkinfo in LDAP-Pfad umbauen

234.            $CutDomain=$Link.SOMPath.replace("$CanonicalDomain/","").split("/")

235.            $RelatedLDAPLink=$LDAPDomain

236.            if ($CutDomain -notlike $CanonicalDomain) {

237.             foreach ($Part in $CutDomain) {

238.              $RelatedLDAPLink=$RelatedLDAPLink.insert(0,"OU=$Part,")

239.             }

240.            }

241.            # Je nachdem, ob gerade der Benutzer oder Computerabschnitt durchlaufen wird, die potentiell betroffenen Benuzter- oder Computerobjekte feststellen

242.            $Objectlist=if ($UC -eq "User") {

243.             Get-ADUser -f * -searchbase $RelatedLDAPLink

244.            } else {

245.             Get-ADComputer -f * -searchbase $RelatedLDAPLink

246.            }

247.            # Wenn es sich nicht um einen erzwungenen Link handelt

248.            if (!$Link.NoOverride) {

249.             LogDetail "Die Vererbung des Links $($Link.SOMPath) kann durch einen Block außer Kraft gesetzt werden."

250.             # Dokumentieren, wenn es keine Objekte gefunden wurden

251.             if (!$Objectlist) {

252.              Document "Unterhalb des Links $($Link.SOMPath) wurden keine $UC Objekte gefunden."

253.             } else {

254.              # Wenn nur ein Objekt gefunden wurden...

255.              if (!$Objectlist.Count) {

256.               LogDetail "Unterhalb des Links $($Link.SOMPath) wurde 1 $UC Objekt gefunden ohne Berücksichtigung eines eventuellen Blocks der Vererbung."

257.              # Wenn mehrere Objekte gefunden wurden...

258.              } else {

259.               LogDetail "Unterhalb des Links $($Link.SOMPath) wurden $($Objectlist.count) $UC Objekte gefunden ohne Berücksichtigung eines eventuellen Blocks der Vererbung."

260.              }

261.              # Liste mit OUs vom Link an abwärts erstellen, die einen Vererbungsblock gesetzt haben

262.              $RemoveBlockOnLinkLevel=$DisabledInheritance | ? {$_ -like "*$RelatedLDAPLink"} | ? {$_ -notlike $RelatedLDAPLink}

263.              if ($RemoveBlockOnLinkLevel) {

264.               LogDetail "Auf folgenden OUs unterhalb von $($Link.SOMPath) wurden Vererbungsblocks gefunden: $RemoveBlockOnLinkLevel"

265.               foreach ($Exclusion in $RemoveBlockOnLinkLevel) {

266.                # Objekte entfernen, die unterhalb von OUs mit Vererbungsblock liegen

267.                $Objectlist=$Objectlist | ? {"$($_.distinguishedname)" -notlike "*$Exclusion"}

268.               }

269.              } else {

270.               LogDetail "Keine Vererbungsblocks unterhalb von $($Link.SOMPath) identifiziert."

271.              }

272.              if (!$Objectlist) {

273.               LogDetail "Nach Anwenden der Blockliste wurde festgestellt, dass keine $UC Objekte die Einstellungen erhalten."

274.              } elseif (!$Objectlist.count) {

275.               LogDetail "Nach Anwenden der Blockliste erhält nur noch 1 $UC Objekt die Einstellungen aus der Richtlinie."

276.              } else {

277.               LogDetail "Nach Anwenden der Blockliste erhalten noch $($Objectlist.count) $UC Objekte die Einstellungen aus der Richtlinie."

278.               $Objectlist | select Distinguishedname,ObjectClass,Enabled >> "$Logfile"

279.              }

280.             }

281.            } else {

282.             LogDetail "Der Link $($Link.SOMPath) ist erzungen und kann nicht durch einen Block der Vererbung zurückgehalten werden."

283.            }    

284.            if (!$Objectlist) {

285.             Document "Kein $($UC) Objekt unterhalb des Links $($Link.SOMPath) gefunden: GPO wird hier nicht angewandt."

286.            # Wenn keine Standard-SID-Einstellung gefunden wird und somit ein SID-Filter zum Einsatz kommt..

287.            } elseif ($TrusteeException) {

288.             LogDetail "SID-Filter gefunden: $TrusteeException"

289.             $LeafList=@()

290.             # Jeden nicht standard Eintrag prüfen...

291.             foreach ($Trustee in $TrusteeException) {

292.              if ($Trustee -ne $Null) {

293.               # SAMAccountname aus Trustee extrahieren

294.               $ReducedName=$Trustee.split("\")[1]

295.               $ADObj=Get-ADObject -f {SAMAccountname -like $ReducedName} -Properties SAMAccountname

296.               LogDetail "Objekt-Typ: $($ADObj.objectclass)"

297.               # Handelt es sich um eine Gruppe -> Mitglieder ausfindig machen

298.               if ($ADObj.objectclass -eq "group") {

299.                $Member=Get-ADGroupMember $ADObj | Get-ADObject

300.                if (!$Member) {

301.                 Document "Gruppe $($ADObj.SAMAccountname) hat keine Mitglieder. GPO auf Link $($Link.SOMPath) für $($ADObj.SAMAccountname) nicht angewandt."

302.                 continue

303.                } else {

304.                 "Mitglieder:" >> "$Logfile"

305.                 $Member | select distinguishedname,objectclass,Enabled >> "$Logfile"

306.                 $LeafList+=$Member

307.                }

308.               } else {

309.                $Leaflist+=$ADObj

310.               }

311.               # SAMAccountnamelisten erstellen

312.               $LLSAMNames=$Leaflist | select -expand name

313.               $OLSAMNames=$Objectlist | select -expand Samaccountname

314.               # und bei Computern das angehängte $-Zeichen abschneiden

315.               if ($UC -eq "Computer") {

316.                $OLSAMNames=$OLSAMNames | % {$_.trimend('$')}

317.               }

318.               # Übereinstimmung vor und nach dem SID-Filter identifizieren

319.               $Compare=Compare-Object $LLSAMNames $OLSAMNames -IncludeEqual -ExcludeDifferent

320.               if ($Compare) {

321.                Document "$($Compare.count) Objekte nach Anwendung des SID-Filter für Link $($Link.SOMPath) gefunden! Link wird erfolgreich auf diese Objekte angewandt."

322.                LogDetail "SID-Filter am Link $($Link.SOMPath) trifft:"

323.                LogDetail $($Compare | select -ExpandProperty Inputobject)

324.               } else {

325.                Document "Keine dem SID-Filter entsprechenden Objekte unterhalb des Links $($Link.SOMPath) gefunden! Link ohne Effekt."

326.               }

327.              }

328.             }

329.            } else {

330.             if ($Objectlist.count) {

331.              Document "$($Objectlist.count) Objekte unterhalb des Links $($Link.SOMPath) gefunden! Link erfolgreich angewandt."

332.              "Diese Objekte befinden sich unterhalb des Links $($Link.SOMPath):" >> "$Logfile"

333.              $Objectlist >> "$Logfile"

334.             }

335.            }

336.           }

337.          } else {

338.           Document "$($UC)-Abschnitt: GPO-Status $(if ($GPO.$UC.enabled) {"enabled"} else {"disabled"}), aber hat $(if (!$CorrespondingGPOLinkInfo."Has$($UC)Settings") {"keine"}) $($UC)-Einstellungen. Keine weitere Prüfung."

339.          }

340.         }

341.        }

342.       }

Das Skript ist reichlich mit Kommentarzeilen versehen. Was eine Kommentarzeile nicht erklärt, findet sich dann meist als Ausgabe auf dem Bildschirm bzw. wird in eine Protokolldatei geschrieben. Daher will ich nur kurz den groben Aufbau skizzieren. Es gibt 3 Funktionen im Skript, wovon 2 nur für die Protokollerstellung (Document und LogDetail) zuständig sind. Die Hauptfunktion ist Get‑GPODetailedInfo, welche die wesentlichen Aufgaben erfüllt. Diese verwendet das Cmdlet Get-GPOReport aus dem Modul Gruppenrichtlinien, um Informationen über die einzelne GPOs im XML-Format zu erhalten. Der Clou an der Geschichte, ist der Umbau des XML-Reports in ein PowerShell-Objekt. Dies ermöglicht dann eine einfache objektorientierte Auswertung der Informationen.

4.9.6.2   WMI-Filter in Gruppenrichtlinien

Leider gibt es auch keine Cmdlets, um auf die WMI-Filter zuzugreifen. Um alle WMI-Filter Ihrer Domäne zu ergattern, gibt es die Möglichkeit über Get-ADObject zuzugreifen:

Get-ADObject -LDAPFilter "(objectClass=msWMI-Som)" -Properties *

Dies können Sie natürlich auch gerne in eine Array-Variable packen, um Sie zu verarbeiten und mit Set-ADObjekt zu modifizieren. Weiterhin wäre eine Funktion Get-WMIFilter denkbar. Das Schreiben einer solchen Funktion fällt Ihnen an dieser Stelle des Buches sicher leicht.

Vorsicht ist jedoch beim Löschen von WMI-Filtern angesagt. Wenn Sie einfach nur den Filter löschen, bleibt der Link zum WMI-Filter in der Gruppenrichtlinie erhalten. Dieser deutet dann auf einen nicht existenten WMI-Filter. Je nach Client-Betriebssystem wird die Gruppenrichtlinie dann angwandt, oder auch nicht. Um einen WMI-Filter vor dem Löschen von einer Gruppenrichtlinie zu entfernen, benötigen Sie zunächst das Gruppenrichtlinienobjekt, bzw. alle Gruppenrichtlinienobjekte, die auf den WMI-Filter deuten.

$Filter=Get-GPO -All | ? {$_.WmiFilter.name -like "NameDesFilters"}

$FilteredGPOs=$Filter.Path | % {Get-ADObject $_}

Um dann den Link aus den GPOs zu löschen, verwenden Sie:

$FilteredGPOs | % {Set-ADObject $_ -clear gPCWQLFilter}

Jetzt dürfen Sie auch den eigentlichen WMI-Filter löschen:

Get-ADObject -LDAPFilter "(&(objectClass=msWMI-Som)(msWMI-Name= NameDesFilters))" | Remove-ADObject

4.10    Mit PowerShell fremden XML-Dateien arbeiten

PowerShell ist vollständig objektorientiert und kann mittels Export-Clixml und Import‑Clixml wunderbar die eigenen Objekte als XML ex- und importieren.  Im .NET-Framework gibt es z.B. die XML-Dateien machine.config oder web.config unter:

C:\Windows\Microsoft.NET\Framework\Versionsnummer\Config

Versuchen Sie doch einmal diese Dateien mit Import‑Clixml zu laden. Das wird leider nicht klappen. Dennoch ist es möglich, obgleich die XML-Bearbeitung alles andere als intuitiv ist. Weiterhin gilt es bei der XML-Bearbeitung die Groß- und Kleinschreibung in der XML-Datei zu beachten!

XML-Files bestehen aus Knoten, die auch Element genannt werden, sowie Attributen und Werten. Eine Beispiel web.config aus dem IIS soll dies verdeutlichen:

<?xml version="1.0" encoding="UTF-8"?>

<configuration>

    <system.webServer>

        <defaultDocument>

            <files>

                <clear />

                <add value="index.html" />

                <add value="Default.htm" />

                <add value="Default.asp" />

                <add value="index.htm" />

                <add value="iisstart.htm" />

            </files>

        </defaultDocument>

    </system.webServer>

</configuration>

Weiterhin sollten Sie wissen, dass ein Element nicht zwingend ein Attribut haben muss, wie z.B. clear. Auch ein Attribut muss nicht zwingend einen Wert haben.

4.10.1                     XML-Datei laden

Das Laden der XML-Datei geht ja noch recht simpel von statten:

[XML]$File=Get-Content ` C:\Windows\Microsoft.NET\Framework\Versionsnummer\Config\web.config

Wichtig hierbei ist die Typedeklaration als XML vorweg. Ansonsten haben Sie eine stinknormale Textdatei.

4.10.2                     XML-Objekt untersuchen

Wie üblich läßt sich das $File  Objekt nun mit Get-Member näher untersuchen:

$File | Get-Member

Zunächst sollten Sie erkennen, dass Sie es mit einem System.Xml.XmlDocument Objekt zu tun haben. Danach können Sie bei MSDN gerne suchen.

Gerne können Sie auch in der gewohnten .NET-Schreibweise auch wieder in die einzelnen Äste des XML-Objektes abtauchen. Leider ist es aber nicht in der gewohnten Weise möglich das Objekt zu bearbeiten! Die interessanten Bearbeitungsmöglichkeiten (Methoden) gehen alle direkt vom $File Objekt aus, nicht von den per .NET-Schreibweise ansteuerbaren Elementen. Die intressantesten Methoden werden im Folgenden näher erläutert.

4.10.3                     XPath Syntax um auf einzelne Knoten zu verweisen

Da Sie nicht per .NET-Schreibweise auf die Knoten direkt zugreifen können, müssen Sie zunächst Objekte generieren, die den jeweiligen Knoten bearbeiten lassen. Hierzu sind die Methoden SelectNodes() und SelectSingleNode() interessant.

4.10.3.1                SelectSingleNode

Laden Sie für die nachfolgenden Beispiele zunächst eine web.config Datei als XML aus dem Verzeichnis C:\Windows\Microsoft.NET\Framework\Versionsnummer\Config. Beispiel:

[XML]$File=gc ‘
C:\Windows\Microsoft.NET\Framework\v4.0.30319\Config\web.config

Mit SelectSingleNode() können Sie ein neues Objekt erstellen, das den ersten gefundenen Knoten referenziert:

$File.SelectSingleNode(‘//securityPolicy’)

Das Ergebnis sollte so aussehen:

trustLevel

----------

{Full, High, Medium, Low...}

Die entsprechende Passage aus der web.config sieht so aus:

            <securityPolicy>

                <trustLevel name="Full" policyFile="internal" />

                <trustLevel name="High" policyFile="web_hightrust.config" />

                <trustLevel name="Medium" policyFile="web_mediumtrust.config" />

                <trustLevel name="Low"  policyFile="web_lowtrust.config" />

                <trustLevel name="Minimal" policyFile="web_minimaltrust.config" />

            </securityPolicy>

Das Ganze können Sie dann natürlich auch einer Variablen als Referenzobjekt zuweisen:

$Knoten=$File.SelectSingleNode(‘//securityPolicy’)

Was ist hier passiert? In der Methodenklammer muss eine sogenannte XPath-Anweisung stehen. Gewöhnen Sie sich am besten gleich einfache Hochkommata an, denn in den XML-Files können auch „normale“ Anführungszeichen vorkommen und diese müssen auch mit angegeben werden. Der erste / sagt aus, dass Sie im gesamten XML-File, von der ersten Zeile (hierarchisch höchste Stelle) an das XML-File (bzw. das XML-Objekt) nach dem Begriff (bzw. dem Knoten/Element) securityPolicy durchsuchen. Für das Suchen steht der zweite /.

Geben Sie nun einmal Folgendes ein:

$File.SelectSingleNode('//trustlevel')

Ergebnis? Nichts? Ja, nun sehen Sie, wie wichtig die Groß- und Kleinschreibung ist. Versuchen Sie mal:

$File.SelectSingleNode('//trustLevel')

Ergebnis sollte so aussehen:

name policyFile

---- ----------

Full internal

Wie Sie sehen wurde nun das erste trustLevel Element gewählt. Möchten Sie ein anderes haben, können Sie die XPath-Suchfunktion erweitern:

$File.SelectSingleNode('//trustLevel[@name="Medium"]')

Mit der eckigen Klammer gefolgt vom @-Zeichen können Sie sagen, dass Sie gerne das Element haben möchten, dessen Attribut den Wert „Medium“ hat. An diesem Beispiel sehen Sie auch gleich die Anführungszeichen-Problematik. Das Ergebnis sollte nun so aussehen:

name   policyFile

----   ----------

Medium web_mediumtrust.config

4.10.3.2                SelectNodes

Mit SelectNodes() können Sie mehrere Knoten auf einmal auswählen und einem Referenzobjekt zuweisen. Um alle trustLevels aus der web.config auszuwählen gehen Sie so vor:

$File.SelectNodes('//trustLevel')

Ergenis sollte so aussehen:

name    policyFile

----    ----------

Full    internal

High    web_hightrust.config

Medium  web_mediumtrust.config

Low     web_lowtrust.config

Minimal web_minimaltrust.config

Für weitere XPath Suchmöglichkeiten sei hier auf die Dokumentation von Microsoft im Internet verwiesen:

https://msdn.microsoft.com/de-de/library/ms256471(v=vs.120).aspx

4.10.4                     Neues XML-Element erstellen

Um eine neues XML-Element zu erstellen, müssen Sie die Methode CreateElement() des Root-Objekts nutzen.

$NeuesElement=$File.CreateElement("Neu")

Gerne dürfen Sie sich das neue Element einmal mit Get-Member näher anschauen.

4.10.5                     Einem XML-Element Attribute und Werte zuweisen

Um dem neuen XML-Element nur ein Attribut (ohne Wert) zuzuweisen, nutzen Sie die „geheime“ (weil nicht sichtbar im Get-Member oder Select *) Eigenschaft InnerText am ausgewählten Element:

$NeuesElement.InnerText="Mensch"

Für ein Attribut mit Wert, verwenden Sie die Methode SetAttribute() direkt am Objekt:

$NeuesElement.SetAttribute("Vorname","Martin")

4.10.6                     XML-Element in Dokument plazieren

Das neu erstellte Element können Sie nun in das bestehende web.config Objekt einsetzen, indem Sie zunächst einmal mittels SelectNode() das Elternelement bestimmen, in welches Sie Ihr neu erstelltes Objekt einfügen möchten:

$Eltern=$File.SelectSingleNode('//securityPolicy')

4.10.6.1                XML-Element vor anderen Elementen einfügen

Die securityPolicy enthält das Element trustLevel. Mit der Methode PrependChild() können Sie Ihr Objekt vor trustLevel positionieren:

$Eltern.PrependChild($NeuesElement)

4.10.6.2                XML-Element nach anderen Elementen einfügen

Mit der Methode AppendChild() können Sie Ihr Objekt nach trustLevel positionieren:

$Eltern.AppendChild($NeuesElement)

4.10.6.3                XML-Element an einer bestimmten Position einfügen

Das Element trustLevel enthält mehrere Elemente. Hier möchten Sie Ihr neues Element vielleicht an einer ganz bestimmten Stelle einfügen. Dies können Sie mit den Methoden InsertBefore() und InsertAfter() realisieren. Dazu benötigen Sie zunächst eine neue Objektreferenz auf trustLevel:

$Eltern=$File.SelectSingleNode('//securityPolicy')

Weiterhin brauchen Sie noch eine 2. Objektreferenz vor bzw. nach welchem Element Sie Ihres positionieren möchten. Hier soll Ihnen der Medium trustLevel als Referenz dienen:

$Referenz=$File.SelectSingleNode('//trustLevel[@name="Medium"]')

Mit der Methode InsertBefore() können Sie Ihr Objekt vor dem Element Medium trustLevel positionieren:

$Eltern.InsertBefore($NeuesElement)

Mit InsertAfter() entsprechend danach.

4.10.7                     XML-Attribut löschen

Um ein Attribut zu löschen, verwenden Sie die Methode RemoveAttribute() direkt am Element, welches das zu löschende Attribut enthält:

$Referenz.RemoveAttribute("name")

4.10.8                     XML-Element löschen

Um ein komplettes Element zu löschen verwenden Sie die Methode RemoveChild()  an der Objektreferenz, die das zu löschende Element beinhaltet:

$Eltern.RemoveChild($Referenz)

4.10.9                     XML-Dokument speichern

Um Ihr bearbeitetes XML-Dokument zu speichern verwenden Sie die Methode save() am Root-Objekt:

$File.save("C:\Fertig.xml")

Ein einfaches $File > Fertig.xml funktioniert leider nicht.

4.10.10                XML-Dokument wie ein PSObject verwenden

Wenn Ihnen das alles zu blöd ist (was ich sehr gut verstehen kann), installieren Sie sich das Modul HostUtilities der PowerShell Gallery:

Install-Module -Name HostUtilities

Danach steht Ihnen das Cmdlet ConvertFrom-XML zur Verfügung. Damit können Sie ein XML Objekt in ein PSCustomObject konvertieren lassen und dann mit den von PowerShell her bekannten Möglichkeiten bearbeiten.

[xml]$xml=Get-Content IhrXmlFile.xml

$PSObject=ConvertFrom-XML $xml

Leider ist in dem Modul aktuell kein Cmdlet enthalten das wieder ein XML Objekt in Form der Eingabe erstellen kann. ConvertTo-XML vermag dies leider ebenso wenig wie Export-CliXML, da diese rein auf PowerShell eigene XML Formate ausgelegt sind. Wenn ich was rausfinde wird es zukünftig hier zu lesen sein.

5        Anhang

5.1     Lösungen

Das Kapitel Lösungen gliedert sich in zwei Abschnitte. Im ersten Abschnitt finden Sie die Lösungen zu den Kontrollfragen und im zweiten die Musterlösungen zu den gestellten Skriptaufgaben.

5.1.1   Kontrollfragen

Hier finden Sie noch einmal die im Buch gestellten Fragen, allerdings mit den Antworten.

5.1.1.1   Einführung

Frage:

Auf welchen Betriebssystemen können Sie die PowerShell verwenden?

Antwort:

Auf den Client Betriebssystemen: XP, Vista, Windows 7 +8

Und auf den Serverbetriebssystemen: 2003, 2003 R2, 2008 nur GUI, 2008 R2 Core+GUI, 2012

Für Linux gibt es PASH (PowerShell + Bash), was sich aber zum Zeitpunkt da ich diese Zeilen Schreibe noch im Alphastadium befindet.

Frage:

Was müssen Sie bei Windows Server 2008 R2 in der Core Installation tun um PowerShell verwenden zu können?

Antwort:

Zunächst benötigen Sie .NET-Version 2.0 welches Si emit folgendem Befehl nachinstallieren können:

ocsetup “NetFx2-ServerCore”

Erst mit installiertem .NET-Framework können Sie danach die PowerShell durch folgenden Befehl hinzufügen:

ocsetup “MicrosoftWindowsPowerShell”

5.1.1.2   Die Arbeitsumgebung

Frage:

Wie können Sie PowerShell starten?

Antwort:

1.       Klicken Sie auf das PowerShell Symbol in der Taskleiste.

2.       Tippen Sie die Windows-Taste und ein R und geben Sie im auftauchenden Dialogfeld powershell ein.

3.       Ab Window Vista/Server 2008 tippen Sie powershell im Suchfeld des Startmenüs ein.

4.       Im Startmenü unter Alle Programme/Zubehör/Windows PowerShell klicken Sie auf das Icon Windows PowerShell.

Frage:

Wie finden Sie die Version der PowerShell heraus, die auf dem PC installiert ist?

Antwort:

1.       Durch eintippen der Variable $host in der PowerShell Konsole.

2.       Durch eintippen des Cmdlet Get-Host in der PowerShell Konsole.

Frage:

Wie können Sie die Hintergrundfarbe der aktuell geöffneten PowerShell ändern?

Antwort:

Indem Sie auf das Systemmenü, das Icon links oben in der Ecke des geöffneten PowerShellfensters klicken und dort den Eintrag Eigenschaften auswählen. Wechseln Sie zum Reiter Farben, stellen Sie sicher, dass im Optionsfeld Fensterhintergrund angeklickt ist und wählen Sie eine Farbe die Ihnen besser gefällt.

5.1.1.3   Eingabebefehle

Frage:

Wie springen Sie zum Zeilenanfang in der PowerShell Konsole?

Antwort:

Mit der Pos1-Taste.

Frage:

Wie löschen Sie alle Zeichen von der aktuellen Position bis zum Zeilenende?

Antwort:

Mit Strg+Ende gleichzeitig.

Frage:

Wie kann man einen Text in die Zwischenablage kopieren?

Antwort:

Indem Sie zunächst prüfen ob der QuickEdit Modus aktiviert ist. Dazu klicken Sie mit der rechten Maustaste in der linken oberen Ecke des PowerShell Fensters auf das PowerShell Icon und wählen im Kontextmenü den Punkt Eigenschaften. Schauen Sie ob bei QuickEdit ein Häkchen gesetzt ist. Wenn nein, setzen Sie es und bestätigen mit einem Klick auf OK. Nun können Sie einfach bei gedrückter Maustaste über den Text streichen und anschließend einen Rechtsklick ausführen. Dadurch wird der markierte Text in die Zwischenablage befördert und kann nun in anderen Programmen oder der PowerShell selbst wieder eingefügt werden.

Frage:

Wie kann man einen Text aus der Zwischenablage einfügen?

Antwort:

Um einen Text aus der Zwischenablage wieder in die PowerShell einzufügen klicken Sie einfach mit der rechten Maustaste in das PowerShell Fenster. Die Position innerhalb des Fensters spielt keine Rolle, da der Text immer an der aktuellen Eingabeposition eingefügt wird. Bilder aus der Zwischenablage einzufügen macht wohl wenig Sinn ;-).

Frage:

Wie ist der grundlegende Aufbau eines PowerShell Cmdlet?

Antwort:

Verb-Noun, oder zu deutsch: Tätigkeitswort-Hauptwort. Ggf. folgen noch Schalter, die mit einem einleitenden Minus-Zeichen angegeben werden.

Frage:

Welches Cmdlet beendet einen Dienst?

Antwort:

Stop-Service NamedesDienstes

Frage:

Welches Cmdlet zeigt die Prozessliste an?

Antwort:

Get-Process

Frage:

Welches Cmdlet zeigt Ihnen alle zur Verfügung stehenden Cmdlets an?

Antwort:

Get-Command

Frage:

Wie finden Sie heraus, welche Cmdlets zur Informationsbeschaffung dienen?

Antwort:

Get-Command –Verb Get

Das Verb (Tätigkeitswort) steht steht für abholen/beschaffen. Dem entsprechend listet Get‑Command mit dem Schalter –Verb Get nur die Cmdlets, die Informationen beschaffen.

Frage:

Wie erfahren Sie, mit welchen Cmdlets Sie Prozesse verwalten können?

Antwort:

Get-Command –Noun Process

Zeigt nur die Cmdlets, dessen Noun (Hauptwort) Process ist.

Frage:

Wie bekommen Sie eine Auflistung aller Verben, die Sie in Cmdlets verwenden können?

Antwort:

Get-Verb

Frage:

Was müssen Sie eingeben um ein Cmdlet wie z.B. Start-Process vollständig erklärt zu bekommen?

Antwort:

Help Start-Process –Full

Die Funktion Help funktioniert ähnlich wie das Cmdlet Get-Help. Der Unterschied liegt darin, dass automatisch nach jeder Bildschirmseite ein Stop eingelegt wird. Bei dem Schalter –Full bekommen Sie sehr viele Informationen, die am Bildschirm einfach so vorbei rauschen würden. Dank Verwendung von Help statt Get-Help können Sie von Anfang an bequem durch den Text lesen.

Frage:

Wie bekommen Sie heraus, ob ein Befehl wie z.B. spps ein Alias ist und falls es ein Alias ist auf welches Cmdlet es deutet?

Antwort:

Zwei Möglichkeiten. Entweder Sie rufen die Hilfe auf: Get-Help spps. Unter Name würden Sie den Namen des Cmdlets sehen, auf das der Alias verweist. Oder Sie geben ein Get-Alias spps.

Frage:

Wie erfahren Sie, ob es für ein Cmdlet wie z.B. Get-Childitem bereits einen Alias gibt?

Antwort:

Get-Alias –Definition Get-Childitem

Listet gleich drei Alias Definitionen für Get-Childitem auf.

Frage:

Sie möchten gerne selbst einen Alias erstellen. Welches Cmdlet verwenden Sie dazu?

Antwort:

Set-Alias IhrName OriginalCmdlet

Frage:

Können Sie einen Alias names ... erstellen, um zwei Verzeichnisebenen nach oben zu gelangen?

Antwort:

Nein, da es kein Cmdlet gibt, das zwei Verzeichnisebenen nach oben wechselt. Sie können dies aber durch eine Funktion realisieren. Aliase können nur direkte Verweise auf Cmdlets beinhalten. Sobald Schalter oder gar mehrere Befehle ins Spiel kommen müssen Funktionen verwendet werden.

Frage:

Wie lange gilt eine Ihrer eigenen Alias-Definitionen?

Antwort:

Bis zum schließen der Konsole auf der der Alias definiert wurde. Er würde auch nur innerhalb dieses Fensters Gültigkeit haben. Wenn Sie also noch ein weiteres PowerShell Fenster öffnen wäre der Alias dort auch nicht vorhanden.

Frage:

Wie können Sie Ihre Alias-Definitionen für die spätere Verwendung konservieren?

Antwort:

Mittels Export-Alias Alias.csv können Sie die Alias-Definitionen in eine Text basierte Datei exportieren und bei Bedarf daraus mit Import-Alias Alias.csv wieder importieren.

Frage:

Wie kommen Sie von der PowerShell aus in die Registrierdatenbank?

Antwort:

cd HKLM:

oder

Set-Location HKLM:

Um in den Zweig Local_Maschine zu wechseln. Wenn Sie in den Current_User Zweig möchten geben Sie statt HKLM HKCU ein.

Frage:

Wie löschen Sie einen Alias?

Antwort:

Da es kein Cmdlet für die Löschung eines Alias gibt bleibt nur in das PS-Drive für den Alias zu wecheln und dort den Alias mittels rm, del oder Remove-Item zu löschen oder direkt durch Eingabe von:

Remove-Item Alias:NameDesZuLöschendenAlias

Frage:

Wie können Sie die Ausgabe mithilfe eines Umleitungsoperators in eine Datei schreiben?

Antwort:

CmdletDasDieAusgabeErzeugt > Datei.txt

Oder als Beispiel:

Get-ChildItem > Inhalt.txt

Frage:

Wie protokollieren Sie am besten Fehler in einer Textdatei?

Antwort:

Get-ChildItem VerzeichnisDasEsNichtGibt 2>> Fehler.log

Indem Sie ein Verzeichnis angeben, das nicht exisitiert provozieren Sie eine Fehlermeldung. Durch den Umleitungsoperator 2> würde die Fehlermeldung in die Datei Fehler.log geschrieben werden dabei aber auch eventuell schon vorhandene Informationen in dieser Datei löschen. Durch das zweite > Zeichen bleibt der Inhalt (z.B. schon vorher aufgeschriebene Fehlermeldungen) erhalten und die neue Meldung wird am Ende der Datei angehängt.

Frage:

Wie können Sie mehrere Befehle in einer Zeile hintereinander absetzen?

Antwort:

Durch Verwendung eines Semikolons zwischen den Befehlen, also z.B. so:

Get-ChildItem; Get-Process; Get-Service

Frage:

Wie können Sie ein Passwort (ohne Benutzername!) sicher entgegen nehmen und für die spätere Verwendung zwischenparken?

Antwort:

$a=Read-Host „Geben Sie Ihr Kennwort ein“ –AsSecureString

Die Variable $a dient zum zwischenparken der Kennwortangabe. Mit dem Cmdlet Read-Host können Eingaben der Tastatur entgegen genommen werden. Der Text in den Anführungszeichen wird dem Benutzer als Anweisung was einzugeben ist angezeigt und der Schalter –AsSecureString bewirkt, dass man bei der Eingabe Platzhalterzeichen sieht und das Kennwort auch verschlüsselt in der Variable $a hinterlegt wird.

Frage:

Wie können Sie eine Variable komplett (nicht nur den Inhalt) löschen?

Antwort:

Remove-Variable a

Wichtig dabei ist zu bemerken, dass man beim löschen der Variable das sonst üblich $- Zeichen nicht mit angeben darf!

Frage:

Wie können Sie den Inhalt einer Datei auslesen?

Antwort:

Get-Content Datei.txt

oder mittels Alias

type Datei.txt

cat Datei.txt

gc Datei.txt

Frage:

Wie können Sie Datum und/oder Uhrzeit in Erfahrung bringen?

Antwort:

Get-Date

Frage:

Wie erzeugen Sie eine Zufallszahl?

Antwort:

Get-Random –Min KleinsteZahl –Max GrößteZahl

Wollen Sie also ein zufällige Zahl zwischen 500 und 1000, müssten Sie eingeben:

Get-Random –Min 500 –Max 1000

5.1.1.4   Ausgabebefehle

Frage:

Was kommt dabei raus, wenn Sie 2+“5“ eintippen?

Antwort:

7! Die 5 ist durch die Anführungszeichen zwar als Text gekennzeichnet, aber PowerShell nimmt den ersten Operanden einer mathematischen Aufgabe und entdeckt eine Zahl. So wird versucht alle nachfolgenden Operanden ebenfalls in eine Zahl zu konvertieren und eine mathematische Berechnung durchzuführen.

Frage:

Was kommt dabei raus, wenn Sie „2“+5 eintippen?

Antwort:

25! Hier ist der erste Operand ein Text. Daher konvertiert PowerShell alle nachfolgenden Operanden ebenfalls in Text und führt eine Textverkettung durch. Er sieht die 2 und die 5 also nicht mehr als Zahlen, sondern als Text an und klebt einfach beide Zeichen zusammen.

Frage:

Was kommt dabei heraus, wenn Sie 2+“Fünf“ eintippen?

Antwort:

So clever ist Ihre PowerShell nun doch nicht. Beim Versuch den zweiten Operanden (die Fünf) in eine Zahl zu konvertieren scheitert die PowerShell, da sie nicht über die Intelligenz verfügt den Text Fünf in Zahl Fünf umzubauen. Daher erscheint eine Fehlermeldung, dass der Text nicht in eine Zahl konvertiert werden kann. Im Umgekehrten Fall “Fünf“+2 hätten Sie statt der Fehlermeldung Fünf2 auf dem Bildschirm stehen, da die Konvertierung von der Zahl 2 in einen Text 2 kein Problem darstellt. Sicher ist dies aber auch nicht das gewünschte Ergebnis.

Frage:

Mit welchen Cmdlet können Sie eine Textdatei nach einem Stichwort durchsuchen?

Antwort:

Select-String –Path NameDerDatei.txt –Pattern „Suchbegriff“

Frage:

Mit welchem Cmdlet können Sie das Datum einstellen?

Antwort:

Set-Date 20.05.2012

Stellt die Systemzeit auf den 20.05.2012 um 00:00 Uhr. Vorsicht! Datum und Uhrzeit sind sehr wichtig für Anmeldevorgänge im Netzwerk oder auch bei Verschlüsselung. Wenn Sie den Befehl ausprobieren und die Zeit nicht wieder richtig stellen, können Sie sich anschliessend u. U. nicht mehr an Ihrem PC anmelden.

Frage:

Was ist der Unterschied zwischen dem Cmdlets Select-String und Select-Object?

Antwort:

Mit Select-String können Sie einfache Text durchsuchen, wie z.B. den Inhalt einer Datei. Select-Object stellt auch Suchfunktionen zur Verfügung, arbeitet aber Objekt orientiert. Das bedeutet, die zu durchsuchenden Daten müssen als Objekt vorliegen. Da in der PowerShell eigentlich alles Objekte (ausser Inhalte von Textdateien) sind, wird dies wohl Ihr meist genutztes Cmdlet sein. Daher ist Select-Object auch das 3. heilige Cmdlet.

Frage:

Auf welches Cmdlet verweist der Alias Select?

Antwort:

Select-Object

Frage:

Mit welchem Cmdlet können Sie einen Durchschnittswert berechnen lassen?

Antwort:

Measure-Object -Average

Frage:

Wie können Sie eine bestimmte Spalte absteigend sortieren lassen?

Antwort:

Sort-Object Spaltenüberschrift -Descending

Frage:

Mit welchem Cmdlet können Sie gleiche Daten zusammenfassen lassen?

Antwort:

Group-Object Eigenschaft

Frage:

Wie können Sie eine Zahl in Hexadezimal darstellen?

Antwort:

“{0:x}“ –f 27

Zeigt 1b auf dem Bildschirm. Die 27 wird durch den –f Operator x (nach dem Doppelpunkt) in Hexadezimal konvertiert. Eine dezimale 15 ist in Hexadezimal ein F, eine 16 wäre ein 10. So bleiben Ihnen von 16 bis 27 noch 11 übrig. Eine 10 dezimal wäre in hex ein A und 11 ein B. So kommt es also zu 1b in der Ausgabe.

Frage:

Mit welchen Cmdlets können Sie eine Ausgabe in Listen- bzw. in Tabellenform erzwingen?

Antwort:

Format-List bzw. Format-Table

Als Alias können Sie fl bzw. ft verwenden.

Bei Format-Table können Sie noch den Schalter –AutoSize einsetzen, um die Spalten automatisch ordentlich auszurichten.

Frage:

Wie können Sie am Bildschirm farbige Texte darstellen?

Antwort:

Das Cmdlet Write-Host ermöglicht Ihnen mit den Schalter –ForegroundColor die Schriftfarbe festzulegen und mit –Backgroundcolor die Hintergrundfarbe, ähnlich eines Textmarkers. Welche Farben Ihnen zur Verfügung stehen verrät Ihnen die Hilfe in Form eines Get‑Help Write-Host –Detailed.

Frage:

Welches Cmdlet erlaubt Ihnen in eine Datei zu schreiben?

Antwort:

Entweder Set-Content oder Out-File. Out-File wird meist am Ende einer Pipeline, statt des Umleitungsoperators > eingesetzt.

Frage:

Wie können Sie eine tabellarische Ausgabe des Bildschirms in Excel weiter verarbeiten?

Antwort:

Get-Process | Export-CSV –Delimiter “,” Prozesse.csv

Erstellt eine Prozessliste und exportiert dies mit einem Komma als Spaltentrennzeichen in die Datei Prozesse.csv. Diese Datei können Sie nun mit Excel oder einem anderen Tabellenkalkulationsprogramm weiter bearbeiten. Wenn Sie lieber einen Tabstop als Spaltentenner haben möchten können Sie statt “,“ das hier “`t“ schreiben.

Frage:

Wie können Sie Ausgaben auf den Drucker schicken?

Antwort:

Mithilfe des Cmdlet Out-Printer am Ende einer Verarbeitungskette.

Frage:

Wie können Sie Ausgaben komplett unterdrücken?

Antwort:

Mithilfe des Cmdlet Out-Null am Ende einer Verarbeitungskette.

Frage:

Was ist der Unterschied zwischen den Zeichen " ' `?

Antwort:

Texte innerhalb " werden interpretiert, innerhalb ' nicht. Der Backtick ` kann für einen Zeilenumbruch verwendet werden, aber auch innerhalb interpretierter Texte (") dafür sorgen, dass die Variablen die vor dem $-Zeichen den Backtick tragen nicht expandiert werden.

Frage:

Wie kann man eine Zählvariable $x am einfachsten um den Wert 1 erhöhen?

Antwort:

$x++

Etwas umständlicher würde auch folgendes funktionieren:

$x=$x+1

5.1.1.5   Objektorientierung

Frage:

Wie können Sie herausfinden der wievielte Tag des Jahres gerade ist?

Antwort:

(get-date).dayofyear

Mit dem rund geklammerten Cmdlet Get-Date erstellen Sie ein Datum/Zeit-Objekt mit der aktuellen Zeit. In der Eigenschaft dayofyear finden Sie die Information der wievielte Tag in diesem Jahr gerade ist.

Frage:

Wieviele Prozesse laufen gerade auf Ihrem System?

Antwort:

(ps).count

ps ist der wohlbekannte Alias auf Get-Process. Auch hier bewirkt die runde Klammerung, dass ein Objekt generiert wird. Dieses Objekt ist vom Typ Array, der die einzelnen Prozess-Objekte enthält. An einem Array gibt es die versteckte Eigenschaft count. Diese gibt Auskunft über die Anzahl der Elemente in einem Array.

Frage:

In welcher Variable finden Sie den Pipelineinhalt?

Antwort:

$_

Frage:

Was ist der Unterschied zwischen Measure-Command und Measure-Object?

Antwort:

Measure-Object kann von mehreren Objekten aus einer bestimmten Eigenschaft den kleinsten und größten, oder z.B. den Durchschnittswert ermitteln, während Measure-Command die Laufzeit eines Cmdlet, einer Befehlskette oder eines Skriptes misst.

Frage:

Wie filtern Sie aus dem Windows-Verzeichnis alle *.exe Dateien, die größer sind als 100KB?

Antwort:

Get-ChildItem –Path c:\windows –Filter *.exe | ? {$_.Length –gt 100KB}

Wäre wegen der Verwendung des Filters am Get-ChildItem schneller als die Logik im Where‑Object:

ls c:\windows | ? {$_.name -like "*.exe" -and $_.length -gt 100KB}

Bauen Sie doch einmal ein Measure-Command um beide Versionen und messen Sie es nach!

Abgesehen von diesen beiden Versionen gibt es sicher noch 100 andere.

Frage:

Was ist der Unterschied zwischen den Vergleichs-Operatoren –eq, -like und –match?

Antwort:

-eq führt einen binären Vergleich durch, der absolut keine Abweichungen zulässt. Die zu vergleichenden Werte müssen exakt übereinstimmen. Kann sowohl bei Zahlen, als auch Texten gut eingesetzt werden.

-like und –match sind großzügiger in der Interpretation als –eq. Beide können Muster definieren, wie etwas ungefähr aussehen soll, damit es passt. Bei –like können Sie mit dem ? und dem *-Platzhalterzeichen arbeiten, wie Sie es von DOS her gewohnt sind. Bei –match haben ? und * andere Bedeutungen, da Sie bei –match mit den mächtigen regulären Ausdrücken arbeiten können.

Frage:

Welche Datentypen (Arten von Variablen) kennen Sie?

Antwort:

Puh, wenn wir alle aufzählen wollen, schauen Sie weiter hinten im Anhang. Hier die wichtigsten:

Int für ganze Zahlen

Double für Kommazahlen

String für Texte

DateTime für Datums und Zeitangaben

Array für eine mit Zahlen indizierte Sammlung von anderen Datentypen

Hash für eine mit Texten indizierten Sammlung von anderen Datentypen

Frage:

Was ist der Unterschied zwischen einem Array und einem Hash?

Antwort:

Der Array verwendet eine automatisch generierte Indexnummer um auf die einzelnen Elemente zuzugreifen, während der Hash einen von Ihnen selbst benamten Index verwendet.

Zugriff auf einen Array: $array[2]

Zugriff auf einen Hash: $hash.Name

Nebensächlich ist noch zu erwähnen, dass es beim Array keine Methode zum entfernen von Elementen gibt und man etwas tricksen muss.

Frage:

Wie nennt sich das Vorgehen um durch einen Hash Parameter an ein Cmdlet zu geben?

Antwort:

Splatting. Beim Splatten definiert man die Schalter in einem Hash z. B. so:

$hash=@{„Parameter1“=“Wert“;“Parameter2“=“Wert“}

Um den Hash zum splatten zu verwenden, darf man aber nicht das $-Zeichen verwenden, sondern muss es durch ein @-Zeichen ersetzen:

Cmdlet @hash

Frage:

Wenn Sie einen Array oder Hash in einem Text wiedergeben möchten, worauf sollten Sie achten?

Antwort:

Das der Hash bzw. der Array entsprechend gekapselt wird:

“$($hash) bzw. $($array)“

Frage:

Wie können Sie auf Umgebungsvariablen zugreifen?

Antwort:

Verwenden Sie die Vorsilbe Env: zwischen dem $-Zeichen und dem Variablennamen.

$Env:Umegungsvariable

Frage:

Wo finden Siedie Dokumentation zum .NET-Framework?

Antwort:

Im MSDN von Microsoft im Internet. In der Regel brauchen Sie nur die .NET Klasse in einer Suchmaschine eingeben und den ersten Link mit msdn in der URL anzuklicken und schon stehen Sie mitten in der Erklärung für die gesuchte .NET-Klasse.

Frage:

Wie ist die grundlegende Syntax um auf ein .NET-Objekt zuzugreifen?

Antwort:

[Stamm.Objektklasse.Unterklasse]::Eigenschaft oder Methode()

Frage:

Mit welchem Cmdlet können Sie .NET-Objekte erweitern?

Antwort:

Add-Member

Frage:

Einige Cmdlets erzeugen keine Ausgabe. Mit welchem Parameter können diese Cmdlets überreden doch eine Ausgabe zu erstellen?

Antwort:

-passthru

Frage:

Wie nennt sich das Cmdlet, um neue Objekte zu erstellen?

Antwort:

New-Object

Frage:

Was müssten Sie eingeben um die installierten Updates des Betriebssystems mit im Objekt zu haben?

Antwort:

Je nachdem ob Sie es statisch

add-member –inputobject $EigenesObjekt -MemberType noteproperty -name Hotfix -value (get-hotfix)

oder dynamisch

add-member –inputobject $EigenesObjekt -MemberType scriptproperty -name DynHotfix -value {(get-hotfix)}

im Objekt haben möchten.

5.1.1.6   WMI Objekte

Frage:

Welcher Port muss für WMI-Abfragen auf anderen Rechnern geöffnet sein?

Antwort:

TCP 135

Frage:

Können WMI-Abfragen auch auf NT 4.0 Systeme durchgeführt werden?

Antwort:

Ja, da WMI nicht WinRM nutzt, sondern RPC, um auf andere Rechner zuzugreifen. Auch PowerShell muss auf dem Zielsystem nicht installiert sein.

Frage:

Wie nennt sich der für Windows Systeme wichtigste Namespace?

Antwort:

\Root\CimV2

Frage:

Wie können Sie die sichere Konfiguration von WMI überprüfen?

Antwort:

Mit dem Managementkonsolen Snap-In der WMI-Steuerung: wmimgmt.msc

Frage:

Wie erfahren Sie welche Namespaces auf einem System zur Verfügung stehen?

Antwort:

Get-WmiObject -namespace root -class "__namespace" | ft name

Frage:

Wie erfahren Sie auf welche Objektklassen Sie in einem Namespace zugreifen können?

Antwort:

gwmi –list

5.1.1.7   COM Objekte

Frage:

Wie können Sie sich die auf Ihrem System verfügbaren COM-Objektklassen auflisten lassen (Sie dürfen nachschauen ;-)?

Antwort:

ls REGISTRY::HKEY_CLASSES_ROOT\CLSID -include PROGID -recurse | foreach {$_.GetValue("")}

Frage:

Wie instanziieren Sie aus einer COM-Objektklasse ein Objekt?

Antwort:

$Objekt=New-Object –COM Klasse

Frage:

Wenn Sie COM-Anwendungen auf der grafischen Oberfläche starten, aber die Anwendung nicht zu sehen ist, woran könnte das liegen?

Antwort:

Viele Anwendungsobjektklassen verfügen über die Eigenschaft Visible. Diese ist standardmäßig False. Wenn man die Objekt-Eigenschaft auf $true setzt, sollte die Anwendung auf dem Desktop sichtbar werden.

5.1.1.8   Skripting: Einführung

Frage:

Welche Dateinamenerweiterung muss ein PowerShell Skript haben?

Antwort:

ps1

Frage:

Ein Skript auf Ihrem Rechner läßt sich nicht starten. Woran liegt das? Wie können Sie das prüfen? Und wie können Sie das Problem beheben?

Antwort:

Die Ausführung von Skripten ist standardmäßig aus Sicherheitsgründen untersagt. Mit dem Get‑Executionpolicy können Sie die aktuelle Skriptausführungsrichtlinie abfragen und mit Set‑Executionpolicy –unrestricted das Ausführen von Skripten erlauben.

Frage:

Sie möchten gerne ein Skript auf einem anderen Rechner ausführen. Wie können Sie sicherstellen, dass das Skript dort läuft, egal welche Ausführungsrichtlinie eingestellt ist?

Antwort:

Starten Sie das Skript durch den Aufruf des PowerShell Prozesses mit der Information, die Ausführungsrichtlinie des PCs zu umgehen:

powershell –executionpolicy unrestricted –file C:\IhrPowerShellSkript.ps1

Frage:

Wie können Sie in einem Skript einen Kommentar schreiben?

Antwort:

Durch ein #-Zeichen am Anfang der Zeile, oder ab PowerShell 2.0 zwischen einem einleitenden

<#

und einem ausleitenden

#>

Frage:

In welcher Variable können Sie in einem Skript übergebene Parameter auslesen und von welchem Typ ist die Variable?

Antwort:

$args ist vom Typ Array.

Frage:

Wie greifen Sie auf das 2. übergebene Element dieser Variable zu?

Antwort:

$args[1]

Da $args bei 0 zu zählen anfängt.

5.1.1.9   Skripting: Schleifen

Frage:

Sie möchten gerne von 50 bis 5 in Abständen von 0,5 herunterzählen. Welche Schleifenform ist dafür am besten geeignet? Erstellen Sie die Schleife und lassen Sie die Werte zur Überprüfung am Bildschirm anzeigen.

Antwort:

for ($i=50;$i -ge 5;$i=$i-0.5) {$i}

Frage:

In einem Temp-Verzeichnis auf Ihrer Festplatte befindet sich eine unbestimmte Anzahl von Dateien. Sie möchten diese gerne löschen. Welche Schleifenform verwenden Sie und wie würde diese Schleife aussehen?

Antwort:

ls c:\windows\temp | foreach {rm $_.fullname -recurse -confirm:$false}

ls ist dabei ein Alias auf Get-ChildItem und rm auf Remove-Item. Kein Beispiel für gutes Skripting ;-). Warum? Wegen der Aliase! Der Schalter –recurse sorgt für das Löschen der Unterverzeichnisse (falls vorhanden) und der Parameter –confirm:$false, dass eventuelle Rückfragen, ob die Datei wirklich gelöschten werden soll automatisch mit ja beantwortet wird. Die Eigenschaft Fullname des jeweiligen Dateiobjekts enthält die vollständige Pfadangabe zur Datei, denn u. U. rufen Sie den Befehl ja nicht aus dem Windows\Temp Verzeichnis selbst auf. Dann würde PowerShell versuchen die Dateien im aktuellen Verzeichnis zu löschen.

Frage:

Wie können Sie eine Endlosschleife erzeugen?

Antwort:

While (1 –eq 1) {"Nervtöter"}

oder schneller von der Ausführung her, da der Vergleich entfallen kann und direkt „wahr“ geliefert wird

While ($true) {"Nervtöter"}

Selbstverständlich gibt es noch etliche andere Lösungswege.

Frage:

Was ist der Unterschied zwischen der while und der do-while Schleife?

Antwort:

Die Do-While Schleife wird min. ein Mal durchlaufen. Ist hingegen die Bedingung einer While Schleife schon zu beginn nicht erfüllt, wird der Inhalt des Schleifenrumpf komplett übersprungen.

5.1.1.10                Skripting: Tuning

Frage:

Mit welchem Cmdlet können Sie die Verarbeitungsgeschwindigkeit überprüfen?

Antwort:

Measure-Command {Cmdlet, Alias, Funktion oder Skript}

Frage:

Was ist schneller Alias oder Cmdlet?

Antwort:

Das kommt auf die jeweilige Situation an! Hier kann keine generelle Aussage getroffen werden. Um zu überprüfen was schneller klappt, können Sie das Cmdlet Measure-Command einsetzen.

Frage:

Wie würden Sie nach einem bestimmten Dateityp (z.B.: *.ps1 oder *.bat) suchen?

Antwort:

Mit dem Parameter –Filter von Get-Childitem, weil es schneller funktioniert als ein pipen an das Where-Object Cmdlet. Nur wenn ein Cmdlet keine entsprechenden Parameter zur Verfügung stellt (z.B. wenn Sie Dateien nach Ihrer Größe oder dem Zugriffsdatum filtern möchten), greifen Sie zu der langsameren Methode mit dem Where-Object.

Frage:

Wie können Sie die Verarbeitung von Funktionen beschleunigen?

Antwort:

In dem Sie Aufgaben einer Pipelineverarbeitung, die beim nur beim ersten Aufruf der Funktion ausgeführt werden müssen in einen Begin {} Block einschliessen und solche die nur beim letzten Aufruf durchgeführt werden müssen in einen End {} Block einschliessen. Das was jedes Mal durchgeführt wird kommt dabei in einen Process {} Block. Ohne diese Einteilung würde jedes Mal der komplette Code ausgeführt werden, was entsprechend Zeit raubend ist.

5.1.1.11                Skripting: Troubleshooting

Frage:

In welcher automatischen Variablen ist festgelegt, was bei einem Fehler geschehen soll?

Antwort:

$ErrorActionPreference

Frage:

In welcher automatischen Variablen ist festgelegt, ob das vorangegangene Kommando erfolgreich war?

Antwort:

$!

Frage:

In welcher automatischen Variablen sind die bisher passierten Fehler notiert und von welchem Typ ist diese Variable?

Antwort:

$Error ist vom Typ Array.

Frage:

Sie haben eine Trap-Anweisung in Ihr Skript eingebaut, weil Sie einen Fehler erwarten. Statt Ihren Trap-Code auszuführen, wird aber nur die standard PowerShell Fehlermeldung ausgespuckt. Was ist die wahrscheinlichste Ursache und wie können Sie dafür Sorge tragen, dass Ihr Trap-Code ausgeführt wird?

Antwort:

Es ist ein Fehler ohne Abbruch. Um diesen mit Ihrer Trap-Anweisung zu kaschieren, können Sie beim Cmdlet, dass den Fehler produziert den CommonParameter –ea bzw. –ErrorAction gefolgt von dem Wort Stop einsetzen, dass denFehler ohne zu einem Fehler mit Abbruch macht, oder Sie setzen vor dem fehlerhaften Cmdlet den die automatische Variable $ErrorActionPreference auf Stop.

Frage:

Was tun Sie damit die PowerShell Sie auf Tippfehler in Variablen aufmerksam macht?

Antwort:

Set-PSDebug -Strict

Frage:

Wie können Sie in einem Skript in der Zeile 5 und 10 den interaktiven Skriptdebugger aktivieren?

Antwort:

Set-PSBreakpoint –Script NameIhresSkripts.ps1 –Line 5,10

Frage:

Wie verlassen Sie den interaktiven Debugger?

Antwort:

Durch tippen des Buchstaben q gefolgt von der Eingabetaste.

Frage:

Wie können Sie im interaktiven Debugger die nächste Code-Zzeile ausführen lassen?

Antwort:

s

Frage:

Wie können Sie eine Code-Zeile im interaktiven Debugger überspringen?

Antwort:

v

Frage:

Wie rufen Sie im interaktiven Debugger die Hilfe auf?

Antwort:

h

5.1.1.12                PowerShell 2.0: Troubleshooting

Frage:

Wie sieht das grundlegende Konstrukt aus mit dem ab Version 2.0 Fehler behandelt werden können?

Antwort:

try {Cmdlet oder ganze Verarbeitungskette die möglicher Weise einen Fehler produziert}

catch [.NET-Fehlerklasse] {Code der ausgeführt wird, wenn dieser spezifische Fehler passiert}

catch {Code der ausgeführt wird, wenn die vorangegangenen Fehlerklassen nicht zutreffen}

finally {Dieser Code wird auf jeden Fall ausgeführt, egal ob ein Fehler passiert, oder nicht}

Frage:

Muss jede Feherbehandlung einen Finally-Block haben?

Antwort:

Nein!

Frage:

Wie erfahren Sie den .NET-Fehlerklasse, wenn ein Fehler aufgetreten ist?

Antwort:

$error[0].Exception.Gettype().Fullname

5.1.1.13                PowerShell 2.0: Module

Frage:

Wie stellen Sie fest, welche Module Ihnen zur Verfügung stehen?

Antwort:

Get-Module -List

Frage:

Wie stellen Sie fest welche Module bereits geladen sind?

Antwort:

Get-Module

Frage:

Wie laden Sie ein Modul?

Antwort:

Import-Module NamedeszuladendenModules

Frage:

Wie lautet die Dateinamenerweiterung für ein Modul?

Antwort:

*.psm1

Frage:

Wo müssen Sie Ihre Modul-Datei ablegen, damit Sie allen Benutzern zur Verfügung steht?

Antwort:

C:\Windows\System32\WindowsPowerShell\v1.0\Modules

Frage:

Wo müssen Sie Ihre Modul-Datei ablegen, damit Sie einem bestimmten Benutzer zur Verfügung steht?

Antwort:

Bei XP und Windows Server 2003:

C:\Dokumente und Einstellungen\Name des Benutzers\Eigene Dateien\
WindowsPowerShell\Modules

Bei Vista, Server 2008 und neueren Betriebssystemen unter:

C:\Benutzer\Name des Benutzers\Dokumente\WindowsPowerShell\Modules

Frage:

Wie könnnen Sie in einem selbst erstellten Modul die Hilfetexte für Ihre Cmdlets generieren?

Antwort:

In einem Kommentarblock eingeschlossen vor der Funktion durch spezielle Schlüsselwörter wie z.B.: .EXAMPLE, :DESCRIPTION oder .LINK

5.1.1.14                PowerShell 2.0: Remotezugriff

Frage:

Über welches Protokoll und welchen Port läuft die Kommunikation von WinRM?

Antwort:

http über Port 5985 und https über 5986.

(In der Version 1.0 von WinRM noch über 80 und 443.)

Frage:

Wie aktivieren Sie auf einem einzelnen PC die Remotefunktion der PowerShell?

Antwort:

Eine Konsole mit erhöhten Administratorrechten starten und dort Enable-PSRemoting eingeben.

Frage:

Wo können Sie feststellen, welche Rechner ausserhalb eines Active-Directory sich auf Ihr System remote verbinden dürfen?

Antwort:

Unter WSMan:\localhost\Client\TrustedHosts

Frage:

Wie lautet das Cmdlet, um einen einzelnen Befehl, eine Verarbeitungskette oder  ein Skript einmalig auf einem anderen PC auszuführen?

Antwort:

Invoke-Command

Frage:

Wenn Sie mehrere unterschiedliche Dinge auf einem anderen PC erledigen wollen, wie können Sie die Konsole auf einen anderen PC umschalten?

Antwort:

Enter-PSSession

Frage:

Wie können Sie eine Remotesitzung wieder beenden?

Antwort:

Exit-PSSession

Frage:

Sie müssen mehrere Informationen zum Verbindungsaufbau angeben. Wie machen Sie das am besten?

Antwort:

Sie könnten das Cmdlet New-PSSession verwenden. Dies hat jedoch den Nachteil, dass die Verbindung die ganze Zeit offen steht und Ressourcen verschwendet. Besser ist die Parameter in einer Hashvariablen zu hinterlegen und dann mit Enter-PSSession die Hashvariable an das Cmdlet zu splatten.

5.1.1.15                PowerShell 2.0: Jobs

Frage:

Mit welchem Cmdlet können Sie einen Befehl im Hintergrund ausführen lassen?

Antwort:

Start-Job

Frage:

Wie können Sie feststellen welche Hintergrundjobs aktuell vorliegen?

Antwort:

Get-Job

Frage:

Wie können Sie sich die Ausgabe eines Hintergrundjobs anzeigen lassen und wie sorgen Sie dafür, dass Sie das Ergebnis mehrfach abholen können?

Antwort:

Receive-Job JobID -keep

Frage:

Wie können Sie einen Hintergrundjob abbrechen?

Antwort:

Stop-Job

Frage:

Wie können Sie einen Hintergrundjob aus der Liste löschen?

Antwort:

Remove-Job JobID

5.1.2   Übungen

Hier finden Sie die Lösungen zu den Übungsaufgaben

5.1.2.1   svchost Prozesse auflisten

ps | ? {$_.name –eq "svchost"}

5.1.2.2   spps

Wenn Sie

ps | ? {$_.name –like "powershell"} | spps

eingeben ist Ihre Konsole plötzlich verschwunden. ps ist ein Alias auf Get-Process und liefert somit eine komplette Prozessliste. Durch das nachfolgende Where-Object in Form des Aliases ? {$_.name –like "powershell"} wird die Liste auf den PowerShell-Prozess, der Ihre Konsole darstellt reduziert. Das ominöse ist das finale spps. Wenn Sie help spps aufrufen, werden Sie feststellen, das spps ein Alias auf das Cmdlet Stop-Process ist. Da die Pipe nur den Prozess Ihrer PowerShell Konsole enthält, bekommt dieser Process die Anweisung beendet zu werden. Nun, das schließt Ihre Konsole.

5.1.2.3   RegEx

ls c:\windows\system32 | ? {$_.Name -match "^.[wt].*\.(dll|exe)$"}

Beim Filter sehen Sie nach dem ^-Zeichen einen Punkt. Das bedeutet, dass uns das erste Zeichen egal ist, danach an der 2. Stelle soll aber unbedingt ein w oder ein t stehen. Den Filter für die Dateinamenerweiterung finden Sie in \.(dll|exe). Der Backslash vor dem Punkt sorgt dafür, dass der Punkt seiner Sonderfunktion beraubt wird und einfach nur als Punktzeichen gesehen wird. Durch die runden Klammern legen legen Sie mithilfe der Pipe eine Oder Beziehung zwischen dll und exe fest. Das finale $ sagt aus, dass also .exe oder .com am Schluß stehen muss.

5.1.2.4   Where-Object

Harte Nuss, was? Zunächst wird geprüft, ob die Eigenschaft LastWriteTime nach dem 20.11.2010 liegt. Der Vergleichs-Operator hierfür ist –gt. Leider ist die PowerShell hier nicht so clever direkt eine Angabe 20.11.2010 als Datumsangabe zu konvertieren. Um ihr auf die Sprünge zu helfen, rufen Sie in runden Klammern (die werden wie in der Mathematik zuerst aufgelöst) das Cmdlet Get-Date zur Hilfe. Das macht aus der Angabe 20.11.2010 automatisch einen Datumswert, den die PowerShell dann auch klaglos mit der LastWriteTime vergleicht und Ihnen nur noch Dateien/Verzeichnisse liefert, die nach dem 20.11.2010 verändert wurden. Dann kommt die –and Verknüpfung, doch was ist das!? Da geht noch eine runde Klammer auf? Auch hier wieder ein Logik-Problem. Sie wollen ja nicht nur, dass die Verzeichnisse nach dem Datum liegen, sondern auch die Log-Dateien. Ohne Klammer würde die –and Verknüpfung nur mit dem direkt danach folgenden Statement (nur Verzeichnisse) ausgewertet werden, aber nicht mehr in Bezug auf die Log-Dateien. Daher wird der Rest geklammert, damit diese zusammen erst einmal eine Liste bilden, aus der im nach hinein alle älteren Dateien/Verzeichnisse herausgefiltert werden. Für die Verzeichnisse steht einfach nur $_.PsIsContainer. Wenn diese Eigenschaft Wahr ist, handelt es sich um ein Verzeichnis. Dann folgt das –or welches Ihnen im Dateinamen nach der Log-Erweiterung sucht. So entsteht zunächst eine Liste mit allen Verzeichnissen und allen Log-Dateien, die danach (wegen der runden Klammer) noch mit dem Datum verglichen wird.

5.1.2.5   Datei löschen Methode

Die einzige Schwierigkeit hierbei ist herauszufinden, wie die Methode zum löschen der Datei heißt. Dies finden Sie heraus indem Sie ein ls an gm pipen:

ls | gm

Hier finden Sie die Methode delete. Um die Methode anzuwenden, müssen Sie ein Objekt generieren, das mit der Datei Opfer.txt verknüpft ist um die Objektmethode auf diese spezielle Datei anzuwenden. Sie wollen doch nicht gleich das gesamte Verzeichnis abmurksen?

(ls Opfer.txt)

Generiert durch die runden Klammern das benötigte Objekt. Nun brauchen Sie nur noch die Methode darauf anzuwenden:

(ls Opfer.txt).delete()

Bei der Methode nie die runden Klammern hinter der Bezeichnung der Methode vergessen, sonst würde die PowerShell den Vorgang nur simulieren, aber die Methode nicht wirklich anwenden.

5.1.2.6   String

$VollerName="Lieschen Müller"

Sorgt für die Zuweisung des Textes Lieschen Müller in die Variable $VollerName.

Wenn Sie nun die Methode Split auf den String anwenden und als Trenner das Leerzeichen vorgeben, bekommen Sie einen Array mit zwei Elementen. Dementsprechend können Sie Sie auf die Elemente  des Arrays mit der Indexnummer in eckigen Klammern zugreifen:

5.1.2.7   Splatting Hash

$Boot=@{"Logname"="System";"InstanceID"="12"}

Daran denken! Bei der Hash Variablen steht wie üblich das $-Zeichen vor dem Variablennamen und das @-Zeichen hinter dem =. Nur, wenn Sie den Hash zum Splatten verwenden möchten geben Sie ausnahmsweise das @-Zeichen ein um diese Form von normalen Variablen abgrenzen zu können.

5.1.3   Skripte

5.1.3.1   Ampel

10.        $Ampel="Lila"

11.        If ($Ampel –eq "Grün") {

12.         Write-Host "Die Ampel ist grün." –Foregroundcolor green

13.        } ElseIf ($Ampel –eq "Gelb") {

14.         Write-Host "Die Ampel ist gelb." –Foregroundcolor yellow

15.        } ElseIf ($Ampel –eq "Rot") {

16.         Write-Host "Die Ampel ist rot." –Foregroundcolor red

17.        } Else {

18.         For ($i=0;$i –lt 10;$i++) {

19.          write-host "`rIch glaube die Ampel ist kaputt!" -fore yellow –nonew

20.          sleep 1

21.          write-host "`rIch glaube die Ampel ist kaputt!" –nonew

22.          sleep 1

23.         }

24.        }

5.1.3.2   1 x 1

10.        # Die erste Zeile erstellt ein zweidimensionales Array-Objekt mit Speicherplatz von 10x10 Elementen und als Trennzeichen wird ein Komma verwendet.

11.        $array=New-Object 'object[,]' 10,10

12.        # Die beiden verkapselten Schleifen befüllen das zweidimensionale Array-Objekt mit den errechneten Werten.

13.        for ($x=1;$x -lt 10;$x++) {

14.         for ($y=1;$y -lt 10;$y++) {

15.          $array[$x,$y]=$x*$y

16.         }

17.        }

18.        # Die Spaltenüberschriften in Rot:

19.        write-host "  1  2  3  4  5  6  7  8  9`n" -foregroundcolor red

20.        # …und das Schreiben der ersten Spalte in rot, sowie der errechneten Wert:

21.        for ($x=1;$x -lt 10;$x++) {

22.         # erste Spalte in rot: -nonew bewirkt, dass keine neue Zeile erfolgt.

23.         write-host "$x " -foregroundcolor red -nonew

24.         for ($y=1;$y -lt 10;$y++) {

25.         # Je nachdem, ob die Zahl 1 oder 2 stellig ist, noch ein Leerzeichen hinzufügen

26.          if ($array[$x,$y] -lt 10) {

27.          # 2 Leerzeichen vor dem hinteren Anführungszeichen

28.           write-host "$($array[$x,$y])  " -nonew

29.          } else {

30.           # 1 Leerzeichen vor dem hinteren Anführungszeichen

31.           write-host "$($array[$x,$y]) " -nonew

32.          }

33.         }

34.         # Zeilenumbruch nach der letzten Ziffer einer Reihe

35.         "`n"

36.        }

Wenn Sie Zeit und Lust haben, überlegen Sie doch noch zusätzlich wie Sie die einstelligen Ziffern rechts-, statt linksbündig ausgeben könnten.

5.2     Formatierungen Übersicht

5.2.1   -f Format-Operatoren

Der –f Operator ermöglicht Ausgabe Formatierungen. Grundsätzliche Syntax ohne Formatierung:

{0} –f $wert

Tabelarische Übersicht für Zahlen:

{0:x}

Hexadezimal

{0:x8}

Hexadezimal, 8-stellig

{0:c}

Währung (vom Englischen currency)

{0:f}

F=Floatingpoint=Fließkomma (Standard=2 Nachkommastellen)

{0:f1}

auf 1 Nachkommastelle fixiert

{0:f4}

auf 4 Nachkommastelle fixiert

{0:0,000}

Tausendertrennzeichen

{0:#,000}

3 Stellen vor dem Komma festlegen. Bei 4 Stellen vor dem Komma, wird optional ein Tausendertrennzeichen eingefügt.

{0:0,0##}

ist auf 1 Nachkommastellen fixiert. Die 2. und 3. Nachkommastellt wird nur angezeigt, wenn die 2. und 3. Nachkommastelle im Wert vorhanden ist.

Für binäre Ausgabe (Beispiel: 001101010) gibt es leider keine Entsprechung.

Tabelarische Übersicht für Texte:

{0,10}

rechtsbündig ausrichten und 10 Zeichen reservieren

{0,-10}

linksbündig ausrichten und 10 Zeichen reservieren

 

Beispiel für Datum und Uhrzeit:

“Und es gibt sehr viele Möglichkeiten Zeiten darzustellen {0:yyyy.MMM.dd:HH.mm.ss}“ –f $wert

Tabelarische Übersicht für Datum und Uhrzeit:

{0:yyyy}

Jahr, 4-stellig

{0:yy}

Jahr, 2-stellig

{0:MMMM}

Monat, vollständiger Name als Text

{0:MMM}

Monat, 3-stellig, als Text

{0:MM}

Monat, 2-stellig, als Zahl

{0:dddd}

Tag, vollständiger Name als Text

{0:ddd}

Tag, 3-stellig, als Text

{0:dd}

Tag, 2-stellig, als Zahl

{0:HH}

Stunde (24h Modus, 3 Uhr Mittags=15), 2-stellig

{0:hh}

Stunde (12h Modus, 3 Uhr Mittags=03), 2-stellig

{0:mm}

Minute, 2-stellig

{0:ss}

Sekunde, 2-stellig

 

Kombinationsbeispiel:

“Hier {0,-10:f7} eingefügt“ –f 27

Reserviert 10 Zeichen für die Darstellung der Zahl 27 mit 7 Nachkommastellen und richtet die Darstellung linksbündig aus.

5.2.2   Datentypen

Hier finden Sie eine Auflistung der Standard Datentypen:

[string]          Text in Unicode Zeichen (z.B. Umlaute wie ü oder Ä sind zugelassen)

[char]             Ein einzelnes Unicode 16-bit Zeichen

[byte]             Eine 8-bit Ganzzahl zwischen 0-255 (ganze Zahl, ohne Komma)

[int]                32-bit signed integer (ganze Zahl, ohne Komma) = Ganzzahl von −2.147.483.648 bis 2.147.483.647

[long]             64-bit signed integer (ganze Zahl, ohne Komma) = Ganzzahl von −9.223.372.036.854.775.808 bis 9.223.372.036.854.775.807

[bool]            Boolean = Wahrheitswert, kann nur $True (wahr) oder $False (falsch) sein

[decimal]      128-bit decimal (auch Kommazahlen möglich – Achtung, Komma = Dezimalpunkt, also .) Kommazahl von -79228162514264337593543950335 bis 79228162514264337593543950335

[single]          Single-precision 32-bit floating point = Kommazahl von -3,402823E+38 bis 3,402823E+38

[double]        Double-precision 64-bit floating point = Kommazahl von -1,79769313486232E+308 bis 1,79769313486232E+308

[DateTime]   Kombiniertes Datums- und Uhrzeitformat, geht von Montag, 1. Januar 0001 00:00:00 bis Freitag, 31. Dezember 9999 23:59:59

[xml]              Xml Objekt (Nicht x-beliebige, sondern nur die, die Microsoft auch vom Schema her kennt)

[array]           Eine Sammlung/Liste beliebiger numerisch indizierter (auch unterschiedlicher) Objekt-Typen

[hashtable]   Eine Sammlung/Liste belibiger benamt indizierter (auch unterschiedlicher) Objekt-Typen

Sollten Sie einmal über einen Datentyp stolpern, der hier nicht gelistet ist, können Sie in der Regel über die Eigenschaften MinValue und MaxValue die Ober- und Untergrenzen herausfinden. Hier am Beispiel des Integers:

PS C:\> [int]::MinValue

-2147483648

Und für die ganz schrägen Datentypen verschaffen Sie sich einen Überblick, was man die statt MinValue noch fragen könnte mit Hilfe eines Get-Member –static:

PS C:\> [int] | gm -static

 

   TypeName: System.Int32

 

Name            MemberType

----            ----------

Equals          Method

Parse           Method

ReferenceEquals Method

TryParse        Method

MaxValue        Property

MinValue        Property

5.3     Variablen Übersicht

5.3.1   Automatische Variablen

$$ Enthält den letzten Wert des vorangegangenen Befehls. ls c: -exclude *.log

$? Gibt $True oder $False zurück, je nachdem, ob das vorangegangene Cmdlet erfolgreich, oder mit Fehler ausgeführt wurde.

$^ Enthält den ersten Wert des vorangegangenen Befehls. ls c: -exclude *.log

$_ Enthält bei einer Schleifenbearbeitung das aktuelle Element, oder nach einer Pipe das was über die Pipe geliefert wurde.

$Args Ist ein Array von Werten die an eine Function oder ein Script übermittelt wurden. Erklärung: Einfache Parameterübergabe an Skripte.

$Error Ist ein Array mit der Historie der aufgetretenen Fehler. Erklärung: Troubleshooting in Version 2.0

$PSDefaultParameterValues Erlaub Standardwerte für bestimmte Parameter festzulegen. Diese müssen dann nicht mehr jedesmal angegeben werden. Dies geschieht dann automatisch.
Beispiel 1:
$PSDefaultParameterValues[„Get-Help:ShowWindow“]=$true
Dies würde dann bei jedem Get-Help ein separates Fenster mit der Hilfe öffnen und Sie können auf der Konsole weiter arbeiten, während das Fenster offen stehen bleibt.
Beispiel 2:
$PSDefaultParameterValues[„Send-MailMessage:From“]=Ihr.Mail@domain.de
Dies würde dann bei jedem Send-Mail automatisch Ihre Absender-Emailadresse eintragen.

$PSScriptRoot Enthält innerhalb eines Scriptes, das Verzeichnis in dem das Script liegt. Spannend, wenn Sie auf Unterstützungsdateien für Ihr Script zugreifen möchten, die im selben Verzeichnis liegen. Zieht das Script mit seinen Hilfsdateien um, brauchen Sie nichts weiter anzupassen.

5.3.2   Umgebungs Variablen

Umgebungs Variablen finden am einfachsten über ls env:

5.3.3   Vordefinierte PowerShell Variablen

5.4     Operatoren Übersicht

5.4.1   Vergleichs-Operatoren

5.4.1.1   -eq

-eq entspricht dem =. Bedeutet demnach, das was vor dem = steht muss gleich dem sein, was hinter dem = steht. Also z.B.:

 1 = 1

oder:

12+3 = 3*5

aber auch:

ABC = abc

Da PowerShell standardmäßig nicht zwischen Groß- und Kleinschreibung unterscheided.

5.4.1.2   -ieq

Ist gleichzusetzen mit -eq, nur dass hier explizit Groß- und Kleinschreibung außer Acht gelassen wird.

5.4.1.3   -ceq

Auch ähnlich wie -eq, nur wird hierbei auf Groß- und Kleinschreibung geachtet.

5.4.1.4   -ne, -ine, -cne

-ne ist das genaue Gegenteil von -eq. In anderen Programmiersprachen findet man häufig != als Ausdruck für ungleich.

-ine igrnoriert wieder explizit Groß- und Kleinschreibung bei einem Ungleich, währen -cne explizit darauf achtet.

5.4.1.5   -like

Ist besondes für Textvergleiche geeignet. Ein * ist ein Platzhalterzeichen für 0, 1 oder mehrere Zeichen, während ein ? für exakt ein Zeichen steht. Beispiele:

“ABC“ – like “a*“ # Alles was mit a oder A beginnt.

“ABC -like “*C“   # Alles was mit c oder C endet.

“ABC -like “*B*“  # Alles indem ein b oder B vorkommt.

“ABC“ -like “?B*“ # Alles was an der 2. Stelle ein b oder B aufweist.

5.4.1.6   -clike, -ilike

Auch hier sagt das vorangestellte c wieder aus, dass Groß- und Kleinschreibung beachtet werden müssen und bei vorangestelltem i explizit ignoriert wird.

5.4.1.7   -notlike, -cnotlike, -inotlike

Wie -ne wird hier nun der -like Operator ins genaue Gegenteil verkehrt.

“ABC“ -notlike “DEF“

Würde also $true ergeben, weil ABC nun einmal so gar nichts gemein mit DEF hat. C und i … Sie wissen schon…

5.4.1.8   -match, -notmatch, -cmatch, -imatch, -cnotmatch, -inotmatch

OK, das ist viel auf einmal, aber im Großen und Ganzen wieder dasselbe Spiel wie bereits bei den vorangegangenen Operatoren erläutert. -match erlaubt Ihnen mit den Perl Regular Expressions zu arbeiten, oder auch kurz RegEx genannt.

Ein . funktioniert, wie das ? beim -like. Ist also ein Platzhalter für exakt ein Zeichen. Beispiel:

“ABC“ -match “.B.“   # Von 3 Zeichen muss das in der Mitte ein b oder B sein.

Das ^ fordert, dass die Zeichenkette mit dem nachfolgenden Zeichen beginnen muss.

“ABC“ -match “^A“    # Die Zeichenkette muss mit a oder A beginnen was danach kommt ist egal.

Das $ ist das Gegenteil von ^, sprich die Zeichenkette muss mit dem Zeichen vor dem $ aufhören.

“ABC“ -match “C$“    # Egal, was vor dem C steht, aber die Zeichenkette muss mit c oder C enden.

Das * funktioniert hier komplett anders als bei -like. Es ist ein sogenannter Wiederholungsoperator. D.h. das Zeichen, dass vor dem * steht kann 0, 1, oder x-beliebig oft auftauchen.

“ABC“ -match “D*“    # Macht keinen Sinn! Ergibt immer $true, denn das D kann in der Zeichenkette stehen, oder auch nicht. Ganz egal wie oft.

Eher interessant wird es, wenn man komplexere Vergleiche anstellt:

“ABCDEFG“ -match “^ab*c“   # Beginnt mit einem A und zwischen A und C kann ein B stehen – muss es aber nicht.

“ACDEFG“ -match “^ab*c“    # würde also auch passen

“ABBBCDEFG“ -match “^ab*c“ # ebenso

“ABDCEFG“ -match “^ab*c“   # allerdings nicht

Am interssantesten ist die Kombination von .*. Dies entspricht nämlich tatsächlich dem * bei -like, sprich ein Platzhalter für irgendeine Zeichenkette. Da der . ein Platzhalterzeichen für irgendein Zeichen ist und diese durch das nachgestellte * 0-mal, 1-mal oder X-beliebeig oft auftreten kann.

“ABXYZC“ -match “^.B.*c$“

An der 2. Stelle muss ein B stehen, und der Text muss mit C enden. Was an erster Stelle steht, spielt keine Rolle, aber irgendwas muss da sein und was sich zwischen B und C abspielt ist auch egal.

Der \ hingegen ist ein Escape-Zeichen. So wie in Texten der ` aus Sonderzeichen normale Zeichen macht und umgekehrt, aus normalen Zeichen Sonderzeichen, so funktioniert innerhalb eines regulären Ausdrucks der \. Wollen Sie nun nach Dateien mit einer bestimmten Endung suchen, kann dies hilfreich sein:

“NotePad.exe“ -match “^Power.*\.exe$“

Das gesuchte Wort beginnt mit Power danach kommt irgendwas und es muss mit .exe aufhören (liefert also sowohl PowerPoint.exe, also auch PowerShell.exe). Der . ist, wie Sie bereits wissen, ein Sonderzeichen, das als Platzhalter dient. Damit hier nicht xexe z.B. gefunden wird, sondern tatsächlich .exe steht der \ vor dem Punkt, der sagt, dass dieser hier nicht als Platzhalter, sondern tatsächlich als . angesehen wird.

Das ? ist ebenfalls, genau wie das * ein Wiederholungsoperator. Dieser sagt aus, dass das vorangestellte Zeichen 0- oder 1-mal, aber nicht öfter vorkommen darf. Dies wird z.B. bei URL-Filtern sehr gerne eingesetzt:

„https://www.irgendwas.de“  würde genauso gut zu -match „^https?://www\..*\.de$“ passen wie „http://www.irgendwas.de“ . Also egal ob die Website SSL/TLS Verschlüsselung anbietet oder nicht.

Ein weiterer Wiederholungsoperator ist das +. Hier muss das vorangestellte Zeichen midestens 1-mal auftauchen.

“ABC“ -match “^AB+C$“ # passt

“ABBBBC“ -match “^AB+C$“   # passt

“AC“ -match “^AB+C$“ # passt nicht

Geschweifte Klammer erlauben die mehr oder minder genaue Anzahl anzugeben.

-match “a{3}“   # es müssen genau 3 a vorkommen.

-match “a{,3}“  # Maximal 3 Mal a hintereinander.

-match “a{3,}“  # Mindestens 3 Mal a hintereinander.

-match “a{2,5}“ # 2-5 Mal hintereinander ein a.

Eckige Klammer helfen dabei mehrere unterschiedliche Zeichen zu vergleichen.

“ABD“ -match “^A[BC]D$“    # würde genauso passen wie

“ACD“ -match “^A[BC]D$“    # 😊

“123.bmp“ -match “^[A-Z]*\.bmp“  # passt nicht (nur die 26 Buchstaben), aber

“ABC.bmp“ -match “^[A-Z]*\.bmp“  # passt

“A23.bmp“ -match “^[A-Z0-9]*\.bmp“  # passt auch wieder

Sie könen mit Bindestrichen in den eckigen Klammern also auch ganze Zeichenfolgen angeben.

Achtung! Wenn Sie ^ innnerhalb der eckigen Klammer verwenden dreht es die Aussage rum.

“123.bmp“ -match “^[^A-Z]*\.bmp“  # passt, weil es keine Buchstaben enthält!

Runde Klammern hingegen wirken auf ganze Zeichenketten.

Get-ChildItem | Where-Object {$_.Name -match “^.*\.(mpg|mpeg|mov)“}

Bewirkt, dass Ihnen nur Dateien, die auf .mpg, .mpeg oder .mov enden geliefert werden. Die | wirkt innerhalb der runden Klammern wie ein „ODER“.

Vordefinierte Zeichenklassen gibt es in Form von \d, \w oder \s.

\d entspricht einer Dezimalzahl.

\w allen Buchstaben (inkl. Umlaute und Zahlen) und

\s allen Worttrennzeichen (Leerzeichen, Tabstop, Umbruch)

Als Großbuchstabe dreht sich die Aussage herum. \D sind also alles nur keine Dezimalzahlen.

 

5.4.2   Zuweisungs-Operatoren

5.5     Sonderzeichen Übersicht

5.6     Datenbank-Typen

Data type

Access

SQLServer

Oracle

MySQL

PostgreSQL

boolean

Yes/No

Bit

Byte

N/A

Boolean

integer

Number (integer)

Int

Number

Int
Integer

Int
Integer

float

Number (single)

Float
Real

Number

Float

Numeric

currency

Currency

Money

N/A

N/A

Money

string (fixed)

N/A

Char

Char

Char

Char

string (variable)

Text (<256)
Memo (65k+)

Varchar

Varchar
Varchar2

Varchar

Varchar

binary object

OLE Object Memo

Binary (fixed up to 8K)
Varbinary (<8K)
Image (<2GB)

Long
Raw

Blob
Text

Binary
Varbinary

6        Glossar

Abk. – Abkürzung
always at your fingertips – zu Deutsch: Zur Hand haben, Griffbereit
Assembler – Sehr Hardware nahe Programmiersprache, bei der die einzelnen Bits in Form von Transistoren auf 0 (kein Strom) oder 1 (Strom fließt) gesetzt werden.
Backslash - eine Backslash \ wird meist als Trennzeichen in Verzeichnisstrukturen auf der Festplatte verwendet. Dieses zeichen erhalten Sie indem Sie die AltGr-Taste (rechts neben der Leertaste) drücken dazu die Taste mit dem ß (rechts neben der 0).
CNI – Abk. Certified Novell Instructor
Compiler – Programm das Quellcode in exe Dateien übersetzt, damit man diese als Programm verwenden kann.
Dateinamenerweiterung – Dateien haben in der Regel ein Dateinamenerweiterung. Das ist so ähnlich wie ein Nachname. Der Teil eines Dateinamens hinter dem Punkt ist die Dateinamenerweiterung. In den Standardeinstellungen werden die meisten Dateinamenerweiterungen versteckt. Auch aus Sicherheitsgründen sollten Sie die Standardeinstellungen ändern, um bösartige Software z.B. auch in Outlook als Anhang sofort identifizieren zu können. Die Standardeinstellung können Sie im Windows-Explorer (zum Aufrufen die Windows-Taste drücken und ein E auf der Tastatur) vornehmen. Dazu tippen Sie die Alt-Taste (Links neben der Leertaste) und anschliessend auf X wie Extras und klicken auf den Punkt Ordneroptionen. Unter dem Reiter Ansicht finden Sie ein Häkchen bei dem Text Erweiterungen bei bekannten Dateitypen ausblenden. Entfernen Sie es und bestätigen dies mit OK. Danach werden Sie die Dateinamenerweiterungen bei allen Dateien sehen können. Anhand der Dateinamenerweiterung erkennt Windows was es mit der jeweiligen Datei zu tun hat, wenn z.B. ein Doppelklick darauf erfolgt. Dateien mit der Erweiterung exe sind Executables, also ausführbare Programme die als Prozess gestartet werden können, wie z.B. Winword. Dateien mit der Erweiterung doc oder docx sind Briefe die Sie mit dem Programm Winword geschrieben haben. Macht man auf solche Dateien einen Doppelklick wird erst das Programm Winword gestartet und anschliessend die doppelt geklickte Datei in Word geladen, damit Sie Ihren Brief ausdrucken oder weiterbearbeiten können. Wenn Sie nun in Outlook beispielsweise einen Anhang haben, der aussieht wie ein PDF durch das Symbol vom Arcobat Reader, aber Sie als Dateiname Rechnung.pdf.exe sehen, sollten Sie den Anhang unter keinen Umständen öffnen. Haben Sie das Häkchen im Explorer nicht entfernt, hätten Sie nur Rechnung.pdf gesehen und wären auf den Angriff hereingefallen und sich schein schädliches Programm installiert.
Distribution – Erweiterung des Linux Kerns um weitere OpenSource Produkte wie z. B. Gimp oder Libre Office (ein Ableger von Open Office). Umgangssprachlich wird meist von einem Linux-Betriebssystem wie Ubuntu gesprochen. Genau genommen ist damit aber kein Betriebssystem, sondern eine Software Distribution gemeint.
DNS – Domain Name System. DNS ist in der IT etwas anders als in der Biologie, hat also nichts mit dem Genom zu tun. Um den Weg, z. B. Microsoft zu finden ist die IP-Adresse (so etwas wie eine Telefonnummer) unbedingt nötig. Im Browser gibt man aber statt „wilden Zahlen“ lieber sprechende Namen wie z.B. www.microsoft.de ein. Die Zuordnung von diesem Namen, zu der weltweit eindeutigen IP-Adresse macht DNS für uns. Ein DNS-Server ist ein spezieller Computer der genau diese Aufgabe der Zuordnung durchführt.
FQDN – Full Qualified Domain Name, bedeutet zu Deutsch vollqualifizierter Domänenname. Damit ist der komplette DNS Name eines Computer gemein. Der FQDN setzt sich in der Regel aus dem Hostname und einem oder mehreren Domänennamen zusammen. Als Beispiel soll hier einmal meine Internetadresse dienen: www.martinlehmann.de. www ist dabei der Hostname (wenn man so will der Vorname der Computers), martinlehmann und de sind Domänennamen (so ählich wie ein Nachname bzw. Familienname). Alle Computer die im „Familiennamen“ de ganz rechts tragen, stehen aller voraussicht nach in Deutschland, oder stellen zumindest Deutsch sprachige Inhalte bereit (Ausnahmen bestätigen wie üblich die Regel). Unter martinlehmann sind dann alle Computer die „mir gehören“. Einer davon nennt sich eben www und der stellt die Internetseite dar.
Icon – Miniturbild eines Programmes. Ein Doppelklick darauf startet das Programm. Ist das Miniaturbild in der Taskleiste, oder im Startmenü reicht ein einfacher Klick aus.
Kompilieren – ist der Übersetzungsvorgang den ein Compiler durchführt um Programme ausführbar zu machen.
Indiziert – Durchnummeriert
Juju – bedeutet soviel wie Karma. Begriff stammt aus dem Voodoo.
Linux – OpenSource Betriebssystem Kern von Linus Torvalds
LPIC – Abk. Linux Professional Institute Certificate. Bescheinigung über distributionsunabhängige (RedHat, SuSE, Debian, Ubuntu, etc…) Linuxkenntnisse. Es gibt 3 Level, wobei 1 der niedrigste ist.
MCT – Abk. Microsoft Certified Trainer
Multitasking – Gleichzeitige Verarbeitung mehrerer Aufgaben.
MOF – Abk. Managed Object Format
OpenSource
– zu Deutsch: Quelloffen. D. h. die Software ist für jedermann im Quell Code z. B. C einsehbar, kann abgeändert und selbst neu kompiliert werden.
Out-of-the-Box – Aus dem Englischen: „Direkt aus der Schachtel“  oder einfach „direkt“
Pfad – Gibt an wo sich etwas befindet. Meist in Form von C:\Ordner\Unterordner\Datei.Erweiterung zu verstehen. C: gibt dabei das Festplattenlaufwerk an und durch sog. Backslashs (das Zeichen \) getrennt die Ordner-Struktur bis hin zur gewünschten Datei.
PowerShell – zu Deutsch: MachtMuschel ;-)
Quellcode – Rohinformationen wie ein Programm anlaufen soll. Im Gegensatz zu PowerShell Skripten können Quellcodes nicht einfach gestartet werden, sondern müssen erst kompiliert, sprich in eine für den Computer verständliche Sprache aus 0 und 1 übersetzt werden. Quellcode liegt meist in der Programmiersprache C vor. Fast alle heutigen Programme und auch Betriebssysteme wie Windows oder Linux werden in der Programmiersprache C verfasst. Mit Hilfe eines speziellen Programmes, dem sog. Compiler kann der Quellcode  dann Beispielsweise in eine Datei mit der Endung exe übersetzt werden. Nur die exe Datei kann gestartet und vom PC ausgeführt werden, nicht aber der Quellcode.
RTFM – Abk. aus dem Englischen: Read the fucking manual, ziemlich frei übersetzt: Lies die verdammte Anleitung
Script – Englisch für Skript (siehe Skript)
SGD – Abk. Studiengemeinschaft Darmstadt, eine Fernschule
Shell – Englisch für Muschel. Kennen Sie sicherlich von der „Tanke“. Bei Computern ist das aber etwas im übertragenen Sinne zu verstehen. Wenn man von einer Muschel spricht, meint man meistens die Schale, in der eine Klasse vom Stamm der Weichtiere lebt. Der Begriff Shell bezieht sich auf die harte Kalkschale die unser Weichtier umgibt. Mit dem Weichtier hingegen ist der Betriebssystemkern gemeint. Die Shell ist kurzum die Oberfläche, die Schnittstelle zwischen Mensch und Betriebssystemkern über die wir Menschen dem Betriebssystemkern klar machen können, was wir von ihm wollen. Daher könnten wir eigentlich auch die Windowsoberfläche mit Ihren Fenstern als Shell bezeichnen. Allerdings ist mit einer Shell im computersprachlichen Sinne in der Regel eine textbasierte Oberfläche gemeint. Beispielsweise die Eingabeaufforderung oder auch „DOS-Box“ genannt, sowie die unzähligen UNIX und LINUX Shells (das ist schon fast ein ganzes Riff) wie ash, bash, zsh & Co.
Skript - Ein Plan zum Ablauf eines Programmes. Im Gegensatz zu einem Quellcode, der erst in eine dem Computer verständliche Sprache übersetzt werden muss, können Skripte direkt ausgeführt werden. Das was bei einem Quellcode der Compiler einmalig für den kompletten Ablaufplan durchgeführt wird (und recht lange dauern kann) macht bei einem Skript der Interpreter Zeile für Zeile und zwar jedes Mal, wenn das Skript abläuft. Bei kleinen Aufgaben und ungeübten Gelegenheitsprogrammierern ist das Skript die bessere Wahl, da man hier immer wieder ganz schnell zwischendrin einmal das Skript starten und testen kann. Bei größeren professionellen Programmen kann es sein, dass die jedes Mal statt findende zeilenweise Übersetzung des Ablaufplans zu lange dauern würde, daher wird dafür dann häufig C eingesetzt, dass zwar im kompilierter Form viel schneller als jedes Skript abläuft, aber dafür gerade während der Entwicklungsphase vor jedem Start übersetzt werden muss. Das kann während der Entwicklung wiederum sehr viel Zeit kosten. Nur einen Betriebssystem Kern zu übersetzen kann dann je nach Leistungsfähigkeit des PCs schon einmal ein paar Stunden dauern.
VBS – Abk. für Visual Basic Script. Ebenfalls eine Skriptprogrammiersprache wie PowerShell, nur älter, komplizierter und langsamer.


 

7        Index