【PowerShell】PSCustomObjectの作り方|一覧表示・CSV出力に向くオブジェクト設計

【PowerShell】PSCustomObjectの作り方|一覧表示・CSV出力に向くオブジェクト設計 PowerShell

PowerShellで「1件分のデータ(レコード)」を表すのに使うのがPSCustomObjectです。名前・年齢・部署のように、複数の項目をひとまとめにして持ち、一覧表示やCSV出力にきれいに対応できます。ログの集計結果や、処理対象のリストを作るときに欠かせません。

似たものにハッシュテーブルがありますが、用途が違います。特に重要なのが、ハッシュテーブルはCSVに出力するとデータではなく内部情報が出てしまうのに対し、PSCustomObjectはきれいな表として出力できる点です。この記事では、実機で確認しながら、PSCustomObjectの作り方と使いどころを整理します。

先に結論

  • 作り方は[PSCustomObject]@{ Name = "山田"; Age = 30 }です。書いた順番(プロパティの順序)が保たれます
  • 値の参照は$obj.Nameのようにプロパティでアクセスします。
  • ハッシュテーブルは「キーで値を引く」用途、PSCustomObjectは「1件のレコードを表す」用途です。
  • CSV出力やテーブル表示にはPSCustomObjectを使います。ハッシュテーブルをExport-Csvすると内部情報が出てしまいます。
  • プロパティの追加はAdd-Member、ループで[PSCustomObject]@{}を集めれば一覧データになります。
  • 一覧表示はFormat-Table、CSV出力はExport-Csv -NoTypeInformationです。

ハッシュテーブルとの基本的な違いは配列とハッシュテーブルの使い方、JSONとの相互変換はJSONを読み書きする方法、一覧の絞り込みや計算プロパティはWhere-Object・Select-Object・Sort-Objectもあわせて参考になります。

スポンサーリンク

PSCustomObjectを作る

作り方はとても簡単で、ハッシュテーブルの前に[PSCustomObject]を付けるだけです。プロパティ名と値のペアを書きます。

create-pscustomobject.ps1
# [PSCustomObject] を付けて作る
$user = [PSCustomObject]@{
    Id   = 1
    Name = "山田"
    Age  = 30
}

# プロパティでアクセスする
$user.Name    # 山田
$user.Age     # 30

# 型を確認すると PSCustomObject
$user.GetType().Name   # PSCustomObject

実機でも、$user.Name山田が取れ、型はPSCustomObjectになりました。プロパティの順番(Id → Name → Age)は、書いた順のまま保たれます。これが、次に説明するハッシュテーブルとの大きな違いです。

【重要】ハッシュテーブルとの違い

[PSCustomObject]@{}@{}(ハッシュテーブル)は見た目が似ていますが、性質と用途が違います。実機で確認した結果をまとめます。

項目 ハッシュテーブル @{} PSCustomObject
主な用途 キーで値を引く(辞書) 1件のレコードを表す
アクセス $ht["Name"] / $ht.Name $obj.Name
順序 保証されない 書いた順を保つ
一覧表示 崩れる きれいな表になる
CSV出力 内部情報が出る(使えない) 列としてきれいに出る
hashtable-vs-object.ps1
# ハッシュテーブルは順序が保証されない
$ht = @{ Id = 1; Name = "山田"; Age = 30 }
$ht.Keys   # 実機では Age, Name, Id の順(書いた順ではない)

# PSCustomObject は書いた順を保つ
$obj = [PSCustomObject]@{ Id = 1; Name = "山田"; Age = 30 }
$obj.PSObject.Properties.Name   # Id, Name, Age(書いた順)

実機でも、同じ内容でもハッシュテーブルのキー順はAge, Name, Idと入れ替わり、PSCustomObjectId, Name, Ageと書いた順を保ちました。一覧やCSVでは列の順番が大事なので、表示・出力用のデータはPSCustomObjectで作ります。

プロパティを追加・変更する

作ったあとに値を変えるのは、プロパティへの代入でできます。新しいプロパティを足すにはAdd-Memberを使います。

add-member.ps1
$user = [PSCustomObject]@{ Id = 1; Name = "山田" }

