PowerShellでCSVデータを読み取り、付け足し、保存する

# やりたいこと

`order.csv` に 担当列を追加し、`担当.csv` を参照しながら商品担当者分行を追加して、`export.csv` に出力する。
つまり、 **DBでやれ** てな事をやります。

`order.csv`

order_no | item_no | item_name
|--:|:--:|:---:|
|1|"001"|アイスコーヒー
|2|"002"|A定食
|3|"004"|ハンバーグ定食|"001"|アイスコーヒー
|2|"002"|A定食
|3|"004"|ハンバーグ定食

`担当.csv`

| item_no | staff_name     |
|---------|----------------|
| 001     | 山田           |
| 002     | 山田;吉田;古田 |
| 003     | 田中           |
| 004     | 岩田;石澤      |

`export.csv`

| order_no | item_no | item_name      | 担当 |
|----------|---------|----------------|------|
| 1        | 001     | アイスコーヒー | 山田 |
| 2        | 002     | A定食          | 山田 |
| 2        | 002     | A定食          | 古田 |
| 2        | 002     | A定食          | 吉田 |
| 3        | 004     | ハンバーグ定食 | 石澤 |
| 3        | 004     | ハンバーグ定食 | 岩田 |

# 実装

## 環境

Windows7 SP1,PowerShell 3.0

## こんな感じになりました。

```powershell
# Import-CSVと同時に列を追加する。
$orderDat = Import-Csv -Encoding UTF8 -Path ".\order.csv" |
Select-Object *,@{Name='担当';Expression={''}}

$staffDat = Import-Csv -Encoding UTF8 -Path ".\担当.csv"



# Main logic
[System.Array] $ExportDat = $null
# すべての入荷データを確認
foreach($currentDat in $orderDat){
    # 担当スタッフを検索する。できればnull判定まで一行でやりたいが。。。
    $staff = $($staffDat | Where-Object{$_.item_no -eq $currentDat.item_no}).staff_name
    if($staff -eq $null){$staff = ""}
    # 重複と空白を削除し、文字列の配列にする。
    $staffArray = $($staff.Split(";")) | Sort-Object | Get-Unique | Where-Object{$_ -ne "" -and $_ -ne $null} 
    

    # CSVデータを追加する。
    if($staffArray -ne $null){
        foreach($st in $staffArray){
            $currentDat.担当 = $st
            # そのまま代入すると参照になってしまうのでExport用の変数を用意する。
            $tmpDat = New-Object psobject
            # 元データから情報をいれる。
            $currentDat.psobject.Properties | ForEach-Object{
                $tmpDat | Add-Member -MemberType $_.MemberType -Name $_.Name -Value $_.Value
            }
            # 出力用配列にデータを追加する。
            $ExportDat = $ExportDat + $tmpDat
        }
    }else {
        $tmpDat = New-Object psobject
        $currentDat.psobject.Properties | ForEach-Object{
            $tmpDat | Add-Member -MemberType $_.MemberType -Name $_.Name -Value $_.Value
        }
        $ExportDat += $tmpDat
    }
}
#データをソートする。
$ExportDat = $ExportDat | Sort-Object order_no
# CSVファイルを出力する。
$ExportDat | Export-Csv  -Encoding Default -NoTypeInformation -Force -Path ".\export.csv"
```

## 要点メモ

### CSVのインポートとEncode

`Import-CSV` の一文でさっくりデータをとれます。
ただし、`-Encoding` がPowershell2.0ではうまく使えないのでWindows7SP1環境には別途3.0をインストールする必要があります。
これが結構な罠でそこそこ嵌った。

```powershell
$orderDat = Import-Csv -Encoding UTF8 -Path ".\order.csv" 
```
CSVデータへの列追加は、`Select-Object` で行う。

```ps1
| Select-Object *,@{Name='担当';Expression={''}}
```

### 出力用の変数は型指定で

ちゃんと指定しないと行を足す時に動いてくれません。

```ps1
[System.Array] $ExportDat = $null
```
### ディープコピーは根性で

これが一番嵌った。StackOverflowで調べた結果このようになった。

まず `psobject` を新規に作成し（そもそもpsobjectが何なのかわかっていない）

```ps1
$tmpDat = New-Object psobject
```
`Object[]` である `currentDat` のプロパティを `ForEach-Object` ですべて `Add-Member` する。

```ps1
$currentDat.psobject.Properties | ForEach-Object{
$tmpDat | Add-Member -MemberType $_.MemberType -Name $_.Name -Value $_.Value
}
```

晴れてディープコピー完了となるので、`$ExportDat` 配列に追加する。

```ps1
$ExportDat = $ExportDat + $tmpDat
```

もう、わからない感がひしひししてます。もっとスマートにやりたい・・・

### CSVExportには-NoTypeInformationを

エキスポートも超簡単、一行、PowerShell最高

```ps1
$ExportDat | Export-Csv  -Encoding Default -NoTypeInformation -Force -Path ".\export.csv"
```

`-Encoding` を気をつける事は前述したが、追加で `-NoTypeInformation` を付けることを忘れずに。
巷の解説ページではこのオプションがついていないことが殆どだが、これを付けないと出力されたCSVの一行目にいらぬ情報がつく。
実務ではいらない情報が付いて来るのがデフォルトになっているのだ・・・

# 蛇足：PowerShellのエディタについて

巷のPowerShell関連情報ではあまりエディタについて語られていない。
PowerShellを使う ~~変態~~ プロにはそんな情報はいらないのだろう。

あまり前置きが長くても何なので、結論だけ。

**PowerShellエディタはVisual Studio Code！！**

ISEより全然使いやすいと思います。