PowerShell - Best Practice - bessere Skripts erstellen
PowerShell bietet relativ viel Freiraum in der Gestaltung der Skripts. Um Skripts leserlich und verständlich zu schreiben, ist es von Vorteil, wenn bestimmte Regeln eingehalten werden. In PowerShell können für die Erklärung des Codes Kommentare eingefügt werden, bzw. können im Header eines cmdlets Informationen zum Skript und eine Hilfe hinterlegt und die möglichen Parameter dokumentiert werden. Das Ziel sollte sein, dass der Code möglichst selbsterklärend geschrieben wird, was so manchen Kommentar überflüssig macht und eine spätere Anpassung durch einen selbst oder durch andere vereinfacht. Zusätzlicher Code für eine Fehlerbehandlung kann die Stabilität erhöhen und die Fehlersuche beschleunigen. Der Einsatz eines geeigneten Editors unterstützt bei der Entwicklung, siehe PowerShell Editoren im Vergleich: ISE, Visual Studio Code.
selbsterklärend schreiben
- Beim Aufruf sollte der ganze cmdlet-Namen verwendet werden (keine alias).
Als Beispiel kann das cmdlet: "Get-ChildItem" auch mit dem Alias "gci" aufgerufen werden. Beim Lesen des Sourcecode lässt der Name "Get-ChildItem" rein anhand des Namens den Einsatzzweck der Funktion vermuten, laut Hilfe: get-help get-childitem:
"Gets the items and child items in one or more specified locations." - Als Parameter sollten sogenannte "Named Parameter" in Skripts verwendet werden:
Als Beispiel könnten alle Dateien eines bestimmten Ordners mit dem cmdlet: "Get-ChildItem" angezeigt werden:
Nicht empfohlen: "get-childitem c:\temp", oder "gci c:\temp"
Der Aufruf sollte wie folgt verwendet werden:
"get-childitem -Path c:\temp"
- Dokumentation innerhalb des Codes
- In PowerShell ISE kann mit Ctrl+J mit Cmdlet (advanced) eine Vorlage für den PowerShell-Header eingefügt werden, siehe: Cmdlet (erweiterte Funktion)
- Bei Befehlen die nicht selbsterklärend sind: Kommentare hinzufügen:
- Ein einzeiliger Kommentar kann mit "#" erstellt werden
#Beschreibung des folgenden Aufrufes
- mehrzeilige Kommentare beginnen mit einem "<#" und enden mit "#>"
<# mehrzeiliger Kommentar #>
- Ein einzeiliger Kommentar kann mit "#" erstellt werden
Für die Funktionsnamen gibt es vorgegebene Präfixe. Für eigene Funktionen sollte ein gültiger Präfix (Verb) verwendet werden:
Get-Verb - zulässige cmdlet-Namen für eigene Funktionen
Mit dem Befehl Get-Verb ist es möglich alle zulässigen Verben für eigene Befehle anzeigen zu lassen:
PS C:\Windows\system32> get-verb
Verb Group
---- -----
Add Common
Clear Common
Close Common
Copy Common
Enter Common
Exit Common
Find Common
Format Common
Get Common
Hide Common
Join Common
Lock Common
Move Common
New Common
Open Common
Optimize Common
Pop Common
Push Common
Redo Common
Remove Common
Rename Common
Reset Common
Resize Common
Search Common
Select Common
Set Common
Show Common
Skip Common
Split Common
Step Common
Switch Common
Undo Common
Unlock Common
Watch Common
Use Other
Verb Group
---- -----
Backup Data
Checkpoint Data
Compare Data
Compress Data
Convert Data
ConvertFrom Data
ConvertTo Data
Dismount Data
Edit Data
Expand Data
Export Data
Group Data
Import Data
Initialize Data
Limit Data
Merge Data
Mount Data
Out Data
Publish Data
Restore Data
Save Data
Sync Data
Unpublish Data
Update Data
Verb Group
---- -----
Approve Lifecycle
Assert Lifecycle
Complete Lifecycle
Confirm Lifecycle
Deny Lifecycle
Disable Lifecycle
Enable Lifecycle
Install Lifecycle
Invoke Lifecycle
Register Lifecycle
Request Lifecycle
Restart Lifecycle
Resume Lifecycle
Start Lifecycle
Stop Lifecycle
Submit Lifecycle
Suspend Lifecycle
Uninstall Lifecycle
Unregister Lifecycle
Wait Lifecycle
Verb Group
---- -----
Debug Diagnostic
Measure Diagnostic
Ping Diagnostic
Repair Diagnostic
Resolve Diagnostic
Test Diagnostic
Trace Diagnostic
Verb Group
---- -----
Connect Communications
Disconnect Communications
Read Communications
Receive Communications
Send Communications
Write Communications
Verb Group
---- -----
Block Security
Grant Security
Protect Security
Revoke Security
Unblock Security
Unprotect Security
Es sollte keine Mehrzahl für das Verb verwendet werden, die vorgeschlagenen Verben sind alle in der Einzahl ...
keine langen Einzeiler
Durch das Verwenden mehrerer Parameter sind Befehle des Öfteren etwas schwerer zu lesen:
Get-ChildItem -Path "c:\temp" -Recurse -Depth 2 -Include "*.txt" -Force -Exclude "*temp*" -WarningAction Continue -ErrorAction Stop
Etwas übersichtlicher wird es, wenn die Parameter in eigene Zeilen geschrieben werden:
Zeilenumbrüche
mit einem "`" am Ende der Zeile kann der Befehl auf mehrere Zeilen aufgeteilt werden:
Get-ChildItem `
-Path "c:\temp" `
-Recurse `
-Depth 2 `
-Include "*.txt" `
-Force `
-Exclude "*temp*" `
-WarningAction Continue `
-ErrorAction Stop
Alternativ können die Parameter auch über eine Hashtable übergeben werden:
Splatting
Durch das Auslagern der Parameter in eine Hashtable, können diese übersichtlicher gestaltet werden:
$HashArguments = @{
Path = "c:\temp"
Recurse = $true
Depth = 2
Include = "*.txt"
Force = $true
Exclude = "*temp*"
WarningAction = "Continue"
ErrorAction = "Stop"
}
Get-ChildItem @HashArguments
ein Einsatzzweck pro Funktion
Funktionen sollten, wie auch bei anderen Skriptsprachen, einen bestimmten Einsatzzweck haben und nicht mehrere Aufgaben in einer Funktion vereinen. Als Beispiel werden mit dem Befehl "Get-ChildItem" alle Dateien oder Ordner eines Verzeichnisses ausgegeben. Um auf den Dateien eine bestimmte Aktion, zum Beispiel das Löschen aller Dateien (Items) auszuführen, kommt ein weiteres cmdlet zum Einsatz: "Remove-Item". Die beiden cmdlet können im Aufruf kombiniert werden:
Get-ChildItem -path "c:\temp" | Remove-Item
Get-ChildItem hat als Einsatzzweck eine Liste von Dateien zu liefern, Remove-Item einzig ein bestimmtes Item zu entfernen (löschen). Das Beispiel ist für die interaktive Verwendung in PowerShell und sollte nur die genau definierte Aufgabe der Cmdlets verdeutlichen.
Funktionen nicht mit einem exit beenden
Funktionen sollten im Fehlerfall mit einem throw beendet werden, nicht mit einem exit. Der Grund dafür ist, dass beim Verwenden von exit das komplette Skript beendet wird. Würde hingegen "Throw" verwendet werden, kann ein Fehler der Funktion mit einem try/catch behandelt werden. Ohne try/catch beendet sich das Skript an dieser Stelle dennoch mit einem Fehler.
function test{
Param($x)
if($x){
Write-Output $x
}else{
Throw 'no $X passed'
}
}
try {
Test
} catch {
Write-Output "Param x fehlt"
}
Abstraktion in eigene Funktionen, wenn es Sinn macht
Funktionen machen Sinn, wenn diese einen Mehrwert bieten:
- Wenn ein bestimmter Codeblock mehrfach verwendet werden soll
- Für bestimmte Logiken, die sich schlichtweg nur mit Funktionen umsetzen lassen.
- Wenn das Skript dadurch zuverlässiger wird.
- Wenn das Skript dadurch verständlicher und einfacher wird
Als Beispiel könnte die Funktion Get-ChildItem in eine neue Funktion verpackt werden und im Anschluss über diese aufgerufen werden:
<#
.Synopsis
Get-MyChildItem
.DESCRIPTION
Wrapper für Get-ChildItem
.EXAMPLE
Get-MyChildItem
#>
function Get-MyChildItem
{
Param
(
$Path
)
Get-ChildItem -path $path
}
#Aufruf der Funktion:
Get-MyChildItem -path "c:\temp"
Der Aufruf der eigenen Funktion ist in dem Beispiel genauso leserlich, wie die erstellte Funktion: Das Beispiel macht in der Form natürlich keinen Sinn, es steht aber repräsentativ für eine Funktion, deren einzige Aufgabe es ist eine andere Funktion aufzurufen. Ich habe bereits Beispiele gesehen, bei denen eine eigene Funktion das Skript weder leserlicher, noch einfacher macht. Richtig kompliziert wird es, wenn eine eigene Funktion eine andere aufruft, diese wiederum eine weitere Funktion usw. Möglich, dass der Verfasser zum Zeitpunkt des Erstellen des Skriptes zwar noch einen Überblick hat, aber sich nach einiger Zeit dabei wiederfindet das Skript erst einmal reverse-engineeren zu müssen, bevor er eine Anpassung daran machen kann und noch schwerer hat es eine andere Person. An dieser Stelle macht etwas Mitleid mit den Personen die das Skript später lesen oder apassen sollen durchaus Sinn. Zudem sollte nicht vergessen werden, dass eine Funktion, wie bereits erwähnt, nach Möglichkeit nur eine einzige Aufgabe haben soll: Weniger ist da oft mehr und je einfacher desto besser.
{{percentage}} % positiv