[Powershell]自作オブジェクト作る練習


# 概要

とりあえず基礎っぽいものってことで、以下の構成のオブジェクトを作成してみた。
実際は、Get-Hogeでなんかの情報取得して、Where-ObjectやSelect-Object使って特定のプロパティ抜き出したものでオブジェクト作ることが多いと思う。
CSV読み込んでデータ処理したい場合なんかにもたぶん便利。

↓それっぽいクラス図

```rb
TestObject
  + Index[]:int
  + Name[]:string  #最初に適当に定義しておく
  + Width[]:int  #値は適当に乱数で決める
  + Height[]:int  #値は適当に乱数で決める
  + TriangleArea[]:double  #上記2つから求めた三角形の面積
  + SearchForIndex():object  #Index番号を指定して検索するメソッド
  + Search():object  #検索に使うプロパティ名と値を指定して検索するメソッド
```

先に最終的なコードを掲載

```powershell
function Get-TestObject{
    $obj_ary = @()    # return用オブジェクト
    
    $names = @(
        "foo"
        "bar"
        "hoge"
        "fuga"
        "fuga"    # 同名でも可
    )
    
    # 各プロパティを定義
    $i = 1
    foreach($item in $names){
        $obj = New-Object PSCustomObject
        $obj | Add-Member -NotePropertyMembers @{
            Index = $i
            Name = $item
            Height = ( 1 + (Get-Random 9) )    # ランダムで1～10代入
            Width = ( 1 + (Get-Random 9) )    # ランダムで1～10代入
        }
        $obj | Add-Member -MemberType ScriptProperty -Name TriangleArea -Value {
            $this.Width * $this.Height / 2
        }

        $i += 1
        $obj_ary += ($obj | Select-Object Index, Name, Width, Height, TriangleArea)
    }

    # Index番号を指定して検索するメソッド
    $obj_ary | Add-Member -MemberType ScriptMethod -Name SearchForIndex -Value {
        param($index)
        $ret = ($this | Where-Object { $_.Index -eq $index })
        return $ret
    }

    # 検索に使うプロパティ名と値を指定して検索するメソッド
    $obj_ary | Add-Member -MemberType ScriptMethod -Name Search -Value {
        param($prop, $value)
        $ret = ($this | Where-Object { $_.$prop -eq $value })
        return $ret
    }

    # return
    $obj_ary
}
```

```bat
# テスト用実行コマンド
$test = Get-TestObject
echo '##### $test ###############'
echo $test
echo '##### $test | ft ###############'
echo $test | ft
echo '##### $test | Get-Member ###############'
echo $test | Get-Member
echo '##### ($test).name ###############'
echo ($test).name
echo '##### ($test).SearchForIndex(1) | ft ###############'
echo ($test).SearchForIndex(1) | ft
echo '##### ($test).Search("Name", "fuga") | ft ###############'
echo ($test).Search("Name", "fuga") | ft
echo '##### $test | Where-Object { $_.TriangleArea -gt 10 } | ft ###############'
echo $test | Where-Object { $_.TriangleArea -gt 10 } | ft
echo '####################'
```

----

# 申し訳程度の解説

だいたいコード見ればわかるけど、ポイントはたぶん3つ。

1. PSCustomObjectで1レコード分のオブジェクトを作る
    - 単純に連想配列で作ると綺麗にならない 
2. オブジェクトにパイプでAdd-Memberに渡してプロパティやメソッドを追加する
3. 追加するときにSelect-Objectを指定すると表示される順番を決められる

特にAdd-Memberはオプションが多くてややこしい。
あんまりわかってないけど、とりあえず以下の2つだけわかってれば使えると思う。

- 静的なプロパティを定義したい場合

```powershell
Add-Member -NotePropertyMembers 配列
#※Powershell 3.0以上なら「[PSCustomObject] 配列」でも可
#※プロパティが一つだけなら、以下と同じく-Nameと-Valueで指定しても可
```
    
- 静的なプロパティを使って加工したプロパティを定義したい場合

```powershell
Add-Member -MemberType ScriptProperty -Name プロパティ名 -Value {スクリプト}
#※{ }の中で、$thisを使って静的プロパティの値を参照できる
```
    
----

# 実行結果の例

テスト用のコードの結果は以下のようになる。
オブジェクトになっているから、特定のプロパティ名だけ抜き出したり、Format-Tableに渡して表示したりってことが通常のオブジェクトを扱うのと同じようにできる。
CSVやHTMLへのエクスポートもできる。

