PowerShell ist ein sehr mächtiges Werkzeug geworden mit dem man sehr schnell komplexe Scripte schreiben kann die beispielsweise komplette Infrastrukturen in Azure aufbauen können.
Funktionen
Wie in jeder anderen Programmiersprache auch neigen komplexe Programmierungen schnell dazu, unübersichtlich zu werden, da ist auch PowerShell keine Ausnahme. Zum Glück kann man Funktionalität in PowerShell in Funktionen kapseln. Eine Funktion ist dabei recht einfach aufgebaut. Sie beginnt mit dem Schlüsselwort Function, ggf. einer Parameterliste in Klammern und dann einem Block der von geschweiften Klammern umgeben ist in dem die eigentliche Funktionalität der Funktion untergebracht ist. Als Beispiel habe ich einmal die folgende Funktion herausgesucht die den Namen der aktuelle Administratorengruppe ermittelt:
Function Get-AdministratorsGroup { if(!$builtinAdminGroup) { $builtinAdminGroup = (Get-WmiObject -Class Win32_Group ` -computername $env:COMPUTERNAME ` -Filter "SID='S-1-5-32-544' ` AND LocalAccount='True'" ` -errorAction "Stop").Name } Return $builtinAdminGroup }
Was die Funktion im Einzelnen tut soll uns an dieser Stelle einmal egal sein, es geht uns nur darum, dass wir eine Funktion haben die irgendeine Funktionalität kapselt. Die Funktion kann man nun in PowerShell mit dem Befehl
Get-AdministratorsGroup
aufrufen. Wie erwartet wird der Name der lokalen Administratoregruppe zurückgeliefert:
PS C:\Windows\system32> Get-AdministratorsGroup Administratoren
Das Aufteilen von Funktionalität innerhalb eines PowerShell Scriptes mit Hilfe von Funktionen ist schon mal ein erster Schritt zur Modularisierung, führt aber dazu, dass das Script immer noch sehr lang ist, was der Übersichlichkeit nicht zugutekommt. Außerdem wollen wir unsere Funktion ja vielleicht auch in anderen Scripten weiterverwenden und Copy & Paste ist aus meiner Sicht hier der falsche Ansatz.
Include
Zum Glück bietet PowerShell aber auch hier eine wertvolle Hilfe an. Man kann Scripte, so wie man es von anderen Programmiersprachen her kennt in andere Scripte „includen“, d.h. am Anfang eines Scriptes kann man ein anderes Script laden. Auch das möchte ich hier kurz demonstrieren. Dazu speichere ich die Funktion Get-AdministratorsGroup in einer eigenen Datei ab die ich Adminfunctions.ps1 nenne. Nun schreibe ich ein zweites Script das ich main.ps1 nenne. Das Ergebnis sieht dann so aus:
Nun kann ich das Script AdminFunctions.ps1 in das Script Main.ps1 einbinden, indem ich folgendes programmiere:
. "$PSScriptRoot\Adminfunctions.ps1" Get-AdministratorsGroup
Ich kann ein Script also in ein anderes Script über den Punkt-Operator (.) einbinden. Schon mal gut zu wissen. Wichtig bei dieser Aktion ist, dass man den Pfad an dem das einzubindende Script liegt exakt angibt, da das einzubindende Script sonst vom einbindenden Script aus logischerweise nicht gefunden werden kann. Befindet sich das einzubindende Script im gleichen Verzeichnis wie das einbindende Script kann man das so wie oben im Code zu sehen machen, indem man sich über die Variable $PSScriptRoot das aktuelle Verzeichnis besorgt in dem das einbindende Script liegt. Ist das nicht der Fall muss man über die Möglichkeiten die PowerShell zur Navigation im Dateisystem bietet an die richtige Stelle verzweigen. Lassen wir unser Script laufen bekommen wir das folgende Ergebnis:
PS C:\Windows\system32> Get-AdministratorsGroup Administratoren
PowerShell Module
Neben der Einbindung von Scripten über den Punkt-Operator können ist es auch möglich in PowerShell so genannte PowerShell Module zu schreiben. Im Prinzip ist ein PowerShell-Modul nichts anderes als eine „normale“ PowerShell Script-Datei mit dem Dateisuffix .psm1. Diese Datei muss man in einen Ordner legen der denselben Namen besitzt wie die Datei selbst. Idealerweise legt man diesen Ordner in einem Ordner Module ab, wo sich sämtliche PowerShell Module befinden die man so benötigt. Auch dies möchte ich an unserem einfachen Beispiel einmal demonstrieren.
Hierzu nenne ich die Datei AdminFunctions.ps1 in Adminfunctions.psm1 um, erstelle einen Ordner Modules\AdminFunctions und verschiebe die Datei in diesen Ordner.
Wir sind an dieser Stelle schon fast fertig, eine wichtige Voraussetzung dass wir unser PowerShell Modul mit Import-Module laden können fehlt aber noch. Wir müssen den Pfad den wir für die Module erstellt haben (also Power Shell Scripts\Test\Modules) noch in die Umgebungsvariable$env:PSModulePath einbinden. In dieser Variablen sind alle Orte gespeichert in denen PowerShell beim Aufruf von Import-Module nachschaut. Hat man ein Script das selbst ein Modulverzeichnis mitbringt ist es am einfachsten dieses Modulverzeichnis über den folgenden Befehl der Environment-Variablen anzuhängen:
if (!($env:PSModulePath -like "*$PSScriptRoot*")) { $env:PSModulePath = $env:PSModulePath+";"+$PSScriptRoot+"\Modules" }
Der Code ist sehr simpel aufgebaut. Zunächst wird geschaut ob der aktuelle Sciptroot ($PSScriptRoot) bereits im Modulpfad ($env:PSModulePath) enthalten ist. Ist das nicht der Fall, dann wird der neue Modulpfad einfach durch Semikolon getrennt an den aktuellen Modulpfad angehängt. Die If-Abfrage ist dahingehend wichtig als dass ansonsten der Modulpfad bei jedem Aufruf des Scriptes an die Umgebungsvariable angehängt würde, was wir ja so nicht wollen. Nachdem unser Modulpfad nun bekannt ist können wir unser eigenes PowerShell Modul mit Import-Module laden. Der Code des aufrufenden Scriptes sieht also wie folgt aus:
if (!($env:PSModulePath -like "*$PSScriptRoot*")) { $env:PSModulePath = $env:PSModulePath+";"+$PSScriptRoot+"\Modules" } Import-Module -name "AdminFunctions" Get-AdministratorsGroup
Dass unser Modul geladen wurde kann man einerseits daran sehen, dass Get-AdministratorsGroup funktioniert und andererseits kann man es auch mit Get-Module überprüfen.
PS C:\Windows\system32> Get-Module ModuleType Version Name ExportedCommands ---------- ------- ---- ---------------- Script 0.0 AdminFunctions Get-AdministratorsGroup Script 1.0.0.0 ISE {Get-IseSnippet, Import-IseSnippet, New-IseSnippet} Manifest 3.1.0.0 Microsoft.PowerShell.Management {Add-Computer, Add-Content, Checkpoint-Computer, Clear-Content...} Manifest 3.0.0.0 Microsoft.PowerShell.Security {ConvertFrom-SecureString, ConvertTo-SecureString, Get-Acl, Get-AuthenticodeSignature...} Manifest 3.1.0.0 Microsoft.PowerShell.Utility {Add-Member, Add-Type, Clear-Variable, Compare-Object...} Manifest 3.0.0.0 Microsoft.WSMan.Management {Connect-WSMan, Disable-WSManCredSSP, Disconnect-WSMan, Enable-WSManCredSSP...}
Das war es eigentlich auch schon. Ist meiner Meinung nach relativ simpel und „straight forward“. Viel Spaß beim Aufteilen Ihrer PowerShell Scripte in Includes und Module.