PowerShellの引数設計とモジュール化の知識マップ(知識グラフ)
PowerShellスクリプトを人に渡せる品質にするには、paramブロックに[CmdletBinding()]を付けて高度な関数にし、共通パラメーターを自動的に使えるようにすることが出発点になる。必須引数は[Parameter(Mandatory)]で渡し忘れによる既定値実行を防ぎ、ValidateSetなどの検証属性は形式が不正な値を実行前に弾いて途中まで実行されてから壊れる事故を防ぐ。パイプライン入力を受ける関数はValueFromPipelineとprocessブロックを組み合わせないと最後の1件しか処理されない罠があり、変更系の関数はSupportsShouldProcessで-WhatIfによる意図しない実行の防止を組み込む。複数スクリプトで使う共通処理は.psm1モジュールに切り出してExport-ModuleMemberで公開範囲を明示し、PSModulePath配下に置くと自動読み込みされ、配る段階でモジュールマニフェストを追加する。
paramブロック
関数やスクリプトの引数を宣言するPowerShellの構文要素で、型・既定値・各種属性をパラメーターごとに指定できる。
.psm1スクリプトモジュール
関数を書いたファイルを.psm1拡張子で保存するだけで作れるPowerShellのスクリプトモジュール形式。
[Parameter(Mandatory)]
引数を必須にする属性で、指定し忘れた呼び出しは対話実行では入力を促され、無人実行では失敗する。
必須パラメーター
検証属性(ValidateSet/ValidateRange/ValidateScript等)
パラメーター宣言に検証条件を持たせる一群の属性で、検証は関数本体の実行前に行われ、失敗すると関数は呼び出されない。
[switch]パラメーター
指定の有無だけで$trueまたは$falseになるオン/オフ引数の宣言方法。
ValueFromPipelineとprocessブロック
パイプラインから流れてくる値を1件ずつ関数に受け取らせるための、パラメーター属性ValueFromPipelineとprocessブロックの組み合わせ。
パイプライン入力
[CmdletBinding()]属性
関数やスクリプトに付けることで、コンパイル済みコマンドレットと同様の「高度な関数」として動作させ、共通パラメーターや$PSCmdletを自動的に使えるようにするPowerShellの属性。
高度な関数
共通パラメーター
[CmdletBinding()]を付けた高度な関数に自動的に追加される、-Verbose・-Debug・-WarningAction・-InformationAction・-ErrorActionなどのパラメーター群。
SupportsShouldProcessと-WhatIf/-Confirm
[CmdletBinding(SupportsShouldProcess)]を宣言すると自動で追加される-WhatIf/-Confirmパラメーターと、$PSCmdlet.ShouldProcess()による実行可否の分岐の仕組み。
最後の1件しか処理されない罠
パイプライン入力を受ける高度な関数がbegin/process/endを1つも書かないと、本体はendブロック扱いになりパイプラインで複数件を流しても最後の1件しか処理されない典型的な事故。
processブロック欠落バグ
ドットソース
スクリプトのパスの前にドットと空白を付けて実行することで、そのスクリプト内で定義した関数・変数が呼び出し元のスコープにそのまま残る実行方法。
$env:PSModulePath(モジュールの探索パス)
PowerShellがモジュールを検索するフォルダーの一覧を保持する環境変数。Windows PowerShell 5.1とPowerShell 7でユーザースコープの既定パスが異なる。
Export-ModuleMember
スクリプトモジュールから外部へ公開する関数・エイリアスを明示するコマンドレットで、未指定時は全関数とエイリアスがエクスポートされる。
公開関数の明示
モジュールマニフェスト(.psd1)
モジュールのバージョン番号・公開する関数・依存モジュール・対応するPowerShellのバージョンやエディションを宣言するファイル。New-ModuleManifestで雛形を作成する。
PowerShell実行ポリシー
PowerShellスクリプトの実行可否と署名要求を制御する設定。既定はクライアントOSでRestricted、Windows ServerとPowerShell 7ではRemoteSigned。
コメントベースヘルプ
.SYNOPSISや.EXAMPLEなどのキーワード付きコメントを関数やスクリプトに書くことで、Get-Helpが標準コマンドレットと同じ形式でヘルプを表示する仕組み。
必須引数の渡し忘れによる既定値実行
必須のはずの引数を渡し忘れたまま、チェックがないために既定値や空の状態で処理が進んでしまう事故。
Mandatory未指定の事故
途中まで実行されてから壊れる事故
引数の形式チェックを関数本体のif文で行うと、チェックより前に副作用が走ってしまい、不正な値のまま処理が一部実行された状態で壊れる事故。
タイプミス引数の黙殺
高度な関数でない場合、未定義のパラメーター名や余分な位置引数を渡してもエラーにならず黙って無視され、既定値のまま動いてしまう事故。
変更系処理の意図しない実行
削除・移動・設定変更などの変更系処理が、影響範囲を確認する手段がないまま意図せず実行されてしまうこと。
[Parameter(Mandatory)]はparamブロック内のパラメーター宣言に付与する属性である
ValidateSetなどの検証属性はparamブロック内のパラメーター宣言に付与する
[switch]パラメーターはparamブロック内で宣言する
ValueFromPipelineはparamブロック内のパラメーター属性として宣言する
[CmdletBinding()]はparamブロックの先頭に付与することで、その関数を高度な関数にする
[CmdletBinding()]を付けた関数は共通パラメーターの実装を担い、-Verboseや-ErrorActionなどが自動で使えるようになる
SupportsShouldProcessは[CmdletBinding()]のオプションとして宣言し、これにより-WhatIf/-Confirmが自動追加される
ValueFromPipelineとprocessブロックを組み合わせて書けば、パイプラインで流した複数件のうち最後の1件しか処理されない罠を防げる
1つのスクリプトを分割する程度ならドットソースで足り、2本目のスクリプトから同じ関数を使いたくなった時点でモジュール化に進むのが判断の目安である
モジュール名と同名のフォルダーを作ってPSModulePath配下に置けば、Import-Module不要で自動読み込みされる
公開する関数の範囲を明示的に絞りたい場合、Export-ModuleMemberで明示するのがベストプラクティスとされる(未指定時は全関数とエイリアスがエクスポートされる)
RootModuleに.psm1スクリプトモジュールを指定する場合、モジュールマニフェストはその.psm1本体の存在を前提に作られる。RootModuleがバイナリモジュール(.dll)やマニフェストのみの場合はこの前提は当てはまらない。
共有フォルダー配布したモジュールやスクリプトの実行は、実行ポリシー次第で拒否されることがある
他人に渡す・配布するモジュールにはコメントベースヘルプを書き、Get-Helpを効かせることが本記事の推奨である
[Parameter(Mandatory)]を付けると、指定し忘れた呼び出しは実行前に止まるか入力を促されるため、渡し忘れたまま既定値で動く事故を防げる
検証属性による形式チェックは関数本体の実行前に行われ失敗すれば本体は1行も実行されないため、途中まで実行されてから壊れる事故を防げる
高度な関数では未定義のパラメーター名や余分な位置引数を渡すとバインドが失敗するため、タイプミスが黙って無視される事故を防げる
SupportsShouldProcessによる-WhatIf/-Confirmで実行前に影響範囲を確認できるため、変更系処理が意図せず実行されるリスクを減らせる。ただし-WhatIfの伝播は呼び出し先のコマンドまで常に保証されるわけではなく、利用者が-WhatIfを付けて確認しなければ効果を発揮しない