PowerShellパラメータ:スクリプトのパワーを解放する

すべてのPowerShellコマンドには1つ以上のパラメータ(引数とも呼ばれることがあります)があります。PowerShell関数でPowerShellパラメータを使用していない場合、適切なPowerShellコードを書いていません!

この記事では、PowerShellパラメータまたは引数の作成と使用のほぼすべての側面について学びます!

これは私の書籍「PowerShell for SysAdmins」の一部です。PowerShellを学びたい、またはトレードのいくつかのトリックを学びたい場合は、チェックしてみてください

なぜパラメータが必要なのですか?

関数の作成を開始すると、パラメータを含めるかどうか、およびそれらのパラメータの動作方法を選択できます。

たとえば、Microsoft Officeをインストールする関数があるとしましょう。おそらく、関数内部でOfficeインストーラをサイレントモードで呼び出しているかもしれません。関数が行うことは、私たちの目的には関係ありません。関数の基本的な構造は、次のようになります(関数名とスクリプトブロック)。

function Install-Office {
    ## サイレントインストーラを実行する
}

この例では、パラメータなしでInstall-Officeを実行しました。それはやりたいことを行いました。

Install-Office関数にパラメータがあるかどうかは問題ではありませんでした。明らかに、必須のパラメータがなかったようです。そうでなければ、PowerShellはパラメータを使用せずに実行させてくれなかったはずです。

いつPowerShellパラメータを使用するか

Officeにはさまざまなバージョンがあります。おそらくOffice 2013と2016をインストールする必要があります。現在、これを指定する方法はありません。振る舞いを変更するたびに関数のコードを変更する必要があります。

たとえば、異なるバージョンをインストールするために2つの別々の関数を作成することができます。

function Install-Office2013 {
    Write-Host 'I installed Office 2013. Yippee!'
}

function Install-Office2016 {
    Write-Host 'I installed Office 2016. Yippee!'
}

これを行うことはできますが、スケーラブルではありません。Officeの各バージョンごとに個別の関数を作成する必要があります。不必要にコードを重複することになります。

代わりに、関数の振る舞いを変更するためにランタイムで異なる値を渡す方法が必要です。どのようにすれば良いですか?

そうです!パラメータ、または一部の人々が引数と呼ぶものです。

コードを変更せずに異なるOfficeのバージョンをインストールしたいのであれば、この関数に少なくとも1つのパラメータを追加する必要があります。

すばやくPowerShellのパラメータを考える前に、まず自分に質問することが重要です。「この関数で必要な最小の変更または変更は何ですか?」

この関数のコードを変更せずにこの関数を再実行する必要があることを覚えておいてください。この例では、おそらくVersionパラメータを追加する必要があると思われます。ただし、数十行のコードを持つ関数の場合、答えは明らかではありません。できるだけ正確にその質問に答える限り、常に役立ちます。

したがって、Versionパラメータが必要であることを知っています。では、次はどうすればよいでしょうか?追加することができますが、優れたプログラミング言語のように、それを実現するための複数の方法があります。

このチュートリアルでは、私の10年近いPowerShellの経験に基づいて、パラメータを作成するための「最良の」方法を紹介します。ただし、これが唯一の方法ではありませんことを知っておいてください。

位置パラメータというものが存在します。これらのパラメータを使用すると、パラメータ名を指定せずに値をパラメータに渡すことができます。位置パラメータは機能しますが、「ベストプラクティス」とは考えられていません。なぜなら、特に多くのパラメータが関数に定義されている場合、読みにくくなるからです。

シンプルなPowerShellパラメータの作成

関数にパラメータを作成するには、2つの主要なコンポーネントが必要です。1つはparamブロックであり、もう1つはパラメータ自体です。paramブロックは、paramキーワードに続くカッコで定義されます。

An example of a param block
function Install-Office { [CmdletBinding()] param() Write-Host 'I installed Office 2016. Yippee!' }

この時点では、関数の実際の機能はまったく変わりません。ただ、いくつかのプランビングをまとめて、最初のパラメータに備えています。

paramブロックが準備できたら、次にパラメータを作成します。私が提案するパラメータの作成方法は、Parameterブロックの後に、パラメータの型を記述し、その下にパラメータ変数名を続ける方法です。

