PowerShell ValidateSet: Tab-Completion & Parameter Values

Wenn Sie ein PowerShell-Skript oder eine Funktion schreiben, möchten Sie oft Benutzereingaben über Parameter akzeptieren. Wenn Sie die Werte, die diese Parameter akzeptieren, nicht einschränken, können Sie nicht garantieren, dass es Situationen gibt, in denen unangemessene Werte bereitgestellt werden. In diesem Artikel erfahren Sie, wie Sie das PowerShell-Parametervalidierungsattribut ValidateSet verwenden, um diese Werte auf nur die von Ihnen definierten zu beschränken.

Beim Schreiben eines PowerShell-Skripts oder einer Funktion können Sie viele verschiedene Validierungsattribute verwenden, um zu überprüfen, ob die Werte, die Ihren Parametern übergeben werden, akzeptabel sind, und den Benutzer benachrichtigen, wenn dies nicht der Fall ist.

Dieser Artikel konzentriert sich auf das Validierungsattribut ValidateSet. Sie erfahren, was ValidateSet bewirkt, warum Sie ValidateSet in Ihrem Code verwenden möchten und wie Sie dies tun können. Sie erfahren auch mehr über die Tabulatorvervollständigungsfunktion, die durch ValidateSet aktiviert wird und Benutzern Ihres Codes dabei hilft, gültige Parameterwerte bereitzustellen.

PowerShell ValidateSet: Eine kurze Übersicht

ValidateSet ist ein Parameterattribut, mit dem Sie eine Reihe von Elementen definieren können, die nur als Wert für diesen Parameter akzeptiert werden.

Zum Beispiel haben Sie möglicherweise ein Skript, das mit Active Directory-Domänencontrollern funktioniert. Dieses Skript verfügt über einen Parameter, der den Namen eines Domänencontrollers angibt. Wäre es nicht sinnvoll, die Liste der akzeptablen Werte auf die tatsächlichen Namen der Domänencontroller zu beschränken? Es gibt keinen Grund, warum der Benutzer „foobar“ als Wert verwenden sollte, wenn Sie im Voraus wissen, welche Werte das Skript benötigt. ValidateSet gibt Ihnen diese Möglichkeit.

Anforderungen

Dieser Artikel wird ein Lernleitfaden sein. Wenn Sie mitmachen möchten, benötigen Sie Folgendes:

  • Visual Studio Code oder einen anderen Code-Editor. Ich werde Visual Studio Code verwenden.
  • Mindestens PowerShell 5.1 für den Großteil des Codes in diesem Artikel. Es gibt einen Abschnitt, der PowerShell 6.1 oder neuer erfordert, und ich werde das kennzeichnen, wenn wir dazu kommen.

Der gesamte Code in diesem Artikel wurde in den folgenden Umgebungen getestet:

Operating System PowerShell Versions
Windows 7 SP1 5.1, Core 6.2
Windows 10 1903 5.1, Core 6.2
Linux Mint 19.2 Core 6.2

Um die Konzepte rund um ValidateSet zu erklären, werden Sie ein kleines Skript namens Get-PlanetSize.ps1 erstellen. Dieses Skript liefert Informationen über die Größen der Planeten in unserem Sonnensystem.

Sie beginnen mit einem einfachen Skript und verbessern nach und nach seine Fähigkeit, Eingaben vom Benutzer zu verarbeiten, und erleichtern ihnen die Entdeckung möglicher Parameterwerte.

Erste Schritte

Um loszulegen, kopieren Sie den untenstehenden PowerShell-Code in Ihren bevorzugten Texteditor und speichern Sie ihn als Get-PlanetSize.ps1.

$planets = [ordered]@{
     'Mercury' = 4879
     'Venus'   = 12104
     'Earth'   = 12756
     'Mars'    = 6805
     'Jupiter' = 142984
     'Saturn'  = 120536
     'Uranus'  = 51118
     'Neptune' = 49528
     'Pluto'   = 2306
 }
 $planets.keys | Foreach-Object {
     $output = "The diameter of planet {0} is {1} km" -f $_, $planets[$_]
     Write-Output $output
 }

