PowerShellにおけるパラメータバインディング

以前書いた記事[Powershellにおけるバインディングについてのまとめ](https://qiita.com/yasaram/items/7715af168cfc8b962be9)を改めて整理した記事です。
パラメータバインディングについてよくわからなくなったけど調べても日本語で書かれた記事は少ないように感じたので書きました。

##パラメータバインディング
コマンドにより生成されたオブジェクトは、パイプの先にある次のコマンドの引数になろうとします。
PowerShellは次のコマンドのパラメータとパイプラインオブジェクトの型にもとづいて解析を行い、それに応じた挙動をします。
そうしたパラメータバインディング（パラメータとパイプラインオブジェクトの関連づけ）は次の3つの条件を満たすパラメータを探します。

1.  パラメータがパイプライン入力（パイプラインオブジェクトがそのパラメータの引数になること）を許可する（ヘルプで確認可能）
2.  パラメータの型（パラメータが要求する型）とパイプラインオブジェクトの型が一致している、あるいは型変換が可能である
3.  パラメータがすでにそのコマンド内で使用されていない（パラメータが他の引数とバインドされていない）

パラメータバインディングの対象になるパイプラインオブジェクトは自動的に入力されるため実際に<u>入力する必要はありません</u>。

##パラメータバインディングの例と検証
###ベースとなるコード
次の例を使ってパラメータバインディングがどのように行われるか見ていきます。
これは、配列の要素ひとつひとつがパイプラインオブジェクトとなり次のコマンドに渡しています。

```powershell
>> 1..3 | Write-Host
1
2
3
```

###成立条件を満たしているかを検証

**条件1. パラメータがパイプライン入力を許可**
`Write-Host`のヘルプのパラメータ欄の「パイプラインの入力を許可する」が`True`になっているものを探します。

```powershell
>> Get-Help Write-Host -Parameter * |
    Where-Object {$_.PipelineInput -match "true" }
# $_: 現在のパイプラインオブジェクト
```
```
-Object <Object>
    Specifies objects to display in the console.

    必須                         false
    位置                         0
    既定値                       None
    パイプライン入力を許可する   True (ByValue)
    ワイルドカード文字を許可する false
```
以上から、-Objectは条件を満たしています。

**条件2. パラメータの型とパイプラインオブジェクトの型が一致している、あるいは型変換が可能**

【パイプラインオブジェクトの型】
`GetType()`プロパティで調べます。

```powershell
1..3 | ForEach-Object{$_.GetType()}
```
```
IsPublic IsSerial Name           BaseType
-------- -------- ----           --------
True     True     Int32          System.ValueType
True     True     Int32          System.ValueType
True     True     Int32          System.ValueType
```

【パラメータが要求する型】
先ほど開いたヘルプから、`System.Object`であることがわかります。
`System.ValueType`は`System.Object`から継承されたものなので、型変換が可能となり条件を満たします。

**条件3. パラメータがすでにそのコマンド内で使用されていない**
上記の例ではWrite-Hostの-Objectパラメータが使われていない（他に引数がない）ため、条件を満たしています。

##パイプラインオブジェクトの入力パターン
###2種類のパイプライン入力
パイプライン入力を許可するパラメータは`ByValue`と`ByPropertyName`の2種類に分けられます。「パイプライン入力を許可する」がTrueであっても、この`ByValue`と`ByPropertyName`の入力条件を満たしていないとパイプライン入力は行われません。
そのパラメータがどちらに属するかはヘルプのパラメータ欄「パイプライン入力を許可する」の右に記載されています。

###ByValueとByPropertyValueが入力オブジェクトに求める条件・パラメータへの入力内容・例
`ByValue`
【条件】パイプラインオブジェクトの型とパラメータで指定された型が一致
【入力内容】パイプラインオブジェクトの値
【例】前項を参照

`ByPropertyName`
【条件】パイプラインオブジェクトのプロパティ名とパラメータ名が一致
【入力内容】パイプラインオブジェクトのプロパティ値
【例】次項を参照

##ByPropertyNameによるバインディング
オブジェクトがパイプラインを通じて`New-Item`に渡される例を以下に示します。
併せて、パラメータバインディングの条件を満たさない場合はパイプラインオブジェクトをどのように使うかを示します。

###新規ファイルの作成（パイプライン入力を許可しない場合のパイプラインオブジェクトの受け渡し）

パイプラインオブジェクトの受け渡し方法を確認するためのファイルを作成します。
デスクトップ上に2つのフォルダ（PipelineInput1とPipelineInput2とする）を作成し、PipelineInput1をカレントディレクトリにします。

```powershell
>> cd $home\desktop
>> 1, 2 | ForEach-Object{ md PipelineInput$_ }
>> cd PipelineInput1
```
次に`New-Item`を使い、PipelineInput1内にテキストファイルを3ファイル作成します。

```powershell
>> 1..3|
     ForEach-Object {
         New-Item -Name "file_$_.txt" -ItemType File -Value "$_`と書き込みました"
    }