An example of a param block
function Install-Office { [CmdletBinding()] param( [Parameter()] [string]$Version ) Write-Host 'I installed Office 2016. Yippee!' }

これで、PowerShellで関数パラメータを作成しましたが、ここで実際に何が起こったのでしょうか?

Parameterブロックは、パラメータのオプションですが推奨される部分です。paramブロックと同様に、これはパラメータに追加の機能を追加するための「関数プランビング」です。2行目では、パラメータの型を定義します。

この場合、Versionパラメータを文字列にキャストすることを選択しました。明示的な型の定義は、このパラメータに渡される値が既に文字列でない場合、常に文字列に「変換」されることを意味します。

型は必須ではありませんが、強く推奨されます。パラメータの型を明示的に定義することで、将来の多くの予期しない状況を大幅に減らすことができます。信じてください。

パラメータが定義されたので、Versionパラメータを使用してInstall-Officeコマンドを実行できます。バージョン文字列(2013など)をVersionパラメータに渡します。パラメータに渡される値は、パラメータの引数または値と呼ばれることがあります。

Passing a Parameter to the Function
PS> Install-Office -Version 2013 I installed Office 2016. Yippee!

ここで何が起こっているのでしょうか?バージョン2013をインストールしたいと伝えたのに、まだ2016がインストールされていると言われています。パラメータを追加すると、関数のコードを変数に変更することを忘れないでください。パラメータが関数に渡されると、その変数は渡された値に展開されます。

2016の静的テキストを変更し、Versionパラメータの変数に置き換え、シングルクォーテーションをダブルクォーテーションに変換して、変数が展開されるようにします。

Modifying function code account for a parameter
function Install-Office { [CmdletBinding()] param( [Parameter()] [string]$Version ) Write-Host "I installed Office $Version. Yippee!" }

これで、Versionパラメータに渡された値は、$Version変数として関数に渡されることがわかります。

必須パラメータ属性

[Parameter()]行はただの「関数の配管」であり、関数をさらなる作業に準備する必要があると述べたことを思い出してください。パラメータにパラメータ属性を追加することが、先ほど話していた追加作業です。

A parameter doesn’t have to be a placeholder for a variable. PowerShell has a concept called parameter attributes and parameter validation. Parameter attributes change the behavior of the parameter in a lot of different ways.

たとえば、最も一般的なパラメータ属性の1つはMandatoryキーワードです。デフォルトでは、Versionパラメータを使用せずにInstall-Office関数を呼び出すことができ、正常に実行されます。Versionパラメータはオプションです。ただし、関数内の$Version変数を展開しないため、値がない場合でも実行されます。

パラメータを作成する際には、ユーザーに常にそのパラメータを使用してもらいたい場合があります。関数のコードの中でパラメータの値に依存し、パラメータが渡されない場合、関数は失敗します。このような場合、ユーザーにこのパラメータ値を関数に渡すように強制したいと思います。そのパラメータを必須にしたいのです。

パラメータを強制することは、ここで構築した基本的なフレームワークがあれば簡単です。パラメータのカッコ内にMandatoryキーワードを含める必要があります。これを設定すると、パラメータなしで関数を実行すると、値が入力されるまで実行が停止します。

Using a mandatory parameter
function Install-Office { [CmdletBinding()] param( [Parameter(Mandatory)] [string]$Version ) Write-Host "I installed Office $Version. Yippee!" } PS> Install-Office cmdlet Install-Office at command pipeline position 1 Supply values for the following parameters: Version:

関数はVersionパラメータの値が指定されるまで待機します。値を指定してEnterを押すと、PowerShellは関数を実行し、次に進みます。パラメータに値を指定すると、PowerShellはパラメータを毎回プロンプトで要求しません。

PowerShellパラメータの検証属性

パラメータを必須にすることは、追加できる最も一般的なパラメータ属性の1つですが、パラメータの検証属性も使用できます。プログラミングでは、ユーザーの入力をできるだけ厳密に制限することが常に重要です。関数やスクリプトにユーザー(あるいはあなた自身)が渡すことができる情報を制限することで、あらゆる状況に対応するための不要なコードを関数内に含める必要がなくなります。

例による学習

