ルドルフもわたるふもいろいろあってな

Microsoft 365、Power Platform、PowerShellについて調べたことや検証したことなどを投稿します。技術の話は面白い。

PowerShellのソースコードを読みやすく・エラーに強くするTIPS 4つ(2021/9/28)

主旨

PowerShellを使ったスクリプトを作成するうえで、ソースコードを読みやすく・エラーに強くするために行っている自分なりの経験則を書きたいと思います。

この投稿では特に出現頻度が高いもの4つを記載します。
分かりやすくするために「手直し前」のソースコードと「手直し後」のソースコードを順番に提示します。

その1:コマンドのパラメータは縦に並べたほうが読みやすい

手直し前はこんな感じです。
パラメータと値を横にずらずらっと並べます。パラメータの数が1つか2つであればそこまで気になりませんが、3個以上になるとパッ見たときに理解しづらいです。

手直し前

$responseFromAPI = Invoke-RestMethod -uri $url -Method Get -Headers $headerParams 

これを以下のようにパラメータと値ごとに改行します。
PowerShellソースコードでは 1つの式が長くなる場合などに式の途中で改行するために文末にバッククォート ( ` ) を記述します。

手直し後

$responseFromAPI = Invoke-RestMethod `
-uri $url `
-Method Get `
-Headers $headerParams 

さらに手直しして、値の位置を揃えます。これでかなり読みやすくなります。

手直し後

$responseFromAPI = Invoke-RestMethod `
-uri     $url `
-Method  Get `
-Headers $headerParams 

その2:返り値が配列の場合は「+=」演算子を使って受け取る

手直し前は以下の通りです。REST APIから返ってきた値を変数で受け取っています。
この書き方だと、返ってきた配列に含まれる値の数が「0」なのか「1」なのか「2以上」なのかによってデータの型が変わってしまいます。そのため、後続の処理内容によっては不具合の原因になります。

手直し前

$responseFromAPI = Invoke-RestMethod `
-uri     $url `
-Method  Get `
-Headers $headerParams 

$result = $responseFromAPI.value

以下の通り手直しします。こうすれば値の個数に関わらず常にArray型になります。

手直し後

$responseFromAPI = Invoke-RestMethod `
-uri     $url `
-Method  Get `
-Headers $headerParams 

$result = @()
$result += $responseFromAPI.value

その3:配列を作るときはAdd-Memberを使う

CSVなどのアウトプットを出力することを目的にしたスクリプトでは、出力する内容を配列で作ることが多いです。

配列の作り方のひとつが以下のソースコードです。ですが、この書き方には以下の弱点があります。
・プロパティ名を変更するときに2箇所なおす必要がある
・プロパティの数が多くなるほどselect文が長くなる(並べ替え等がしづらい)

手直し前

$obj = New-Object PSObject | Select "DisplayName","UserPrincipalName","Department","JobTitle"
$obj.DisplayName       = $currentUser.DisplayName
$obj.UserPrincipalName = $currentUser.UserPrincipalName
$obj.Departmen         = $currentUser.Departmente
$obj.JobTitle          = $currentUser.JobTitle

これを手直ししたものが以下のソースコードです。
こうすれば、プロパティ名の変更は1箇所でなおせます。プロパティの数が増えても読みやすさが保てます。プロパティの並べ替えは行ごと移動すればOKです。

手直し後

$obj = New-Object PSObject
$obj | Add-Member -MemberType NoteProperty -Name DisplayName -Value $currentUser.DisplayName
$obj | Add-Member -MemberType NoteProperty -Name Mail        -Value $currentUser.UserPrincipalName
$obj | Add-Member -MemberType NoteProperty -Name Department  -Value $currentUser.Department
$obj | Add-Member -MemberType NoteProperty -Name JobTitle    -Value $currentUser.JobTitle

その4:CSVの出力には追記(Append)を使う

これは要件にもよるかと思いますが、基本的にはCSVを出力することを目的としたスクリプトでは処理途中でエラーが発生したときに途中までのアウトプットが得られるようにしたほうがよいかと思います。
具体的には1回で全データを出力させるのではなく、ある程度まとまった処理の単位で追記するかたちで出力を行うことで、スクリプトが途中で止まった場合でも正常に処理が完了した分のデータはCSVに出力させます。

1回で出力を行うソースコードの例が以下の通りです。
このソースコードでは繰り返し(foreach)処理でエラーが発生した場合はCSVには何も出力されません。

手直し前

$outputData = @()
foreach($currentUser in $users)
{
    $obj = New-Object PSObject
    $obj | Add-Member -MemberType NoteProperty -Name DisplayName -Value $currentUser.DisplayName
    $obj | Add-Member -MemberType NoteProperty -Name Mail        -Value $currentUser.UserPrincipalName
    $obj | Add-Member -MemberType NoteProperty -Name Department  -Value $currentUser.Department
    $obj | Add-Member -MemberType NoteProperty -Name JobTitle    -Value $currentUser.JobTitle

    $outputData += $obj
}

$outputData |  Export-Csv -Path $outputFile -Encoding UTF8 -NoTypeInformation

それを手直ししたものが以下のソースコードです。
こうすれば途中でエラーが発生しても、正常に処理が完了した分はCSVに出力されます。

手直し後

foreach($currentUser in $users)
{
    $obj = New-Object PSObject
    $obj | Add-Member -MemberType NoteProperty -Name DisplayName -Value $currentUser.DisplayName
    $obj | Add-Member -MemberType NoteProperty -Name Mail        -Value $currentUser.UserPrincipalName
    $obj | Add-Member -MemberType NoteProperty -Name Department  -Value $currentUser.Department
    $obj | Add-Member -MemberType NoteProperty -Name JobTitle    -Value $currentUser.JobTitle

    $obj | Export-Csv -Path $outputFile -Encoding UTF8 -NoTypeInformation -Append
}

以上です。