Tips

PowerShellのValidateSetの動的生成

概要

ValidateSetは以下の効果を持ちます。

  1. 引数の入力値候補の自動補完
  1. 許される入力値の候補以外の排除

通常は、以下のようにべた書きで配列を入力します。

[ValidateSet("A", "B", "C")]

今回は、発展的内容として、
許容入力値の配列を動的に生成する方法と、
それができないときの代替案を紹介します。

動的生成

IValidateSetValuesGeneratorを実装したクラスを定義し、その中で入力値リストを生成するGetValidValues()メソッドを定義する。(PowerShell Core限定)

代替案

[ArgumentCompleter({許容値配列生成})][ValidateScript({$_ -in $(許容値配列生成)})]を組み合わせる(PowerShell5以降)

動的生成

PowerShell Core以降なら、特別な理由がない限りこの方法を使います。

フォーマット

using namespace System.Management.Automation

class 動的生成するValidateSetのクラス名 : IValidateSetValuesGenerator {
    [string[]] GetValidValues() {
        return $(配列生成処理)
    }
}

function 関数名{
    param(
        [ValidateSet([動的生成するValidateSetのクラス名])]
        [型] $引数名
    )
}

コード例

using namespace System.Management.Automation

class PsScriptRelativePaths : IValidateSetValuesGenerator {
    [string[]] GetValidValues() {
        return $(Resolve-Path -Relative -LiteralPath $(Get-ChildItem -Path "./" -Filter "*.ps1").FullName)
    }
}

function Invoke-PsScript{
    <#
    .SYNOPSIS
        PowerShellScriptを実行します
    .DESCRIPTION
        この関数を使うと、以下のメリットがあります:
            1. PowerShellのスクリプトパスのみ補完してくれる
            
        一方備え付けのドット演算子には以下のデメリットがあります:
            1. PowerShellのスクリプト以外のパスを補完してしまう
            
        関数別名は、以下のようになっています:
            1. `..`            -> ドット演算子との類似性を保つため
            2. `Invoke-Script` -> PowerShell内でのScriptは基本PowerShellスクリプトのため
    .PARAMETER Path
        人間にとって扱いやすい相対パスで、
        現在地直下のスクリプトを指定します。
        実際には、現在直下のスクリプトの相対パス一覧が自動的に補完されます。
        なので、その中から選ぶだけです。
        それ以外の値を入力すると、検査に違反してエラーになります。
    .INPUTS
        パイプラインでの入力には対応していません。
    .OUTPUTS
        スクリプトの実行結果を返します。
    .EXAMPLE
        PS C:\Sample> Invoke-PsScript .\sample.ps1
    .EXAMPLE
        PS C:\Sample> .. .\sample.ps1
    .EXAMPLE
        PS C:\Sample> Invoke-Script .\sample.ps1
    #> 
    
    [Alias("..", "Invoke-Script")]
    [CmdletBinding()]
    param(
        [ValidateSet([PsScriptRelativePaths])]
        [string] $Path
    )
    
    . $Path
}

代替案

PowerShell5なら、以下の代替案を使います。

フォーマット

function 許容値配列生成の関数名{
    処理
}

function 関数名{
    param(
        [ArgumentCompleter({許容値配列生成の関数名})]
        [ValidateScript({$_ -in $(許容値配列生成の関数名)})]
        [型名] $変数名
    )
}

コード例

function Get-PsScriptRelativePaths{
    [OutputType([String[]])]
    [CmdletBinding()]
    Param()
      
    return $(Resolve-Path -Relative -LiteralPath @(Get-ChildItem -Path "./" -Filter "*.ps1").FullName)
}

function Invoke-PsScript{
    <#
    .SYNOPSIS
        PowerShellScriptを実行します
    .DESCRIPTION
        この関数を使うと、以下のメリットがあります:
            1. PowerShellのスクリプトパスのみ補完してくれる
            
        一方備え付けのドット演算子には以下のデメリットがあります:
            1. PowerShellのスクリプト以外のパスを補完してしまう
            
        関数別名は、以下のようになっています:
            1. `..`            -> ドット演算子との類似性を保つため
            2. `Invoke-Script` -> PowerShell内でのScriptは基本PowerShellスクリプトのため
    .PARAMETER Path
        人間にとって扱いやすい相対パスで、
        現在地直下のスクリプトを指定します。
        実際には、現在直下のスクリプトの相対パス一覧が自動的に補完されます。
        なので、その中から選ぶだけです。
        それ以外の値を入力すると、検査に違反してエラーになります。
    .INPUTS
        パイプラインでの入力には対応していません。
    .OUTPUTS
        スクリプトの実行結果を返します。
    .EXAMPLE
        PS C:\Sample> Invoke-PsScript .\sample.ps1
    .EXAMPLE
        PS C:\Sample> .. .\sample.ps1
    .EXAMPLE
        PS C:\Sample> Invoke-Script .\sample.ps1
    #>
    
    [Alias("..", "Invoke-Script")]
    [CmdletBinding()]
    param(
        [ArgumentCompleter({Get-PsScriptRelativePaths})]
        [ValidateScript({$_ -in $(Get-PsScriptRelativePaths)})]
        [String] $Path
    )
    
    . $Path
}

実行結果

両者ともに実用上の効果は同じなので、
1つの動画で紹介します。

補足

DynamicParamを使う方法もありますが、特別な理由がない限り使わないでください。
理由は、現時点で、この記事で紹介した方法に比べて、
同等の効果を得るためのコーディング労力が大きすぎるためです。
DynamicParamが推奨されていたのは、
この記事で述べた方法が登場する以前だと考えてください。

参照資料

今回の記事の参考になった記事

この記事を書くにあたり大変参考にした記事です。

  1. Creating Dynamic Sets for ValidateSet

DynamicParamを使った古い方法の記事

下の記事の内容は、
当該記事が書かれた当初は最適の
素晴らしい解決策でしたが、
現在では非推奨です。
参考としてお読みください。

  1. PowerShell の ダイナミックパラメータを利用して動的にパラメータを組み立てる

Recent News

Recent Tips

Tag Search