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
      #>

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.

positive Bewertung({{pro_count}})
Beitrag bewerten:
{{percentage}} % positiv
negative Bewertung({{con_count}})

DANKE für deine Bewertung!


veröffentlicht am 09.09.2021 von Bernhard
geändert am 10.09.2021 von Bernhard



Fragen / Kommentare


Wir verwenden Cookies, um Inhalte und Anzeigen zu personalisieren, Funktionen für soziale Medien anbieten zu können und die Zugriffe auf unsere Website zu analysieren. Außerdem geben wir Informationen zu Ihrer Nutzung unserer Website an unsere Partner für soziale Medien, Werbung und Analysen weiter. Details anzeigen.