Führen Sie das Skript von einer PowerShell-Eingabeaufforderung aus und Sie sollten Folgendes erhalten:

PS51> .\Get-PlanetSize.ps1
 The diameter of planet Mercury is 4879 km
 The diameter of planet Venus is 12104 km
 The diameter of planet Earth is 12756 km
 The diameter of planet Mars is 6805 km
 The diameter of planet Jupiter is 142984 km
 The diameter of planet Saturn is 120536 km
 The diameter of planet Uranus is 51118 km
 The diameter of planet Neptune is 49528 km
 The diameter of planet Pluto is 2306 km

Informativ, aber nicht sehr flexibel; die Informationen für jeden Planeten werden zurückgegeben, auch wenn Sie nur die Informationen für den Mars möchten.

Vielleicht möchten Sie die Möglichkeit haben, einen einzelnen Planeten anzugeben, anstatt alle zurückzugeben. Dies können Sie durch Einführung eines Parameters erreichen. Schauen wir uns an, wie das funktioniert.

Eingabe mit Parameter akzeptieren

Um das Skript die Annahme eines Parameters zu ermöglichen, fügen Sie dem Anfang des Skripts einen Param()-Block hinzu. Nennen Sie den Parameter Planet. Ein geeigneter Param()-Block sieht wie folgt aus.

Im folgenden Beispiel stellt die Zeile [Parameter(Mandatory)] sicher, dass immer ein Planetenname an das Skript übergeben wird. Wenn er fehlt, wird das Skript danach fragen.

Param(
     [Parameter(Mandatory)]
     $Planet
 )

