PowerShell ValidateSet: Completar com guia e valores de parâmetros

Quando você está escrevendo um script ou função do PowerShell, muitas vezes deseja aceitar a entrada do usuário por meio de parâmetros. Se você não limitar os valores que esses parâmetros aceitam, não poderá garantir que haja situações em que valores inadequados sejam fornecidos. Neste artigo, aprenda como usar o atributo de validação de parâmetro ValidateSet do PowerShell para limitar esses valores apenas aos que você define.

Ao escrever um script ou função do PowerShell, você pode usar muitos atributos de validação diferentes para verificar se os valores fornecidos para seus parâmetros são aceitáveis e alertar o usuário se não forem.

Este artigo se concentra no atributo de validação ValidateSet. Você aprenderá o que ValidateSet faz, por que você pode querer usar ValidateSet em seu código e como fazer isso. Você também aprenderá sobre o recurso de conclusão de tabulação que é habilitado pelo ValidateSet e ajudará os usuários do seu código a fornecer valores de parâmetro válidos.

PowerShell ValidateSet: Uma Breve Visão Geral

ValidateSet é um atributo de parâmetro que permite definir um conjunto de elementos que são aceitos apenas como o valor para esse parâmetro.

Por exemplo, talvez você tenha um script que é definido para funcionar com controladores de domínio do Active Directory. Este script tem um parâmetro que especifica o nome de um controlador de domínio. Não faria sentido limitar a lista de valores aceitáveis aos nomes reais dos controladores de domínio? Não há motivo para o usuário poder usar “foobar” como um valor quando você sabe de antemão quais valores o script precisa. ValidateSet oferece essa capacidade.

Requisitos

Este artigo será um guia de aprendizagem. Se você planeja acompanhar, precisará do seguinte:

  • Visual Studio Code ou qualquer outro editor de código. Vou usar o Visual Studio Code.
  • Pelo menos PowerShell 5.1 para a maior parte do código neste artigo. Há uma seção que exige PowerShell 6.1 ou posterior, e eu vou identificar quando chegarmos lá

Todo o código neste artigo foi testado nos seguintes ambientes:

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

Para ajudar a explicar os conceitos em torno de ValidateSet, você vai construir um pequeno script chamado Get-PlanetSize.ps1. Este script retorna informações sobre os tamanhos dos planetas em nosso sistema solar.

Você começará com um script simples e gradualmente melhorará sua capacidade de lidar com a entrada do usuário final e facilitar a descoberta de seus possíveis valores de parâmetros.

Começando

Para começar, copie e cole o código PowerShell abaixo no seu editor de texto favorito e salve-o como 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
 }

Execute o script a partir de um prompt do PowerShell e você deverá obter o seguinte:

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, mas não muito flexível; as informações para todos os planetas são retornadas, mesmo se você só quiser as informações para Marte.

Talvez você queira a capacidade de especificar um único planeta em vez de retornar todos eles. Você pode fazer isso introduzindo um parâmetro. Vamos ver como fazer isso a seguir.

Aceitando Entrada usando um Parâmetro

Para permitir que o script aceite um parâmetro, adicione um bloco Param() ao topo do script. Chame o parâmetro de Planet. Um bloco Param() adequado se parece com o exemplo abaixo.

No exemplo abaixo, a linha [Parameter(Mandatory)] garante que um nome de planeta seja sempre fornecido ao script. Se estiver faltando, o script solicitará um.

Param(
     [Parameter(Mandatory)]
     $Planet
 )