たとえば、Install-Office関数では、値2013を渡すことで、それが動作することを示しました。私がコードを書いたからです!私は(コードでは絶対にこれをしないでください!)あなたが何かを知っている人はバージョンを2013または2016と指定するということが明らかであると仮定しています。しかし、あなたにとって明らかなことが他の人にとってはそうではないかもしれません。

バージョンに関して技術的なことを言うと、Microsoftが過去に使用していたバージョン管理スキーマを引き続き使用している場合、2013バージョンを15.0、2016バージョンを16.0と正確に指定する方が適切かもしれません。しかし、もしも彼らが2013または2016のバージョンを指定すると仮定して、そのバージョンのフォルダを探すなどのコードが関数内にある場合はどうでしょうか?

以下は、$Version文字列をファイルパスで使用している例です。もしも誰かがOffice2013またはOffice2016のフォルダ名を完全に指定しない値を渡した場合、エラーが発生するか、予期しないフォルダが削除されたり、予期しない変更が行われる可能性があります。

Assuming Parameter Values
function Install-Office { [CmdletBinding()] param( [Parameter(Mandatory)] [string]$Version ) Get-ChildItem -Path "\\SRV1\Installers\Office$Version" } PS> Install-Office -Version '15.0' Get-ChildItem : Cannot find path '\SRV1\Installers\Office15.0' because it does not exist. At line:7 char:5 Get-ChildItem -Path "\\SRV1\Installers\Office$Version" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ CategoryInfo : ObjectNotFound: (\SRV1\Installers\Office15.0:String) [Get-ChildItem], ItemNotFoundExcep tion FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.GetChildItemCommand

ユーザーが入力することを予想される内容に制限を加えるために、PowerShellのパラメーター検証を追加することができます。

ValidateSetパラメーター検証属性を使用する

使用できるさまざまな種類のパラメーター検証があります。完全なリストについては、Get-Help about_Functions_Advanced_Parametersを実行してください。この例では、ValidateSet属性がおそらく最適です。

ValidateSet検証属性では、パラメーターの値として許可される値のリストを指定できます。ここでは、文字列2013または2016のみを考慮しているため、ユーザーがこれらの値のみを指定できるようにしたいと思います。それ以外の場合、関数はすぐに失敗し、なぜ失敗したのかを通知します。

パラメーターの検証属性は、元のParameterキーワードのすぐ下に追加できます。この例では、パラメーター属性の括弧の中に、20132016のアイテムの配列があります。パラメーターの検証属性は、Versionの有効な値が2013または2016であることをPowerShellに伝えます。セット内に存在しない値を渡そうとすると、特定のオプションのみが利用可能であることを示すエラーメッセージが表示されます。

Using the ValidateSet parameter validation attribute
function Install-Office { [CmdletBinding()] param( [Parameter(Mandatory)] [ValidateSet('2013','2016')] [string]$Version ) Get-ChildItem -Path "\\SRV1\Installers\Office$Version" } PS> Install-Office -Version 15.0 Install-Office : Cannot validate argument on parameter 'Version'. The argument "15.0" does not belong to the set "2013,2016" specified by the ValidateSet attribute. Supply an argument that is in the set and then try the command again. At line:1 char:25 Install-Office -Version 15.0 ~~~~ CategoryInfo : InvalidData: (:) [Install-Office], ParameterBindingValidationException FullyQualifiedErrorId : ParameterArgumentValidationError,Install-Office

ValidateSet属性は一般的なバリデーション属性です。パラメータの値を制限する方法の詳細については、Get-Help about_Functions_Advanced_Parametersを実行して、Functions_Advanced_Parametersのヘルプトピックを参照してください。

パラメータセット

たとえば、特定のPowerShellパラメータを他のパラメータと一緒に使用したい場合があります。おそらく、Install-Office関数にPathパラメータを追加しました。このパスは、インストーラのバージョンをインストールします。この場合、ユーザーがVersionパラメータを使用しないようにしたいです。

パラメータセットが必要です。

パラメータはセットにグループ化され、同じセット内の他のパラメータとのみ使用できるようになります。以下の関数を使用すると、VersionパラメータとPathパラメータの両方を使用してインストーラのパスを作成できます。

