しばたテックブログ

気分で書いている技術ブログです。

PowerShellのCountプロパティについてあれやこれや

【2018/10/07追記】

最新の情報をふまえて本エントリを新しく書き直しました。

blog.shibata.tech

上記エントリをご覧ください。


PowerShellの基本的なことがわかってなかったシリーズ第6弾です。

便利なCountプロパティ

コマンドを実行して出力した結果の件数を取得したい場合、([コマンド]).Countとするとその件数を取得することができます。

たとえば以下の様にコマンドを実行するとC:\Program Files配下にあるEXEファイルの総数を取得することができます。

# Countプロパティを使うと、評価した結果(System.IO.FileInfo[])の要素数を取得することができます。
PS C:\> (ls -Path "C:\Program Files" -File -Filter "*.exe" -Recurse).Count
217

Countプロパティの実体

System.Array.Count?

このCount、要はコマンドを実行した結果(この場合はSystem.IO.FileInfo[])の要素数を取得しているだけなのですが、しかしながら、.NETの配列(System.Array)にはLengthプロパティはあれどもCountプロパティはありません。

それでもPowerShellではCountプロパティを利用することができています。

Countプロパティの実体

この謎めいたCountプロパティの実体はSystem.Arrayに追加された型データになります。

型データとは.NETの型にPowerShell独自のデータを付与できる機能になります。
詳細については牟田口先生の以下のエントリを参照してください。

winscript.jp

型データの定義が記載されているC:\Windows\System32\WindowsPowerShell\v1.0\types.ps1xmlを見るとCountプロパティは以下の様に定義されており、System.Array.Lengthに対するAliasPropertyとなっていることがわかります。

# C:\Windows\System32\WindowsPowerShell\v1.0\types.ps1xmlより抜粋
    <Type>
        <Name>System.Array</Name>
        <Members>
            <AliasProperty>
                <Name>Count</Name>
                <ReferencedMemberName>Length</ReferencedMemberName>
            </AliasProperty>
        </Members>
    </Type>

Get-Memberで空の配列@()のメンバを確認してみるとこんな結果を返します。

PS C:\> Get-Member -InputObject @() -View Extended

   TypeName: System.Object[]

Name  MemberType    Definition
----  ----------    ----------
Count AliasProperty Count = Length

この型データにより本来System.Arrayには存在しないCountプロパティを利用することができます。

型データでは説明のつかないCountプロパティの進化

PowerShell 2.0までの動作

このCountプロパティはSystem.Arrayに対するものなので配列以外では使うことができません。

PowerShell 2.0では以下の様なコードは$nullを返し、配列以外ではCountプロパティが存在しないことがわかります。

# PowerShell 2.0
PS C:\> $PSVersionTable.PSVersion

Major  Minor  Build  Revision
-----  -----  -----  --------
2      0      -1     -1

PS C:\> (123).Count
PS C:\>

配列でないオブジェクトに対してCountプロパティを使いたいときは配列化してやる必要があります。

# PowerShell 2.0
# 要素数1の配列化してCountプロパティを取得
PS C:\> @(123).Count
1

PowerShell 3.0以降の動作

ただし、この動作はPowerShell 3.0以降になると変わります。
先ほどのコードをPowerShell 3.0以降の環境で実行すると以下の様になり1を返す様になります。

# PowerShell 3.0 以降 (以下の例では4.0)
PS C:\> $PSVersionTable.PSVersion

Major  Minor  Build  Revision
-----  -----  -----  --------
4      0      -1     -1

PS C:\> (123).Count
1

ちなみに$nullを渡すと0が返されます。

# PowerShell 3.0 以降
PS C:\> ($null).Count
0

型データでは説明のつかないCountプロパティの進化

この変更についてはWindows PowerShell Blogのこの記事で触れられる程度の情報しかなく、機能の実体については正直謎です。

とはいえ$nullにプロパティを生やして0を返す事が出来るというのはどう考えても普通の動作ではないのでシェルまたはスクリプトエンジンレベルでの機能追加と思われます。*1

この変更により対象オブジェクトが配列かどうかを気にすることなくCountプロパティで件数を取得できる様になりました。

残念な点の残るCountプロパティ

これで万事OKかと思いきや一点だけ残念な点があります。

ぎたぱそ先生の以下のエントリによるとPScustomObjectに対してはCountプロパティは$nullを返してしまいます。

tech.guitarrapc.com

実際に試してみると確かに$nullを返します。

# PowerShell 3.0 以降
PS C:\> ([PScustomObject]@{hoge = "hoge"}).Count
PS C:\>

PScustomObjectに対してCountプロパティを使用する際は注意が必要になります。

本当に残念です。

*1:もしかしたら機能追加ではなく糖衣構文なのかもしれません…