# 既存プロパティの変更
$user.Name = "佐藤"

# プロパティの追加
$user | Add-Member -NotePropertyName "City" -NotePropertyValue "東京"
$user.City   # 東京

# まとめて複数追加するなら、最初から全部書くほうが簡単
$user2 = [PSCustomObject]@{
    Id = 2; Name = "鈴木"; City = "大阪"; Age = 28
}

実機でも、Add-MemberCityを追加すると、プロパティがId, Name, Cityと末尾に足されました。後から1つ2つ足すならAdd-Member、最初から項目が決まっているなら、宣言時にまとめて書くほうがすっきりします。

オブジェクトの配列を作る(ループで集める)

PSCustomObjectの真価は、同じ形のオブジェクトを並べて一覧データにするときに発揮されます。ループの中で[PSCustomObject]@{}を出力し、全体を変数で受け取ります。

object-array.ps1
# ループで同じ形のオブジェクトを集める
$results = foreach ($i in 1..3) {
    [PSCustomObject]@{
        No     = $i
        Square = $i * $i
    }
}

$results.Count       # 3
$results[1].Square   # 4

# 実例: ファイル一覧を整形して持つ
$files = Get-ChildItem "C:\work" -File | ForEach-Object {
    [PSCustomObject]@{
        名前   = $_.Name
        サイズKB = [math]::Round($_.Length / 1KB, 1)
        更新日 = $_.LastWriteTime.ToString("yyyy/MM/dd")
    }
}

実機でも、ループで作った$resultsは3件のオブジェクト配列になり、$results[1].Square4が取れました。Get-ChildItemの結果を必要な項目だけに整形して持つ、といった使い方が実務では頻出します。ループの結果を変数で受ける書き方はループ完全ガイドでも解説しています。

一覧で表示する(Format-Table / Format-List)

オブジェクトの配列は、そのまま表示すると自動で表(テーブル)になります。明示的に整形するならFormat-Table(表)やFormat-List(縦並び)を使います。

format-table-list.ps1
# 表形式で表示(プロパティが少ないと自動でこの形)
$results | Format-Table

# 縦並びで表示(プロパティが多いときや詳細確認に)
$results | Format-List

# 表示する列を選ぶ
$results | Format-Table No, Square -AutoSize

プロパティが4つ以下なら自動で表に、5つ以上だと自動で縦並び(リスト)になります。-AutoSizeを付けると、列幅が内容に合わせて調整されます。なお、Format-Tableなどの整形コマンドは表示専用です。整形した後のデータを変数に入れて再利用したり、CSVに出したりはできないため、整形は一番最後に使います。

CSVに出力する(Export-Csv)

オブジェクトの配列はExport-CsvでそのままCSVファイルにできます。プロパティ名がそのまま列名になります。ここで、ハッシュテーブルとの決定的な違いが出ます。

export-csv.ps1
# PSCustomObject はきれいなCSVになる
$results | Export-Csv "C:\work\out.csv" -NoTypeInformation -Encoding UTF8
# ヘッダー: "No","Square" ← プロパティ名が列になる

# NG: ハッシュテーブルを Export-Csv すると内部情報が出る
@{ Id = 1; Name = "山田" } | Export-Csv "C:\work\ng.csv" -NoTypeInformation
# ヘッダー: "IsReadOnly","IsFixedSize","Keys","Values","Count" ...
# ← データではなくハッシュテーブルの内部プロパティが出てしまう
CSV出力にハッシュテーブルを使わない

実機で確認したところ、オブジェクト配列のExport-Csvはヘッダーが"No","Square"ときれいな列になりました。一方、ハッシュテーブルをExport-Csvすると、ヘッダーが"IsReadOnly","IsFixedSize","Keys","Values","Count"などになり、データではなくハッシュテーブルの内部情報が出力されてしまいました。CSVやテーブル表示に使うデータは、必ずPSCustomObjectで作ってください。-NoTypeInformationは先頭の型情報行を消すための指定です(PowerShell 6以降は既定で付きません)。CSVの文字コードは-Encoding UTF8で指定します。

Select-Objectや計算プロパティと組み合わせる

