PowerShellのパイプラインは、PowerShellシェルとスクリプト言語の中でも最も重要で便利な機能の一つです。その動作原理と可能性を理解すれば、自分自身の関数でそのパワーを活用することができます。このチュートリアルでは、まさにそれを行います!
PowerShellのパイプラインを使用すると、コマンドを連鎖させて単一の「パイプライン」を構築することができます。これによりコードを簡素化し、並列処理などを可能にします。パイプラインを学び、自分自身の関数を作成してパイプラインを活用する準備ができたら、始めましょう!
前提条件
この投稿はチュートリアルであり、すべて実演を行います。一緒に進める場合は、PowerShell v3以上が必要です。このチュートリアルでは、Windows PowerShell v5.1を使用します。
PowerShellパイプラインの理解
ほとんどのPowerShellコマンドは、パラメーターを介して入力を受け取ります。コマンドは入力としてオブジェクトを受け取り、内部で何かを行います。そして、オプションで出力としてオブジェクトを返します。
パイプライン内のコマンドは、リレー競技の人間ランナーのように振る舞います。最初と最後以外のすべてのランナーは、前のランナーからバトン(オブジェクト)を受け取り、次のランナーに渡します。
例えば、Stop-Service
コマンドレットには、InputObject
というパラメータがあります。このパラメータを使用すると、Stop-Service
に渡す特定のタイプのオブジェクトを指定できます。
InputObject
パラメータを使用するには、Get-Service
を介してサービスオブジェクトを取得し、次にオブジェクトを以下に示すようにInputObject
パラメータに渡すことができます。この方法では、InputObject
パラメータを介してStop-Service
コマンドレットに入力を提供することができます。
このStop-Service
コマンドへの入力の渡し方は、2つの異なるステップが必要です。PowerShellはまずGet-Service
を実行し、出力を変数に保存し、その値をInputObject
パラメータを介してStop-Service
に渡す必要があります。
さて、上記のスニペットを下記のスニペットと比較してみましょう。同じことを行いますが、はるかに簡単です。 $services
変数を作成する必要も、InputObject
パラメータを使用する必要もありません。代わりに、PowerShellはInputObject
パラメータを使用する意図を「知っています」。これは、パラメータバインディングと呼ばれる概念によって実現されます。
これで、|
演算子を使用してコマンドを「チェイン」しました。 パイプラインを作成しました。
ただし、パイプラインを作成するために2つのコマンドだけを使用する必要はありません。コマンドのパラメータがサポートしている場合、チェインするコマンドの数は任意です。例えば、以下のコードスニペット:
Get-Service
cmdletが返すすべてのオブジェクトをWhere-Object
cmdletに渡します。Where-Object
cmdletは、それぞれのオブジェクトのStatus
プロパティを確認し、値がRunning
のオブジェクトのみを返します。- その後、それらのオブジェクトは
Select-Object
に送られ、オブジェクトのName
およびDisplayName
プロパティのみを返します。 Select-Object
の出力を受け入れる他のcmdletが存在しないため、コマンドはオブジェクトを直接コンソールに返します。
Where-Object
およびSelect-Object
cmdletは、パイプライン入力を処理する方法をパラメータバインディングという概念で理解しています。次のセクションで説明します。
パイプラインに関する詳細な情報については、
Get-Help about_pipelines
コマンドを実行してください。
パイプラインパラメータバインディング
一見すると、パイプラインは些細なものに見えるかもしれません。結局、それは単にコマンドから別のコマンドにオブジェクトを渡しているだけです。しかし、実際にはパイプラインはもっと複雑です。コマンドはパラメータを介してのみ入力を受け付けます。パイプラインは、明示的に定義しなくてもどのパラメータを使用するかを何らかの方法で判断する必要があります。
コマンドがパイプライン経由で入力を受け取る際に、どのパラメータを使用するかを判断するタスクは、パラメータバインディングとして知られています。パイプラインから入ってくるオブジェクトをパラメータに正しくバインドするためには、受信コマンドのパラメータがサポートしている必要があります。コマンドパラメータは、ByValue
および/またはByPropertyName
のいずれかの方法でパイプラインのパラメータバインディングをサポートします。
ByValue
Get-ChildItem
コマンドレットには、Path
というパラメータがあり、文字列オブジェクト型とByValue
を介したパイプライン入力を受け入れます。そのため、'C:\Windows' | Get-ChildItem
のように実行すると、C:\Windows
は文字列であるため、C:\Windowsディレクトリ内のすべてのファイルが返されます。
ByPropertyName
コマンドパラメータは、オブジェクト全体ではなく、そのオブジェクトの単一のプロパティを受け入れます。これはオブジェクトの型ではなく、プロパティ名を見て行います。
Get-Process
コマンドレットには、パイプライン入力ByPropertyName
を受け入れるように設定されたName
パラメータがあります。したがって、[pscustomobject]@{Name='firefox'} | Get-Process
のようにName
プロパティを持つオブジェクトをGet-Process
コマンドレットに渡すと、PowerShellは入力オブジェクトのName
プロパティをName
パラメータとマッチングまたはバインドし、その値を使用します。
パイプライン入力をサポートするコマンドパラメータの発見
前述したように、すべてのコマンドがパイプライン入力をサポートしているわけではありません。コマンドの作成者は開発時にその機能を作成する必要があります。コマンドには少なくとも1つのパイプラインをサポートするパラメータが必要であり、ByValue
またはByPropertyName
を指定する必要があります。
どのコマンドとそのパラメータがパイプライン入力をサポートしているかを知る方法はありますか?試行錯誤するだけでもできますが、Get-Help
コマンドを使用したPowerShellヘルプシステムを使用するともっと良い方法があります。
例えば、以下のGet-ChildItem
コマンドレットのPath
パラメータを見てみましょう。両方のタイプのパイプライン入力をサポートしていることがわかります。

