PowerShell ValidateSet: Tab-Voltooiing & Parameterwaarden

Wanneer je een PowerShell-script of -functie schrijft, wil je vaak gebruikersinvoer accepteren via parameters. Als je de waarden die deze parameters accepteren niet beperkt, kun je niet garanderen dat er situaties zullen zijn waarin ongepaste waarden worden opgegeven. In dit artikel leer je hoe je de parametervalidatieattribuut ValidateSet van PowerShell kunt gebruiken om deze waarden te beperken tot alleen diegene die je definieert.

Bij het schrijven van een PowerShell-script of -functie kun je verschillende validatieattributen gebruiken om te controleren of de waarden die aan je parameters worden doorgegeven acceptabel zijn, en de gebruiker waarschuwen als dat niet het geval is.

Dit artikel richt zich op het validatieattribuut ValidateSet. Je leert wat ValidateSet doet, waarom je ValidateSet in je code zou willen gebruiken en hoe je dat kunt doen. Je leert ook over de tabcompletion-functie die wordt ingeschakeld door ValidateSet en die gebruikers van je code helpt om geldige parameterwaarden op te geven.

PowerShell ValidateSet: Een beknopt overzicht

ValidateSet is een parameterattribuut waarmee je een reeks elementen kunt definiëren die alleen worden geaccepteerd als waarde voor die parameter.

Stel bijvoorbeeld dat je een script hebt dat is gedefinieerd om te werken met Active Directory-domeincontrollers. Dit script heeft een parameter die de naam van een domeincontroller specificeert. Zou het niet logisch zijn om de lijst met acceptabele waarden te beperken tot de daadwerkelijke namen van de domeincontrollers? Er is geen reden waarom de gebruiker “foobar” als waarde zou moeten kunnen gebruiken, terwijl je van tevoren weet welke waarden het script nodig heeft. ValidateSet biedt je die mogelijkheid.

Vereisten

Dit artikel zal een leerproces zijn. Als je van plan bent om mee te doen, heb je het volgende nodig:

  • Visual Studio Code of een andere code-editor. Ik zal Visual Studio Code gebruiken.
  • Minstens PowerShell 5.1 voor het grootste deel van de code in dit artikel. Er is één sectie die vereist PowerShell 6.1 of later en ik zal dat identificeren wanneer we eraan toe zijn

Alle code in dit artikel is getest in de volgende omgevingen:

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

Om de concepten rond ValidateSet uit te leggen, ga je een klein script bouwen genaamd Get-PlanetSize.ps1. Dit script geeft informatie terug over de groottes van planeten in ons zonnestelsel.

Je begint met een eenvoudig script en verbetert geleidelijk zijn vermogen om input van de eindgebruiker te verwerken en het voor hen gemakkelijk te maken om de mogelijke parameterwaarden te ontdekken.

Aan de slag

Om te beginnen, kopieer en plak eerst de PowerShell-code hieronder in je favoriete teksteditor en sla het op 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
 }

Voer het script uit vanaf een PowerShell-prompt en je zou het volgende moeten krijgen:

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

Informatief maar niet erg flexibel; de informatie voor elke planeet wordt geretourneerd, zelfs als je alleen de informatie voor Mars wilt.

Misschien wil je de mogelijkheid hebben om een enkele planeet te specificeren in plaats van ze allemaal terug te krijgen. Dat zou je doen door een parameter te introduceren. Laten we eens kijken hoe we dat kunnen bereiken.

Input accepteren met behulp van een parameter

Om het script een parameter te laten accepteren, voeg je een Param()-blok toe bovenaan het script. Noem de parameter Planeet. Een geschikt Param()-blok ziet er als volgt uit.

In het onderstaande voorbeeld zorgt de regel [Parameter(Verplicht)] ervoor dat er altijd een planeetnaam aan het script wordt geleverd. Als deze ontbreekt, zal het script erom vragen.

Param(
     [Parameter(Mandatory)]
     $Planet
 )

