PowerShell ValidateSet:タブ補完&パラメーター値

PowerShellスクリプトや関数を作成する際、パラメーターを介してユーザーからの入力を受け取ることがよくあります。これらのパラメーターが受け入れる値を制限しない場合、不適切な値が指定される状況が発生する可能性があります。この記事では、PowerShellのValidateSetパラメーター検証属性を使用して、指定した値のみを許可する方法について学びます。

PowerShellスクリプトや関数を作成する際、さまざまな検証属性を使用して、パラメーターに指定された値が受け入れ可能かどうかを確認し、受け入れ不可の場合にはユーザーに警告することができます。

この記事では、ValidateSet検証属性に焦点を当てます。ValidateSetの役割や、コードでValidateSetを使用する理由、使用方法について学びます。また、ValidateSetによって有効になるタブ補完機能についても説明します。この機能は、コードのユーザーが有効なパラメーターの値を提供するのに役立ちます。

PowerShell ValidateSet: 簡概

ValidateSetは、パラメーター属性であり、そのパラメーターの値として受け入れられる要素のセットを定義することができます。

たとえば、Active Directoryドメインコントローラーと連携するスクリプトがあるとします。このスクリプトには、ドメインコントローラーの名前を指定するパラメーターがあります。事前にどの値がスクリプトに必要であるかわかっている場合、ユーザーが「foobar」という値を使用できる理由はありません。ValidateSetを使用すると、そのような制限が可能になります。

要件

この記事は学習用の手順書です。この手順に従って進める場合、以下のものが必要です:

  • Visual Studio Codeまたは他のコードエディタ。私はVisual Studio Codeを使用します。
  • この記事のほとんどのコードには、少なくともPowerShell 5.1が必要です。ただし、1つのセクションではPowerShell 6.1以降が必要であり、そのセクションに到達した時点でそれを識別します。

この記事のすべてのコードは、以下の環境でテストされています:

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

ValidateSetの概念を説明するために、Get-PlanetSize.ps1という小さなスクリプトを作成します。このスクリプトは、太陽系の惑星のサイズに関する情報を返します。

まずはシンプルなスクリプトから始めて、徐々にユーザーからの入力を処理し、パラメータの値を簡単に見つけることができるように改善していきます。

はじめに

まず、以下のPowerShellコードをお好きなテキストエディタにコピー&ペーストし、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
 }

PowerShellプロンプトからスクリプトを実行すると、以下のような結果が表示されるはずです:

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

情報は提供されますが、柔軟性に欠けます。全ての惑星の情報が返されますが、たとえば火星の情報だけが欲しい場合でもです。

おそらく、すべての惑星を返す代わりに、単一の惑星を指定する能力が欲しいと思われるかもしれません。それを実現するためには、パラメータを導入する必要があります。次に、それを実現する方法を見てみましょう。

パラメータを使用した入力の受け入れ

スクリプトがパラメータを受け入れるようにするには、スクリプトの先頭にParam()ブロックを追加します。パラメータをPlanetと呼びます。適切なParam()ブロックは以下のようになります。

以下の例では、[Parameter(Mandatory)]の行により、スクリプトに常に惑星名が指定されることが保証されます。もし指定されていない場合は、スクリプトはプロンプトを表示して惑星名を要求します。

Param(
     [Parameter(Mandatory)]
     $Planet
 )