A maneira mais simples de incorporar esse parâmetro Planet ao script é alterar a linha $planets.keys | Foreach-Object { para $Planet | Foreach-Object {. Agora você não está mais dependendo da hashtable definida estaticamente anteriormente e, em vez disso, está lendo o valor do parâmetro Planet.

Agora, se você executar o script e especificar um planeta usando o parâmetro Planet, verá apenas informações sobre aquele planeta específico.

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

Excelente. Script concluído? Talvez não.

As Opções são Muito Abertas

O que acontece se você tentar encontrar o diâmetro do planeta Barsoom usando Get-PlanetSize.ps1?

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

Hmm, isso não está certo. Barsoom não está na lista de planetas, mas o script é executado mesmo assim. Como podemos corrigir isso?

O problema aqui é que o script aceita qualquer entrada e a utiliza, independentemente de ser ou não um valor válido. O script precisa de uma maneira de limitar quais valores são aceitos para o parâmetro Planet. Introduza ValidateSet!

Garantindo que Apenas Certos Valores são Utilizados

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
 )

Tente executar o script novamente usando Barsoom como seu parâmetro Planet. Agora, uma mensagem de erro útil é retornada. A mensagem é específica sobre o que deu errado e até fornece uma lista de valores possíveis para o parâmetro.

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 

Tornando o ValidateSet do PowerShell Sensível a Maiúsculas e Minúsculas

Por padrão, o atributo ValidateSet não faz distinção entre maiúsculas e minúsculas. Isso significa que permitirá qualquer string, desde que esteja na lista permitida com qualquer esquema de capitalização. Por exemplo, o exemplo acima aceitará Mars tão facilmente quanto aceitaria mars. Se necessário, você pode forçar o ValidateSet a ser sensível a maiúsculas e minúsculas usando a opção IgnoreCase.

A opção IgnoreCase no ValidateSet, um atributo de validação, determina se os valores fornecidos ao parâmetro correspondem exatamente à lista de valores válidos. Por padrão, IgnoreCase está definido como $True (ignorar maiúsculas e minúsculas). Se você definir isso como $False, fornecer mars como um valor para o parâmetro Planet em Get-PlanetSize.ps1 geraria uma mensagem de erro.

Você usaria a opção IgnoreCase atribuindo a ela um valor $true no final da lista de valores válidos, como mostrado abaixo.

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

Agora, quando você tenta usar um valor para Planet que não é exatamente como um valor na lista, a validação falhará.

Usando Complemento de Abas

Outra vantagem de usar ValidateSet é que ele oferece complemento de abas. Isso significa que você pode percorrer os valores possíveis para um parâmetro usando a tecla TAB. Isso melhora muito a usabilidade de um script ou função, especialmente no console.

Nos exemplos abaixo, há algumas coisas a serem observadas:

  • O complemento de abas volta ao primeiro valor depois de exibir o último.
  • Os valores são apresentados em ordem alfabética, mesmo que não estejam listados no ValidateSet em ordem alfabética.
  • Ao digitar uma letra inicial e pressionar TAB, os valores oferecidos pelo complemento de abas são restritos aos que começam com essa letra.
Cycling through parameter values using Tab Completion
Restricting values returned by Tab Completion

Você também pode aproveitar o complemento de abas ValidateSet no Ambiente de Script Integrado do PowerShell (ISE), conforme mostrado no exemplo abaixo. O recurso de IntelliSense do ISE mostra a você a lista de valores possíveis em uma caixa de seleção agradável.

O IntelliSense retorna todos os valores que contêm a letra que você digita, em vez de apenas aqueles que começam com ela.

Tab Completion and Intellisense in ISE

Agora que cobrimos os atributos de validação ValidateSet como estão no Windows 5.1, vamos dar uma olhada no que foi adicionado no PowerShell Core 6.1 e ver se isso pode fornecer mais capacidades de validação para nosso script.

Compreendendo as Mudanças no ValidateSet no PowerShell 6.1

Com a chegada do PowerShell Core 6.1, duas novas capacidades foram adicionadas aos atributos de validação ValidateSet:

  • A propriedade ErrorMessage
  • Uso de classes em ValidateSet através do acesso ao System.Management.Automation.IValidateSetValuesGenerator

A propriedade ErrorMessage

A mensagem de erro padrão gerada quando você fornece um nome de planeta incorreto para Get-PlanetSize.ps1 é útil, mas um pouco longa:

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 

Use a propriedade ErrorMessage do atributo de validação ValidateSet para definir uma mensagem de erro diferente, como mostrado no exemplo abaixo. {0} é automaticamente substituído pelo valor enviado e {1} é automaticamente substituído pela lista de valores permitidos.

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

Substitua o bloco Param() no arquivo do script e salve-o. Em seguida, tente novamente Get-PlanetSize.ps1 -Planet Barsoom. Observe que o erro agora é mais sucinto e descritivo.

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 

Em seguida, dê uma olhada em uma nova maneira de definir os valores aceitáveis em ValidateSet através de uma classe PowerShell classe.

Classes PowerShell

Tipos personalizados, conhecidos no PowerShell como classes, estão disponíveis desde a versão 5. Com a chegada do PowerShell Core 6.1, há um novo recurso que permite o uso de uma classe para fornecer os valores para o ValidateSet.

Usar uma classe permite contornar a principal limitação de um ValidateSet – ele é estático. Ou seja, ele está incorporado como parte da função ou script e só pode ser alterado editando o próprio script.

A nova funcionalidade que funciona com ValidateSet é a capacidade de usar a classe System.Management.Automation.IValidateSetValuesGenerator. Podemos usar isso como base para nossas próprias classes usando herança. Para trabalhar com ValidateSet, a classe deve ser baseada em System.Management.Automation.IValidateSetValuesGenerator  e deve implementar um método chamado GetValidValues().

O método GetValues() retorna a lista de valores que você deseja aceitar. Um bloco Param() com a lista estática de planetas substituída por uma classe [Planet] ficaria assim. Este exemplo ainda não funcionará. Continue lendo para aprender como implementar isso.

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

Usando uma Classe para uma Lista de Valores ValidateSet: Um Exemplo Real

Para demonstrar o uso de uma classe para uma lista de valores ValidateSet, você vai substituir a lista estática de planetas usada anteriormente por uma lista carregada de um arquivo de texto CSV. Você não precisará mais manter uma lista estática de valores dentro do próprio script!

Criando uma Fonte de Dados para a Classe

Primeiro, você precisará criar um arquivo CSV contendo cada um dos valores válidos. Para fazer isso, copie e cole este conjunto de dados em um novo arquivo de texto e salve-o como planets.csv na mesma pasta que o script Get-PlanetSize.ps1.

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

Adicionando a Classe ao Script

Qualquer classe usada pelo ValidateSet deve ser definida antes de o ValidateSet tentar usá-la. Isso significa que a estrutura do arquivo Get-PlanetSize.ps1 como está não funcionará.

A classe [Planet] deve ser definida antes de poder ser usada, então ela deve estar no início do script. Uma definição de classe esqueleto adequada se parece com abaixo:

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

Dentro do método GetValidValues() da classe, use o cmdlet Import-CSV para importar o arquivo de texto planetas.csv criado anteriormente. O arquivo é importado para uma variável com escopo global, chamada $planetas, para acessá-lo posteriormente no script.

Use o comando return para retornar a lista de nomes de planetas através de GetValidValues(). Após essas mudanças, a classe deve agora parecer como abaixo.

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

Em seguida, remova a declaração da tabela de hash $planetas do script, como mostrado abaixo. A variável global $planetas, que é preenchida pela classe [Planet], contém os dados do planeta.

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

Agora envolva o restante do código original em uma função e a chame de Get-PlanetDiameter para diferenciá-la do nome do script. Coloque o bloco Param() que estava no início do script dentro da função. Substitua a lista estática de planetas por uma referência à classe [Planet], como mostrado abaixo.

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

$output = "O diâmetro do planeta {0} é {1} km" -f $_, $planets[$_]  substituir pela seguinte duas linhas. Estas permitem que o script procure um planeta no array de objetos criado por Import-CSV, em vez da tabela de hash que você criou anteriormente e removeu do script:

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

Após este conjunto de mudanças, seu script final precisa se parecer com isso:

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

Lembre-se, a partir deste ponto, o script só funciona no PowerShell 6.1 ou posterior

Agora, como você utiliza este script?

Executando o Script

Você não pode usar a nova versão do script diretamente executando o script. Todo o código útil está agora envolvido em uma função. Você precisa dot source o arquivo em vez disso para permitir o acesso à função de dentro da sessão do PowerShell.

PS61> . .\Get-PlanetSize.ps1

Uma vez dot-sourced, agora você tem acesso à nova função Get-PlanetDiameter na sessão do PowerShell, com autocompletar.

Qual é o benefício de todo esse trabalho?”, eu ouço você perguntar. “O script parece funcionar da mesma forma, mas é mais difícil usar o código!”

Tente isso:

  • Abra o arquivo planets.csv que você criou anteriormente.
  • Adicione uma nova linha com um novo nome e diâmetro.
  • Salve o arquivo CSV.

Na mesma sessão em que você inicialmente carregou seu script, tente procurar o diâmetro do novo planeta usando Get-PlanetDiameter. Funciona!

Usar uma classe dessa maneira nos proporciona vários benefícios:

  • A lista de valores válidos agora está separada do próprio código, mas quaisquer alterações nos valores no arquivo são captadas pelo script.
  • O arquivo pode ser mantido por alguém que nunca acessa o script.
  • A more complex script could look up information from a spreadsheet, database, Active Directory or a web API.

Como você pode ver, as possibilidades são quase infinitas ao usar uma classe para fornecer valores de ValidateSet.

Concluindo

Cobrimos muito terreno enquanto construímos Get-PlanetSize.ps1, então vamos recapitular.

Neste artigo, você aprendeu:

  • O que é um atributo de validação ValidateSet e por que você pode querer usá-lo
  • Como adicionar ValidateSet a uma função ou script do PowerShell
  • Como o preenchimento automático funciona com ValidateSet
  • Como usar a propriedade IgnoreCase para controlar se o seu ValidateSet é sensível a maiúsculas e minúsculas
  • Como usar a propriedade ErrorMessage com seu ValidateSet e PowerShell 6.1
  • Como usar uma classe para criar um ValidateSet dinâmico com PowerShell 6.1

O que você está esperando? Comece a usar ValidateSet hoje!

Leitura adicional

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