function Install-Office {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [ValidateSet('2013','2016')]
        [string]$Version,
        
        [Parameter(Mandatory)]
        [string]$Path
    )
    
    if ($Version) {
        Get-ChildItem -Path "\\SRV1\Installers\Office$Version"
    } elseif ($Path) {
        Get-ChildItem -Path $Path
    }
}

ただし、これには問題があります。ユーザーは両方のパラメータを使用する可能性があります。さらに、両方のパラメータが必須であるため、望ましくない場合でも両方を使用する必要があります。これを修正するには、以下のように各パラメータをパラメータセットに配置します。

function Install-Office {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory,ParameterSetName = 'ByVersion')]
        [ValidateSet('2013','2016')]
        [string]$Version,
        
        [Parameter(Mandatory, ParameterSetName = 'ByPath')]
        [string]$Path
    )
    
    if ($Version) {
        Get-ChildItem -Path "\\SRV1\Installers\Office$Version"
    } elseif ($Path) {
        Get-ChildItem -Path $Path
    }
}

各パラメータにパラメータセット名を定義することで、パラメータのグループを制御できます。

デフォルトのパラメータセット

ユーザーがパラメータなしでInstall-Officeを実行しようとした場合はどうなりますか?これは考慮されておらず、フレンドリーなエラーメッセージが表示されます。

No parameter set

これを修正するには、CmdletBinding()の範囲内でデフォルトのパラメーターセットを定義する必要があります。これにより、パラメーターが明示的に使用されなかった場合に、関数が使用するパラメーターセットを指定します。つまり、[CmdletBinding()][CmdletBinding(DefaultParameterSetName = 'ByVersion')]に変更します。

これで、Install-Officeを実行すると、使用されるパラメーターセットがVersionパラメーターを要求します。

パイプライン入力

これまでの例では、通常の-ParameterName Valueの構文を使用してのみ渡すことができるPowerShellパラメーターを持つ関数を作成してきました。しかし、既に学んだように、PowerShellには直感的なパイプラインがあり、通常の構文を使用せずにコマンドの出力を別のコマンドにシームレスに渡すことができます。

パイプラインを使用すると、パイプ記号|を使用してコマンドを「チェーン」することができます。これにより、ユーザーはGet-Serviceの出力をStart-Serviceに送信することで、NameパラメーターをStart-Serviceに渡すショートカットを作成できます。

「古い」方法:ループを使用する

作業中のカスタム関数では、Officeをインストールし、Versionパラメーターを持っています。次に、1行にコンピューター名のリストがあり、それらのコンピューターにインストールする必要があるOfficeのバージョンが2行目にあります。CSVファイルは次のようなものです:

ComputerName,Version
PC1,2016
PC2,2013
PC3,2016

各コンピューターの隣にあるOfficeのバージョンをそのコンピューターにインストールしたいとします。

まず、各イテレーションごとに異なるコンピュータ名を渡すために、関数にComputerNameパラメータを追加する必要があります。以下に、架空の関数内に存在する可能性のあるコードを示し、変数が関数内でどのように展開されるかを確認するためにWrite-Hostインスタンスを追加しました。