De eenvoudigste manier om deze Planeet-parameter in het script op te nemen, is door de regel $planets.keys | Foreach-Object { te wijzigen in $Planeet | Foreach-Object {. Nu vertrouw je niet meer op de hashtable die eerder statisch is gedefinieerd, maar lees je in plaats daarvan de waarde van de Planeet-parameter.

Als je nu het script uitvoert en een planeet opgeeft met behulp van de Planeet-parameter, zie je alleen informatie over die specifieke planeet.

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

Uitstekend. Is het script compleet? Misschien niet helemaal.

De opties zijn te open.

Wat gebeurt er als je probeert de diameter van de planeet Barsoom te vinden met behulp van Get-PlanetSize.ps1?

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

Hmm, dat klopt niet. Barsoom staat niet op de lijst van planeten, maar het script wordt toch uitgevoerd. Hoe kunnen we dit oplossen?

Het probleem hier is dat het script elk invoer accepteert en gebruikt, ongeacht of het een geldige waarde is of niet. Het script heeft een manier nodig om te beperken welke waarden worden geaccepteerd voor de Planet-parameter. Voer ValidateSet in!

Ervoor zorgen dat alleen bepaalde waarden worden gebruikt

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
 )

Probeer het script opnieuw uit te voeren met Barsoom als uw Planet-parameter. Nu wordt er een nuttige foutmelding geretourneerd. Het bericht is specifiek in wat er mis is gegaan en geeft zelfs een lijst met mogelijke waarden voor de 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 

PowerShell laten Valideren Set Hoofdlettergevoelig

Standaard is het ValidateSet-attribuut niet hoofdlettergevoelig. Dit betekent dat het elke tekenreeks zal toestaan, mits deze in de toegestane lijst staat met een willekeurig hoofdlettergebruik. Bijvoorbeeld, het bovenstaande voorbeeld zal zowel Mars accepteren als mars. Indien nodig kunt u afdwingen dat ValidateSet hoofdlettergevoelig is door de optie IgnoreCase te gebruiken.

De IgnoreCase-optie in ValidateSet is een validatieattribuut dat bepaalt of de waarden die aan de parameter worden geleverd exact overeenkomen met de lijst met geldige waarden. Standaard staat IgnoreCase ingesteld op $True (hoofdletters negeren). Als u dit instelt op $False, zal het leveren van mars als waarde voor de Planet-parameter voor Get-PlanetSize.ps1 een foutmelding genereren.

U kunt de IgnoreCase-optie gebruiken door deze aan het einde van de lijst met geldige waarden een $true-waarde toe te wijzen zoals hieronder getoond.

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

Nu wanneer je probeert een waarde te gebruiken voor Planet die niet exact overeenkomt met een waarde in de lijst, zal validatie mislukken.

Het Gebruik van Tab Completering

Nog een voordeel van het gebruik van ValidateSet is dat het je tab completering geeft. Dit betekent dat je door de mogelijke waarden voor een parameter kunt bladeren met behulp van de TAB-toets. Dit verbetert aanzienlijk de bruikbaarheid van een script of functie, vooral vanuit de console.

In de onderstaande voorbeelden zijn een paar dingen om op te merken:

  • De tab completering gaat terug naar de eerste waarde nadat de laatste is weergegeven.
  • De waarden worden in alfabetische volgorde gepresenteerd, ook al zijn ze niet alfabetisch gesorteerd in de ValidateSet.
  • Het typen van een initiële letter en op TAB drukken beperkt de waarden die worden aangeboden door tab completering tot die die met die letter beginnen.
Cycling through parameter values using Tab Completion
Restricting values returned by Tab Completion

Je kunt ook profiteren van ValidateSet tab completering in de PowerShell Integrated Scripting Environment (ISE), zoals weergegeven in het onderstaande voorbeeld. De ISE Intellisense-functie toont je de lijst met mogelijke waarden in een mooie selectiebox.

Intellisense retourneert alle waarden die de letter bevatten die je typt, in plaats van alleen die die ermee beginnen.

Tab Completion and Intellisense in ISE

Nu we ValidateSet validatieattributen hebben behandeld zoals ze zijn in Windows 5.1, laten we eens kijken naar wat er is toegevoegd in PowerShell Core 6.1 en kijken of dat ons script meer validatiemogelijkheden kan geven.

Begrijpen van Veranderingen in ValidateSet in PowerShell 6.1

Met de komst van PowerShell Core 6.1 zijn er twee nieuwe mogelijkheden toegevoegd aan ValidateSet-validatieattributen:

  • De ErrorMessage-eigenschap
  • Gebruik van klassen in ValidateSet via toegang tot System.Management.Automation.IValidateSetValuesGenerator

De ErrorMessage-eigenschap

Het standaardfoutbericht dat wordt gegenereerd wanneer u een onjuiste planeetnaam opgeeft aan Get-PlanetSize.ps1 is nuttig, maar een beetje langdradig:

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 

Gebruik de ErrorMessage-eigenschap van het ValidateSet-validatieattribuut om een ander foutbericht in te stellen, zoals weergegeven in het onderstaande voorbeeld. {0} wordt automatisch vervangen door de ingediende waarde en {1} wordt automatisch vervangen door de lijst met toegestane waarden.

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

Vervang het Param()-blok in het scriptbestand en sla het op. Probeer vervolgens opnieuw Get-PlanetSize.ps1 -Planet Barsoom. Let op dat de foutmelding hieronder veel minder langdradig en meer beschrijvend is.

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 

Kijk vervolgens naar een nieuwe manier om de acceptabele waarden te definiëren in ValidateSet via een PowerShell klasse.

PowerShell-klassen

Aangepaste typen, in PowerShell bekend als klassen, zijn beschikbaar sinds versie 5. Met de komst van PowerShell Core 6.1 is er een nieuwe functie om het gebruik van een klasse toe te staan om de waarden voor ValidateSet te leveren.

Met behulp van een klasse kunt u omgaan met de belangrijkste beperking van een ValidateSet – het is statisch. Dat wil zeggen, het is ingebed als onderdeel van de functie of script en kan alleen worden gewijzigd door het script zelf te bewerken.

De nieuwe functie die werkt met ValidateSet is de mogelijkheid om de System.Management.Automation.IValidateSetValuesGenerator-klasse te gebruiken. We kunnen dit als basis gebruiken voor onze eigen klassen met behulp van overerving. Om met ValidateSet te werken, moet de klasse zijn gebaseerd op System.Management.Automation.IValidateSetValuesGenerator en het moet een methode implementeren genaamd GetValidValues().

De GetValues()-methode retourneert de lijst met waarden die u wilt accepteren. Een Param()-blok met de statische lijst van planeten vervangen door een [Planet]-klasse zou er als volgt uitzien. Deze voorbeeld zal nog niet werken. Blijf lezen om te leren hoe dit te implementeren.

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

Het gebruik van een klasse voor een ValidateSet-waardelijst: Een echt voorbeeld

Om het gebruik van een klasse voor een ValidateSet-waardelijst te demonstreren, gaat u de statische lijst van planeten die eerder is gebruikt, vervangen door een lijst die is geladen vanuit een CSV-tekstbestand. U hoeft niet langer een statische lijst van waarden binnen het script zelf te onderhouden!

Creëren van een gegevensbron voor de klasse

Ten eerste moet u een CSV-bestand maken met elk van de geldige waarden. Kopieer hiervoor deze gegevensset en plak deze in een nieuw tekstbestand en sla het op als planets.csv in dezelfde map als het Get-PlanetSize.ps1-script.

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

Het toevoegen van de klasse aan het script

Elke klasse die door ValidateSet wordt gebruikt, moet al gedefinieerd zijn voordat ValidateSet probeert deze te gebruiken. Dit betekent dat de structuur van Get-PlanetSize.ps1 zoals deze is, niet zal werken.

De klasse [Planet] moet gedefinieerd zijn voordat deze kan worden gebruikt, dus deze moet aan het begin van het script worden geplaatst. Een geschikte skeletklassedefinitie ziet er als volgt uit:

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

Binnen de methode GetValidValues() van de klasse, gebruik de Import-CSV cmdlet om het tekstbestand planets.csv dat eerder is aangemaakt, te importeren. Het bestand wordt geïmporteerd in een variabele met een globale reikwijdte, genaamd $planets, om er later in het script toegang toe te hebben.

Gebruik de return-verklaring om de lijst met planeetnamen via GetValidValues() terug te geven. Na deze wijzigingen zou de klasse er nu als volgt uit moeten zien.

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

Vervolgens, verwijder de hash-tabelverklaring $planets uit het script zoals hieronder wordt weergegeven. De globale variabele $planets die wordt gevuld door de klasse [Planet], bevat de planeetgegevens in plaats daarvan.

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

Wikkel nu de resterende originele code in een functie en noem deze Get-PlanetDiameter om deze te onderscheiden van de naam van het script. Plaats het Param()-blok dat aan het begin van het script stond binnen de functie. Vervang de statische lijst met planeten door een verwijzing naar de klasse [Planet], zoals hieronder wordt weergegeven.

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

Vervang de regel $output = "De diameter van planeet {0} is {1} km" -f $_, $planets[$_] door de volgende twee regels. Hierdoor kan het script een planeet opzoeken in de reeks objecten gemaakt door Import-CSV, in plaats van de hashtable die je eerder hebt gemaakt en die je uit het script hebt verwijderd:

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

Na deze set wijzigingen moet je uiteindelijke script er als volgt uitzien:

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

Onthoud, vanaf dit punt werkt het script alleen in PowerShell 6.1 of later

Hoe gebruik je dit script?

Het Script Uitvoeren

Je kunt de nieuwe versie van het script niet direct gebruiken door eenvoudigweg het script uit te voeren. Alle bruikbare code zit nu in een functie. In plaats daarvan moet je het bestand dot sourcen om toegang te krijgen tot de functie binnen de PowerShell-sessie.

PS61> . .\Get-PlanetSize.ps1

Zodra je het hebt gedot-sourced, heb je nu toegang tot de nieuwe functie Get-PlanetDiameter in de PowerShell-sessie, met tab-completion.

“Wat is het voordeel van al dat werk?”, hoor ik je vragen. “Het script lijkt op dezelfde manier te werken, maar het is moeilijker om de code te gebruiken!”

Probeer dit:

  • Open het bestand planets.csv dat je eerder hebt gemaakt.
  • Voeg een nieuwe rij toe met een nieuwe naam en diameter.
  • Sla het CSV-bestand op.

In dezelfde sessie waarin je oorspronkelijk je script hebt geïmporteerd, probeer de diameter van de nieuwe planeet op te zoeken met Get-PlanetDiameter. Het werkt!

Het gebruik van een klasse op deze manier biedt ons verschillende voordelen:

  • De lijst met geldige waarden is nu gescheiden van de code zelf, maar eventuele wijzigingen in de waarden in het bestand worden opgepikt door het script.
  • Het bestand kan worden onderhouden door iemand die nooit toegang heeft tot het script.
  • A more complex script could look up information from a spreadsheet, database, Active Directory or a web API.

Zoals je kunt zien, zijn de mogelijkheden bijna eindeloos bij het gebruik van een klasse om ValidateSet-waarden te leveren.

Samenvattend

We hebben veel terrein behandeld terwijl we Get-PlanetSize.ps1 hebben gebouwd, dus laten we even opfrissen.

In dit artikel heb je geleerd:

  • Wat een ValidateSet-validatieattribuut is en waarom je er een zou willen gebruiken
  • Hoe je ValidateSet aan een PowerShell-functie of script toevoegt
  • Hoe tab-completion werkt met ValidateSet
  • Hoe je de IgnoreCase-eigenschap gebruikt om te bepalen of je ValidateSet hoofdlettergevoelig is
  • Hoe je de ErrorMessage-eigenschap gebruikt met je ValidateSet en PowerShell 6.1
  • Hoe je een klasse gebruikt om een dynamische ValidateSet te maken met PowerShell 6.1

Waar wacht je nog op? Begin vandaag nog met het gebruik van ValidateSet!

Verdere Lezing

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