Parameterdesign und Modularisierung für PowerShell-Skripte — Von der „funktionierenden“ zur „übergabefähigen“ Skript
· Go Komura · PowerShell, Windows, Skript, Automatisierung, Betriebsoptimierung, Betriebliche Effizienz, Nutzung vorhandener Ressourcen, Kommandozeile
„Ein PowerShell-Skript, das ein einzelner Mitarbeiter geschrieben hat, läuft im Produktivbetrieb, aber nur diese Person kann es anfassen.“ „Servernamen und Pfade sind überall im Code fest verdrahtet, sodass wir das Skript selbst umschreiben müssen, sobald sich die Umgebung ändert.“ „Wir haben ein falsches Argument übergeben, und es lief trotzdem einfach weiter – wir haben es erst später bemerkt.“ In Beratungsgesprächen zur Betriebsautomatisierung liegt das Problem sehr häufig nicht im Skript selbst, sondern darin, wie das Skript übergeben und weiterentwickelt wird.
Solange Sie es nur auf Ihrem eigenen Rechner ausführen, ist ein „funktionierendes Skript“ mit fest verdrahteten Variablen völlig in Ordnung. Doch sobald Sie es in die Aufgabenplanung einbinden, an eine Kollegin oder einen Kollegen weitergeben oder auf mehreren Servern wiederverwenden, entscheiden das Design der Parameter und die Organisation der gemeinsam genutzten Logik über die Qualität. Glücklicherweise stellt PowerShell die Werkzeuge für diesen Weg zum „übergabefähigen Skript“ von Anfang an als Sprachfunktionen bereit: den param-Block, Validierungsattribute, kommentarbasierte Hilfe und Module.
Dieser Artikel nimmt die „funktionierende Skripte“, die IT- und Betriebsmitarbeiter in kleinen und mittleren Unternehmen bereits besitzen, als Ausgangspunkt und beschreibt eine praxisnahe, stufenweise Vorgehensweise zur Qualitätssteigerung: Parameterdesign → Eingabevalidierung → Hilfe- und -WhatIf-Unterstützung → .psm1-Modularisierung → interne Verteilung. Als Grundlage dient PowerShell 7.x, mit Hinweisen für Arbeitsplätze, an denen nur Windows PowerShell 5.1 verfügbar ist.
1. Die wichtigsten Punkte vorab
- Der Ausgangspunkt ist, Parameter in einem param-Block zu deklarieren und mit [CmdletBinding()] eine erweiterte Funktion daraus zu machen. Gemeinsame Parameter (-Verbose, -ErrorAction und andere) werden automatisch hinzugefügt, und die Übergabe eines nicht definierten Parameters führt zu einem Bindungsfehler, sodass ein Tippfehler nicht stillschweigend ignoriert wird.1
- Deklarieren Sie erforderliche Argumente mit [Parameter(Mandatory)] und geben Sie ihnen stets einen Typ. Ein Aufruf, der ein erforderliches Argument vergisst, stoppt vor der Ausführung, und ein Wert vom falschen Typ wird ebenfalls vor der Ausführung abgelehnt.2
- Verlagern Sie Formatprüfungen in Validierungsattribute (ValidateSet/ValidateRange/ValidateScript/ValidateNotNullOrEmpty), nicht in if-Anweisungen. Schlägt die Validierung fehl, wird die Funktion gar nicht erst aufgerufen – das schließt den Fall „läuft zur Hälfte durch und bricht dann ab“ strukturell aus. Der Grundsatz lautet: Fehler früh, am Eingang, auslösen.2
- Deklarieren Sie Ein/Aus-Argumente als [switch]. Selbstgebaute Flag-Parameter, die $true oder $false als Zeichenfolge entgegennehmen, sind eine Quelle für Fehler auf Aufruferseite.2
- Schreiben Sie kommentarbasierte Hilfe (.SYNOPSIS/.EXAMPLE), dann funktioniert Get-Help auch für Ihre eigenen Befehle. Von „den Code lesen, um die Nutzung zu verstehen“ wegzukommen, ist die Mindestvoraussetzung für eine Übergabe.3
- Deklarieren Sie SupportsShouldProcess bei verändernden Funktionen, damit sie -WhatIf/-Confirm unterstützen. Dem Aufrufer die Möglichkeit zu geben, den Wirkungsbereich vor der Ausführung zu prüfen, hat das beste Kosten-Nutzen-Verhältnis aller Sicherheitsmechanismen in einem Betriebsskript.4
- Lagern Sie über mehrere Skripte hinweg wiederverwendete Funktionen in ein .psm1-Modul aus und legen Sie es unter $env:PSModulePath ab. Stimmt der Ablageort, wird es ohne Import-Module automatisch geladen. Ein psd1-Manifest kann bis zur Verteilungsphase warten.5678
- Verwalten Sie Module und Skripte mit Git und behalten Sie bei der Verteilung über einen Freigabeordner das Zusammenspiel mit der Ausführungsrichtlinie im Blick. Skripte auf einem UNC-Pfad können unter RemoteSigned an der Ausführung gehindert werden.9
2. Der param-Block und [CmdletBinding()] ── der Eingang zu einer erweiterten Funktion
Zunächst das typische „funktionierende Skript“, das man überall antrifft.
# Ein gängiges Beispiel: fest verdrahtete Variablen. Bei jeder Umgebungsänderung muss das Skript selbst umgeschrieben werden
$logDir = "D:\Logs\AppA"
$days = 90
Get-ChildItem $logDir -Filter *.log |
Where-Object { $_.LastWriteTime -lt (Get-Date).AddDays(-$days) }
Schreiben wir es mit einem param-Block und [CmdletBinding()] um.
[CmdletBinding()]
param(
# Erforderlich. Ein Aufruf, der die Angabe vergisst, stoppt vor der Ausführung
# (Hinweis: [string] drückt die Absicht aus, lehnt aber nichts ab. Zahlen und Ähnliches werden automatisch
# in Zeichenfolgen konvertiert und durchgereicht – Bedingungen, die Sie streng ablehnen wollen, gehören
# in die Validierungsattribute weiter unten)
[Parameter(Mandatory)]
[string]$LogDir,
# Geben Sie optionalen Argumenten einen aus geschäftlicher Sicht sicheren Standardwert
[int]$Days = 90
)
Get-ChildItem -LiteralPath $LogDir -Filter *.log -File |
Where-Object { $_.LastWriteTime -lt (Get-Date).AddDays(-$Days) }
[CmdletBinding()] ist eine Deklaration, dass „diese Funktion (dieses Skript) sich wie ein kompiliertes Cmdlet verhalten soll“, und eine Funktion, die dies trägt, wird zu einer erweiterten Funktion (advanced function). Sie hat drei Effekte.1
- Gemeinsame Parameter werden automatisch hinzugefügt. Aufrufer können -Verbose, -Debug, -ErrorAction, -ErrorVariable und weitere nutzen, ohne dass Sie sie implementieren. Sie erhalten das Standardverhalten, bei dem Write-Verbose im Skript nur erscheint, wenn -Verbose angegeben wurde.
- Argumentfehler stoppen vor der Ausführung. Bei einer erweiterten Funktion führt die Übergabe eines nicht definierten Parameternamens oder eines überzähligen Arguments ohne passenden Positionsparameter dazu, dass die Parameterbindung fehlschlägt. Der Unfall, dass ein Tippfehler wie
-Dyas 30stillschweigend ignoriert und der Standardwert verwendet wird, entfällt.1 - $PSCmdlet wird verfügbar. Das ist der Zugang zu cmdlet-orientierten Funktionen wie ShouldProcess, auf die wir später eingehen.10
Wird ein als Mandatory markiertes Argument weggelassen, fragt PowerShell vor der Ausführung interaktiv danach. Das ist der erste Sicherheitsmechanismus gegen den Fall, „es lief mit dem Standardwert, weil ich vergessen habe, ein Pflichtargument zu übergeben“.2 Der eine Vorbehalt: Dieses „nach Eingabe fragen“ verträgt sich schlecht mit unbeaufsichtigter Ausführung. Fehlt bei einem Start durch die Aufgabenplanung ein Pflichtargument, kann der Job unbegrenzt an einer Eingabeaufforderung hängen bleiben, die niemand beantworten kann. Für unbeaufsichtigte Ausführung ist der Standardweg, mit -NonInteractive zu starten, sodass sofort ein Fehler ausgelöst wird, statt nachzufragen.
Übrigens: Wenn Sie eine Funktion benennen und veröffentlichen, verwenden Sie die Verb-Substantiv-Form und wählen Sie das Verb aus den zulässigen Verben, die Sie mit Get-Verb auflisten können. Nicht zugelassene Verben funktionieren zwar trotzdem, erzeugen aber beim Import des Moduls eine Warnung.11
3. Eingaben am Eingang validieren ── mit Validate-Attributen Fehler früh auslösen
Argument-Formatprüfungen als if-Anweisungen im Funktionsrumpf zu schreiben, neigt dazu, Fehler einzuschleppen – übersehene Prüfungen oder Nebenwirkungen, die vor der Prüfung selbst laufen. In PowerShell lässt sich die Validierung mit Validierungsattributen direkt neben der Parameterdeklaration unterbringen. Die Validierung erfolgt, bevor die Funktion aufgerufen wird, und schlägt sie fehl, läuft keine einzige Zeile des Funktionsrumpfs.2
[CmdletBinding()]
param(
# Nur existierende Ordner akzeptieren. $_ ist der zu validierende Wert
[Parameter(Mandatory)]
[ValidateScript({ Test-Path -LiteralPath $_ -PathType Container })]
[string]$LogDir,
# Aufbewahrungszeitraum auf 1-3650 Tage begrenzen. Verhindert den Unfall, dass 0 oder eine negative Zahl "alle Dateien" bedeutet
[ValidateRange(1, 3650)]
[int]$Days = 90,
# Die Auswahlmenge festlegen. Auch die Tab-Vervollständigung funktioniert dann
[ValidateSet('Zip', 'Move', 'ReportOnly')]
[string]$Mode = 'ReportOnly',
# Leere Zeichenfolgen und $null ablehnen. Standardausstattung für String-Parameter
[ValidateNotNullOrEmpty()]
[string]$ReportName = 'log-report',
# Ein/Aus ist ein switch. Angegeben bedeutet $true, weggelassen bedeutet $false
[switch]$IncludeSubfolders
)
Hier eine Faustregel für die Auswahl.
| Attribut | Zweck | Typisches Praxisbeispiel |
|---|---|---|
| ValidateSet | Einen Wert auf eine feste Auswahlmenge beschränken und Tab-Vervollständigung aktivieren2 | Betriebsmodus, Umgebungsname (Dev/Test/Prod) |
| ValidateRange | Bereichsgrenzen für Zahlen und Datumsangaben2 | Aufbewahrungstage, Wiederholungsanzahl, Portnummer |
| ValidateScript | Validierung mit einem beliebigen Skript. Schlägt bei $false oder einer Ausnahme fehl2 | Prüfen, ob ein Pfad existiert, Prüfen der Datumsreihenfolge |
| ValidateNotNullOrEmpty | $null, leere Zeichenfolgen und leere Sammlungen ablehnen2 | Praktisch jeder String-Parameter |
| ValidatePattern | Formatprüfung mit einem regulären Ausdruck2 | Belegnummern, Namenskonventionen für Hosts |
Zwei weitere Hinweise. Erstens ist die Reihenfolge der Deklaration von Validierungsattributen von Bedeutung. Schreiben Sie ein Validierungsattribut nach dem Typ, kann der Wert bereits vor der Typkonvertierung validiert werden, was zu unerwarteten Fehlschlägen führen kann – deshalb empfiehlt die offizielle Dokumentation als Best Practice die Reihenfolge Attribut → Typ → Variablenname.2 Zweitens: Das ErrorMessage-Argument von ValidateScript (eine benutzerdefinierte Fehlermeldung) ist eine Funktion ab PowerShell 6 und steht unter Windows PowerShell 5.1 nicht zur Verfügung.2 In gemischten Umgebungen mit 5.1 ist es sicherer, entweder im Validierungsskript selbst per throw eine eigene Meldung auszugeben oder die Standardmeldung unverändert zu belassen.
Sobald Sie in ValidateScript aufwendige Validierungslogik schreiben, ist das auch ein Signal, dass es Zeit für Tests wird. Die Verhaltensprüfung der Validierungslogik selbst mit den in „PowerShell mit Pester testen“ beschriebenen Mustern abzusichern macht sie schwerer kaputtzumachen.
4. Grundlagen der Pipeline-Eingabe ── ValueFromPipeline und der process-Block
Eigene Funktionen werden auf dieselbe Weise kombinierbar wie PowerShells eingebaute Befehle, sobald sie sich in einer Pipeline verwenden lassen, etwa in Get-Content servers.txt | Test-AppServer. Dafür genügen zwei Dinge: eine ValueFromPipeline-Deklaration und ein process-Block.2
function Test-AppServer {
[CmdletBinding()]
param(
[Parameter(Mandatory, ValueFromPipeline)]
[string[]]$ComputerName
)
begin { $results = @() } # Läuft einmal vor der Pipeline-Verarbeitung
process {
# Läuft für jedes aus der Pipeline eintreffende Element
foreach ($name in $ComputerName) {
$results += [pscustomobject]@{
ComputerName = $name
# -ComputerName funktioniert sowohl unter 5.1 als auch 7 (-TargetName, hinzugekommen in 7, existiert unter 5.1 nicht)
Reachable = Test-Connection -ComputerName $name -Count 1 -Quiet
}
}
}
end { $results } # Läuft einmal am Ende
}
Es gibt nur einen Punkt, den Sie sich einprägen müssen. Wenn Sie Pipeline-Eingaben entgegennehmen, gehört die eigentliche Arbeit in den process-Block. Ohne process-Block führt das Senden mehrerer Werte durch die Pipeline zu dem klassischen Fehler, dass nur das letzte Element verarbeitet wird.10 begin und end sind optional; im Zweifel reicht für die Praxis die Faustregel „Arbeit in process; begin/end nur bei Aggregationsbedarf“.
5. Get-Help und -WhatIf zum Laufen bringen ── die Mindestvoraussetzungen für eine Übergabe
5.1. Kommentarbasierte Hilfe
PowerShell-Anwender greifen bei einem unbekannten Befehl zuerst zu Get-Help. Ob Ihre eigene Funktion an dieser Kultur teilnehmen kann, hängt davon ab, ob Sie kommentarbasierte Hilfe geschrieben haben. Es genügt bereits, Kommentare mit speziellen Schlüsselwörtern zu schreiben, damit Get-Help die Hilfe im selben Format wie bei einem Standard-Cmdlet anzeigt.3
5.2. SupportsShouldProcess und -WhatIf
Für Funktionen, die löschen, verschieben oder Einstellungen ändern, deklarieren Sie [CmdletBinding(SupportsShouldProcess)]. Allein das fügt automatisch die Parameter -WhatIf und -Confirm hinzu, und im Funktionsrumpf entscheiden Sie anhand des Rückgabewerts von $PSCmdlet.ShouldProcess(), ob die Änderung tatsächlich ausgeführt wird.4
Hier die fertige Form einer Funktion in Übergabequalität, die beides einbindet.
function Remove-OldAppLog {
<#
.SYNOPSIS
Löscht Protokolldateien, deren Aufbewahrungszeitraum abgelaufen ist, aus dem angegebenen Ordner.
.DESCRIPTION
Löscht *.log-Dateien, deren LastWriteTime älter als der Aufbewahrungszeitraum ist.
Mit -WhatIf lässt sich nur prüfen, welche Dateien gelöscht würden.
.EXAMPLE
Remove-OldAppLog -LogDir 'D:\Logs\AppA' -Days 90 -WhatIf
Zeigt nur die Löschziele an; es wird nichts tatsächlich gelöscht.
#>
[CmdletBinding(SupportsShouldProcess)]
param(
[Parameter(Mandatory)]
[ValidateScript({ Test-Path -LiteralPath $_ -PathType Container })]
[string]$LogDir,
[ValidateRange(1, 3650)]
[int]$Days = 90
)
process {
$limit = (Get-Date).AddDays(-$Days)
# -File schließt Ordner aus (damit ein Ordner, dessen Name auf ".log" endet, nicht versehentlich gelöscht wird)
Get-ChildItem -LiteralPath $LogDir -Filter *.log -File |
Where-Object { $_.LastWriteTime -lt $limit } |
ForEach-Object {
# ShouldProcess liefert $false bei -WhatIf und wenn -Confirm abgelehnt wird
if ($PSCmdlet.ShouldProcess($_.FullName, "Delete")) {
Remove-Item -LiteralPath $_.FullName
}
}
}
}
Geben Sie Remove-OldAppLog -LogDir D:\Logs\AppA -WhatIf ein, erhalten Sie eine Liste von „What if: …“-Zeilen, ohne dass etwas gelöscht wird. Wenn Sie ein veränderndes Skript weitergeben, geben Sie das -WhatIf-Probelauf-Verfahren gleich mit – das ist das Betriebsmuster, das wir auf dieser Website immer wieder empfehlen. Ein durchgearbeitetes Beispiel, wie sich das in ein reales Skript zur Protokollpflege einbauen lässt, wird ausführlich in „PowerShell-Skripte in der Praxis ── Protokollauswertung, Archivierung und Reporting sicher automatisieren“ beschrieben.
Noch ein Vorbehalt aus dem offiziellen Vertiefungsartikel: Verlassen Sie sich nicht zu sehr darauf, dass sich -WhatIf immer an die aufgerufenen Befehle weitergibt. Um sicherzugehen, übergeben Sie -WhatIf:$WhatIfPreference explizit an innere Befehle wie Remove-Item.4
6. Gemeinsame Logik in ein .psm1-Modul überführen
6.1. .psm1 und Export-ModuleMember
Mit wachsender Funktionsanzahl möchten Sie irgendwann dieselbe Funktion aus mehreren Skripten heraus verwenden. Vervielfältigen Sie sie per Copy-Paste, erreichen Korrekturen nicht mehr alle Kopien – lagern Sie gemeinsam genutzte Funktionen daher in ein Skriptmodul (.psm1) aus. Die Erstellung ist fast schon verblüffend einfach: Speichern Sie die Datei mit Ihren Funktionen einfach mit der Endung .psm1.5
# AppOpsTools.psm1 — gemeinsames Modul für interne Betriebswerkzeuge
function Remove-OldAppLog { <# die Funktion aus dem vorherigen Abschnitt #> }
function Get-AppLogSummary { <# Aggregationsfunktion #> }
# Interne Hilfsfunktion. Wird nicht nach außen exponiert
function ConvertTo-InternalPath { <# ... #> }
# Die zu veröffentlichenden Funktionen explizit angeben. Ohne diese Angabe wird jede Funktion veröffentlicht
Export-ModuleMember -Function Remove-OldAppLog, Get-AppLogSummary
Schreiben Sie kein Export-ModuleMember, werden alle Funktionen und Aliase des Moduls exportiert (Variablen nicht). Es ist optional, doch die öffentliche Schnittstelle explizit zu machen gilt als Best Practice.12 Interne Hilfsfunktionen zu verbergen lässt Ihnen später Spielraum für ungehindertes Refactoring.
6.2. Wohin damit ── $env:PSModulePath und automatisches Laden
Module gehören unterhalb eines der in $env:PSModulePath aufgeführten Ordner, in einem Ordner mit demselben Namen wie das Modul (AppOpsTools\AppOpsTools.psm1). Stimmen Ordnername und der Basisname der Datei nicht überein, wird es nicht als Modul erkannt.65 Die Standardorte sind wie folgt, und dass sich die Pfade zwischen Windows PowerShell 5.1 und PowerShell 7 unterscheiden, ist in der Praxis eine häufige Stolperfalle.6
| Geltungsbereich | PowerShell 7 | Windows PowerShell 5.1 |
|---|---|---|
| Eigener Gebrauch (CurrentUser) | $HOME\Documents\PowerShell\Modules |
$HOME\Documents\WindowsPowerShell\Modules |
| Alle Benutzer (AllUsers) | $env:ProgramFiles\PowerShell\Modules |
$env:ProgramFiles\WindowsPowerShell\Modules |
Legen Sie es am richtigen Ort ab, importiert PowerShell es beim ersten Aufruf eines der Modulbefehle automatisch, ohne dass Sie Import-Module schreiben müssen (automatisches Laden von Modulen).7 Anders gesagt: Anwender können so tun, als „wäre der Befehl schon immer dagewesen“. In der Praxis bewährt sich der Rhythmus, während der Experimentierphase per vollständigem Pfad mit Import-Module zu arbeiten und das Modul erst nach der Konsolidierung unter PSModulePath zu verschieben.
Es gibt zwei umgebungsabhängige Vorbehalte. Der Ordner „Dokumente“ kann durch OneDrive-Ordnerumleitung verschoben worden sein, sodass benutzerbezogene Module ebenfalls unter OneDrive landen.6 Außerdem erfordert das Ablegen von Modulen im Geltungsbereich „alle Benutzer“ Administratorrechte. Wenn Sie es auf einem Server ablegen, verwenden Sie den AllUsers-Geltungsbereich, damit auch das Ausführungskonto der Aufgabenplanung darauf zugreifen kann – so vermeiden Sie das Problem „bei mir läuft es, auf dem Server nicht“.
6.3. Das psd1-Manifest kommt in der Verteilungsphase
Ein Modulmanifest (.psd1) ist eine Hashtabellen-Datei, die Metadaten wie die Version des Moduls und Abhängigkeiten beschreibt, und ist nicht zwingend erforderlich. Der einzige zwingende Schlüssel eines Manifests ist ModuleVersion.8 Solange Sie es nur innerhalb des eigenen Teams nutzen, genügt ein eigenständiges .psm1; erreichen Sie die Stufe der Verteilung an andere Abteilungen oder eine strikte Versionsverwaltung, erzeugen Sie eines mit New-ModuleManifest.8
New-ModuleManifest -Path .\AppOpsTools\AppOpsTools.psd1 `
-RootModule 'AppOpsTools.psm1' `
-ModuleVersion '1.0.0' `
-FunctionsToExport 'Remove-OldAppLog', 'Get-AppLogSummary' `
-PowerShellVersion '5.1'
Das erzeugte psd1 ist eine kommentierte Vorlage, sodass Sie nur die Schlüssel ergänzen können, die Sie tatsächlich brauchen.8
6.4. Das Wichtigste zu interner Weitergabe und Versionsverwaltung
- Bewahren Sie die Masterkopie in Git auf. Skripte und Module sind Text und eignen sich daher gut für Git, und nachvollziehen zu können, „wann, von wem und warum etwas geändert wurde“, ist selbst schon ein Stück Zuverlässigkeit eines Betriebsskripts. Aktualisierungen von ModuleVersion mit Commits in Einklang zu bringen macht es leicht, festzustellen, welche Version auf einem Server installiert ist.
- Die grundlegende Verteilungsform lautet „aus einem Freigabeordner in den PSModulePath jedes Rechners kopieren“. Das Kopieren des gesamten Modulordners entspricht einer manuellen Installation.7 Einen Pfad auf einem Freigabeordner direkt in PSModulePath aufzunehmen, empfehlen wir für den alltäglichen Gebrauch nicht, da Freigaben grundsätzlich „langsam, getrennt oder schlicht nicht erreichbar“ sein können (Details in „Stolperfallen bei Netzlaufwerken und UNC-Pfaden“).
- Behalten Sie das Zusammenspiel mit der Ausführungsrichtlinie im Blick. Die Standardrichtlinie RemoteSigned erlaubt lokal erstellte, unsignierte Skripte, aber auf Systemen, die nicht zwischen UNC-Pfaden und Internetpfaden unterscheiden, können Skripte auf einem UNC-Pfad an der Ausführung gehindert werden. Auch Dateien mit einem Download-Kennzeichen werden blockiert und müssen mit Unblock-File freigegeben oder signiert werden.9 Wenn Sie die interne Verteilung ausbauen wollen, prüfen Sie die Kombination mit der Codesignierung in „PowerShell-Ausführungsrichtlinie und Skriptsignierung“.
7. Übliche Praxis vor Ort (Entscheidungstabelle)
| Frage | Optionen | Faustregel |
|---|---|---|
| Argumente entgegennehmen | Fest verdrahtete Variablen / param-Block | Wird es mehr als einmal oder von jemand anderem verwendet, führt an param kein Weg vorbei. Standardwerte auf die sichere Seite legen2 |
| [CmdletBinding()] | Weglassen / Hinzufügen | Bei allem, was Sie weitergeben oder produktiv einsetzen, immer hinzufügen. Tippfehler stoppen vor der Ausführung1 |
| Eingabeprüfungen | if-Anweisungen im Funktionsrumpf / Validierungsattribute | Formatprüfungen an einem einzelnen Parameter gehören in Attribute. Nur parameterübergreifende Validierung gehört in den Funktionsrumpf2 |
| Sicherheitsmechanismus für Änderungen | Ein selbst gebautes -TestMode-Argument / SupportsShouldProcess | Erfinden Sie kein eigenes Flag. Nutzen Sie das Standard-Paar -WhatIf/-Confirm4 |
| Wie gemeinsame Logik aufbewahren | Copy-Paste / Dot-Sourcing / .psm1-Modul | Sobald ein zweites Skript mitnutzt, modularisieren. Öffentliche Funktionen mit Export-ModuleMember angeben512 |
| Wohin mit dem Modul | Beliebiger Ordner + Import-Module / Unter PSModulePath | Regelmäßig genutzte Module an den vorgesehenen Ort legen und das automatische Laden nutzen. Auf Servern den AllUsers-Geltungsbereich verwenden67 |
| psd1-Manifest | Von Anfang an erstellen / Erst in der Verteilungsphase erstellen | New-ModuleManifest verwenden, sobald es über das eigene Team hinausgeht oder eine strikte Versionsverwaltung nötig wird8 |
8. Zusammenfassung
- Eine Funktion mit param-Block und [CmdletBinding()] zu einer erweiterten Funktion zu machen, ist der Ausgangspunkt für ein „übergabefähiges Skript“. Gemeinsame Parameter werden hinzugefügt, und Argumentfehler stoppen vor der Ausführung.
- Typdeklarationen, [Parameter(Mandatory)], sichere Standardwerte und Validierungsattribute lösen Fehler früh, am Eingang, aus. ValidateSet verbessert zudem die Bedienbarkeit durch Tab-Vervollständigung.
- Pipeline-Eingaben nehmen Sie mit dem Paar aus ValueFromPipeline und einem process-Block entgegen. Vergessen Sie process, wird nur das letzte Element verarbeitet.
- Bringen Sie Get-Help mit kommentarbasierter Hilfe zum Laufen, und lassen Sie verändernde Funktionen -WhatIf über SupportsShouldProcess unterstützen. Einen Probelauf durchführen zu können, ist ein betrieblicher Sicherheitsmechanismus.
- Lagern Sie gemeinsam genutzte Funktionen in ein .psm1 aus, geben Sie die öffentliche Schnittstelle mit Export-ModuleMember an und legen Sie es unter PSModulePath ab. Beachten Sie, dass sich die Pfade zwischen 5.1 und 7 unterscheiden.
- Das psd1-Manifest kommt in der Verteilungsphase. Verwalten Sie die Masterkopie in Git, und prüfen Sie bei der Verteilung über einen Freigabeordner die Ausführungsrichtlinie (das Zusammenspiel von UNC-Pfaden und RemoteSigned).
Verwandte Artikel
- PowerShell-Befehlsgrundlagen ── Was Sie zuerst lernen sollten und wie Sie es sicher einsetzen
- PowerShell-Skripte in der Praxis ── Protokollauswertung, Archivierung und Reporting sicher automatisieren
- PowerShell mit Pester testen ── ein praxisnaher Ansatz, um Betriebsskripte robuster zu machen
- PowerShell-Ausführungsrichtlinie und Skriptsignierung
- Fehlerbehandlung und Wiederholungsdesign in PowerShell
- Zugangsdaten in PowerShell sicher handhaben ── Klartext-Passwörter aus Ihren Skripten verbannen
Verwandte Beratungsbereiche
Die KomuraSoft LLC übernimmt das Aufräumen und Modularisieren von PowerShell-Skripten, die zur Privatdomäne einer einzelnen Person geworden sind, Design-Reviews von Betriebsautomatisierungsskripten sowie den Aufbau von Mechanismen für interne Verteilung und Versionsverwaltung. Wir beginnen gerne auch mit einer Bestandsaufnahme von Skript-Assets, die „funktionieren, die aber niemand anzufassen wagt“.
- Technische Beratung & Design-Review
- Windows-App-Entwicklung
- Nutzung und Migration vorhandener Ressourcen
- Kontakt
Referenzlinks
-
Microsoft Learn, about_Functions_CmdletBindingAttribute. Dazu, dass das CmdletBinding-Attribut eine Funktion sich wie ein kompiliertes Cmdlet verhalten lässt, gemeinsame Parameter automatisch hinzugefügt werden, $PSCmdlet verfügbar wird, die Bindung bei unbekannten Parametern oder nicht zuordenbaren Positionsargumenten fehlschlägt, sowie dazu, dass SupportsShouldProcess die Parameter Confirm/WhatIf hinzufügt. ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, about_Functions_Advanced_Parameters. Zum Parameter-Attribut und Mandatory, ValueFromPipeline, switch-Parametern, zur Angabe von Validierungsattributen wie ValidateSet/ValidateRange/ValidateScript/ValidateNotNullOrEmpty/ValidatePattern, dazu, dass die Funktion bei fehlgeschlagener Validierung nicht aufgerufen wird, dazu, dass die Deklaration der Attribute vor dem Typ Best Practice ist, sowie dazu, dass das ErrorMessage-Argument von ValidateScript erst ab PowerShell 6 verfügbar ist. ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9 ↩10 ↩11 ↩12 ↩13 ↩14 ↩15
-
Microsoft Learn, about_Comment_Based_Help. Dazu, dass kommentarbasierte Hilfe mit Schlüsselwörtern wie .SYNOPSIS/.DESCRIPTION/.PARAMETER/.EXAMPLE bewirkt, dass Get-Help sie im selben Format wie XML-Hilfe anzeigt, sowie zu den jeweiligen Platzierungsregeln für Skripte und Funktionen. ↩ ↩2
-
Microsoft Learn, Everything you wanted to know about ShouldProcess. Dazu, dass -WhatIf/-Confirm allein durch die Angabe von SupportsShouldProcess automatisch erzeugt werden, zur Verzweigung anhand von $PSCmdlet.ShouldProcess(), sowie zur Empfehlung, sich nicht zu sehr auf die Weitergabe von -WhatIf zu verlassen, sondern es an innere Befehle explizit zu übergeben. ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, How to Write a PowerShell Script Module. Dazu, dass allein das Speichern mit der Endung .psm1 daraus ein Skriptmodul macht, dass es in einem Ordner mit demselben Namen wie das Skript gespeichert werden sollte, dass standardmäßig alle Funktionen veröffentlicht werden, Variablen jedoch nicht, sowie zur Empfehlung, die veröffentlichten Funktionen mit Export-ModuleMember explizit anzugeben. ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, about_PSModulePath. Dazu, dass $env:PSModulePath die Liste der Modulsuchordner ist, dass sich die Standardpfade für CurrentUser/AllUsers zwischen PowerShell 7 und Windows PowerShell 5.1 unterscheiden, sowie dazu, dass sich der Ort von Dokumente durch OneDrive oder Ordnerumleitung ändern kann. ↩ ↩2 ↩3 ↩4 ↩5
-
Microsoft Learn, about_Modules. Dazu, dass Module unter PSModulePath beim ersten Aufruf eines ihrer Befehle automatisch importiert werden (automatisches Laden von Modulen), zur manuellen Installationsmethode durch Kopieren des gesamten Modulordners, sowie zu den Standardorten für Module. ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, New-ModuleManifest. Dazu, dass ein Modulmanifest (.psd1) eine Hashtabelle ist, die Inhalt, Attribute und Voraussetzungen eines Moduls beschreibt, und nicht zwingend erforderlich ist; dazu, dass der einzige erforderliche Schlüssel ModuleVersion ist; sowie dazu, dass New-ModuleManifest ein als Vorlage nutzbares Grundgerüst erzeugt. ↩ ↩2 ↩3 ↩4 ↩5
-
Microsoft Learn, about_Execution_Policies. Dazu, dass RemoteSigned lokal erstellte, unsignierte Skripte erlaubt, für aus dem Internet stammende Skripte jedoch eine Signatur verlangt, dass Skripte auf einem UNC-Pfad auf Systemen, die nicht zwischen UNC-Pfaden und Internetpfaden unterscheiden, unter RemoteSigned mitunter nicht ausgeführt werden dürfen, sowie zur Freigabe mit Unblock-File. ↩ ↩2
-
Microsoft Learn, about_Functions_Advanced_Methods. Zu den in erweiterten Funktionen verfügbaren Verarbeitungsmethoden begin/process/end sowie dazu, dass die Methode ShouldProcess aus dem process-Block heraus aufgerufen wird und die Deklaration über das CmdletBinding-Attribut voraussetzt. ↩ ↩2
-
Microsoft Learn, Approved Verbs for PowerShell Commands. Dazu, dass Befehlsnamen in der Form Verb-Substantiv erwartet werden, zur Liste der zulässigen Verben und wie man sie mit Get-Verb prüft, sowie dazu, dass beim Import eines Moduls mit nicht zugelassenen Verben eine Warnung angezeigt wird. ↩
-
Microsoft Learn, Export-ModuleMember. Dazu, dass Export-ModuleMember das Cmdlet ist, das festlegt, welche Member ein Skriptmodul exportiert, dass ohne Angabe Funktionen und Aliase, nicht aber Variablen exportiert werden, sowie dazu, dass es optional, aber Best Practice ist, um die Absicht des Autors zu verdeutlichen. ↩ ↩2
Verwandte Artikel
Aktuelle Artikel mit denselben Schlagwörtern führen zu verwandten Themen weiter.
COM und .NET aus PowerShell aufrufen — Was Ihre Skripte erreichen können, erweitern
Ein praktischer Leitfaden zum Aufrufen von .NET-Klassen aus PowerShell, zum Einbetten von C#- und Win32-APIs mit Add-Type, zur Steuerung ...
Die Unterschiede zwischen Windows PowerShell 5.1 und PowerShell 7 ── Ein praktischer Leitfaden zur Migration interner Skripte
Das Verhältnis zwischen Windows PowerShell 5.1 und PowerShell 7 (Nebeneinander-Koexistenz und pwsh.exe), Microsofts offizielle Haltung, d...
PC-Kitting mit winget + PowerShell automatisieren — Aus dem Handbuch ein ausführbares Skript machen
Wie Sie die Einrichtung von PCs für neue Mitarbeitende reproduzierbar gestalten. Behandelt die Installation von Anwendungen mit winget so...
Wo Sie nachsehen sollten, wenn ein PowerShell-Skript langsam ist — Arrays, Pipelines und Abgleich
Die klassischen Ursachen langsamer PowerShell-Skripte im Überblick. Warum += bei einem Array O(n²) ist, der Unterschied zwischen Pipeline...
Schluss mit Write-Host — PowerShells Ausgabeströme und Log-Design
Wie Sie zwischen den sechs Ausgabeströmen von PowerShell wählen, welche Probleme Write-Host mit sich bringt und wo es tatsächlich hingehö...
Verwandte Themen
Diese Seiten ordnen den Artikel in einen größeren Leistungs- und Entscheidungskontext ein.
Technische Windows-Themen
Portal zu Windows-Entwicklung, Fehleranalyse und der Nutzung bestehender Assets.
Leistungen zu diesem Thema
Dieser Artikel ist direkt mit den folgenden Leistungen verbunden.
Windows-App-Entwicklung
Geschäftsanwendungen, Geräteintegration und Kommunikationstools von den Anforderungen bis zur Umsetzung.
Häufige Fragen
Fragen, die in Beratungen zu diesem Artikelthema häufig gestellt werden.
- Was ändert sich, wenn ich einem PowerShell-param-Block [CmdletBinding()] hinzufüge?
- Die Funktion oder das Skript wird als erweiterte Funktion (advanced function) behandelt und erhält dasselbe Verhalten wie ein kompiliertes Cmdlet. Konkret werden gemeinsame Parameter wie -Verbose und -ErrorAction automatisch hinzugefügt, die Variable $PSCmdlet wird verfügbar, und die Übergabe eines nicht definierten Parameters oder eines überzähligen Positionsarguments führt zu einem Bindungsfehler. Da ein vertippt übergebenes Argument nicht mehr stillschweigend ignoriert wird, gehört das Hinzufügen zur Grundausstattung von Betriebsskripten.
- Sollte ich Argumentprüfungen mit Validate-Attributen oder mit if-Anweisungen schreiben?
- Übliche Praxis ist es, Formatprüfungen von Parametern in Validierungsattribute wie ValidateSet, ValidateRange und ValidateScript zu verlagern. Die Validierung erfolgt, bevor der Funktionsrumpf läuft, sodass ein ungültiger Wert einen Fehler auslöst, ohne dass auch nur eine Zeile ausgeführt wird – das verhindert den Unfall, mitten in der Verarbeitung abzubrechen. ValidateSet bringt zudem den praktischen Vorteil der Tab-Vervollständigung. Prüfungen, die mehrere Parameter gemeinsam betreffen oder vom Laufzeitzustand abhängen, gehören in if-Anweisungen im Funktionsrumpf.
- Wo sollte ich ein eigenes PowerShell-Modul (.psm1) ablegen?
- Legen Sie unterhalb eines der in $env:PSModulePath aufgeführten Ordner einen Ordner mit demselben Namen wie das Modul an. Nur für den eigenen Gebrauch ist der Standardpfad unter PowerShell 7 $HOME\Documents\PowerShell\Modules, für alle Benutzer $env:ProgramFiles\PowerShell\Modules. Beachten Sie, dass sich die Pfade unter Windows PowerShell 5.1 unterscheiden und stattdessen WindowsPowerShell\Modules verwenden. Legen Sie das Modul an einem dieser Orte ab, wird es beim ersten Aufruf eines seiner Befehle automatisch geladen, ohne dass Sie Import-Module schreiben müssen.
- Muss ich immer ein Modulmanifest (psd1) erstellen?
- Nein. Ein eigenständiges .psm1 ohne Manifest funktioniert einwandfrei als Modul. Ein Manifest wird erst in der Verteilungsphase nötig, wenn Sie Metadaten wie eine Versionsnummer, die erforderliche PowerShell-Version, abhängige Module und eine explizite Liste exportierter Befehle hinterlegen möchten; Sie erzeugen es mit New-ModuleManifest. Der einzige zwingende Schlüssel eines Manifests ist ModuleVersion, sodass es völlig legitim ist, minimal zu beginnen und bei Bedarf zu erweitern.
- Warum wird ein auf einem internen Freigabeordner abgelegtes Skript von der Ausführungsrichtlinie blockiert?
- Die Standardrichtlinie RemoteSigned erlaubt die Ausführung lokal erstellter Skripte auch unsigniert, verlangt jedoch eine Signatur für Skripte, die als aus dem Internet stammend markiert sind. Die offizielle Dokumentation hält ausdrücklich fest, dass auf Systemen, die nicht zwischen UNC-Pfaden und Internetpfaden unterscheiden, Skripte auf einem Freigabeordner unter RemoteSigned an der Ausführung gehindert werden können. Wenn Sie die interne Verteilung ausbauen wollen, prüfen Sie die Kombination aus Codesignierung mit AllSigned oder die Konfiguration der Intranetzone.
Autorenprofil
Profilseite des Artikelautors.
Go Komura
Geschäftsführer von KomuraSoft LLC
Spezialisiert auf Windows-Softwareentwicklung, technische Beratung und Fehleranalyse, insbesondere bei bestehenden Systemen und schwer reproduzierbaren Störungen.