このPlanetパラメータをスクリプトに組み込む最も簡単な方法は、$planets.keys | Foreach-Object {の行を$Planet | Foreach-Object {に変更することです。これにより、事前に定義された静的なハッシュテーブルに依存せず、Planetパラメータの値を読み取るようになります。

これでスクリプトを実行し、Planetパラメータを使用して特定の惑星についての情報のみを表示できます。

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

素晴らしいですね。スクリプトは完了しましたか?でも、まだかもしれません。

オプションがあまりにもオープンすぎる

Barsoomという惑星の直径をGet-PlanetSize.ps1を使って調べようとすると、どうなるでしょうか?

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

うーん、それは正しくありませんね。Barsoomは惑星のリストにはありませんが、スクリプトは実行されてしまいます。これを修正するにはどうすればよいでしょうか?

問題は、スクリプトが有効な値かどうかに関係なく、任意の入力を受け入れて使用してしまうことです。スクリプトには、Planetパラメータに受け入れる値を制限する方法が必要です。ValidateSetを入力してみてください。

特定の値のみを使用するようにするための方法

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
 )

を入力して、スクリプトを再実行してみてください。PlanetパラメータにBarsoomを使用してください。すると、役立つエラーメッセージが返されます。メッセージは、何が間違っているのか具体的に示しており、パラメータの可能な値のリストも提供しています。

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のValidateSetを大文字と小文字を区別するようにする

ValidateSet属性は、デフォルトでは大文字と小文字を区別しません。つまり、許可されたリスト内の任意の文字列を、大文字小文字の区別なく受け入れます。たとえば、上記の例では、Marsと同じくらい簡単にmarsも受け入れます。必要に応じて、IgnoreCaseオプションを使用してValidateSetを大文字と小文字を区別するように強制することができます。

ValidateSetのIgnoreCaseオプションは、検証属性で、パラメータに指定された値が正確に有効な値リストと一致するかどうかを判断します。デフォルトでは、IgnoreCase$True(大文字小文字を区別しない)に設定されています。これを$Falseに設定すると、Get-PlanetSize.ps1Planetパラメータにmarsを指定すると、エラーメッセージが生成されます。

以下に示すように、有効な値リストの末尾に$trueの値を割り当てることで、IgnoreCaseオプションを使用できます。

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

今回は、リスト内の値と Planet の値が完全に一致しない場合、バリデーションが失敗します。

タブ補完の使用

ValidateSetを使用すると、タブ補完が利用できます。これにより、パラメータの可能な値をTABキーを使用してサイクルすることができます。これにより、スクリプトや関数の使いやすさが大幅に向上します。特にコンソールからの利用においては、非常に便利です。

以下の例には、いくつかの注意点があります。

  • タブ補完は、最後の値を表示した後に最初の値に戻ります。
  • 値はアルファベット順に表示されますが、ValidateSet 内でアルファベット順にリスト化されているわけではありません。
  • 最初の文字を入力して TAB キーを押すと、タブ補完によって提供される値がその文字で始まる値に制限されます。
Cycling through parameter values using Tab Completion
Restricting values returned by Tab Completion

また、ValidateSet のタブ補完を PowerShell 統合スクリプト環境 (ISE) でも利用することができます。以下の例では、ISEの Intellisense 機能が、素敵な選択ボックスで可能な値のリストを表示しています。

Intellisense は、入力した文字を含むすべての値を返します。文字で始まる値だけでなく。

Tab Completion and Intellisense in ISE

Windows 5.1 の ValidateSet バリデーション属性について説明しましたが、PowerShell Core 6.1 で追加された機能を見てみて、スクリプトにより多くのバリデーション機能を追加できるかどうかを見てみましょう。

PowerShell 6.1 での ValidateSet の変更の理解

PowerShell Core 6.1の登場により、ValidateSet検証属性には2つの新機能が追加されました。

  • ErrorMessageプロパティ
  • ValidateSetを介したクラスの使用、System.Management.Automation.IValidateSetValuesGeneratorへのアクセス

ErrorMessageプロパティ

Get-PlanetSize.ps1に間違った惑星名を指定すると生成されるデフォルトのエラーメッセージは役立ちますが、やや冗長です。

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 

以下の例に示すように、ValidateSet検証属性のErrorMessageプロパティを使用して、異なるエラーメッセージを設定します。{0}は自動的に提出された値に置き換えられ、{1}は許可される値のリストに自動的に置き換えられます。

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

スクリプトファイルのParam()ブロックを置き換えて保存します。その後、Get-PlanetSize.ps1 -Planet Barsoomをもう一度試してください。以下のように、エラーが非常に簡潔で具体的になっていることに注意してください。

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 

次に、PowerShell クラスを使用してValidateSetで受け入れ可能な値を定義する新しい方法を見てみましょう。

PowerShellクラス

PowerShellでは、バージョン5以降からカスタムタイプであるクラスが利用可能でした。PowerShell Core 6.1の登場により、ValidateSetで値を提供するためにクラスを使用するための新機能が追加されました。

クラスを使用すると、ValidateSetの主な制限を回避することができます-それは静的なものであるということです。つまり、それは関数やスクリプトの一部として埋め込まれており、スクリプト自体を編集することでのみ変更することができます。

ValidateSetと一緒に機能する新しい機能は、System.Management.Automation.IValidateSetValuesGeneratorクラスを使用できる能力です。これを継承を使って自分自身のクラスのベースとして使用することができます。ValidateSetと一緒に動作するためには、クラスはSystem.Management.Automation.IValidateSetValuesGeneratorに基づいている必要があり、GetValidValues()というメソッドを実装する必要があります。

GetValues()メソッドは、受け入れたい値のリストを返します。静的な惑星のリストが[Planet]クラスに置き換えられたParam()ブロックは、以下のようになります。この例はまだ動作しません。これを実装する方法を学ぶために読み続けてください。

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

ValidateSet値リストのためのクラスの使用:実際の例

クラスを使用してValidateSet値リストをデモンストレーションするために、以前に使用した静的な惑星のリストをCSVテキストファイルから読み込むリストに置き換えます。もはやスクリプト自体内で静的な値のリストを維持する必要はありません!

クラスのためのデータソースの作成

まず、有効な値のそれぞれを含むCSVファイルを作成する必要があります。そのために、このデータセットを新しいテキストファイルにコピーして貼り付け、それをplanets.csvとして保存し、Get-PlanetSize.ps1スクリプトと同じフォルダに保存します。

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

スクリプトにクラスを追加する

ValidateSetで使用されるクラスは、ValidateSetが使用する前に定義されている必要があります。これは、Get-PlanetSize.ps1の構造では機能しません。

使用する前に[Planet]クラスを定義する必要があるため、スクリプトの先頭に配置する必要があります。適切なスケルトンクラス定義は以下のようになります。

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

クラスのGetValidValues()メソッド内では、テキストファイルplanets.csvをインポートするためにImport-CSVコマンドレットを使用します。ファイルは$planetsというグローバルスコープの変数にインポートされるため、スクリプト内で後でアクセスすることができます。

GetValidValues()を介して惑星名のリストを返すためにreturnステートメントを使用します。これらの変更後、クラスは以下のようになります。

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

次に、スクリプトから$planetsハッシュテーブルの宣言を削除します。代わりに、[Planet]クラスによってポピュレートされるグローバル変数$planetsに惑星データが含まれます。

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

残りのオリジナルコードを関数にラップし、それをGet-PlanetDiameterと呼びます。スクリプトの先頭にあったParam()ブロックを関数内に配置します。静的な惑星のリストを[Planet]クラスへの参照に置き換えます。以下のようになります。

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

$output = "惑星 {0} の直径は {1} kmです" -f $_, $planets[$_]という行を以下の2行に置き換えます。これにより、スクリプトは以前に作成したハッシュテーブルではなく、Import-CSVで作成したオブジェクトの配列から惑星を検索できるようになります。

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

これらの変更を行った後、最終的なスクリプトは次のようになります:

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

これ以降のスクリプトは、PowerShell 6.1以降でのみ動作します

それでは、このスクリプトをどのように使用するのでしょうか?

スクリプトの実行

スクリプトを実行するだけでは、スクリプトの新しいバージョンを直接使用することはできません。すべての有用なコードは関数に包まれています。そのため、PowerShellセッション内から関数にアクセスできるようにするために、ファイルをdot sourceする必要があります。

PS61> . .\Get-PlanetSize.ps1

dot source化された後は、PowerShellセッション内で新しいGet-PlanetDiameter関数にアクセスできるようになり、タブ補完も利用できます。

その作業の利点は何ですか?」と聞かれた場合、「スクリプトは同じように機能するようですが、コードの使用がより困難です!」

次のことを試してみてください:

  • 以前に作成したplanets.csvファイルを開きます。
  • 新しい名前と直径を持つ新しい行を追加します。
  • CSVファイルを保存します。

同じセッションでスクリプトを最初にdot sourcedした際に、Get-PlanetDiameterを使用して新しい惑星の直径を調べてみてください。うまくいきます!

この方法でクラスを使用すると、いくつかの利点があります:

  • 有効な値のリストがコード自体から分離されていますが、ファイル内の値の変更はスクリプトによって取り込まれます。
  • ファイルはスクリプトにアクセスしない人がメンテナンスできます。
  • A more complex script could look up information from a spreadsheet, database, Active Directory or a web API.

クラスを使用してValidateSetの値を提供する場合、可能性はほぼ無限です。

まとめ

最後に、Get-PlanetSize.ps1を構築しながら多くの内容をカバーしましたので、総括しましょう。

この記事では、以下のことを学びました:

  • ValidateSet検証属性とその使用目的
  • PowerShell関数またはスクリプトにValidateSetを追加する方法
  • ValidateSetとのタブ補完の動作方法
  • ValidateSetが大文字小文字を区別するかどうかを制御するIgnoreCaseプロパティの使用方法
  • ValidateSetとPowerShell 6.1でのErrorMessageプロパティの使用方法
  • PowerShell 6.1で動的なValidateSetを作成するためのクラスの使用方法

さあ、何を待っていますか?今すぐValidateSetを使いましょう!

さらに読む

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