PowerShell

【PowerShell】配列変数からオブジェクト変数への暗黙の型変換について

私はプログラマーではないので他の言語は知らないのですが、本記事に記載されているような現象はどの言語でも発生する現象なのでしょうか。VBScriptやGoogleAppsScriptはコード経験ありますがこの事象に出会ったことがないです。もし知っていらっしゃる方がおりましたらご教示頂けるとありがたいです...。

【発生した事象について】

Trelloで管理しているタスク情報から次回営業日期限のタスクだけSlackへ送信するというスクリプトをPowerShellで書き、ほぼ完成したので運用を昨日から始めたのですが、二日目で下の画像のような結果がSlackに送信されていたので、めっちゃ笑いました。

image.png
普段なら↓の画像のように届く想定です。
image.png

縦読みになっていますね。

問題の送信部分の構文が以下のところです。
フィルタした変数($TrelloToSlack)をfor文でタスクを一要素ずつ送信しています。

フィルタした変数をfor文で一要素ずつ送信
    $TrelloToSlack = $TrelloTable | Where-Object { $_ -like "*$NEXTDATE*" } | `
                     ConvertFrom-String -Delimiter "," -PropertyNames "ボード","リスト","カード","氏名","更新日","期限","リンク","URL" | `
                     foreach { "{0} {1} {2} {3} {4}" -f $_.ボード,$_.リスト,$_.カード,$_.氏名,$_.URL }
    $TrelloLines = $TrelloToSlack.Length
    $num = 1
    for ($i=0; $i -lt $TrelloLines; $i++ ) {
            $TrelloArray = "[$DATE`期限のタスク-$num`つ目]" + "%20%20%20" + $TrelloToSlack[$i] + "\n"
            $body = "payload={'text':'$TrelloArray'}"
            $Content = [System.Text.Encoding]::UTF8.GetBytes($body)
            $Response = Invoke-RestMethod -Uri <Webhook URL> -Method POST -Body $Content
            $num = $num + 1
    }

【事象発生の原因箇所について】

色々と確認してみた結果、配列変数(ここでは$TrelloToSlack)をフィルタ処理した際にオブジェクトが一つだけになった場合はオブジェクト型に型変換する仕様のようです。↓が調査結果を端的に示したものです。
1.PNG

Select-Object で2つだけ残るようにした際は型がSystem.Object[]ですが、1つの場合はSystem.Stringとなっており暗黙の型変換が起きています。...まあこうやって書くと当然起こりうることかもとは思いました。オブジェクト一個だけの配列ってなんやねんって話しですよね。

【本事象による影響】

スクリプトの動作と照らし合わせると、

次回営業日期限のタスク数 = $TrelloToSlack 変数の要素数

とする必要があります。

冒頭の構文の場合、タスクが複数件の場合は意図した通りに出力されます。↓のような感じで。
image.png

しかしタスクが一件の場合は暗黙の型変換で冒頭の通りオブジェクト変数扱いとなってしまい、for文で一文字一文字切り出されてしまうことになります。

【対策の内容】

そこですぐに以下の構文に直しました。

フィルタした変数をfor文で一要素ずつ送信
    ###Slackへ送信(次回営業日期限のタスクがあった時のみ送信) 
    $TrelloLines = ($TrelloToSlack | Measure-Object -Line).Lines
    $num = 1
    for ($i=0; $i -lt $TrelloLines; $i++ ) {
        if ( $TrelloLines -eq 1 ) { #送信件数が一件の時のみ配列としてではなくオブジェクトとして送信。暗黙の型変換への対応
            $TrelloArray = "[$DATE`期限のタスク-$num`つ目]" + "%20%20%20" + $TrelloToSlack + "\n"
            $body = "payload={'text':'$TrelloArray'}"
            $Content = [System.Text.Encoding]::UTF8.GetBytes($body)
            $Response = Invoke-RestMethod -Uri <Webhook URL> -Method POST -Body $Content
            $num = $num + 1
        } else {
            $TrelloArray = "[$DATE`期限のタスク-$num`つ目]" + "%20%20%20" + $TrelloToSlack[$i] + "\n"
            $body = "payload={'text':'$TrelloArray'}"
            $Content = [System.Text.Encoding]::UTF8.GetBytes($body)
            $Response = Invoke-RestMethod -Uri <Webhook URL> -Method POST -Body $Content
            $num = $num + 1
        }
    }

修正ポイントは以下の二点です。

◆タスク一件だった場合の分岐処理追加

if文で追加しています。

