PSCustomObject について知りたかったことのすべて

PSCustomObject は、PowerShell のツール ベルトに追加できる優れたツールです。 基本事項から始めて、より高度な機能に進みましょう。 PSCustomObject を使用する背景にあるアイデアは、構造化データを簡単に作成することです。 最初の例を見てみると、その意味がよく理解できるでしょう。

注意

この記事のオリジナル バージョンは、@KevinMarquette 氏のブログに掲載されました。 このコンテンツを共有してくださった Kevin 氏に、PowerShell チームより感謝を申し上げます。 PowerShellExplained.com のブログをご確認ください。

PSCustomObject の作成

私は PowerShell で [PSCustomObject] を使うことが気に入っています。 これ以上簡単に有用なオブジェクトを作成する方法はありません。 そのため、オブジェクトを作成するその他の方法はすべてスキップします。ただし、これらの例のほとんどは PowerShell v3.0 以降であることに言及しておく必要があります。

$myObject = [PSCustomObject]@{
    Name     = 'Kevin'
    Language = 'PowerShell'
    State    = 'Texas'
}

私はほとんどすべてのものにハッシュテーブルを使用するため、この方法はうまく機能します。 しかし、PowerShell でハッシュテーブルをよりオブジェクトに近い方法で処理したい場合もあります。 その違いに初めて気付くのは、Format-Table または Export-CSV を使用するときに、ハッシュテーブルがキーと値のペアのコレクションに過ぎないことを認識したときです。

その後、通常のオブジェクトの場合と同様に値にアクセスして使用することができます。

$myObject.Name

ハッシュテーブルの変換

本筋から外れないうちに、次のようにすることもできたことにお気付きでしょうか。

$myHashtable = @{
    Name     = 'Kevin'
    Language = 'PowerShell'
    State    = 'Texas'
}
$myObject = [pscustomobject]$myHashtable

私は初めからオブジェクトを作成する方が好きですが、最初にハッシュテーブルを操作することが必要になる場合もあります。 コンストラクターがオブジェクト プロパティに対してハッシュテーブルを受け取るため、この例は適切です。 重要な注意事項の 1 つは、この方法は機能しますが、まったく同等ではないことです。 最大の違いは、プロパティの順序が維持されないことです。

順序を維持する必要がある場合は、「順序付きハッシュテーブル」を参照してください。

従来の手法

カスタム オブジェクトを作成するために New-Object が使用されるのを見たことがあるかもしれません。

$myHashtable = @{
    Name     = 'Kevin'
    Language = 'PowerShell'
    State    = 'Texas'
}

$myObject = New-Object -TypeName PSObject -Property $myHashtable

この方法はかなり遅くなりますが、初期バージョンの PowerShell では最良の選択肢である可能性があります。

ファイルへの保存

ハッシュテーブルをファイルに保存する最善の方法は、JSON として保存することだと考えられます。 これをもう一度 [PSCustomObject] にインポートすることができます

$myObject | ConvertTo-Json -depth 1 | Set-Content -Path $Path
$myObject = Get-Content -Path $Path | ConvertFrom-Json

ファイルの読み取りと書き込みを行うさまざまな方法に関する記事では、オブジェクトをファイルに保存するその他の方法が説明されています。

プロパティの操作

プロパティの追加

Add-Member を使用して PSCustomObject に新しいプロパティを追加することもできます。

$myObject | Add-Member -MemberType NoteProperty -Name 'ID' -Value 'KevinMarquette'

$myObject.ID

プロパティの削除

オブジェクトからプロパティを削除することもできます。

$myObject.psobject.properties.remove('ID')

.psobject は、ベース オブジェクト メタデータへのアクセスを与える組み込みメンバーです。 組み込みメンバーに関する詳細については、about_Intrinsic_Members に関するページを参照してください。

プロパティ名の列挙

オブジェクトのすべてのプロパティ名の一覧が必要になることがあります。

$myObject | Get-Member -MemberType NoteProperty | Select -ExpandProperty Name

この同じ一覧を psobject プロパティからも取得できます。

$myobject.psobject.properties.name

Note

Get-Member では、プロパティがアルファベット順に返されます。 メンバー アクセス演算子を使用してプロパティ名を列挙すると、オブジェクトで定義された順序でプロパティが返されます。

プロパティへの動的なアクセス

プロパティ値に直接アクセスできることは既に説明しました。