Adding the ComputerName parameter
function Install-Office { [CmdletBinding()] param( [Parameter(Mandatory)] [ValidateSet('2013','2016')] [string]$Version, [Parameter()] [string]$ComputerName ) <# ## ここにリモートへの接続コードを追加 Invoke-Command -ComputerName $ComputerName -ScriptBlock { ## このコンピュータにOfficeのバージョンをインストールするための処理を実行 Start-Process -FilePath 'msiexec.exe' -ArgumentList 'C:\Setup\Office{0}.msi' -f $using:Version } #> Write-Host "I am installing Office version [$Version] on computer [$ComputerName]" }

関数にComputerNameパラメータが追加されたら、CSVファイルを読み取り、コンピュータ名とバージョンの値をInstall-Office関数に渡すことができます。

$computers = Import-Csv -Path 'C:\ComputerOfficeVersions.csv'
foreach ($pc in $computers) {
    Install-Office -Version $_.Version -ComputerName $_.ComputerName
}

パラメータのためのパイプライン入力の構築

この方法では、CSVの行を読み取り、各行のプロパティを関数に渡すためのループを完全に省略します。このセクションでは、foreachループを完全に省略し、代わりにパイプラインを使用することを望んでいます。

現状では、関数はパイプラインをサポートしていません。パイプラインを使用して各コンピュータ名とバージョンを関数に渡すことができると直感的に思われるかもしれません。以下では、CSVを読み取り、直接Install-Officeに渡していますが、これは機能しません。

No function pipeline input defined
PS> Import-Csv -Path 'C:\ComputerOfficeVersions.csv' | Install-Office

あなたが仮定しようとしても、それだけで動作するわけではありません。 Import-Csv がオブジェクトのプロパティとして送信していることを知っているのに、Version パラメータの入力を求められています。なぜ動作しないのか?それはまだパイプラインのサポートが追加されていないからです。

PowerShell関数には2つの種類のパイプライン入力があります。 ByValue(完全なオブジェクト)と ByPropertyName(単一のオブジェクトのプロパティ)です。 Import-Csv の出力を Install-Office の入力にどのように結合するのが最適だと思いますか?

全くリファクタリングせずに、Import-Csv が既にCSVの列として VersionComputerName のプロパティを返しているため、ByPropertyName メソッドを使用できます。

カスタム関数にパイプラインサポートを追加するのは、思っているよりも簡単です。これは、2つのキーワードで表されるパラメータ属性であり、ValueFromPipeline または ValueFromPipelineByPropertyName のいずれかです。

この例では、Import-Csv から返された ComputerNameVersion のプロパティを Install-OfficeVersionComputerName のパラメータにバインドしたいので、ValueFromPipelineByPropertyName を使用します。

これらのパラメータの両方にこのキーワードを追加し、パイプラインを使用して関数を再実行します。

Adding Pipeline Support
function Install-Office { [CmdletBinding()] param( [Parameter(Mandatory,ValueFromPipelineByPropertyName)] [ValidateSet('2013','2016')] [string]$Version, [Parameter(ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] [string]$ComputerName ) <# ## リモートに接続するためのコードをここに記述 Invoke-Command -ComputerName $ComputerName -ScriptBlock { ## このコンピュータに Office のバージョンをインストールするための処理を実行 Start-Process -FilePath 'msiexec.exe' -ArgumentList 'C:\Setup\Office{0}.msi' -f $using:Version } #> Write-Host "I am installing Office version [$Version] on computer [$ComputerName]" }

それは奇妙です。CSV の最後の行でしか実行されませんでした。なぜでしょうか?これは、パイプラインサポートを持たない関数をビルドする際に必要ではない概念をスキップしたためです。

プロセスブロックを忘れないでください!

パイプラインサポートを必要とする関数を作成する場合、少なくとも “埋め込まれた” ブロックとして process を含める必要があります。このプロセスブロックは、パイプライン入力を受け取った場合に、すべての反復のために関数を実行するように PowerShell に指示します。デフォルトでは、最後の反復のみが実行されます。

理論的には、beginend のような他のブロックを追加することもできますが、スクリプターはそれらをあまり頻繁に使用しません。

PowerShell にこの関数を入力オブジェクトごとに実行するように指示するために、その内部にコードを含む process ブロックを追加します。

Adding the process block
function Install-Office { [CmdletBinding()] param( [Parameter(Mandatory,ValueFromPipelineByPropertyName)] [ValidateSet('2013','2016')] [string]$Version, [Parameter(ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] [string]$ComputerName ) process { <# ## リモートに接続するためのコードをここに記述 Invoke-Command -ComputerName $ComputerName -ScriptBlock { ## このコンピュータに Office のバージョンをインストールするための処理を実行 Start-Process -FilePath 'msiexec.exe' -ArgumentList 'C:\Setup\Office{0}.msi' -f $using:Version } #> Write-Host "I am installing Office version [$Version] on computer [$ComputerName]" } }

今では、Import-Csvから返された各オブジェクトのVersionComputerNameのプロパティが、Install-Officeに渡され、VersionComputerNameのパラメーターにバインドされることがわかります。

リソース

関数のパラメーターの動作についてもっと詳しく知りたい場合は、私のPowerShell関数に関するブログ記事をチェックしてください。

I also encourage you to check my Pluralsight course entitled Building Advanced PowerShell Functions and Modules for an in-depth breakdown of everything there is to know about PowerShell functions, function parameters, and PowerShell modules.

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