```

実行するとファイル名が確認できます。

```
ディレクトリ: C:\Users\xxxxx\Desktop\PipelineInput1


Mode                LastWriteTime         Length Name
----                -------------         ------ ----
-a----        7/16/2019   1:01 PM             25 file_1.txt
-a----        7/16/2019   1:01 PM             25 file_2.txt
-a----        7/16/2019   1:01 PM             25 file_3.txt
```
ファイルに書き込まれた内容も確認します。

```powershell
>> Get-Content file_*.txt -encoding UTF8
```
```
1と書き込みました
2と書き込みました
3と書き込みました
```
###既存の情報を使った新規ファイルの作成（パイプライン入力を利用したオブジェクトの受け渡し）
PipelineInput1に作成したファイルの情報を取得し、そこからPipepineInput2に新規のファイルを作成します。
カレントディレクトリをPipelineInput2に変更した後、`Get-ChildItem`で取得したPipelineInput1の情報を`New-Item`に渡します。
作成するものはファイルとします。

```powershell
>> cd $home\desktop\PipelineInput2
>> Get-ChildItem $home\desktop\PipelineInput1 | New-Item -ItemType File
```
```
ディレクトリ: C:\Users\xxxxx\Desktop\PipelineInput2


Mode                LastWriteTime         Length Name
----                -------------         ------ ----
-a----        7/16/2019   1:10 PM             48 file_1.txt
-a----        7/16/2019   1:10 PM             48 file_2.txt
-a----        7/16/2019   1:10 PM             48 file_3.txt
```
先ほどと同様、その内容も確認します。

```powershell
>> Get-Content file_*.txt -encoding UTF8
```
```
C:\Users\xxxxx\desktop\PipelineInput1\file_1.txt
C:\Users\xxxxx\desktop\PipelineInput1\file_2.txt
C:\Users\xxxxx\desktop\PipelineInput1\file_3.txt
```

`New-Item`で作成するファイルの種類しか指定していないにもかかわらず、ファイル名を指定し書き込みもできました。
それでは、どのようにパラメータバインディングが行われたのでしょうか。
そこで、`New-Item`でパイプラインオブジェクトを受け入れるパラメータを確認します。

```powershell
>> Get-Help New-Item -Parameter * | Where-Object {$_.PipelineInput -match "true" }
```

```
#抜粋
-ItemType <String>
       パイプライン入力を許可する       True (ByPropertyName)
-Name <String>
       パイプライン入力を許可する       True (ByPropertyName)
-Value <Object>
       パイプライン入力を許可する       True (ByPropertyName, ByValue)
```

このうち、`-ItemType`はすでに対応する引数を指定しているためパイプライン入力の対象になりません。

次に、`-Value`を見てみると`ByPropertyName, ByValue`となっています。

`ByValue`から確認すると、`Get-ChildItem`で得られるオブジェクトの型は`System.IO.FileSystemInfo`、パラメータの型は`System.Object`なので型の変換が可能となり、`-Value`にはオブジェクトの値がバインドされます。

この時点で`ByPropertyName`にはもうバインドされません（これはPowerShellが解析する順番が決まっているためです。これについては別記事にする予定です）。

`-Name`は`ByPropertyName`となっています。`Get-ChildItem`で得られる値のプロパティには`Name`があるため、パラメータ名と完全に一致します。
他の`ByPropertyName`とされているパラメータ（`-Credential`, `-Path`）はオブジェクトのプロパティ名と一致しないためバインドされません。

そのため、プロパティ値が`-Name`にバインドされることとなります。

##リファレンス
[About_Pipelines(PowerShell Docs)](https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_pipelines?view=powershell-6)

##おわりに
わかりづらいかもしれません。
次はPowerShellのdelay-bindingについてまとめようかと考えています。