まとめ

  • スクリプト コマンドレット パラメーターの補完は極めて便利
  • パラメーター補完のためには関数ではなくスクリプト コマンドレット (高度な関数) として実装
  • 便利な反面、実装は煩雑でパラメーターを認識しづらい
  • CmdletBinding、DynamicParam{}、Begin{}, Process {} が肝
  • PowerCLI と絡めることで vCenter Server のインベントリーも補完対象に!!

はじめに

日頃の検証環境では、1,000 行弱の PowerCLI で VCSA、ESXi * 4、Router、作業用 VM のセットを都度作成/削除しています。数 100 クリック相当がコマンド一発になるので、便利は便利なのですが、如何せんパラメーターの入力に手間が掛かっていました。ESXi や vCenter は仕事柄様々なビルド番号 (現在 7 桁) のモノを使うため、自作スクリプト コマンドレットのパラメーターの値が長くなりがちで、どうにか動的な補完リストを入力時に使えないかと悶々としながら、そして長いパラメーター値を間違えながらも入力していたのでした。

DynamicParam

 

参考情報

さすがに自動化を推進する会社の人間としてまずかろう、ということでいくつか情報を検索します。調べ物のとっかかりとなったのはこちらのサイト。参考になりました、ありがとうございます。

もっとも参考になったのはこちらのサイト。デコレーターでパラメーターの属性を定義する方法から DynamicParam での定義まで、ステップ バイ ステップで丁寧に解説されています。

パラメーターの補完 = 動的にパラメーター候補を決定する、ということで DynamicParam キーワードのお世話になります。

コード

以下はサンプルコードになります。Connect-VIServer は実行済み、$podFolder は任意の仮想マシン フォルダー、パラメーター名は &“Name” としています。

    Function Try-DynamicParamByViFolder {
        [CmdletBinding()]
        Param(
        )
        DynamicParam {
            $runtimeDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary
    
            $attrs = New-Object System.Collections.ObjectModel.Collection[System.Attribute]
            $attr = New-Object System.Management.Automation.ParameterAttribute
            $attr.Mandatory = $true
            $attr.Position = 1
            $attrs.Add($attr)
    
            $attrSet = Get-Folder -Location $podFolder -NoRecursion | Select-Object -ExpandProperty Name
            $validateSetAttr = New-Object System.Management.Automation.ValidateSetAttribute($attrSet)
    
            $attrs.Add($validateSetAttr)
            $runtimeParam = New-Object System.Management.Automation.RuntimeDefinedParameter("Name", [string], $attrs)
    
            $runtimeDictionary.Add("Name", $runtimeParam)
    
            Write-Host "DynamicParam block finished."
    
            return $runtimeDictionary
        }
        Begin {
            $Name = $PSBoundParameters["Name"]
        }
        Process {
            Write-Host $Name
        }
    }

コード詳細

スクリプト コマンドレットでの実装が必要となるため、冒頭の CmdletBinding、Begin/Process/End ブロックに追加して、今回の肝となる DynamicParam ブロックが必要となります。

DynamicParam ブロックは、各パラメーターの情報を含む RuntimeDefinedParameterDictionary を返します。RuntimeDefinedParameterDictionary には、パラメーター名 (ここでは “Name”) をキーとして RuntimeDefinedParameter を格納します。次に、RuntimeDefinedParameter は、パラメーター名、型、属性のコレクションが格納されます。そして属性のコレクションには、基本的なパラメーター属性である ParameterAttribute と、パラメーター候補の集合となる ValidateSetAttribute を格納します。

キーが 2 重になるのは、ちと格好悪い。

パラメーター値の補完は、この ValidateSetAttribute が肝となっており、コンストラクタに候補となる String の配列、String[] をぶち込みます。作り手としては、ValidateSetAttribute に渡す String の配列をどのように作るかがポイントとなります。上の例では、$podFolder に格納されている (仮想マシン) フォルダーの子フォルダーの一覧を、パラメーター候補としています。

最後に Begin ブロックで $Name 変数に $SBoundParameters から引き抜いたパラメーターを代入して、Process ブロックで利用できる様にします。当初の記事では Process ブロックを端折っていたのでこの部分が抜けていました。この辺自動的にやってくれるといいのですが…。

ぱっと見でも感じ取れるように New-Object を多く使っており、可読性が非常に悪いです。もともと C# でコマンドレットを記述することを前提にしている仕組みを、PowerShell では DynamicParam ブロックを抜け道として使えるようにした感があります (私の邪推かもしれません)。about_Functions_Advanced_Parameters にも記載があるように、何でもかんでも補完させると PowerShell の可読性が落ちる弊害があるので、極力 DynamicParam の使い処を減らす、共通した処理は関数にまとめる、などの対応が必要でしょう。

各クラスの詳細はこちらをご参照下さい。それほど説明がないのですが…。

利用風景

PowerShell ISE内での実行風景をいくつかご紹介します。

vCenter Server の仮想マシン インベントリーは以下のようになっています。pods 仮想マシン フォルダーの下にいくつかのフォルダーがあることが確認できます。

様々なバージョンで vSAN の挙動確認をする必要があるのです。

PowerShell ISE 内で、サンプルのスクリプト コマンドレット名 Try-DynamicParamByViFolder を入力し、- (ハイフン) まで入力し パラメーターの候補を表示させます。CmdletBinding しているのでコマンドレット標準のパラメーターも表示されています。

-Name とパラメーター名を入力後、スペースを入力すると、パラメーター値の候補が表示されます。きちんと vCenter Server のインベントリー内の pods 仮想マシン フォルダーの子フォルダーの一覧と一致していることが確認できます。

おわりに

如何せん MSDN にすら情報が少ないので、本当に困ったときに痺れるだろうな、というのが DynamicParam に対しての感想になります。しかしながら、個人用途に近い用途であるならば、スクリプト コマンドレットを使えば使うほどやみつきになること間違いなしの機能だと思います。