$myObject.Name

プロパティ名に文字列を使用しても、やはり機能します。

$myObject.'Name'

これをもう一歩進めて、プロパティ名に変数を使用することができます。

$property = 'Name'
$myObject.$property

これは奇妙に見えますが、うまく機能します。

PSCustomObject をハッシュテーブルに変換する

最後のセクションから作業を続行する場合、プロパティを動的にウォークして、そこからハッシュテーブルを作成することができます。

$hashtable = @{}
foreach( $property in $myobject.psobject.properties.name )
{
    $hashtable[$property] = $myObject.$property
}

プロパティのテスト

プロパティが存在するかどうかを知る必要がある場合は、そのプロパティに値があるかどうかを確認するだけで済みます。

if( $null -ne $myObject.ID )

ただし、値が $null である可能性がある場合は、psobject.properties をチェックすることで、これが存在するかどうかを確認できます。

if( $myobject.psobject.properties.match('ID').Count )

オブジェクト メソッドの追加

スクリプト メソッドをオブジェクトに追加する必要がある場合は、Add-MemberScriptBlock を使用して実行できます。 現在のオブジェクトを参照するには、this 自動変数を使用する必要があります。 オブジェクトをハッシュテーブルに変換する scriptblock を次に示します。 (最後の例と同じコード形式)

$ScriptBlock = {
    $hashtable = @{}
    foreach( $property in $this.psobject.properties.name )
    {
        $hashtable[$property] = $this.$property
    }
    return $hashtable
}

次に、それをスクリプト プロパティとしてオブジェクトに追加します。

$memberParam = @{
    MemberType = "ScriptMethod"
    InputObject = $myobject
    Name = "ToHashtable"
    Value = $scriptBlock
}
Add-Member @memberParam

その後、次のように関数を呼び出すことができます。

$myObject.ToHashtable()

オブジェクトと値の型

オブジェクトと値の型では、変数の割り当ての処理方法が異なります。 値の型を相互に割り当てた場合、値だけが新しい変数にコピーされます。

$first = 1
$second = $first
$second = 2

この場合、$first は 1、$second は 2 です。

オブジェクト変数では、実際のオブジェクトへの参照が保持されます。 新しい変数に 1 つのオブジェクトを割り当てても、それらは引き続き同じオブジェクトを参照します。

$third = [PSCustomObject]@{Key=3}
$fourth = $third
$fourth.Key = 4

$third$fourth はオブジェクトの同じインスタンスを参照するため、$third.key$fourth.Key の両方が 4 になります。

psobject.copy()

オブジェクトを本当にコピーする必要がある場合は、複製することができます。

$third = [PSCustomObject]@{Key=3}
$fourth = $third.psobject.copy()
$fourth.Key = 4

複製では、そのオブジェクトのシャロー コピーが作成されます。 この例では、それぞれが異なるインスタンスを持つようになり、$third.key が 3、$fourth.Key が 4 になります。

私がこれをシャロー コピーと呼ぶのは、入れ子になったオブジェクト (プロパティを持つオブジェクトに他のオブジェクトが含まれている) がある場合、最上位レベルの値だけがコピーされるためです。 子オブジェクトはお互いを参照します。

カスタム オブジェクトの型に対する PSTypeName

これでオブジェクトを作成できたので、これを使用して、まったく明白ではないかもしれないその他の操作をいくつか実行できます。 まず、これに PSTypeName を指定する必要があります。 最もよく使用されている一般的な方法を次に示します。

$myObject.PSObject.TypeNames.Insert(0,"My.Object")

最近、こちらの /u/markekraus による投稿から、これを行う別の方法を発見しました。 私はもう少し詳しく調べて、Adam BertramMike Shepard によるこのアイデアについてのさらなる投稿を見つけました。そこでは、これをインラインで定義できるようにするアプローチが説明されています。

$myObject = [PSCustomObject]@{
    PSTypeName = 'My.Object'
    Name       = 'Kevin'
    Language   = 'PowerShell'
    State      = 'Texas'
}

これはこの言語にとてもよく合っています。 これで、適切な型名を持つオブジェクトを作成できたので、さらにいくつかの操作を行うことができます。

注意

PowerShell クラスを使用して、カスタム PowerShell 型を作成することもできます。 詳細については、PowerShell クラスの概要に関する記事を参照してください。

DefaultPropertySet の使用 (長い道のり)

