Best Practices
Die folgenden einzelnen Artikel stammen aus dem Blog https://www.netz-weise-it.training/weisheiten. html und werden dort regelmässig erweitert.
Die folgenden einzelnen Artikel stammen aus dem Blog https://www.netz-weise-it.training/weisheiten. html und werden dort regelmässig erweitert.
Powershell kennt zwei Kategorien von Fehlern: terminierende und behandelte Fehler. Ein terminierender Fehler tritt unvermittelt auf. Powershell kann den Fehler nicht abfangen, wirft einen Fehler und beendet das Programm oder Skript, in dem der Fehler aufgetreten ist. Das muss nicht zwingend heißen, dass gleich Ihr ganzes Skript abbricht, aber zumindest der Befehl, der den Fehler verursacht hat, wird abgebrochen.
Ein behandelter Fehler ist ein Fehler, den der Programmierer vorhergesehen und für den er einen spezifischen Fehlerbehandlungscode geschrieben hat. Das Cmdlet kann beim Auftreten eines behandelten Fehlers eine Fehlermeldung anzeigen (was aber nicht passieren muss, prinzipiell kann ein behandelter Fehler auch völlig unbemerkt bleiben), läuft dann aber einfach weiter. Eine Zugriffsverweigerung von Get-Childitem ist z.B. ein behandelter Fehler.
Im Beispiel werden zwei Ordner abgerufen, wobei der Zugriff auf C:\Windows\CSC\ einen Fehler wirft. Trotzdem wird das zweite Verzeichnis angezeigt. Wäre der Fehler unbehandelt gewesen, hätte Get-Childitem direkt nach dem Fehler abgebrochen und der zweite Ordner wäre nicht angezeigt worden. Das kann man simulieren, denn man kann Powershell über den Parameter -Erroraction (oder kurz -EA) zwingen, alle Fehler wie unbehandelte Fehler zu verarbeiten.
In diesem Beispiel bricht die Verarbeitung nach dem Fehler sofort ab. Über -ErrorAction SilentlyContinue kann man die Fehlerausgabe auch unterdrücken. Der Parameter -Erroraction überschreibt für die Ausführung des jeweiligen Kommandos einfach die Einstellung der Standardvariable $Erroractionpreference, die in der Konsole festgelegt ist. Alle Einstellungen für $Erroraction finden Sie in der Tabelle ActionPreference-Einstellungen.
Um Fehler in Powershell von vornherein zu vermeiden, sollten einige Dinge beherzigt werden.
Beim Starten des Powershell-Prozesses ist Autodiscovery dafür verantwortlich zu ermitteln, welche Funktionen, Variablen und Aliase in einem Skript vorliegen und diese in Powershell sichtbar zu machen, ohne das Modul-Skript sofort auszuführen. Es gibt allerdings einige Unterschiede im Verhalten zwi-schen Modulen mit und ohne Modulmanifest, die ziemlich viel Verwirrung stiften können.
Autodiscovery funktioniert automatisch und macht, wie wir bereits gesehen haben, alle Funktionen aus dem Skriptmodul sichtbar. Wenn Sie in Ihrem Skriptmodul aber Variablen oder Aliase anlegen, die glo-bal in der Powershell genutzt werden sollen, werden Sie feststellen, dass Aliase erst in der Konsole sichtbar sind und benutzt werden können, nachdem Sie eine Funktion aus Ihrem Skript aufgerufen ha-ben. Variablen sind selbst nach dem Aufrufen des Skriptes nicht sichtbar! Zur Demonstration speichern Sie dazu folgendes Skript in Ihrem persönlichen Profilordner als DemoModule.psm1 und starten Sie einen neuen Powershell-Prozess.
Wenn die Funktion Test-Modulemember nicht aufgerufen werden kann, und Dateiname, Modulordernname und Modulpfad korrekt sind, prüfen Sie Ihre Ausführungsrichtlinie. Wenn die Ausführung von Powershell-Skripten verboten ist, können auch keine Skriptmodule ge-laden werden.
Function Test-ModuleMember { param(
[Parameter()]
$UserName = "Holger"
)
"$Username ist da!"
}
New-Alias -Name TMM -Value Test-ModuleMember
$DefaultPassword = "Passw0rd"
Wenn Sie versuchen, Test-ModuleMember über das Alias TMM zu starten, werden Sie feststellen, dass das Alias nicht vorhanden ist, genauso wenig wie die Variable $DefaultPassword. TestModuleMember lässt sich dagegen ausführen. Sobald Test-Modulemember einmal gestartet wurde, ist plötzlich auch das Alias verfügbar, aber die Variable bleibt weiterhin verschwunden.
Dieses Verhalten hängt damit zusammen, dass Autodiscovery nur Elemente aus Ihrem Modul an Pow-ershell weitergibt, die explizit exportiert werden, wobei Funktionen aus einem Skriptmodul standardmä-ßig exportiert werden. Aliase werden nach dem Aufrufen der ersten Funktion verfügbar, weil Powershell dann das komplette Skriptmodul zum ersten Mal wirklich ausführt und alle Aliase global erstellt. Varia-
blen werden explizit nicht sichtbar, weil Variablen in einem Skriptmodul weiterhin als lokale Variablen ausgeführt werden.
Wenn Sie Aliase oder Variablen exportieren lassen wollen, müssen Sie das explizit angeben, indem Sie in Ihrem Skriptmodul den Befehl Export-ModuleMember aufrufen.
<# Hier steht der Skriptcode #> Export-ModuleMember -Function * Export-ModuleMember -Alias * Export-Modulemember -Variable *
Ohne Export-Modulemember sind Elemente durch Autodiscovery nicht ermittelbar. Die Ausnahme sind Funktionen, die Powershell automatisch exportiert, solange Export-ModuleMember im Skript nicht auf-gerufen wird. Daher sind die Funktionen, die im Skriptmodul Helperfunctions.psm1 angelegt sind, trotzdem sichtbar gewesen. Aliase werden erst dann sichtbar, wenn sie explizit exportiert werden.
Function Write-ReturnArrayFromScriptblock
{
# hier steht der Funktionscode
}
Function Test-ParameterValueFromPipeline
{
# hier steht der Funktionscode
}
$ExecutionTime = Get-Date
New-Alias -Name WRA -Value Write-ReturnArrayFromScriptblock New-Alias -Name PVF -Value Test-ParameterValueFromPipeline
Export-ModuleMember -Function * Export-ModuleMember -Alias * Export-ModuleMember -Variable *
Sie können auch Variablen exportieren, allerdings verhalten sich diese noch einmal anders. Exportierte Variablen werden nämlich durch den Autodiscovery nicht sichtbar, sondern erst bei der ersten Ausfüh-rung des Skriptmoduls als globale Variable angelegt. Das heißt, dass exportierte Variablen trotz des Ex-ports erst nach dem ersten Ausführen Ihres Skriptmoduls erzeugt werden. Das ist leider alles andere als konsistent.
Achten Sie darauf, dass Sie, sobald Sie Export-ModuleMember verwenden, auch Ihre Funktionen ma-nuell exportieren müssen, da der automatische Funktionsexport nur solange funktioniert, wie Sie Ex-port-ModuleMember nicht verwenden.
Wenn Sie eine Manifest-Datei verwenden, können Sie in der Manifest-Datei ebenfalls angeben, welche Elemente exportiert werden sollen. Hier noch einmal der Ausschnitt aus der Manifest-Datei:
# Aus diesem Modul zu exportierende Funktionen. FunctionsToExport = '*'
# Aus diesem Modul zu exportierende Cmdlets. CmdletsToExport = '*'
# Die aus diesem Modul zu exportierenden Variablen VariablesToExport = '*'
# Aus diesem Modul zu exportierende Aliase. AliasesToExport = '*'
Die Manifest-Datei kann aber nur Elemente exportieren, die über Export-Modulemember bereits sichtbar sind. Die Cmdlets, Funktionen, Variablen und Aliase müssen im Skriptmodul exportiert sein, damit sie über das Manifest gesteuert werden können!
Das heißt, dass alle nach außen sichtbaren Funktionen, Aliase und Variablen im Skriptmodul und, sobald Sie eine Manifest-Datei benutzen, auch in dieser explizit zum Export angegeben werden müssen. Ver-gessen Sie Export-Modulemember im Skript anzugeben, können Sie Aliase und Variablen im Manifest nicht mehr exportieren! Aus Performance-Gründen sollten Sie außerdem alle Funktionen und Aliase
namentlich exportieren, selbst wenn Sie alle Funktionen Ihres Skriptes exportieren wollen. Das be-schleunigt den Autodiscovery-Prozess deutlich!
Helperfunctions.psm1 könnte also z.B. so aussehen:
Function Write-ReturnArrayFromScriptblock
{
# hier steht der Funktionscode
}
Function Test-ParameterValueFromPipeline
{
# hier steht der Funktionscode
}
$ExecutionTime = Get-Date
New-Alias -Name WRA -Value Write-ReturnArrayFromScriptblock New-Alias -Name PVF -Value Test-ParameterValueFromPipeline
Export-ModuleMember -Function Write-ReturnArrayFromScriptBlock,` Test-ParameterValueFromPipeline
Export-ModuleMember -Alias WRA,PVF
Export-ModuleMember -Variable ExecutionTime
Wie Sie hier sehen, können Sie auch einzelne Funktionen, Aliase oder Variablen namentlich angeben. Sie können sogar Wildcards in Namen für den Import verwenden. Dadurch wird es möglich, z.B. eine ganze Kategorie von Daten zu exportieren:
Export-ModuleMember -Function Get-*
Wie man in der Ausgabe von Get-Command -Module Helperfunctions sehen kann, gibt Powershell zu jedem Cmdlet eine Versionsnummer aus.
Um Meta-Informationen wie die Version oder den Autoren eines Modules mitzugeben, brauchen Sie eine Manifest-Datei. Es handelt sich um eine Datei mit der Endung .psd1, die aus einer großen Hash-Table besteht, in der alle relevanten Informationen über ein Modul gespeichert werden können.
Um eine Manifest-Datei zu erstellen, können Sie das Cmdlet New-Modulemanifest verwenden.
$Module = Get-Module -Name Helperfunctions
$Psd1 = ( Join-Path -Path $Module.ModuleBase -ChildPath $Module.Name ) + '.psd1' New-ModuleManifest -Path $Psd1 -Author 'Holger' FunctionsToExport * `
ModuleVersion '1.0' RootModule $Module.Path
In der ersten Zeile des Beispiels werden die Informationen über das aktuell installierte Modul abgefragt. Das ist nicht notwendig, erspart es uns aber, die Pfade für die Dateien manuell herauszusuchen und ein-zugeben.
In der zweiten Zeile wird aus dem Ordner ($Module.Modulebase) und dem Namen des Moduls ($Mo-dule.Name) ein neuer Pfad erstellt. Hierfür wird das Kommando Join-Path verwendet, das einfach zwei Strings zu einem gültigen Pfad zusammenfügt. Anschließend wird die Dateiendung .psd1 angehängt.
In der dritten Zeile wird mit dienste New-Modulemanifest eine Manifestdatei generiert. Für alle Meta-daten in der Datei hat New-Modulemanifest einen Parameter. Im Beispiel wird der Pfad der Manifestda-tei, der Autor, die zu exportierenden Funktionen (s. weiter unten), die Version des Moduls und das Root-modul angegeben. Das Rootmodul ist besonders wichtig, denn wenn in einem Modulordner eine <Mo-dulname>.psd1-Datei liegt, wird die .psm1-Datei nicht mehr standardmäßig geladen. Stattdessen gibt die Eigenschaft Rootmodul das Skript vor, das gestartet werden soll.
Die Helperfunctions.psd1 sollte nun ungefähr so aussehen:
@{
# Die diesem Manifest zugeordnete Skript- oder Binärmoduldatei. RootModule = '.\Helperfunctions.psm1'
# Die Versionsnummer dieses Moduls ModuleVersion = '1.0'
# Unterstützte PSEditions
# CompatiblePSEditions = @()
# ID zur eindeutigen Kennzeichnung dieses Moduls GUID = 'bcc731c2-0db3-4e8a-b452-3b5300cf4fd3'
# Autor dieses Moduls Author = 'Me'
# Die für dieses Modul mindestens erforderliche Version des Windows PowerShell-Mo-duls
# PowerShellVersion = "
# Die Module, die vor dem Importieren dieses Moduls in die globale Umgebung geladen werden müssen
# RequiredModules = @()
# Die Skriptdateien (PS1-Dateien), die vor dem Importieren dieses Moduls in der Um-gebung des Aufrufers ausgeführt werden.
# ScriptsToProcess = @()
# Aus diesem Modul zu exportierende Funktionen. FunctionsToExport = '*'
# Aus diesem Modul zu exportierende Cmdlets. CmdletsToExport = '*'
# Die aus diesem Modul zu exportierenden Variablen VariablesToExport = '*'
# Aus diesem Modul zu exportierende Aliase. AliasesToExport = '*'
}
Die Datei ist nur in Auszügen abgebildet, aber man kann erkennen, dass es sich hier um eine Hash-Table handelt. Nicht konfigurierte Parameter sind auskommentiert. Die mit New-Modulemanifest angegebe-nen Parameter sind bereits eingetragen, genauso wie Benutzerinformationen, die Powershell direkt aus dem Host auslesen konnte, und eine eindeutige Modulkennung in Form einer GUID. Wenn Sie eine Manifestdatei von einem bestehenden Modul kopieren, anstatt sie neu anzulegen, müssen Sie auf jeden Fall eine neue GUID erstellen. Das können Sie ab Powershell 5 mit dem Cmdlet New-Guid erledigen, und in älteren Versionen einer .NET-Methode:
New-Guid [guid]::NewGuid()