しばたテックブログ

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

PowerShell Core 6.1 新機能・破壊的変更まとめ

公式ドキュメントはコチラ

docs.microsoft.com

PowerShell Core 6.0の時とは違って*1ドキュメントの分量もそれなりで内容も割ときちんとまとまっているので本エントリでまとめなくても大丈夫な気がしますが、せっかくなので補足を入れつつまとめていきます。

.NET Core 2.1化

以前のエントリで説明した通り内部基盤が.NET Core 2.0から2.1に更新されており、パフォーマンス改善などの恩恵を受けています。

blog.shibata.tech

ちょっと面白い点として、元のドキュメントでは、

.NET global tool support - coming soon to PowerShell

とあり、PowerShellで.NET Core Global Toolsのサポートが計画されている様です。
サポート内容については不明でこれまでも表立った情報は一切ありませんでした。
今後なんらかのアナウンスがされるものと予想されます。

ちなみに.NET Core Global Toolsとは何ぞや?といった話は岩永さんのブログを見てください。

ufcpp.net

Windows Compatibility Pack for .NET Core

Windows PowerShellに対する互換性の強化についての内容です。

blog.shibata.tech

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

Support for Application Whitelisting

Windows版のPowerShell Core 6.1がAppLockerやDevice GuardによるUser Mode Code Integrity (UMCI)に対応しました。
こちらは元々Windows PowerShell 5.1で利用可能であった機能でAppLockerやDevice GuardからPowerShellの言語モードを制限することができる機能になります。

機能の詳細については

が参考になります。

