PowerShell ValidateSet: Completamento automatico e valori dei parametri

Quando si scrive uno script o una funzione PowerShell, spesso si desidera accettare l’input dell’utente tramite parametri. Se non si limitano i valori che tali parametri accettano, si può garantire che ci saranno situazioni in cui vengono forniti valori inappropriati. In questo articolo, imparerai come utilizzare l’attributo di convalida dei parametri ValidateSet di PowerShell per limitare quei valori solo a quelli che definisci tu.

Quando si scrive uno script o una funzione PowerShell, è possibile utilizzare molti attributi di convalida diversi per verificare che i valori forniti ai parametri siano accettabili e avvisare l’utente se non lo sono.

Questo articolo si concentra sull’attributo di convalida ValidateSet. Imparerai cosa fa ValidateSet, perché potresti voler utilizzare ValidateSet nel tuo codice e come farlo. Imparerai anche la funzione di completamento automatico abilitata da ValidateSet, che aiuterà gli utenti del tuo codice a fornire valori validi per i parametri.

PowerShell ValidateSet: una breve panoramica

ValidateSet è un attributo del parametro che consente di definire un insieme di elementi che vengono accettati solo come valore per quel parametro.

Ad esempio, forse hai uno script definito per lavorare con i controller di dominio di Active Directory. Questo script ha un parametro che specifica il nome di un controller di dominio. Avrebbe senso limitare l’elenco dei valori accettabili ai nomi effettivi dei controller di dominio? Non c’è motivo per cui l’utente dovrebbe poter utilizzare “foobar” come valore quando sai in anticipo quali valori ha bisogno lo script. ValidateSet ti offre questa possibilità.

Requisiti

Questo articolo sarà una guida di apprendimento. Se hai intenzione di seguirla, avrai bisogno dei seguenti strumenti:

  • Visual Studio Code o qualsiasi altro editor di codice. Io userò Visual Studio Code.
  • Almeno PowerShell 5.1 per la maggior parte del codice in questo articolo. C’è una sezione che richiede PowerShell 6.1 o successivo e lo indicherò quando ci arriveremo.

Tutto il codice in questo articolo è stato testato negli ambienti seguenti:

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

Per aiutarti a comprendere i concetti attorno a ValidateSet, costruirai uno script chiamato Get-PlanetSize.ps1. Questo script restituisce informazioni sulle dimensioni dei pianeti nel nostro sistema solare.

Inizierai con uno script semplice e migliorerai gradualmente la sua capacità di gestire l’input dall’utente finale e rendere facile la scoperta dei possibili valori dei parametri.

Per iniziare

Per iniziare, copia e incolla il codice PowerShell qui sotto nel tuo editor di testo preferito e salvalo come 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
 }

Esegui lo script da un prompt di PowerShell e dovresti ottenere quanto segue:

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

Informativo ma poco flessibile; vengono restituite le informazioni per ogni pianeta, anche se desideri solo le informazioni su Marte.

Forse vorresti avere la possibilità di specificare un singolo pianeta invece di restituire tutti. Puoi farlo introducendo un parametro. Vediamo come realizzarlo a seguire.

Accettare l’input utilizzando un parametro

Per consentire allo script di accettare un parametro, aggiungi un blocco Param() all’inizio dello script. Chiamalo Planet. Un blocco Param() adeguato appare come segue.

Nell’esempio sottostante, la linea [Parameter(Mandatory)] assicura che un nome di pianeta venga sempre fornito allo script. Se manca, lo script richiederà uno.

Param(
     [Parameter(Mandatory)]
     $Planet
 )