追加したif文
    $TrelloLines = ($TrelloToSlack | Measure-Object -Line).Lines
    $num = 1
        if ( $TrelloLines -eq 1 ) { #送信件数が一件の時のみ配列としてではなくオブジェクトとして送信。暗黙の型変換への対応
            $TrelloArray = "[$DATE`期限のタスク-$num`つ目]" + "%20%20%20" + $TrelloToSlack + "\n"
            $body = "payload={'text':'$TrelloArray'}"
            $Content = [System.Text.Encoding]::UTF8.GetBytes($body)
            $Response = Invoke-RestMethod -Uri <Webhook URL> -Method POST -Body $Content
            $num = $num + 1
        } 

◆送信数カウント部分の修正

もう一つは送信数を$TrelloToSlack に対してLengthメソッドを使用することで算出していたところをMeasure-Objectの-Lineで算出するように修正しました。

修正前
$TrelloLines = $TrelloToSlack.Length
修正後
$TrelloLines = ($TrelloToSlack | Measure-Object -Line).Lines

修正前の問題点

Lengthメソッドは配列変数に対してもオブジェクト変数に対しても要素数をカウントしてくれるのですが、要素の意味合いが型タイプによって異なるため一件の場合だと意図した通りのカウントをしてくれません。

配列変数時のLengthの結果(意図した結果が出力される)
PS C:\Tools\logs> $TrelloTable
Study,ACT,ストレージの原則と技術-4章,HOBO,2018-05-06,2018-05-07,ストレージの原則と技術-4章,https://trello.com/c/9WfT4Seu
Study,ACT,PowerShellでSlack連携-チャット送信,HOBO,2018-05-07,2018-05-08,PowerShellでSlack連携-チャット送信,https://trello.com/c/KhVcUgsd
Study,Pending,ITIL Foundation,HOBO,2018-05-02,2018-06-30,ITIL Foundation,https://trello.com/c/O0buhBxY
Study,Pending,Unityの寺子屋読破,HOBO,2018-05-02,2018-08-31,Unityの寺子屋読破,https://trello.com/c/M62UI8Vl
Study,Pending,野球データをDB格納,HOBO,2018-05-02,2018-05-31,野球データをDB格納,https://trello.com/c/i5DIoQMQ
Study,Pending,Pythonでデータ整形,HOBO,2018-05-02,2018-07-31,Pythonでデータ整形,https://trello.com/c/ew3O4o5q
Study,NextDay,VMware ESXiインストール,HOBO,2018-05-07,2018-05-09,VMware ESXiインストール,https://trello.com/c/jGhQIIFJ
Study,NextDay,PHP-2章,HOBO,2018-05-07,2018-05-08,PHP-2章,https://trello.com/c/GmDaabj9
Study,NextDay,PowerShellでTrello連携-表作成,HOBO,2018-05-07,2018-05-08,PowerShellでTrello連携-表作成,https://trello.com/c/xHEi61iy

PS C:\Tools\logs> $TrelloToSlackArray = $TrelloTable | Select-Object -First 2
PS C:\Tools\logs> $TrelloToSlackArray.Length
2
オブジェクト変数時のLengthの結果(意図した結果が出力されない)
PS C:\Tools\logs> $TrelloTable
Study,ACT,ストレージの原則と技術-4章,HOBO,2018-05-06,2018-05-07,ストレージの原則と技術-4章,https://trello.com/c/9WfT4Seu
Study,ACT,PowerShellでSlack連携-チャット送信,HOBO,2018-05-07,2018-05-08,PowerShellでSlack連携-チャット送信,https://trello.com/c/KhVcUgsd
Study,Pending,ITIL Foundation,HOBO,2018-05-02,2018-06-30,ITIL Foundation,https://trello.com/c/O0buhBxY
Study,Pending,Unityの寺子屋読破,HOBO,2018-05-02,2018-08-31,Unityの寺子屋読破,https://trello.com/c/M62UI8Vl
Study,Pending,野球データをDB格納,HOBO,2018-05-02,2018-05-31,野球データをDB格納,https://trello.com/c/i5DIoQMQ
Study,Pending,Pythonでデータ整形,HOBO,2018-05-02,2018-07-31,Pythonでデータ整形,https://trello.com/c/ew3O4o5q
Study,NextDay,VMware ESXiインストール,HOBO,2018-05-07,2018-05-09,VMware ESXiインストール,https://trello.com/c/jGhQIIFJ
Study,NextDay,PHP-2章,HOBO,2018-05-07,2018-05-08,PHP-2章,https://trello.com/c/GmDaabj9
Study,NextDay,PowerShellでTrello連携-表作成,HOBO,2018-05-07,2018-05-08,PowerShellでTrello連携-表作成,https://trello.com/c/xHEi61iy