すでにあるオブジェクト(コマンドの出力など)から必要な項目を選んでPSCustomObject相当にするなら、Select-Objectが便利です。計算した新しい列も作れます。

select-calculated.ps1
# Get-Process から必要な項目だけ選ぶ(結果は表示・CSV向きのオブジェクト)
Get-Process |
    Select-Object Name, Id, @{
        Name = "メモリMB"
        Expression = { [math]::Round($_.WorkingSet / 1MB, 1) }
    }

# そのまま CSV に出せる
Get-Process |
    Select-Object Name, Id |
    Export-Csv "C:\work\process.csv" -NoTypeInformation -Encoding UTF8

Select-Objectでプロパティを選んだり計算プロパティを足したりした結果も、表示やCSV出力に使えるオブジェクトになります。Select-Objectや計算プロパティの詳細はWhere-Object・Select-Object・Sort-Objectを参照してください。手作りの[PSCustomObject]@{}と、Select-Objectでの加工を、場面で使い分けます。

よくある失敗

CSV出力にハッシュテーブルを使う

ハッシュテーブルをExport-Csvすると内部情報が出ます。CSVやテーブル表示にはPSCustomObjectを使います。

プロパティの順序が崩れる

順序が大事なら[PSCustomObject]@{}を使います。ハッシュテーブルはキーの順序を保証しません。

Format-Tableした結果を再利用しようとする

Format-Tableなどは表示専用です。整形後のデータは加工やCSV出力に使えません。整形は最後に行い、それまではオブジェクトのまま扱います。

ループ内で配列に+=して集める

オブジェクトを集めるなら、$list = foreach (...) { [PSCustomObject]@{} }のようにループ全体を変数で受けるほうが、+=より高速です。

古いNew-Objectの書き方で順序が崩れる

New-Object PSObject -Property @{...}はハッシュテーブルを元にするため順序が保証されません。今は[PSCustomObject]@{}を使えば順序が保たれます。

よくある質問

QハッシュテーブルとPSCustomObjectはどう使い分けますか?
Aキーで値を引く一時的なデータ(設定値の保持や集計のカウンタなど)はハッシュテーブル、表示・CSV出力・1件のレコードとして扱うデータはPSCustomObjectです。特にCSVやテーブル表示にはPSCustomObjectを使ってください。
Qプロパティの順番を保つには?
A[PSCustomObject]@{}を使えば、書いた順番がそのまま保たれます。通常のハッシュテーブルや古いNew-Object PSObjectは順序を保証しないため、列の順番が大事なときは[PSCustomObject]を使います。
Qあとからプロパティを足すには?
A$obj | Add-Member -NotePropertyName "列名" -NotePropertyValue 値で追加できます。最初から項目が決まっているなら、宣言時にまとめて書くほうが簡単です。
Qコマンドの出力をCSVにきれいに出すには?
ASelect-Objectで必要なプロパティを選び、Export-Csv -NoTypeInformation -Encoding UTF8で出力します。選んだプロパティ名がそのまま列名になります。
Q一覧をループで作るときの注意は?
A$list = foreach (...) { [PSCustomObject]@{} }のように、ループ全体を変数で受けると、出力したオブジェクトが配列として集まります。+=で足すより高速で読みやすくなります。

まとめ

  • PSCustomObject[PSCustomObject]@{ ... }で作り、書いた順のプロパティ順序を保ちます
  • ハッシュテーブルは「キーで引く」用途、PSCustomObjectは「1件のレコード」用途です。
  • CSV出力やテーブル表示にはPSCustomObjectを使います。ハッシュテーブルは内部情報が出てしまいます。
  • プロパティ追加はAdd-Member、一覧はループで[PSCustomObject]@{}を集めます。
  • 表示はFormat-TableFormat-List(表示専用なので最後に使う)。
  • CSVはExport-Csv -NoTypeInformation -Encoding UTF8で出力します。

PowerShellでデータを「見せる・出力する」段になったら、PSCustomObjectの出番です。ハッシュテーブルとの使い分け、とりわけCSV出力での違いさえ押さえれば、集計結果やファイル一覧を、きれいな表やCSVとして自在に出力できるようになります。これでPowerShellの基礎は一通りそろいました。