Il modo più semplice per incorporare questo parametro Planet nello script è cambiare la riga $planets.keys | Foreach-Object { in $Planet | Foreach-Object {. Ora non ti stai più basando staticamente sull’hashtable definita in precedenza, ma stai leggendo il valore del parametro Planet.

Ora, se esegui lo script e specifici un pianeta usando il parametro Planet, vedrai solo informazioni su quel particolare pianeta.

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

Eccellente. Script completato? Forse no.

Le opzioni sono troppo aperte

Cosa succede se provi a trovare il diametro del pianeta Barsoom usando Get-PlanetSize.ps1?

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

Mmm, non è corretto. Barsoom non è nella lista dei pianeti, ma lo script viene eseguito comunque. Come possiamo risolvere questo problema?

Il problema qui è che lo script accetta qualsiasi input e lo utilizza, indipendentemente dal fatto che sia un valore valido o meno. Lo script ha bisogno di un modo per limitare i valori accettati per il parametro Planet. Inserisci ValidateSet!

Garantire che vengano utilizzati solo determinati valori

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
 )

Prova a eseguire lo script nuovamente utilizzando Barsoom come parametro Planet. Ora viene restituito un messaggio di errore utile. Il messaggio è specifico su cosa sia andato storto e fornisce persino un elenco di valori possibili per il parametro.

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 

Rendere PowerShell Case Sensitive con ValidateSet

Per impostazione predefinita, l’attributo ValidateSet non fa distinzione tra maiuscole e minuscole. Ciò significa che permetterà qualsiasi stringa a condizione che sia nell’elenco consentito con qualsiasi schema di capitalizzazione. Ad esempio, l’esempio sopra accetterà Mars così facilmente come accetterebbe mars. Se necessario, puoi forzare ValidateSet a essere sensibile alle maiuscole e minuscole utilizzando l’opzione IgnoreCase.

L’opzione IgnoreCase in ValidateSet, un attributo di convalida, determina se i valori forniti al parametro corrispondono esattamente all’elenco dei valori validi. Per impostazione predefinita, IgnoreCase è impostato su $True (ignora maiuscole e minuscole). Se imposti questo su $False, quindi fornire mars come valore per il parametro Planet per Get-PlanetSize.ps1 genererebbe un messaggio di errore.

Utilizzeresti l’opzione IgnoreCase assegnandole un valore $true alla fine dell’elenco di valori validi come mostrato di seguito.

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

Ora, quando si cerca di utilizzare un valore per Planet che non è esattamente come un valore nell’elenco, la convalida non riuscirà.

Utilizzando il completamento automatico con scheda

Un altro vantaggio nell’utilizzare ValidateSet è che fornisce il completamento automatico con scheda. Ciò significa che è possibile ciclare attraverso i possibili valori per un parametro utilizzando il tasto TAB. Ciò migliora notevolmente l’usabilità di uno script o di una funzione, soprattutto dalla console.

Negli esempi seguenti, ci sono un paio di cose da notare:

  • Il completamento automatico con scheda torna al primo valore dopo aver visualizzato l’ultimo.
  • I valori vengono presentati in ordine alfabetico, anche se non sono elencati in modo alfabetico in ValidateSet.
  • Digitando una lettera iniziale e premendo TAB limita i valori offerti dal completamento automatico con scheda a quelli che iniziano con quella lettera.
Cycling through parameter values using Tab Completion
Restricting values returned by Tab Completion

È anche possibile sfruttare il completamento automatico con scheda di ValidateSet nell’Ambiente di scripting integrato di PowerShell (ISE), come mostrato nell’esempio seguente. La funzionalità di IntelliSense dell’ISE mostra l’elenco dei possibili valori in una bella casella di selezione.

L’IntelliSense restituisce tutti i valori che contengono la lettera digitata, anziché solo quelli che iniziano con essa.

Tab Completion and Intellisense in ISE

Ora che abbiamo affrontato gli attributi di convalida di ValidateSet come sono in Windows 5.1, diamo un’occhiata a ciò che è stato aggiunto in PowerShell Core 6.1 e vediamo se questo può fornire al nostro script maggiori capacità di convalida.

Comprensione delle modifiche apportate a ValidateSet in PowerShell 6.1

Con l’arrivo di PowerShell Core 6.1 sono state aggiunte due nuove funzionalità agli attributi di convalida ValidateSet:

  • La proprietà ErrorMessage
  • Utilizzo di classi in ValidateSet tramite accesso a System.Management.Automation.IValidateSetValuesGenerator

La proprietà ErrorMessage

Il messaggio di errore predefinito generato quando si fornisce un nome di pianeta incorretto a Get-PlanetSize.ps1 è utile, ma un po’ prolisso:

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 

Utilizzare la proprietà ErrorMessage dell’attributo di convalida ValidateSet per impostare un messaggio di errore diverso, come mostrato nell’esempio qui sotto. {0} viene automaticamente sostituito con il valore inviato e {1} viene automaticamente sostituito con l’elenco dei valori consentiti.

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

Sostituire il blocco Param() nel file dello script e salvarlo. Quindi provare nuovamente Get-PlanetSize.ps1 -Planet Barsoom. Notare che l’errore è molto meno prolisso e più descrittivo.

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 

Successivamente, dare un’occhiata a un nuovo modo per definire i valori accettabili in ValidateSet tramite una classe di PowerShell classe.

Classi di PowerShell

I tipi personalizzati, conosciuti in PowerShell come classi, sono disponibili dalla versione 5. Con l’arrivo di PowerShell Core 6.1 c’è una nuova funzionalità che consente l’uso di una classe per fornire i valori per ValidateSet.

L’utilizzo di una classe consente di superare la principale limitazione di un ValidateSet: è statico. Ovvero, è incorporato come parte della funzione o script e può essere modificato solo modificando lo script stesso.

La nuova funzionalità che funziona con ValidateSet è la possibilità di utilizzare la classe System.Management.Automation.IValidateSetValuesGenerator. Possiamo utilizzarla come base per le nostre classi utilizzando l’ereditarietà. Per lavorare con ValidateSet, la classe deve essere basata su System.Management.Automation.IValidateSetValuesGenerator e deve implementare un metodo chiamato GetValidValues().

Il metodo GetValues() restituisce l’elenco di valori che desideri accettare. Un blocco Param() con l’elenco statico di pianeti sostituito da una classe [Planet] apparirebbe come segue. Questo esempio non funzionerà ancora. Continua a leggere per imparare come implementarlo.

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

Utilizzo di una classe per un elenco di valori ValidateSet: un esempio reale

Per dimostrare l’utilizzo di una classe per un elenco di valori ValidateSet, sostituirai l’elenco statico di pianeti utilizzato in precedenza con un elenco caricato da un file di testo CSV. Non sarà più necessario mantenere un elenco statico di valori all’interno dello script stesso!

Creazione di una fonte dati per la classe

Innanzitutto, dovrai creare un file CSV contenente ciascuno dei valori validi. Per farlo, copia e incolla questo set di dati in un nuovo file di testo e salvalo come planets.csv nella stessa cartella dello script Get-PlanetSize.ps1.

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

Aggiunta della classe allo script

Qualsiasi classe utilizzata da ValidateSet deve essere già definita prima che ValidateSet cerchi di utilizzarla. Questo significa che la struttura di Get-PlanetSize.ps1 così com’è non funzionerà.

La classe [Planet] deve essere definita prima che possa essere utilizzata, quindi deve essere collocata all’inizio dello script. Una definizione di classe scheletro adeguata appare come segue:

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

All’interno del metodo GetValidValues() della classe, utilizzare il cmdlet Import-CSV per importare il file di testo planets.csv creato in precedenza. Il file viene importato in una variabile con ambito globale, chiamata $planets, così da poterla successivamente accedere nello script.

Utilizzare l’istruzione return per restituire l’elenco dei nomi dei pianeti tramite GetValidValues(). Dopo queste modifiche, la classe dovrebbe ora apparire come segue.

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

Inoltre, rimuovere la dichiarazione dell’hash table $planets dallo script come mostrato di seguito. La variabile globale $planets, che viene popolata dalla classe [Planet], contiene invece i dati del pianeta.

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

Ora avvolgere il codice originale rimanente in una funzione e chiamarlo Get-PlanetDiameter per differenziarlo dal nome dello script. Collocare il blocco Param() che era all’inizio dello script all’interno della funzione. Sostituire l’elenco statico di pianeti con un riferimento alla classe [Planet], come mostrato di seguito.

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

Sostituisci la linea $output = "Il diametro del pianeta {0} è {1} km" -f $_, $planets[$_] con le seguenti due righe. Queste permettono allo script di cercare un pianeta nell’array di oggetti creato da Import-CSV, invece che nell’hash table che hai creato in precedenza e che hai rimosso dallo script:

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

Dopo questa serie di modifiche, il tuo script finale deve assomigliare a questo:

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

Ricorda, da questo punto in poi lo script funziona solo su PowerShell 6.1 o versioni successive

Adesso, come si utilizza questo script?

Esecuzione dello Script

Non puoi utilizzare direttamente la nuova versione dello script eseguendo semplicemente lo script. Tutto il codice utile è ora racchiuso in una funzione. Devi invece importare con punto il file per consentire l’accesso alla funzione dall’interno della sessione PowerShell.

PS61> . .\Get-PlanetSize.ps1

Una volta importato con punto, avrai accesso alla nuova funzione Get-PlanetDiameter nella sessione PowerShell, con completamento automatico dei comandi.

Qual è il vantaggio di tutto questo lavoro?”, ti sento chiedere. “Lo script sembra funzionare allo stesso modo, ma è più difficile utilizzare il codice!”

Prova questo:

  • Apri il file planets.csv che hai creato in precedenza.
  • Aggiungi una nuova riga con un nuovo nome e diametro.
  • Salva il file CSV.

Nella stessa sessione in cui hai originariamente caricato lo script, prova a cercare il diametro del nuovo pianeta usando Get-PlanetDiameter. Funziona!

Utilizzare una classe in questo modo ci offre diversi vantaggi:

  • La lista dei valori validi è ora separata dal codice stesso, ma eventuali modifiche ai valori nel file vengono rilevate dallo script.
  • Il file può essere mantenuto da qualcuno che non accede mai allo script.
  • A more complex script could look up information from a spreadsheet, database, Active Directory or a web API.

Come puoi vedere, le possibilità sono quasi infinite quando si utilizza una classe per fornire valori ValidateSet.

Conclusione

Abbiamo coperto molto terreno mentre abbiamo costruito Get-PlanetSize.ps1, quindi riassumiamo.

In questo articolo hai imparato:

  • Cos’è un attributo di convalida ValidateSet e perché potresti voler usarne uno
  • Come aggiungere ValidateSet a una funzione PowerShell o script
  • Come funziona il completamento automatico con ValidateSet
  • Come utilizzare la proprietà IgnoreCase per controllare se il tuo ValidateSet è sensibile alle maiuscole e minuscole
  • Come utilizzare la proprietà ErrorMessage con il tuo ValidateSet e PowerShell 6.1
  • Come utilizzare una classe per creare un ValidateSet dinamico con PowerShell 6.1

Cosa stai aspettando? Inizia a utilizzare ValidateSet oggi stesso!

Ulteriori letture

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