コマンドのパラメータがパイプライン入力をサポートしているかどうかを知ったら、以下のようにその機能を活用することができます。
しかし、一方で、Get-Service
のDisplayName
パラメータはパイプライン入力をサポートしていません。

独自のパイプライン関数の作成
標準のPowerShellのcmdletがパイプライン入力をサポートしているということは、その機能を活用することができないわけではありません。幸いにも、パイプライン入力を受け入れる関数を作成することができます。
デモンストレーションのために、既存の関数Get-ConnectionStatus
を使用してみましょう。
- この関数には、パイプライン入力を受け入れない単一のパラメータ
ComputerName
があり、それに対して1つ以上の文字列を渡すことができます。ComputerName
パラメータがパイプライン入力を受け入れないことは、パラメータ属性([Parameter()]
)として定義されていないことからわかります。 - その後、関数はそれぞれの文字列を読み取り、
Test-Connection
cmdletを実行します。 - 各文字列のコンピュータ名が渡された場合、
ComputerName
およびStatus
プロパティを持つオブジェクトが返されます。
次に、以下のようにComputerName
パラメーターに1つ以上のホスト名またはIPアドレスを渡して、Get-ConnectionStatus
を呼び出す必要があります。
ステップ1:パイプライン入力の許可
この関数がパイプライン入力を受け入れるようにするには、まず適切なパラメータ属性を定義する必要があります。この属性は、パイプライン入力ByValueを受け入れるためのValueFromPipeline
であるか、パイプライン入力ByPropertyNameを受け入れるためのValueFromPipelineByPropertyName
であるかのいずれかです。
この例では、以下に示すように[Parameter()]
のブラケット内にValueFromPipeline
パラメータ属性を追加します。
この時点では、技術的にはこれで十分です。Get-ConnectionStatus
関数は、渡された任意の文字列オブジェクトをComputerName
パラメーターにバインドします。ただし、パラメーターバインディングが行われているとしても、関数がそれに意味のある処理を行うわけではありません。
ステップ2:プロセスブロックの追加
パイプラインから入力されるすべてのオブジェクトを処理するために、次にProcess
ブロックを追加する必要があります。このブロックは、PowerShellにパイプラインから受け取った各オブジェクトを処理するように指示します。
Process
ブロックがない場合、PowerShellはパイプラインから最初のオブジェクトのみを処理します。Process
ブロックは、PowerShellにオブジェクトの処理を続けるように指示します。
以下のように、Process
ブロックを追加して、関数の機能をすべて囲んでください。
Process
ブロック内から常に出力を送信してください。Process
ブロックからの出力は、オブジェクトをパイプラインにストリームすることで、他のコマンドがパイプラインからそれらのオブジェクトを受け入れることができるようにします。
PowerShell パイプラインにオブジェクトを渡す
上記の関数で Process
ブロックが定義されたら、次のようにパイプラインを介して ComputerName
パラメータに値を渡して関数を呼び出すことができます。
この段階では、パイプラインの真のパワーを活用し、さらに多くのコマンドを組み合わせることができます。たとえば、おそらくテキストファイル C:\Test\computers.txt に、以下のように改行で区切られた IP アドレスの行があるとします。
次に、Get-Content
コマンドレットを使用して、テキストファイル内の各 IP アドレスを読み取り、それらを直接 Get-ConnectionStatus
関数に渡すことができます。
さらに、このセットアップをさらに進めて、Get-ConnectionStatus
が返すオブジェクトを直接 ForEach-Object
コマンドレットにパイプで渡すこともできます。
以下のコード:
- テキストファイル内のすべてのコンピュータ名を読み込み、それらを
Get-ConnectionStatus
関数に渡します。 Get-ConnectionStatus
は、各コンピュータ名を処理し、ComputerName
とStatus
のプロパティを持つオブジェクトを返します。Get-ConnectionStatus
は、それぞれのオブジェクトをForEach-Object
コマンドレットに渡し、シアンで色付けされた単一の文字列として人間が読めるステータスを返します。
ComputerName
パラメーターでパイプライン入力が有効にされていない場合、またはGet-ConnectionStatus
がProcess
ブロック内でオブジェクトを返さなかった場合、PowerShellはすべてのオブジェクト(IPアドレス)が処理されるまで、コンソールにステータスを返しません。
プロパティ名によるパイプラインバインディング
今まで、Get-ConnectionStatus
コマンドレットは、'127.0.0.1'、'192.168.1.100'
のような文字列の配列を受け入れることでパイプライン入力を受け入れるように設定されています。この関数は、IPアドレスのテキストファイルではなく、CSVファイルから入力が受け取られた場合にも正常に動作しますか?
おそらく、C:\Test\pc-list.csvに以下のようなCSVファイルがあるとします。
CSVファイルの
ComputerName
フィールドは、Get-ConnnectionStatus
のComputerName
パラメーターと同じ名前です。
CSVをインポートしてパイプラインを介してGet-ConnectionStatus
に渡す場合、関数はComputerName
列で予期しない結果を返します。
何が間違っているか推測できますか?パラメータ名は一致しているので、なぜPowerShellのパイプラインがImport-CSV
の出力をGet-ConnectionStatus
のComputerName
パラメータにバインドしなかったのでしょうか?それは、パラメータ属性のValueFromPipelineByPropertyName
が必要だからです。
現時点では、関数のComputerName
パラメータは[Parameter(ValueFromPipeline)]
というパラメータ定義を持っています。したがって、入力ByPropertyNameをサポートするためにValueFromPipelineByPropertyName
を追加する必要があります。以下に示します。
パイプラインがByPropertyNameをサポートするようになったら、PowerShellにオブジェクトのプロパティ名とオブジェクトの型を調べるように指示します。この変更を行った後、予想される出力が表示されるはずです。
要約
このチュートリアルでは、PowerShellのパイプラインの動作方法、パラメータのバインド方法、さらにはパイプラインをサポートする独自の関数の作成方法について学びました。
関数はパイプラインなしでも動作しますが、オブジェクトをコマンドからコマンドに「ストリーム化」し、コードを簡素化することはできません。
パイプラインに対応させることで恩恵を受けることができる、作成したり作成しようとしている関数を思い浮かべることはできますか?