PowerShell スクリプト作り 初心者の振り返り

# はじめに
筆者は SE のくせにスクリプトが満足に作れません。が、案件でやらざるを得なくなってしまいました。

なんとか作りましたので、その時に役立った情報をまとめておこうと思います。
なお内容については誤りを含む可能性がありますので、その点ご留意ください。

# 前提となる知識レベル
- PowerShell? 名前は知ってるぜ! というレベル
    - for-each 文や if 文など基本的な構文は分かる
    - コード書くやつ（VSCode）の使い方はブログで読んだことがある
    - 開発系の案件はほぼやったことない（何となく用語はわかる）
    - たまに何となく PowerShell は使っている、バージョンとかは知らん
    - なので、スクリプトで処理させたいこと自体は何とか生成できる
- 認証・認可の知識はある程度ある
- MS ソリューションの知識はある程度ある
- タスクを自動化するスクリプトを本格的に作ったことはない

:::note info
本ブログは "PowerShell を使ったことはあるけれどスクリプトを使った自動化はしたことない人" がスクリプトの実装を任されたら、という観点での内容となります
:::

# 本文

さてなんといっても困ったのがエラーハンドリングです。ただでさえエラーハンドリングのお作法を知らないのに、PowerShell のエラーハンドリングはややこしい（らしく）、筆者は理解するのに苦労しました。自分の中でルール化したので、参考程度ですが気を付けるべきポイントを列挙します。

:::note warn
本ブログではエラーと例外を便宜上まとめて「エラー」と呼んでいます。
正しくは別物と思いますが、理解しやすさを優先しています。
:::


### エラーハンドリングお作法集

- 冒頭に `$ErrorActionPreference = "stop"` をつける
これにより、エラーが発生したときに Try-Catch にて Catch してくれる
また各コマンドで `-ErrorAction` パラメータを指定しなくて済む
- エラーハンドリングは基本 `Try-Catch` 構文にて行う
- エラーハンドリングはエラーが発生しそうな箇所にのみ構成する
- 取得系の処理の後は $Null チェック/ 0 値チェックを入れる

### その他 PowerShell スクリプトを実装する際に覚えておきたいこと

その他覚えておきたいことも挙げておきます。
PowerShell のスクリプトって大体利用目的同じだから結構役立つのでは？と思います。
（PowerShell の話ではなく一般的に抑えるべきことも含まれています）
- スクリプトの中身は以下の順番で記載する
    - スクリプト概要の記載（コメント）
    - ErrorActionPreference の設定
    - 変数宣言
    - 関数宣言
    - ログ出力開始
    - Main 処理
    - ログ出力終了
    - スクリプトの終了（exit）

- ユーザーに指定させる変数は型を前もって指定しておいた方が良い（閾値など）
- 処理の開始、終了時刻も INFO としてログに出力してあげた方がトラブルシューティングに役立つ
- タスクスケジューラでスクリプトを定期実行する場合、実行制限でスクリプト実行がブロックされないようタスク上で excutionpolicy を bypass 等に指定する
- VSCode でスクリプトを作ると良い、自動で怪しい部分を指摘してくれるので内容がきれいになる
    - 指摘の内容として、例えば $Null を比較する時には、左辺に置く必要がある など教えてくれる
- 高度な関数を作るときは `[CmdletBinding()]` をつける、標準のコマンドと同様エラーハンドリングできるようになる
- スクリプトの動作テストはテスト用のコマンドをスクリプトの中に用意すると楽


# 参考情報
インターネットは偉大です。参考になったサイトを挙げさせていただきます。
諸先輩方ありがとうございます。

:metal: 個人ブログ

- エラーハンドリングについて
    - [PowerShell のエラーハンドリングを（今度こそ）理解する](https://qiita.com/mkht/items/24da4850f9d000b35fc4)

    - [【PowerShell】エラーハンドリング(Try-Catch)を使ってみる](https://soma-engineering.com/coding/powershell/error-handling/2018/07/03/)
    - [[04] PowerShell実践例 ... エラー終了させたい場合は素直に例外を使う](https://qiita.com/robozushi10/items/71bdcbcfda44acb7ea11)
    - [PowerShellでの例外処理の方法 try-catch文とErrorAction](https://trend-desk.com/archives/1762)
    - [ちょっと広く例外を学んでみた](https://qiita.com/TairaNozawa/items/8788c4b20c60046ee80c)
- PowerShell Tips
    - [PSScriptAnalyzerを使ってPowerShellのコードをキレイに保とう](https://qiita.com/nimzo6689/items/1c55c6782027a78a51e5)


:metal: 公式情報
- [例外について知りたかったことのすべて](https://learn.microsoft.com/ja-jp/powershell/scripting/learn/deep-dives/everything-about-exceptions?view=powershell-7.4)

:metal: 書籍

- [PowerShell実践ガイドブック ~クロスプラットフォーム対応の次世代シェルを徹底解説~ 単行本（ソフトカバー） – 2018/5/30](https://www.amazon.co.jp/dp/4839965986/)（Amazon リンク）


:metal: その他（学習リソース）

- GitHub のサンプルコード
- Youtube の PowerShell 解説動画
- Bing Chat（Edge の AI） ← スクリプト作成ではとても優秀、もう俺イラン


# [おまけ] サンプルコード

- ログ出力用のフォルダ作成
~~~
$folderPath = "C:\example_folder"

if (-Not (Test-Path -Path $folderPath)) {
    New-Item -ItemType Directory -Path $folderPath
}
~~~

- ログ出力用の関数
~~~
function Write-ErrorLog {
    [CmdletBinding()]
    param (
        $ErrorValue
    )
    $timestamp = (Get-Date).ToString("yyyy/MM/dd HH:mm:ss K")
    $logEntry = "LEVEL ERROR $timestamp $ErrorValue"
    $logEntry | Out-File -FilePath $SystemLog -Append # SystemLog に Error を出力
    $ErrorValue | Out-File -FilePath $ExecutionLog -Append # ExecutionLog に Error の詳細を出力
}

# 使い方
Write-ErrorLog -ErrorValue "<エラー文を記述>"
Write-ErrorLog -ErrorValue $_ # $Error の中身を格納
~~~

- ログ出力用のファイルに出力させたいとき
~~~
Write-Output "<文字列>" | Out-File -FilePath $SystemLog -Append
~~~

- try-catch 文
~~~
try {
    <処理内容>
} catch {
    # Do this if a terminating exception happens
    Write-ErrorLog -ErrorValue $_
    exit
}
~~~

- スクリプト概要のコメント
~~~
#------------------------------------------------------------
# Name: xxxxx 
# Generated by: xxxxx
# Generated on: xxxxx
#------------------------------------------------------------
~~~