PowerShell Core 6.0リリース時には組み込むことができなったものが6.1で導入に至りました。
(この機能を追加するプルリクエストはコチラ(#6133))

パフォーマンス改善

.NET 2.1化による影響を主としてパフォーマンスが改善されています。 ドキュメントでは、

  • Sort-Object
  • Import-Csv
  • ConvertFrom-Json

の改善例がでていますので参考にしてください。

system32フォルダ配下の組み込みモジュールの参照

先述の「Windows Compatibility Pack for .NET Core」と関連する内容なのですが、Windows版のPowerShell Core 6.1ではモジュールの検索パスである$env:PSModulePathC:\WINDOWS\system32\WindowsPowerShell\v1.0\Modulesが追加され、OSがWindows 10 1809 updateやWindows Server 2019であればWindows PowerShellに組み込まれているモジュールの機能をそのまま利用することができます。

なお、古いOSでは検索パスが増えてもモジュール側が対応できていないためこの新機能を利用することができません。

Markdown関連コマンドレットの追加

PowerShell Core 6.1ではMarkdownを扱うコマンドレットが追加されました。

blog.shibata.tech

をご覧ください。

Experimental feature flags

PowerShell Core 6.1では試験的な機能追加に備えるための機能が追加されています。

blog.shibata.tech

をご覧ください。

Web Cmdletsの改善

Invoke-WebRequestInvoke-RestMethodに以下の改善が導入されました。

default encoding set to UTF-8 for application-json responses (#6109)

主にInvoke-RestMethod向けの変更ですが、レスポンスがJSONの場合でエンコーディングが明示されてない場合はUTF-8をデフォルトエンコーディングとする改善が入りました。
これはRFC-8259に合わせた挙動になります。

-SkipHeaderValidation parameter to allow Content-Type headers that aren't standards-compliant (#6018)

-SkipHeaderValidationパラメーターの解釈を拡大し、このパラメーターが指定された際は正しくない形式のContent-Typeヘッダー(または-ContentTypeパラメーター)でも許可する様になりました。

Form parameter to support simplified multipart/form-data support (#5972)

Invoke-WebRequestInvoke-RestMethod-Formパラメーターが追加され、multipart/form-dataを取り扱うことが出来る様になりました。

ドキュメントから簡単な使用例を例示しておきます。

$Uri = 'https://api.contoso.com/v2/profile'
$Form = @{
    firstName  = 'John'
    lastName   = 'Doe'
    email      = 'john.doe@contoso.com'
    avatar     = Get-Item -Path 'c:\Pictures\jdoe.png'
    birthday   = '1980-10-15'
    hobbies    = 'Hiking','Fishing','Jogging'
}
$Result = Invoke-RestMethod -Uri $Uri -Method Post -Form $Form

Compliant, case-insensitive handling of relation keys (#6338)

RFC-8288で規定されているWeb Linkingのリンクのキー名(prevnextなど)の判定で、仕様としては大文字・小文字を区別しないのが正しいのですが、実装で区別されてしまっていたため入った修正です。

Invoke-RestMethod-FollowRelLinkパラメーターの挙動が改善されています。

Add -Resume parameter for web cmdlets (#6447)

Invoke-WebRequestInvoke-RestMethod-Resumeパラメーターが追加され、ファイルのダウンロード時にリジュームができる様になりました。
-Resumeパラメーターは-OutFileパラメーターを指定したときだけ利用できます。

Invoke-WebRequest -Uri 'https://www.contoso.com/contents/some-big-image.iso'
.7.x86_64.rpm -OutFile '.\some-big-image.iso' -Resume

PowerShell Remotingの改善

PowerShell Remoting周りの改善についてです。

blog.shibata.tech

をご覧ください。

MSIインストールオプションの拡張

右クリックメニュー

PowerShell Core 6.1ではMSIインストーラーのインストールオプションが拡張され、エクスプローラーの右クリックメニューからPowerShellを起動できる様になりました。

f:id:stknohg:20181015162059p:plain

インストール時のオプションで、「Add 'Open here' context menus to Explorer」を選んでください。

f:id:stknohg:20181015162038p:plain

ジャンプリスト

ジャンプリストに「Run as Administrator(管理者として実行)」タスクが追加されました。

f:id:stknohg:20181015162456p:plain

その他

その他個別の改善点は以下の通りです。

cd -でひとつ前のディレクトリに戻れる様になりました

こちらはUnix系のcdコマンドと同様の操作を実現するための改善です。
以下の例を見てもらうば十分でしょう。

C:\Windows\System32> cd C:\
C:\> cd -
C:\Windows\System32>

また、引数無しのcdcd --の場合はホームディレクトリに移動します。

Test-Connectionが実装されました

Windows PowerShellのTest-Connectionは内部でWMIを使っていたためPowerShell Core 6.0では移植されず、6.1で再実装されました。
このためパラメーターには互換性があるのですが、結果の表示や返されるオブジェクトは異なります。

  • Windows PowerShell

    • 戻り値の型は [System.Management.ManagementObject#root\cimv2\Win32_PingStatus] (WMIのWin32_PingStatusクラスのインスタンス)
    • 1回のPingメッセージが1オブジェクトとして返される
      例えばデフォルトの4回Pingすると4オブジェクト返される
  • PowerShell Core

    • 戻り値の型は [Microsoft.PowerShell.Commands.TestConnectionCommand+PingReport]
    • 1回のコマンド実行で1オブジェクトとして返される
      例えばデフォルトの4回Pingしても1オブジェクトで返される
      個々のPingメッセージはRepliesプロパティで取得

非管理者ユーザーでもUpdate-Helpができる様になりました

従来のWindows PowerShellではUpdate-Helpを実行するのに管理者権限が必要でしたが、PowerShell Core 6.1からは非管理者ユーザーでも実行可能に改善されています。

PSCustomObjectに新しいプロパティとメソッドが追加されました

新しいと謳われていますが、どちらかというと機能拡張に近いです。

前に

blog.shibata.tech

で触れたCountプロパティですが、PSCustomObjectではこのプロパティを利用することができませんでした。
PowerShell Core 6.1からはPSCustomObjectでもCountプロパティが使えます。

PowerShell 3.0 ~ 6.0

f:id:stknohg:20181015163529p:plain

PowerShell 6.1 ~

f:id:stknohg:20181015163537p:plain

加えてPowerShell 4.0から導入されたForEachWhereメソッドもPSCustomObjectで使える様になりました。
(ただしあまり使いどころは無いと思いますが...)

PowerShell 4.0 ~ 6.0

f:id:stknohg:20181015163709p:plain

PowerShell 6.1 ~

f:id:stknohg:20181015163724p:plain

Where-Object-Notパラメーターが追加されました

こちらは説明不要かと思います。
以下の例の様に使うことが可能です。

Get-Service | Where-Object -Not DependentServices

New-ModuleManifestでBOM無しUTF-8のマニフェストファイルが生成される様になりました

PowerShell Core 6.0がリリースされた当初はWindows PowerShellとの互換も考慮してWindowsではUTF-16、非Windows環境ではUTF-8のマニフェストファイルが生成されていたのですが、これがすべてのプラットフォームにおいてUTF-8に変更されました。

PSMethodからDelegateへの変換機能が追加されました

PSMethodはPowerShellのクラスで定義されたメソッドの型のことで、この改善は基本的にクラスが対象だと思ってください。
Docsのある以下のクラスを例にすると

class M {
    # 文字列長さの2倍の値を出すメソッド
    static [int] DoubleStrLen([string] $value) { return 2 * $value.Length }

    # 与えられた string[] 型の要素を集約するメソッド
    static [long] AggregateString([string[]] $values, [func[string, int]] $selector) {
        [long] $res = 0
        foreach($s in $values) {
            $res += $selector.Invoke($s)
        }
        return $res
    }
}

このクラスを使って

[M]::AggregateString(@("hello", "powershell"), [M]::DoubleStrLen)

といった操作をしたくても、これまでは[M]::DoubleStrLenメソッドはPSMethod型であるため型が合わずエラーとなっていました。

f:id:stknohg:20181015163257p:plain

PowerShell Core 6.1はこの点が改善されました。

f:id:stknohg:20181015163307p:plain

Measure-Objectで(標本)標準偏差が算出可能になりました

Measure-Object-StandardDeviationパラメーターが追加され、標本標準偏差が求められる様になりました。
ExcelのSTDEV.S関数と同じ結果を得ることができます。

Get-PfxCertificate-Passwordパラメーターが追加されました

こちらは純粋な機能追加です。

more関数が削除されました

これまでのmore関数が削除され、PowerShell Core 6.1からコマンド実行結果のページング処理はOS側のコマンド(Windowsならmore.com、他OSならmoreless)を直接扱う形に変更されました。

現在のところ内部的にmore関数を使っているのはhelp関数(Get-Helpをラップした関数)だけであり、この関数の定義も

<#
.FORWARDHELPTARGETNAME Get-Help
.FORWARDHELPCATEGORY Cmdlet
#>
[CmdletBinding(DefaultParameterSetName='AllUsersView', HelpUri='https://go.microsoft.com/fwlink/?LinkID=113316')]
param(
    # ・・・中略・・・
)

# Display the full help topic by default but only for the AllUsersView parameter set.
if (($psCmdlet.ParameterSetName -eq 'AllUsersView') -and !$Full) {
    $PSBoundParameters['Full'] = $true
}

# Nano needs to use Unicode, but Windows and Linux need the default
$OutputEncoding = if ([System.Management.Automation.Platform]::IsNanoServer -or [System.Management.Automation.Platform]::IsIoT) {
    [System.Text.Encoding]::Unicode
} else {
    [System.Console]::OutputEncoding
}

$help = Get-Help @PSBoundParameters

# If a list of help is returned, don't pipe to more
if (($help | Select-Object -First 1).PSTypeNames -Contains 'HelpInfoShort')
{
    $help
}
else
{
    # Respect PAGER, use more on Windows, and use less on Linux
    $moreCommand,$moreArgs = $env:PAGER -split '\s+'
    if ($moreCommand) {
        $help | & $moreCommand $moreArgs
    } elseif ($IsWindows) {
        $help | more.com
    } else {
        $help | less
    }
}

とWindowsであればmore.comを、その他OSであればPAGER環境変数の値かlessコマンドが使われる様になっています。

cd ドライブ名:とした際のカレントディレクトリの改善

これはちょっと説明が不足しているのですが、話の前提として、コマンドプロンプトやWindows PowerShellでcd ドライブ名:とした際の移動先は直近のカレントディレクトリになる仕様があります。

例えば現在のカレントディレクトリがC:\Users\stknohgだとして、そこからcd [他のドライブ]cd C:という操作をするとCドライブに戻りますが、戻り先はC:\Users\stknohgになります。

# コマンドプロンプトやWindows PowerShellの挙動
# そして、PowerShell Core 6.1の挙動でもある
PS C:\Users\stknohg> cd D:
PS D:\> cd C:
PS C:\Users\stknohg>

これがPowerShell Core 6.0の一部バージョンでは以下の様にC:\に戻ってしまう不具合があり、PowerShell Core 6.1で修正されたことを表しています。

# PowerShell Core 6.0の一部のバージョンでの不具合
PS C:\Users\stknohg> cd D:
PS D:\> cd C:
PS C:\>

ただ、この不具合はすべてバージョンで発生していたわけではなく、Set-Location(cd)の改修中に発生した一時的な不具合であったため新機能一覧で取り上げるものではなかった気もします。

Windows PowerShell向け型アクセラレーターの復活

PowerShell 6.1で増えたWindows PowerShellとの互換性の一つとして、PowerShell Core 6.0で削除された以下の型アクセラレーターが復活しました。

簡易表記 実際の型
[adsi] System.DirectoryServices.DirectoryEntry
[adsisearcher] System.DirectoryServices.DirectorySearcher
[wmi] System.Management.ManagementObject
[wmiclass] System.Management.ManagementClass
[wmisearcher] System.Management.ManagementObjectSearcher

ドキュメント上は便利機能の復活という文脈で紹介されていますが、根本的には互換性、とくにPowerShell CoreからWindows PowerShellへPSRemotingした際の互換性確保のための復活です。

すべての-LiteralPathパラメーターに-lpというエイリアスが割り当てられました

こちらも純粋な機能追加です。

単純な機能追加ですが発端は以下のIssueであり、

github.com

-Pathパラメーターののために-LiteralPathを多用する必要があるにも関わらず記述が冗長になるのがツライといったところから始まっています。

破壊的変更

数が少ないため、PowerShell Core 6.0から6.1にかけての破壊的変更もここにまとめられています。

MSIインストーラーでインストールした際のパス

Windowsでの話ですが、MSIインストーラーでPowerShell Coreをインストールするデフォルトパスが、PowerShell Core 6.0までは

  • $env:ProgramFiles\PowerShell\6.x.y\

だったのが、PowerShell Core 6.1からは

  • $env:ProgramFiles\PowerShell\6\ : 安定版
  • $env:ProgramFiles\PowerShell\6-preview\ : プレビュー版

に変更されます。

これは今後計画しているMicrosoft UpdateによるPowerShell Coreの自動アップデートを見据えての変更となります。
詳細についてはこちらのRFCをご覧ください。

テレメトリの無効化は環境変数の設定のみに変更されました

blog.shibata.tech

PowerShell Core 6.0新機能まとめでも軽く触れましたが、テレメトリ収集を無効にする方法からDELETE_ME_TO_DISABLE_CONSOLEHOST_TELEMETRYファイルの削除が撤廃され、POWERSHELL_TELEMETRY_OPTOUT環境変数の設定に一本化されました。

POWERSHELL_TELEMETRY_OPTOUT環境変数をtrueyes1のいずれかに設定するとテレメトリ収集を無効にすることができます。

UnixプラットフォームでHTTPベーシック認証によるPowerShell Remotingが禁止されました

これはpsl-omi-providerのはなしで、UnixプラットフォームにPowerShell Remotingで接続する場合のはなしです。
セキュリティ強化のためUnixプラットフォームへの接続はHTTPSかNTLM/Negotiate認証が必須となりました。

細かい話はこちらのIssueをご覧ください。

github.com

Add-TypeからVisual Basicのサポートが廃止されました

Add-TypeでVisual Basicが使われることが少ない点や、PowerShell全体のサイズ減少のために廃止となりました。

この結論に至るまでには結構議論があったのですが、私も全部追いきれてないため、経緯が気になる方は以下のプルリクから議論を辿ると良いと思います。

github.com

github.com

Workflow関連のコードのクリーンアップ

細かい話はこちらのプルリクをご覧ください。

github.com

*1:PowerShell Core 6.0新機能ドキュメントはボリュームやスケジュールの都合結構カオスでしたからね...

PowerShell Core 6.1のPowerShell Remotingにおける改善点について

本エントリではPowerShell Core 6.1のPowerShell Remotingにおける改善点について解説します。

docs.microsoft.com

元ネタは上記のDocsで、現在こちらに対するまとめを作成しているのですが、PowerShell Remotingについては少しボリュームが増えたので本エントリで先出しします。

コンテナ向けPowerShell Directの改善

PowerShell Direct

PowerShell DirectはWindows10/Windows Server 2016以降で利用でき、ホストから仮想マシンおよびコンテナへネットワークを介さず直接接続する機能になります。

利用可能なホストOSではリモート操作系のコマンドレットに-VMName-VMId-VMGuid-ContainerIdのパラメーターが付いており、これらのパラメーターを指定することで直接ゲストに接続します。

# VM向けPowerShell Directの例
Invoke-Command -VMName TestVM -ScriptBlock { $PSVersionTable } -Credential $cred

# コンテナ向けPowerShell Directの例
Invoke-Command -ContainerId 78438d708b648217899f600c3a0ac72b36814714a8a19d2f4bc03fbb08936519 -ScriptBlock { $PSVersionTable }

仕組みとしては、対VMの場合はHyper-V Socketを使った通信を行い、対コンテナの場合はHost Compute Service(HCS)のAPI(例えばHcsCreateProcessなど)を使ってコンテナ内部のPowerShellを起動したプロセス間通信を行っています。

PowerShell Core 6.1での改善

ここまでをふまえてPowerShell Core 6.1での改善点について説明します。

PowerShell Core 6.0までは対コンテナ向けのPowerShell Directで起動するPowerShellのプロセスはpowershell.exeで決め打ちされていました。
対してPowerShell Core 6.1では、最初にpwsh.exeの起動を試みpwsh.exeが無ければpowershell.exeを起動する様に改善されています。

例としてWindows Server 2019*1で以下のコマンドを試してみます。
予めdockerをインストールし、PowerShell Core 6.1をインストール済みのWindows Server Containerが起動された状態です。*2

# 起動しているコンテナの確認
docker ps

# コンテナIDの取得
# ※起動しているコンテナが1つだけの前提
$containerId = (docker inspect $(docker ps -q) | ConvertFrom-Json).Id

# PowerShell Directによるリモートコマンド実行
Invoke-Command -ContainerId $containerId -ScriptBlock { $PSVersionTable.PSVersion } 

Windows PowerShellからこのコマンドを実行すると、結果のバージョンが5.1とWindows PowerShell(powershell.exe)が実行されていることが分かります。

f:id:stknohg:20181013233855p:plain

対してPowerShell Core 6.1から実行すると、結果のバージョンが6.1.0とPowerShell Core(pwsh.exe)が起動され改善されていることがわかります。

f:id:stknohg:20181013233916p:plain

ちなみに、対VMのPowerShell Directはこれまで通りデフォルト指定ではWindows PowerShellとの通信になり、PowerShell Coreと通信する場合は-ConfigurationNameパラメーターを明示してやる必要があります。
こちらについては次項で説明します。

余談1

PowerShell Core 6.1のリリースバージョンではコンテナ接続に関するバグ(#5794)があるため、この機能を試すには最新のmasterから自分でビルドする必要があります。
おそらく次のリリース(PowerShell Core 6.1.1)に取り込まれると思いますので、ビルド環境のない方はそれまでお待ちください。

【2019.04.23追記】

#5794の修正はPowerShell Core 6.2.0で反映されました。

【追記ここまで】

余談2

当初この点についてドキュメントの内容に疑念があったためIssueを上げたところ回答と改善をしてもらえました。

github.com

あまりいないとは思いますが改善前のドキュメントを見ている人向けの補足です。

PowerShell Remotingのエンドポイントがプレビュー版と分離されました

PowerShell Remotingのエンドポイント

普段意識することは少ないでしょうがPowerShell Remotingでは接続対象となるホストに接続先(エンドポイント)が存在します。

このエンドポイントはGet-PSSessionConfigurationコマンドレットで確認でき、例えばWindows PowerShellにおいては下図の様に4つのエンドポイントがあることが分かります。*3

# 要管理者権限
# 接続される側のホストで実行する
Get-PSSessionConfiguration

f:id:stknohg:20181013234030p:plain

で、PowerShell CoreにおいてPowerShell Remotingを有効にした際のエンドポイントがどうなるかというと、バージョンによって何度か変遷しており、PowerShell Core 6.1では以下の2つのエンドポイントが作成されます。

  • PowerShell.6 : メジャーバージョンのみのエンドポイント
  • PowerShell.6.x.y : バージョンを厳密に指定したエンドポイント

Get-PSSessionConfigurationで確認するとこんな感じです。

# 要管理者権限
# PowerShell Coreインストール時にPowerShell Remotingを有効にしてない場合に実行
Enable-PSRemoting

# 接続される側のホストで実行する
Get-PSSessionConfiguration

f:id:stknohg:20181013234055p:plain

また、今後PowerShell Core 6.2に向けたプレビュー版でPowerShell Remotingを有効にした場合はプレビューバージョン向けの2つのエンドポイントが追加される予定となっています。

  • PowerShell.6-preview : メジャーバージョンのみのエンドポイント
  • PowerShell.6.x-preview.y : バージョンを厳密に指定したエンドポイント
Name          : PowerShell.6-preview
PSVersion     : 6.2
StartupScript :
RunAsUser     :
Permission    : NT AUTHORITY\INTERACTIVE AccessAllowed, BUILTIN\Administrators AccessAllowed, BUILTIN\Remote Management Users AccessAllowed

Name          : PowerShell.6.2-preview.1
PSVersion     : 6.2
StartupScript :
RunAsUser     :
Permission    : NT AUTHORITY\INTERACTIVE AccessAllowed, BUILTIN\Administrators AccessAllowed, BUILTIN\Remote Management Users AccessAllowed

これにより安定版とプレビュー版のエンドポイントを分けて利用できるというのが今回の改善点になります。

接続する側の設定について

補足として接続する側について触れておきます。

接続する側はデフォルトでMicrosoft.Powershellというエンドポイントが設定されており、これはWindows PowerShell/PowerShell Coreどちらでも変わりません。
このためPowerShell CoreからEnter-PSSessionなどのコマンドレットを使ってリモート接続してもデフォルトではWindows PowerShellが利用されます。

PowerShell Coreに対してPowerShell Remotingで接続する場合は-ConfigurationNameパラメーターを使って接続するエンドポイントを明示してやる必要があります。

たとえばInvoke-Commandであれば以下の様にしてやります。

# リモートのPowerShell Core 6に接続する場合はエンドポイントを明示する
$cred = Get-Credential
Invoke-Command -ConputerName [Server Name] -Credential $cred -ScriptBlock { $PSVersionTable.PSVersion } -ConfigurationName 'PowerShell.6'

# VM向けPowerShell Directでも同様
Invoke-Command -VMName [VM Name] -Credential $cred -ScriptBlock { $PSVersionTable.PSVersion } -ConfigurationName 'PowerShell.6'

f:id:stknohg:20181013234124p:plain

f:id:stknohg:20181013234136p:plain

PowerShell Remoting over SSHでuser@host:port形式の接続が可能になりました

ssh.exeといったSSHクライアントで使われるuser@host:port形式の接続がコマンドレットでもサポートされました。
PowerShell Remoting over SSHでは-HostNameパラメーターを使用して接続先を決めますが、ここに上記の形式を指定できます。

# Docsの例をそのまま引用
Enter-PSSession -HostName fooUser@ssh.contoso.com:2222

*1:PowerShell Directを利用するにはHyper-Vモジュールが利用可能である必要があり、このためこの改善を確認するためのホストOSはWindows 10 October 2018 Update(1809)かWindows Server 2019でなければなりません

*2:環境構築の手順は端折ります。

*3:正確にはSession Configurationとエンドポイントは別なのでしょうが、本エントリではわかりやすさのために同一視します

PowerShellのCountプロパティについてあれやこれや (2018年10月版)

3年ほど前に

blog.shibata.tech

というエントリを書いたのですが、その後新たに分かったことや新しいバージョンでの変更も発生したため、内容を更新してリライトしました。

便利なCountプロパティ

たとえばコマンドを実行して出力した結果の件数を取得したい場合、

([コマンド]).Count

とするとその件数を取得することができます。

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

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

Countプロパティの実体

System.Array.Count?

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

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

バージョン間の差異

この謎めいたCountプロパティはPowerShellのバージョンによってその裏にある実体が異なり、その挙動も異なります。

大きくはPowerShell 2.0までとPowerShell 3.0以降に分かれ、加えてPowerShell Core 6.0で若干の仕様変更が入り現在に至っています。

以降、バージョンごとの実体について触れていきます。

PowerShell 2.0までCountプロパティの実体

PowerShell 2.0までの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プロパティを利用することができます。

制限事項

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

例えば以下の様なコードは$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 ~ 5.1のCountプロパティの実体

PowerShell 2.0では配列にしかCountプロパティが使えませんでしたが、PowerShell 3.0からは配列でないスカラ値や$nullに対してもCountプロパティが使える様に機能が拡張されています。

# PowerShell 3.0 以降
PS C:\> $PSVersionTable.PSVersion

Major  Minor  Build  Revision
-----  -----  -----  --------
5      1      17763  1

# スカラ値のCountは 1
PS C:\> (123).Count
1

# $nullのCountは 0
PS C:\> ($null).Count
0

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

しかしながらPowerShellがオープンソースとなったため実際にソースを確認してみたところ、このCountプロパティは動的オブジェクトのメンバーフォールバックとして実装されていることが分かりました。

PowerShellは3.0ではランタイムが.NET 4ベースで刷新され、PowerShellすべてのオブジェクトの基本となるPSObjectDLRを使った動的オブジェクトとして定義されています。
これによりこれまでリフレクションによって行われていたメソッドやプロパティの解決が高速化され、PowerShell全体のパフォーマンス改善につながっています。

そしてDLRでは、主にエラーハンドリングのために、メソッドやプロパティの解決に失敗したときの処理を定義するフォールバック機能が提供されており、Countプロパティにおいては、

  1. オブジェクトのCount(またはLength)プロパティを取得しようとする

  2. Count(またはLength)プロパティが存在しないオブジェクトの場合、DLRのフォールバック(FallbackGetMember)が呼び出される

  3. フォールバック処理内で元のオブジェクトの種類に応じて1または0を返す

といった動作をしています。

(これがすべてではありませんが)ソースコードとしてはこのあたりの処理が参考になります。

参考として以下にフォールバックの一部を列挙しておきます。

//
// PowerShell 6.0時点の Binders.cs より一部引用
// 

/// <summary>
/// Return the binding result when no property exists.
/// </summary>
private DynamicMetaObject PropertyDoesntExist(DynamicMetaObject target, BindingRestrictions restrictions)
{

    // ・・・ 省略 ・・・

    // As part of our effort to hide how a command can return a singleton or array, we want to allow people to iterate
    // over singletons with the foreach statement (which has worked since V1) and a for loop, for example:
    //     for ($i = 0; $i -lt $x.Length; $i++) { $x[$i] }
    // If $x is a singleton, we want to return 1 for the length so code like this works correctly.
    // We do not want this magic to show up in Get-Member output, tab completion, intellisense, etc.
    if (Name.Equals("Length", StringComparison.OrdinalIgnoreCase) || Name.Equals("Count", StringComparison.OrdinalIgnoreCase))
    {
        // $null.Count should be 0, anything else should be 1
        var resultCount = PSObject.Base(target.Value) == null ? 0 : 1;
        return new DynamicMetaObject(
            Expression.Condition(
                Compiler.IsStrictMode(2),
                ThrowPropertyNotFoundStrict(),
                ExpressionCache.Constant(resultCount).Cast(typeof(object))), restrictions);
    }

    // ・・・ 省略 ・・・

}

ちなみに配列に対しては従来通り型データによるAliasPropertyが使われています。

制限事項

PowerShell 3.0で配列以外にもCountプロパティが使える様になったのですが、ぎたぱそ先生の以下のエントリによるとPScustomObjectに対してはCountプロパティは$nullを返してしまいます。

tech.guitarrapc.com

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

# PowerShell 3.0 ~ 6.0
PS C:\> $PSVersionTable.PSVersion

Major  Minor  Build  Revision
-----  -----  -----  --------
5      1      17763  1

PS C:\> ([PScustomObject]@{hoge = "hoge"}).Count
PS C:\>

このためPScustomObjectに対してCountプロパティを使用する際は注意が必要です。

PowerShell 6.0以降のCountプロパティの実体

PowerShell Core 6.0においてもCountプロパティの実体が動的オブジェクトのメンバーフォールバックであることは変わらないのですが、いくつか挙動が変更されています。

AliasPropertyの削除 (PowerShell Core 6.0より)

最初に説明したSystem.Array.Lengthに対するAliasPropertyですが、これはPowerShell Core 6.0で削除されました。

理由としてはこのAliasPropertyが無くても問題がない*1点と、ConvertTo-Json等の処理で不要なCountプロパティが露出してしまう問題(#3222)があったため削除されています。

github.com

PSCustomObjectに対するCount (PowerShell Core 6.1より)

前項で触れたPScustomObjectに対するCountですが、PowerShell Core 6.1で挙動が改善されPScustomObjectでもCountプロパティが取得できる様に改善されています。

PS C:\> $PSVersionTable.PSVersion

Major  Minor  Patch  PreReleaseLabel BuildLabel
-----  -----  -----  --------------- ----------
6      1      0

PS C:\> ([PScustomObject]@{hoge = "hoge"}).Count
1

【補足】PowerShell 4.0のWhere、ForEachメソッドについて

最後にちょっとだけおまけを。

PowerShell 4.0から配列などのコレクションオブジェクトに対してWhere()およびForEach()メソッドが使える様に機能拡張されています。

# PowerShell 4.0以降
PS C:\> @(1,2,3).Where({$_ -eq 2})
2

PS C:\> @(1,2,3).ForEach({$_ * 2})
2
4
6

これらのメソッドも動的オブジェクトのメンバーフォールバックとして実装されいます。
ソースコードとしてはこのあたりが参考になると思いますので興味のある方はご覧ください。

*1:前項で説明を端折りましたが、動的オブジェクトのメンバーフォールバックは配列の場合も考慮されいます