PowerShell で which コマンドを作る

msys2 等でインストールしたり where.exe を使う方法もありますが、PowerShell だけでできる方法にします。

[こちら](https://qiita.com/Hiraku/items/e42bc5756157949a9742)の記事を参考にしました。

## ファイルのパスを取得する方法
`Get-Command` で返ってくる `Definition` あるいは `Source` プロパティにファイルのパスが含まれています。
ですが、 `Source` プロパティよりも `Definition` プロパティの方が汎用性が高いので、こちらを使います。

`Get-member` をしてみると、

``` powershell
PS> gcm pwsh | gm

   TypeName: System.Management.Automation.ApplicationInfo

Name               MemberType     Definition
----               ----------     ----------
Equals             Method         bool Equals(System.Object obj)
GetHashCode        Method         int GetHashCode()
GetType            Method         type GetType()
ResolveParameter   Method         System.Management.Automation.ParameterMetadata ResolveParameter(string name)
ToString           Method         string ToString()
CommandType        Property       System.Management.Automation.CommandTypes CommandType {get;}
Definition         Property       string Definition {get;}
Extension          Property       string Extension {get;}
Module             Property       psmoduleinfo Module {get;}
ModuleName         Property       string ModuleName {get;}
Name               Property       string Name {get;}
OutputType         Property       System.Collections.ObjectModel.ReadOnlyCollection[System.Management.Automation.PST... Parameters         Property       System.Collections.Generic.Dictionary[string,System.Management.Automation.Paramete... ParameterSets      Property       System.Collections.ObjectModel.ReadOnlyCollection[System.Management.Automation.Com... Path               Property       string Path {get;}
RemotingCapability Property       System.Management.Automation.RemotingCapability RemotingCapability {get;}
Source             Property       string Source {get;}
Version            Property       version Version {get;}
Visibility         Property       System.Management.Automation.SessionStateEntryVisibility Visibility {get;set;}
FileVersionInfo    ScriptProperty System.Object FileVersionInfo {get=[System.Diagnostics.FileVersionInfo]::getversio... HelpUri            ScriptProperty System.Object HelpUri {get=$oldProgressPreference = $ProgressPreference...
```

これだけのものが出てきますが、欲しい情報は `Definition` だけです。
それだけを取り出す方法はいくつかあり、上の記事で紹介されている `(gcm $command).Definition` 以外にも

``` powershell
PS > (gcm pwsh).Definition
C:\Program Files\PowerShell\6.0.0\pwsh.exe
PS> gcm pwsh | fl -p Definition


Definition : C:\Program Files\PowerShell\6.0.0\pwsh.exe


PS> gcm pwsh | select -p Definition

Definition
----------
C:\Program Files\PowerShell\6.0.0\pwsh.exe
```

などがあります。
それぞれ表示が違うので好みで使い分けてください。
上の記事で紹介されていた `Format-List` もオプションで `-Property Definition` と指定すれば `Definition` だけを取り出せます。

## コマンドとして使える様にする

意味合い的には `Select-Object -Property Definition` が一番妥当だと思うのですが、 `Select-Object` では表示されず `Format-List` では表示されるものもあるので、 `Format-List` を使ってコマンド化します。
といっても簡単で、

``` powershell
function which ($command) {
    Get-command -Name $command -ShowCommandInfo | Format-List -Property Definition
}
```

これで完了です。

これを毎回使える様にするために、このファンクションを `$PROFILE` に書きましょう。

`$PROFILE` には4種類あり、

``` powershell
PS> $PROFILE | gm -m NoteProperty | select -p Name, MemberType

Name                     MemberType
----                     ----------
AllUsersAllHosts       NoteProperty
AllUsersCurrentHost    NoteProperty
CurrentUserAllHosts    NoteProperty
CurrentUserCurrentHost NoteProperty
```

`AllUsers` か `CurrentUser` かはその名の通り、 `AllHosts` か `CurrentHost` かはちゃんと分かっていませんが、
PowerShell で `CurrentHost` に設定した場合 `PowerShell ISE` には適用されません。
個人で使っている範囲で、PowerShell にも ISE にも適用して欲しいのであれば、 `CurrentUserAllHosts` に書くのが良いでしょう。

``` powershell
# ディレクトリやファイルが無い場合、ファイルを作成
PS> $profilePath = Split-Path -Path $PROFILE.CurrentUserAllHosts -Parent
PS> $profileName = Split-Path -Path $PROFILE.CurrentUserAllHosts -Leaf
PS> mkdir $profilePath
PS> ni -Path $profilePath -Name $profileName
# 既定のエディタで開く
PS> ii $PROFILE.CurrentUserAllHosts
# ファンクションを書いて保存したら読み込む
PS> . $PROFILE.CurrentUserAllHosts
```

これで PowerShell で `which` コマンドのようなものが使える様になりました。


## おまけ

1. 上で

    > `Source` プロパティよりも `Definition` プロパティの方が汎用性が高い

    と言ったのは pwsh(.exe) のようにファイルのものではどちらでも変わりませんが、例えばファンクションやエイリアス、コマンドレットでは

    ``` powershell
    PS > gcm which | fl -Property Source
    
    
    Source :
    
    
    
    PS > gcm gcm | fl -Property Source
    
    
    Source :
    
    
    
    PS > gcm which | fl -Property Definition
    
    
    Definition : param($command)
    
                 Get-Command -Name $command -ShowCommandInfo | Format-List -Property Definition
    
    
    
    
    PS > gcm gcm | fl -Property Definition
    
    
    Definition : Get-Command
    
    
    
    PS > gcm Get-Command | fl -Property Definition
    
    
    Definition :
                 Get-Command [[-ArgumentList] <Object[]>] [-Verb <string[]>] [-Noun <string[]>] [-Module <string[]>] [-FullyQualifiedModule <ModuleSpecification[]>] [-TotalCount <int>] [-Syntax] [-ShowCommandInfo] [-All] [-ListImported] [-ParameterName <string[]>] [-ParameterType <PSTypeName[]>] [<CommonParameters>]
    
                 Get-Command [[-Name] <string[]>] [[-ArgumentList] <Object[]>] [-Module <string[]>] [-FullyQualifiedModule<ModuleSpecification[]>] [-CommandType <CommandTypes>] [-TotalCount <int>] [-Syntax] [-ShowCommandInfo] [-All] [-ListImported] [-ParameterName <string[]>] [-ParameterType <PSTypeName[]>] [<CommonParameters>]

    ```

    と違いが出ます。

    このように `which` コマンドを定義しておけば、ファイルのパスを見つけたい時だけではなく、ファンクションの定義を確認したいとき例えば `which mkdir` やコマンドレットのオプションを確認したいとき `Get-Help Get-Command` の代わりに `which Get-Command` と使える様になります。

1. 上で
    
    > `Select-Object` では表示されず `Format-List` では表示されるものもある
    
    と書いた事について

    これも1つめと同じでファンクションやコマンドレットで差が出ます。 `Definition` の出力が複数行にわたる場合や長い場合 `Select-Object` では省略されます。

    ``` powershell
    PS > gcm which | select -p Definition
    
    Definition
    ----------
    param($command)...

    PS> gcm Get-Command | select -p Definition

    Definition
    ----------
    ...
    ```