PS C:\Tools\logs> $TrelloToSlackObject = $TrelloTable | Select-Object -First 1
PS C:\Tools\logs> $TrelloToSlackObject.Length
96

オブジェクト側の出力結果が96なのは1文字=1要素と捉えてカウントしているためです。

これだと意図した送信件数分のカウントをしてくれないのでMeasure-Objectに構文を変更しています。

配列変数時のLengthの結果(意図した結果が出力される)
PS C:\Tools\logs> $TrelloTable
Study,ACT,ストレージの原則と技術-4章,HOBO,2018-05-06,2018-05-07,ストレージの原則と技術-4章,https://trello.com/c/9WfT4Seu
Study,ACT,PowerShellでSlack連携-チャット送信,HOBO,2018-05-07,2018-05-08,PowerShellでSlack連携-チャット送信,https://trello.com/c/KhVcUgsd
Study,Pending,ITIL Foundation,HOBO,2018-05-02,2018-06-30,ITIL Foundation,https://trello.com/c/O0buhBxY
Study,Pending,Unityの寺子屋読破,HOBO,2018-05-02,2018-08-31,Unityの寺子屋読破,https://trello.com/c/M62UI8Vl
Study,Pending,野球データをDB格納,HOBO,2018-05-02,2018-05-31,野球データをDB格納,https://trello.com/c/i5DIoQMQ
Study,Pending,Pythonでデータ整形,HOBO,2018-05-02,2018-07-31,Pythonでデータ整形,https://trello.com/c/ew3O4o5q
Study,NextDay,VMware ESXiインストール,HOBO,2018-05-07,2018-05-09,VMware ESXiインストール,https://trello.com/c/jGhQIIFJ
Study,NextDay,PHP-2章,HOBO,2018-05-07,2018-05-08,PHP-2章,https://trello.com/c/GmDaabj9
Study,NextDay,PowerShellでTrello連携-表作成,HOBO,2018-05-07,2018-05-08,PowerShellでTrello連携-表作成,https://trello.com/c/xHEi61iy

PS C:\Tools\logs> $TrelloToSlackArray = $TrelloTable | Select-Object -First 2
PS C:\Tools\logs> ($TrelloToSlackArray | Measure-Object -Line).Lines
2
オブジェクト変数時のLengthの結果(意図した結果が出力される)
PS C:\Tools\logs> $TrelloTable
Study,ACT,ストレージの原則と技術-4章,HOBO,2018-05-06,2018-05-07,ストレージの原則と技術-4章,https://trello.com/c/9WfT4Seu
Study,ACT,PowerShellでSlack連携-チャット送信,HOBO,2018-05-07,2018-05-08,PowerShellでSlack連携-チャット送信,https://trello.com/c/KhVcUgsd
Study,Pending,ITIL Foundation,HOBO,2018-05-02,2018-06-30,ITIL Foundation,https://trello.com/c/O0buhBxY
Study,Pending,Unityの寺子屋読破,HOBO,2018-05-02,2018-08-31,Unityの寺子屋読破,https://trello.com/c/M62UI8Vl
Study,Pending,野球データをDB格納,HOBO,2018-05-02,2018-05-31,野球データをDB格納,https://trello.com/c/i5DIoQMQ
Study,Pending,Pythonでデータ整形,HOBO,2018-05-02,2018-07-31,Pythonでデータ整形,https://trello.com/c/ew3O4o5q
Study,NextDay,VMware ESXiインストール,HOBO,2018-05-07,2018-05-09,VMware ESXiインストール,https://trello.com/c/jGhQIIFJ
Study,NextDay,PHP-2章,HOBO,2018-05-07,2018-05-08,PHP-2章,https://trello.com/c/GmDaabj9
Study,NextDay,PowerShellでTrello連携-表作成,HOBO,2018-05-07,2018-05-08,PowerShellでTrello連携-表作成,https://trello.com/c/xHEi61iy

PS C:\Tools\logs> $TrelloToSlackObject = $TrelloTable | Select-Object -First 1
PS C:\Tools\logs> ($TrelloToSlackObject | Measure-Object -Line).Lines
1

【まとめ】

ということで、これからは件数カウントしたい時はMeasure-Objectを使って、分岐処理をしていくようにします。当初は混乱しましたが勉強になってよかったです。

冒頭でも記載しましたが、これは他の言語でも起こりうる現象(配列→オブジェクトへの型変換)なのでしょうか。int型からString型への型変換ぐらいなら複数言語で出遭ったことはありますが、このパターンは初めてでした。

もし知っていらっしゃる方がおりましたら教えて頂けるとありがたいです。

ここまでお読み頂きありがとうございました。