PowerShell では、既定で表示するプロパティが自動的に決定されます。 多くのネイティブ コマンドには、すべての複雑な処理を行う .ps1xml書式設定ファイルがあります。 こちらの Boe Prox による投稿から、PowerShell のみを使用してカスタム オブジェクトに対してこれを行う別の方法があります。 これに使用する MemberSet を指定できます。

$defaultDisplaySet = 'Name','Language'
$defaultDisplayPropertySet = New-Object System.Management.Automation.PSPropertySet('DefaultDisplayPropertySet',[string[]]$defaultDisplaySet)
$PSStandardMembers = [System.Management.Automation.PSMemberInfo[]]@($defaultDisplayPropertySet)
$MyObject | Add-Member MemberSet PSStandardMembers $PSStandardMembers

これで、オブジェクトがシェルに表示されると、既定ではこれらのプロパティのみが表示されます。

Update-TypeData と DefaultPropertySet

これは素晴らしいですが、私は最近、Update-TypeData を使用してデフォルトのプロパティを指定するより良い方法を見ました。

$TypeData = @{
    TypeName = 'My.Object'
    DefaultDisplayPropertySet = 'Name','Language'
}
Update-TypeData @TypeData

これは簡単なので、もし私がこの投稿をクイック リファレンスとして使えなくても、内容をほとんど覚えていたでしょう。 これで、多くのプロパティを持つオブジェクトを簡単に作成し、さらにシェルから見たときにわかりやすい表示にすることができるようになりました。 その他のプロパティにアクセスしたり表示したりする必要がある場合も、それらはまだ存在しています。

$myObject | Format-List *

Update-TypeData と ScriptProperty

このビデオから得たその他の情報は、オブジェクトのスクリプト プロパティを作成することです。 これは既存のオブジェクトに対しても同様に機能することを、ここで指摘しておきましょう。

$TypeData = @{
    TypeName = 'My.Object'
    MemberType = 'ScriptProperty'
    MemberName = 'UpperCaseName'
    Value = {$this.Name.toUpper()}
}
Update-TypeData @TypeData

これは、オブジェクトが作成される前に実行しても後に実行しても機能します。 これが、スクリプト プロパティで Add-Member を使用する場合との相違点です。 以前に言及した方法で Add-Member を使用した場合、これはオブジェクトのその特定のインスタンス上にのみ存在します。 これは、この TypeName を持つすべてのオブジェクトに適用されます。

関数のパラメーター

これで、これらのパラメーターのカスタム型を関数やスクリプト内で使用できるようになりました。 1 つの関数でこれらのカスタム オブジェクトを作成し、他の関数に渡すことができます。

param( [PSTypeName('My.Object')]$Data )

PowerShell では、オブジェクトが指定した型である必要があります。 型が自動的に一致しない場合は検証エラーがスローされるため、自分のコードでそれをテストする手間が省けます。 これは PowerShell に得意な処理を任せる場合の優れた例です。

関数の OutputType

また、高度な関数の OutputType を定義することもできます。

function Get-MyObject
{
    [OutputType('My.Object')]
    [CmdletBinding()]
        param
        (
            ...

OutputType 属性値は、ドキュメントのメモにすぎません。 これを関数のコードから派生させたり、実際の関数の出力と比較したりすることはできません。

出力型を使用する主な理由は、関数に関するメタ情報に自分の意図を反映させることです。 開発環境で利用できる Get-CommandGet-Help のようなものです。 詳細については、そのヘルプを参照してください: about_Functions_OutputTypeAttribute

ただし、関数の単体テストを行うために Pester を使用している場合は、出力オブジェクトが OutputType と一致することを検証することをお勧めします。 これにより、不適切な場合にパイプに送られた変数をキャッチできます。

最後に

このコンテキストはすべて [PSCustomObject] に関するものでしたが、この情報の大部分はオブジェクト全般に適用されます。

これらの機能のほとんどは以前にどこかで見たことがありましたが、PSCustomObject に関する情報のコレクションとして提示されているのは見たことがありませんでした。 つい先週、私はもう一つを偶然見つけて、それを前に見ていなかったことに驚きました。 私は、これらすべてのアイデアをまとめて、皆様が全体像を確認し、使用する機会があればそれらを認識できるようにしたいと思いました。 皆様が何かを学び、ご自分のスクリプトで使用する方法を見つけられれば幸いです。