```rb
##### $test ###############


Index        : 1
Name         : foo
Width        : 1
Height       : 6
TriangleArea : 3

Index        : 2
Name         : bar
Width        : 5
Height       : 9
TriangleArea : 22.5

Index        : 3
Name         : hoge
Width        : 1
Height       : 6
TriangleArea : 3

Index        : 4
Name         : fuga
Width        : 4
Height       : 9
TriangleArea : 18

Index        : 5
Name         : fuga
Width        : 4
Height       : 7
TriangleArea : 14

##### $test | ft ###############



Index Name Width Height TriangleArea
----- ---- ----- ------ ------------
    1 foo      1      6            3
    2 bar      5      9         22.5
    3 hoge     1      6            3
    4 fuga     4      9           18
    5 fuga     4      7           14


##### $test | Get-Member ###############


   TypeName: Selected.System.Management.Automation.PSCustomObject

Name           MemberType   Definition                     
----           ----------   ----------                     
Equals         Method       bool Equals(System.Object obj) 
GetHashCode    Method       int GetHashCode()              
GetType        Method       type GetType()                 
ToString       Method       string ToString()              
Height         NoteProperty int Height=6                   
Index          NoteProperty int Index=1                    
Name           NoteProperty string Name=foo                
TriangleArea   NoteProperty System.Int32 TriangleArea=3    
Width          NoteProperty int Width=1                    
Search         ScriptMethod System.Object Search();        
SearchForIndex ScriptMethod System.Object SearchForIndex();
##### ($test).name ###############
foo
bar
hoge
fuga
fuga
##### ($test).SearchForIndex(1) | ft ###############



Index Name Width Height TriangleArea
----- ---- ----- ------ ------------
    1 foo      1      6            3


##### ($test).Search("Name", "fuga") | ft ###############

Index Name Width Height TriangleArea
----- ---- ----- ------ ------------
    4 fuga     4      9           18
    5 fuga     4      7           14


##### $test | Where-Object { $_.TriangleArea -gt 10 } | ft ###############

Index Name Width Height TriangleArea
----- ---- ----- ------ ------------
    2 bar      5      9         22.5
    4 fuga     4      9           18
    5 fuga     4      7           14


####################
```

# おまけ（特殊フォルダの一覧を取得）

デスクトップとかマイドキュメントとか、環境変数でパスが定義されている特殊なフォルダのパスをすべて取得する。
特殊フォルダすべての名前とパスは元々同じオブジェクトに入っていないため、上手く文字列を結合して表示しないと見づらいものになってしまう。
こんなときオブジェクトを使うと、コードも表示結果も見やすくて扱いやすいスマートな書き方ができる。

```powershell
function Get-SpecialFolders{
    $obj_ary = @()
    $specialfolders = ([environment+specialfolder] | Get-Member -MemberType Properties -static)
    
    foreach($item in $specialfolders){
        $obj = New-Object PSCustomObject
        $obj | Add-Member -NotePropertyMembers @{
            Name = ($item.Name)
            Path = ([environment]::GetFolderPath($item.Name))
        }
    
        $obj_ary += ($obj | Select-Object Name, Path)
    }
    
    $obj_ary
}

```

結果はこんな感じ。

```rb
> Get-SpecialFolders

Name                   Path                                                                                     
----                   ----                                                                                     
AdminTools             C:\Users\Owner\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Administrative Tools
ApplicationData        C:\Users\Owner\AppData\Roaming                                                           
CDBurning              C:\Users\Owner\AppData\Local\Microsoft\Windows\Burn\Burn                                 
CommonAdminTools       C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Administrative Tools  
︙
```

また、例えばDドライブのフォルダだけを取得したい場合はPowershellの基本通り、

```powershell
Get-SpecialFolders | Where-Object { $_.Path -match "^D" }
```

と書けばよい。
一度必要な情報だけでオブジェクトを作ってしまえば、後は余計な変数やプロパティを気にせずに扱えてとても便利。

---

# 参考ページ

カスタムオブジェクトの使い方などの解説
[powershell チートシート](http://qiita.com/jca02266/items/a0c71ae90d055ab3893c)
[PowerShellのカスタムオブジェクト（PSCustomObject）の使い方](http://blog.livedoor.jp/morituri/archives/54579410.html)
[PowerShell再入門：11. PSObjectとは](http://m0t0k1x2.tumblr.com/post/122321069469/powershell%E5%86%8D%E5%85%A5%E9%96%8011-psobject%E3%81%A8%E3%81%AF)

Add-Memberの詳細な解説
[Add-Member を極める](http://tech.blog.aerie.jp/entry/2013/12/23/173004)

特殊フォルダの取得コード
[PowerShell: ◆特殊フォルダーの取得](http://mtgpowershell.blogspot.jp/2010/11/blog-post_22.html)