Der einfachste Weg, um diesen Planet-Parameter in das Skript einzubeziehen, besteht darin, die Zeile $planets.keys | Foreach-Object { in $Planet | Foreach-Object { zu ändern. Nun sind Sie nicht mehr auf die zuvor statisch definierte Hashtable angewiesen, sondern lesen den Wert des Planet-Parameters.

Wenn Sie das Skript ausführen und einen Planeten mit dem Planet-Parameter angeben, sehen Sie nur Informationen über diesen bestimmten Planeten.

PS51> .\Get-PlanetSize.ps1 -Planet Mars
 The diameter of planet Mars is 6805 km

Hervorragend. Skript abgeschlossen? Vielleicht doch nicht.

Die Optionen sind zu offen

Was passiert, wenn Sie versuchen, den Durchmesser des Planeten Barsoom mit dem Skript Get-PlanetSize.ps1 zu finden?

PS51> .\Get-PlanetSize.ps1 -Planet Barsoom
The diameter of planet Barsoom is  km

Hmm, das ist nicht richtig. Barsoom steht nicht auf der Liste der Planeten, aber das Skript wird trotzdem ausgeführt. Wie können wir das beheben?

Das Problem hierbei ist, dass das Skript jede Eingabe akzeptiert und verwendet, unabhängig davon, ob es ein gültiger Wert ist oder nicht. Das Skript benötigt eine Möglichkeit, die akzeptierten Werte für den Planet-Parameter einzuschränken. Geben Sie ValidateSet ein!

Stellen Sie sicher, dass nur bestimmte Werte verwendet werden

A ValidateSet list is a comma-separated list of string values, wrapped in single or double-quotes. Adding a ValidateSet attribute to a script or function parameter consists of adding a line of text to the Param() block, as shown below. Replace the Param() block in your copy of Get-PlanetSize.ps1 with the one below and save the file.

Param(
     [Parameter(Mandatory)]
     [ValidateSet("Mercury","Venus","Earth","Mars","Jupiter","Saturn","Uranus","Neptune","Pluto")]
     $Planet
 )

Versuchen Sie, das Skript erneut mit Barsoom als Planet-Parameter auszuführen. Jetzt wird eine hilfreiche Fehlermeldung zurückgegeben. Die Meldung gibt an, was schief gelaufen ist, und liefert sogar eine Liste möglicher Werte für den Parameter.

PS51> .\Get-PlanetSize.ps1 -Planet Barsoom
 Get-PlanetSize.ps1 : Cannot validate argument on parameter 'Planet'. The argument "Barsoom" does not belong to the set
 "Mercury,Venus,Earth,Mars,Jupiter,Saturn,Uranus,Neptune,Pluto" specified by the ValidateSet attribute. Supply an argument that is in the set and then try the command again.
 At line:1 char:32
 .\Get-PlanetSize.ps1 -Planet Barsoom
 ~~~
 CategoryInfo          : InvalidData: (:) [Get-PlanetSize.ps1], ParameterBindingValidationException
 FullyQualifiedErrorId : ParameterArgumentValidationError,Get-PlanetSize.ps1 

Machen Sie ValidateSet in PowerShell Groß- und Kleinschreibung beachtend

Standardmäßig ist das ValidateSet-Attribut in Bezug auf die Groß- und Kleinschreibung nicht beachtend. Das bedeutet, dass es jeden String akzeptiert, solange er in der erlaubten Liste steht, unabhängig von der Groß- und Kleinschreibung. Zum Beispiel würde das obige Beispiel sowohl Mars als auch mars akzeptieren. Wenn erforderlich, können Sie ValidateSet dazu zwingen, die Groß- und Kleinschreibung zu beachten, indem Sie die Option IgnoreCase verwenden.

Die IgnoreCase-Option in ValidateSet, einer Validierungsattribut, bestimmt, ob die an den Parameter übergebenen Werte genau mit der Liste der gültigen Werte übereinstimmen. Standardmäßig ist IgnoreCase auf $True (Groß- und Kleinschreibung ignorieren) eingestellt. Wenn Sie dies auf $False setzen, würde die Angabe von mars als Wert für den Planet-Parameter für Get-PlanetSize.ps1 eine Fehlermeldung generieren.

Sie würden die IgnoreCase-Option verwenden, indem Sie ihr einen Wert von $true am Ende der Liste der gültigen Werte zuweisen, wie unten gezeigt.

[ValidateSet("Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune", "Pluto", IgnoreCase = $false)]  

Jetzt, wenn Sie versuchen, einen Wert für Planet zu verwenden, der nicht genau wie ein Wert in der Liste ist, schlägt die Validierung fehl.

Verwendung der Tab-Vervollständigung

Ein weiterer Vorteil der Verwendung von ValidateSet besteht darin, dass Sie die Tab-Vervollständigung verwenden können. Dies bedeutet, dass Sie mit der TAB-Taste durch die möglichen Werte für einen Parameter navigieren können. Dies verbessert die Benutzerfreundlichkeit eines Skripts oder einer Funktion erheblich, insbesondere von der Konsole aus.

In den folgenden Beispielen gibt es ein paar Dinge zu beachten:

  • Die Tab-Vervollständigung kehrt nach Anzeige des letzten Wertes zum ersten Wert zurück.
  • Die Werte werden alphabetisch sortiert angezeigt, obwohl sie nicht alphabetisch in der ValidateSet aufgeführt sind.
  • Durch Eingabe eines Anfangsbuchstabens und Drücken der TAB-Taste werden die Werte, die von der Tab-Vervollständigung angeboten werden, auf diejenigen eingeschränkt, die mit diesem Buchstaben beginnen.
Cycling through parameter values using Tab Completion
Restricting values returned by Tab Completion

Sie können auch die Tab-Vervollständigung von ValidateSet in der PowerShell Integrated Scripting Environment (ISE) nutzen, wie im folgenden Beispiel gezeigt. Die ISE Intellisense-Funktion zeigt Ihnen die Liste der möglichen Werte in einer schönen Auswahlbox an.

Intellisense gibt alle Werte zurück, die den eingegebenen Buchstaben enthalten, nicht nur diejenigen, die damit beginnen.

Tab Completion and Intellisense in ISE

Jetzt, da wir die ValidateSet-Validierungsattribute in Windows 5.1 behandelt haben, werfen wir einen Blick darauf, was in PowerShell Core 6.1 hinzugefügt wurde und sehen, ob dies unserem Skript weitere Validierungsmöglichkeiten bietet.

Verständnis der Änderungen an ValidateSet in PowerShell 6.1

Mit der Einführung von PowerShell Core 6.1 wurden zwei neue Funktionen zu den Validierungsattributen ValidateSet hinzugefügt:

  • Die Eigenschaft „ErrorMessage“
  • Verwendung von Klassen in ValidateSet über den Zugriff auf „System.Management.Automation.IValidateSetValuesGenerator“

Die ErrorMessage-Eigenschaft

Die standardmäßige Fehlermeldung, die angezeigt wird, wenn Sie einen falschen Planetennamen an „Get-PlanetSize.ps1“ übergeben, ist hilfreich, aber etwas umständlich:

PS61> .\Get-PlanetSize.ps1 -Planet Barsoom
 Get-PlanetSize.ps1 : Cannot validate argument on parameter 'Planet'. The argument "Barsoom" does not belong to the set
 "Mercury,Venus,Earth,Mars,Jupiter,Saturn,Uranus,Neptune,Pluto" specified by the ValidateSet attribute. Supply an argument that is in the set and then try the command again.
 At line:1 char:32
 .\Get-PlanetSize.ps1 -Planet Barsoom
 ~~~
 CategoryInfo          : InvalidData: (:) [Get-PlanetSize.ps1], ParameterBindingValidationException
 FullyQualifiedErrorId : ParameterArgumentValidationError,Get-PlanetSize.ps1 

Verwenden Sie die „ErrorMessage“-Eigenschaft des Validierungsattributs „ValidateSet“, um eine andere Fehlermeldung festzulegen, wie im folgenden Beispiel gezeigt. „{0}“ wird automatisch durch den übergebenen Wert ersetzt und „{1}“ wird automatisch durch die Liste der erlaubten Werte ersetzt.

Param(
     [Parameter(Mandatory)]
     [ValidateSet("Mercury","Venus","Earth","Mars","Jupiter","Saturn","Uranus","Neptune","Pluto",ErrorMessage="Value '{0}' is invalid. Try one of: '{1}'")]
     $Planet
 )

Ersetzen Sie den „Param()“-Block in der Skriptdatei und speichern Sie sie. Versuchen Sie dann erneut, „Get-PlanetSize.ps1 -Planet Barsoom“ auszuführen. Beachten Sie, dass der Fehler jetzt weniger umständlich und aussagekräftiger ist.

PS61> .\Get-PlanetSize.ps1 -Planet Barsoom
 Get-PlanetSize.ps1 : Cannot validate argument on parameter 'Planet'. Value 'Barsoom' is invalid. Try one of: 'Mercury,Venus,Earth,Mars,Jupiter,Saturn,Uranus,Neptune,Pluto'
 At line:1 char:32
 .\Get-PlanetSize.ps1 -Planet Barsoom
 ~~~
 CategoryInfo          : InvalidData: (:) [Get-PlanetSize.ps1], ParameterBindingValidationException
 FullyQualifiedErrorId : ParameterArgumentValidationError,Get-PlanetSize.ps1 

Als nächstes werfen Sie einen Blick auf eine neue Möglichkeit, die akzeptablen Werte in ValidateSet über eine PowerShell Klasse zu definieren.

PowerShell-Klassen

Benutzerdefinierte Typen, in PowerShell als Klassen bekannt, sind seit Version 5 verfügbar. Mit der Einführung von PowerShell Core 6.1 gibt es eine neue Funktion, die die Verwendung einer Klasse zur Bereitstellung der Werte für ValidateSet ermöglicht.

Die Verwendung einer Klasse ermöglicht es Ihnen, die Hauptbeschränkung eines ValidateSet zu umgehen – es ist statisch. Das heißt, es ist als Teil der Funktion oder des Skripts eingebettet und kann nur durch Bearbeiten des Skripts selbst geändert werden.

Die neue Funktion, die mit ValidateSet funktioniert, ist die Möglichkeit, die Klasse System.Management.Automation.IValidateSetValuesGenerator zu verwenden. Wir können dies als Grundlage für unsere eigenen Klassen verwenden, indem wir Vererbung verwenden. Um mit ValidateSet zu arbeiten, muss die Klasse auf System.Management.Automation.IValidateSetValuesGenerator basieren und eine Methode namens GetValidValues implementieren.

Die Methode GetValues gibt die Liste der Werte zurück, die Sie akzeptieren möchten. Ein Param-Block mit der statischen Liste der Planeten, die durch eine [Planet]-Klasse ersetzt wird, würde wie folgt aussehen. Dieses Beispiel funktioniert noch nicht. Lesen Sie weiter, um herauszufinden, wie Sie dies implementieren können.

Param(
     [Parameter(Mandator)]
     [ValidateSet([Planet],ErrorMessage="Value '{0}' is invalid. Try one of: {1}")]
     $Planet
 )

Verwendung einer Klasse für eine ValidateSet-Werteliste: Ein echtes Beispiel

Um die Verwendung einer Klasse für eine ValidateSet-Werteliste zu demonstrieren, ersetzen Sie die statische Liste der Planeten, die zuvor verwendet wurde, durch eine aus einer CSV-Textdatei geladene Liste. Sie müssen nicht mehr eine statische Liste von Werten im Skript selbst pflegen!

Erstellen einer Datenquelle für die Klasse

Zunächst müssen Sie eine CSV-Datei erstellen, die jeden der gültigen Werte enthält. Kopieren Sie dazu diesen Datensatz in eine neue Textdatei und speichern Sie ihn als planets.csv im selben Ordner wie das Skript Get-PlanetSize.ps1.

Planet,Diameter
 "Mercury","4879"
 "Venus","12104"
 "Earth","12756"
 "Mars","6805"
 "Jupiter","142984"
 "Saturn","120536"
 "Uranus","51118"
 "Neptune","49528"
 "Pluto","2306"

Hinzufügen der Klasse zum Skript

Jede von ValidateSet verwendete Klasse muss bereits definiert sein, bevor ValidateSet versucht, sie zu verwenden. Das bedeutet, dass die Struktur von Get-PlanetSize.ps1 wie sie ist, nicht funktionieren wird.

Die Klasse [Planet] muss definiert sein, bevor sie verwendet werden kann, daher muss sie am Anfang des Skripts stehen. Eine geeignete Skelett-Klassendefinition sieht wie folgt aus:

class Planet : System.Management.Automation.IValidateSetValuesGenerator {
     [String[]] GetValidValues() {
 }
 }

In der Methode GetValidValues() der Klasse verwenden Sie das Import-CSV-Cmdlet, um die zuvor erstellte Textdatei planets.csv zu importieren. Die Datei wird in einer Variablen mit globalen Gültigkeitsbereich namens $planets importiert, um später im Skript darauf zugreifen zu können.

Verwenden Sie die Rückgabeanweisung, um die Liste der Planetennamen über GetValidValues() zurückzugeben. Nach diesen Änderungen sollte die Klasse jetzt wie folgt aussehen:

class Planet : System.Management.Automation.IValidateSetValuesGenerator {
     [String[]] GetValidValues() {
             $Global:planets = Import-CSV -Path planets.csv
             return ($Global:planets).Planet
     }
 }

Entfernen Sie als nächstes die Deklaration der $planets-Hashtabelle aus dem Skript, wie unten gezeigt. Die globale Variable $planets, die von der Klasse [Planet] befüllt wird, enthält die Planetendaten.

$planets = [ordered]@{
     'Mercury' = 4879
     'Venus'   = 12104
     'Earth'   = 12756
     'Mars'    = 6805
     'Jupiter' = 142984
     'Saturn'  = 120536
     'Uranus'  = 51118
     'Neptune' = 49528
     'Pluto'   = 2306
 }

Verpacken Sie den verbleibenden ursprünglichen Code nun in einer Funktion und nennen Sie sie Get-PlanetDiameter, um sie vom Namen des Skripts zu unterscheiden. Platzieren Sie den Param()-Block, der am Anfang des Skripts stand, innerhalb der Funktion. Ersetzen Sie die statische Liste der Planeten durch einen Verweis auf die Klasse [Planet], wie unten gezeigt.

[ValidateSet([Planet],ErrorMessage="Value '{0}' is invalid. Try one of: {1}")]`

Ersetzen Sie die Zeile $output = "Der Durchmesser des Planeten {0} beträgt {1} km" -f $_, $planets[$_] durch die folgenden beiden Zeilen. Diese ermöglichen es dem Skript, einen Planeten im Array der durch Import-CSV erstellten Objekte nachzuschlagen, anstatt der zuvor erstellten Hash-Tabelle, die Sie aus dem Skript entfernt haben:

$targetplanet = $planets | Where -Property Planet -match $_
 $output = "The diameter of planet {0} is {1} km" -f $targetplanet.Planet, $targetplanet.Diameter

Nach dieser Änderung sollte Ihr endgültiges Skript wie folgt aussehen:

class Planet : System.Management.Automation.IValidateSetValuesGenerator {
     [String[]] GetValidValues() {
         $Global:planets = Import-CSV -Path planets.csv
         return ($Global:planets).Planet
     }
 }
 Function Get-PlanetDiameter {
     Param(
         [Parameter(Mandatory)]
         [ValidateSet([Planet],ErrorMessage="Value '{0}' is invalid. Try one of: {1}")]
         $Planet
     )
     $Planet | Foreach-Object {
         $targetplanet = $planets | Where -Property Planet -match $_
         $output = "The diameter of planet {0} is {1} km" -f $targetplanet.Planet, $targetplanet.Diameter
         Write-Output $output
     }
 }

Beachten Sie, dass das Skript ab diesem Punkt nur in PowerShell 6.1 oder höher funktioniert

Wie verwenden Sie nun dieses Skript?

Ausführen des Skripts

Sie können die neue Version des Skripts nicht einfach ausführen. Der gesamte nützliche Code ist nun in einer Funktion verpackt. Stattdessen müssen Sie die Datei dot-sourcen, um auf die Funktion aus der PowerShell-Sitzung zugreifen zu können.

PS61> . .\Get-PlanetSize.ps1

Nach dem Dot-Sourcing haben Sie nun Zugriff auf die neue Funktion Get-PlanetDiameter in der PowerShell-Sitzung, mit Tab-Vervollständigung.

Was ist der Nutzen dieser Arbeit?“, höre ich Sie fragen. „Das Skript scheint auf die gleiche Weise zu funktionieren, aber es ist schwieriger, den Code zu verwenden!“

Versuchen Sie Folgendes:

  • Öffnen Sie die zuvor erstellte Datei planets.csv.
  • Fügen Sie eine neue Zeile mit einem neuen Namen und Durchmesser hinzu.
  • Speichern Sie die CSV-Datei.

In derselben Sitzung, in der Sie Ihr Skript ursprünglich dot sourcten, versuchen Sie, den Durchmesser des neuen Planeten mit Get-PlanetDiameter nachzuschlagen. Es funktioniert!

Die Verwendung einer Klasse auf diese Weise bietet uns mehrere Vorteile:

  • Die Liste der gültigen Werte ist nun vom Code selbst getrennt, aber Änderungen an den Werten in der Datei werden vom Skript erkannt.
  • Die Datei kann von jemandem gepflegt werden, der niemals auf das Skript zugreift.
  • A more complex script could look up information from a spreadsheet, database, Active Directory or a web API.

Wie Sie sehen können, sind die Möglichkeiten nahezu endlos, wenn Sie eine Klasse verwenden, um ValidateSet-Werte bereitzustellen.

Zusammenfassung

Wir haben viel erreicht, während wir Get-PlanetSize.ps1 erstellt haben, also fassen wir zusammen.

In diesem Artikel haben Sie gelernt:

  • Was ein ValidateSet-Validierungsattribut ist und warum Sie es verwenden möchten
  • Wie man ValidateSet zu einer PowerShell-Funktion oder einem Skript hinzufügt
  • Wie die Tab-Vervollständigung mit ValidateSet funktioniert
  • Wie man die Eigenschaft IgnoreCase verwendet, um festzulegen, ob Ihr ValidateSet die Groß-/Kleinschreibung beachtet
  • Wie man die Eigenschaft ErrorMessage mit Ihrem ValidateSet und PowerShell 6.1 verwendet
  • Wie man eine Klasse verwendet, um ein dynamisches ValidateSet mit PowerShell 6.1 zu erstellen

Worauf warten Sie noch? Fangen Sie noch heute an, ValidateSet zu verwenden!

Weiterführende Informationen

Source:
https://adamtheautomator.com/powershell-validateset/