この記事は、PowerShell Advent Calendar 2017 5日目の記事です。
昨日は @atworks さんの PSRemoting を用いたリモートプロセス実行でした。 qiita.com
3日目の前回 PowerShell のコーディングスタイルについて触れました。
次は、残りのAPI デザインを見てみましょう。
※ 2回に分けて書きましたが個人的には個人/チームが書きやすいようにすればいい話だと思っています。しかし、「曖昧だった基本的な指針がわからず困ってた方」にとって、ベストプラクティスはいい材料となると思います。PowerShell コミュニティは結構活発なので、コミュニティの中で皆様がよい PowerShell 生活を送られることを祈っています。
目次
- 目次
- C#のデザインガイド
- Required Development Guidelines
- Design Guidelines Use Only Approved Verbs (RD01)
- (Design Guidelines) Cmdlet Names: Characters that cannot be Used (RD02)
- (Design Guidelines) Support Confirmation Requests (RD04)
- (Design Guidelines) Support Force Parameter for Interactive Sessions (RD05)
- (Design Guidelines) Document Output Objects (RD06)
- Strongly Encouraged Development Guidelines
- (Design Guidelines) Advisory Development Guidelines
- Required Development Guidelines
- PowerShell のデザインガイド
- Building Reusable Tools
- TOOL-01 Decide whether you're coding a 'tool' or a 'controller' script
- TOOL-02 Make your code modular
- TOOL-03 Make tools as re-usable as possible
- TOOL-04 Use PowerShell standard cmdlet naming
- TOOL-05 Use PowerShell standard parameter naming
- TOOL-06 Tools should output raw data
- TOOL-07 Controllers should typically output formatted data
- WAST-01 Don't re-invent the wheel
- WAST-02 Report bugs to Microsoft
- Output and Formatting
- Don't use Write-Host unless you really mean it
- Use Write-Progress to give progress information to someone running your script
- Use Write-Debug to give information to someone maintaining your script
- Use CmdletBinding if you are using output streams
- Use Format Files for your custom objects
- Only output one "kind" of thing at a time
- Two important exceptions to the single-type rule
- Error Handling
- Performance
- Security
- Language, Interop and .Net
- Building Reusable Tools
- 余談 : 個人的に注意していること
- まとめ
C#のデザインガイド
Required Development Guidelines
Design Guidelines と Coding Guidelines がありますが、Design Guidelinesのみ触れます。
Design Guidelines Use Only Approved Verbs (RD01)
- PowerShell の Cmdlet 規則である Verb-Nown 形式で公開する時に、あらかじめ定義されてある 動詞(Verb) を用いましょう
- 動詞は用途ごとにクラス分離されているので、適切なものを使うといいです
- VerbsCommon
- VerbsCommunications
- VerbsData
- VerbsDiagnostic
- T:System.Management.Automation.VerbsLifeCycle
- VerbsSecurity
- VerbsOther
- どの動詞をいつ使うかのガイドラインも公開されています
(Design Guidelines) Cmdlet Names: Characters that cannot be Used (RD02)
- Cmdlet には用いることができない特殊文字があります。その一覧を示しています
- 私は基本的にアルファベットのみ用いるようにすることで沿えるので単純にそう捉えています
- Parameters Names that cannot be Used (RD03)
- PowerShell Cmdlet のパラメータには予約語があります。それを避けるようにしましょう
- Confirm, Debug, ErrorAction, ErrorVariable, OutBuffer, OutVariable, WarningAction, WarningVariable, WhatIf, UseTransaction, and Verbose
(Design Guidelines) Support Confirmation Requests (RD04)
- もしシステム変更を伴う操作を提供する場合は、PowerShell が持っている 確認機構を用いることをさしています
- いわゆる ShouldProcess を指しています
(Design Guidelines) Support Force Parameter for Interactive Sessions (RD05)
- 対話的実行を提供する場合に、
Force
パラメータは提供しましょう - これは PowerShell が自動化を念頭に置かれた言語なため、対話実行でそれを妨害することを防ぐためです
- 以下のような操作が特に注意です
- Prompt
- PSHostUserInterface.PromptForChoice
- IHostUISupportsMultipleChoiceSelection.PromptForChoice
- Overload:System.Management.Automation.Host.PSHostUserInterface.PromptForCredential
- ReadLine
- ReadLineAsSecureString
(Design Guidelines) Document Output Objects (RD06)
- 出力の記述です
- C# のドキュメントXML で説明を公開するといいです
Strongly Encouraged Development Guidelines
次は強く要請するガイドラインです。
Design Guidelines と Coding Guidelines がありますが、Design Guidelinesのみ触れます。
(Design Guidelines) Use a Specific Noun for a Cmdlet Name (SD01)
Server のような汎用的な言葉ではなく、操作対象を明示した Process のような命名が好まれます。
(Design Guidelines) Use Pascal Case for Cmdlet Names (SD02)
- Cmdlet 名は、PascalCase で表現しましょう。
Clear-ItemProperty
のほうがclear-itemProperty
より好ましいです
(Design Guidelines) Parameter Design Guidelines (SD03)
- 標準的なパラメータ名が公開されているのでここにあるものはそれを利用しましょう
- たとえば、名前ならName、出力なら Output といった具合です
- ただ、処理によっては ServiceName のようなより明示的な名前も提供したい場合があるでしょう。その場合は、パラメータ用プロパティにAlias属性 を用いることで
[Alias("ServiceName")]
のように表現できます
- 単一要素を受けるパラメータには単数名を用いて表現しましょう
- もし-es のような複数名を用いる場合は、そのbパラメータがいつでも複数要素を受け入れる場合にしましょう
- パラメータはPascalCaseで。C# であれば Property を用いるので、C# デザインガイドと同じで違和感はないかと思います
- errorAction や erroraction より、
ErrorAction
が好ましいです
- errorAction や erroraction より、
- パラメータの組み合わせで操作が変わる Cmdlet を提供する場合2つの方法があります
- enum を用いて、enumごとに操作を分岐し パラメータを処理する方法
- ValudateSet属性.aspx) を用いて、パラメータの入力を制約する方法
- 私の経験上、複数の操作を提供する Cmdlet は作りたくなります。パラメータ1つだけが特定のパラメータ時に用いないようなの「単純な組み合わせ」であれば、ValidateSet が楽でしょう。が、複数の パラメータの組み合わせを ParameterSet で表現するのはおススメしません。Cmdlet を分離することを検討するといいでしょう
- パラメータ名にはStandard Type を用いましょう
- あらかじめ、どんな用途(Activity) にどんなパラメータが期待されるのかリストされています
- Append など利用者が直感的に利用しやすい API デザインとして一貫性を保つため、該当するものを利用するといいでしょう
- 強く型付けされた .NET Framework 型を利用する
- Object を利用するということは、型に対する意識が強いということです
- .NET Framework の型を意識して利用すると適切な型が入ることがほしょうされるため、よいでしょう
- たとえば、URI には
Uri
型を用いれば、String
型が入ってこないことを保証できます
- パラメータの型に首尾一貫性をもたせる
- たとえば、Process パラメータというのに
Int16
型を当てた場合、他のCmdlet の Process パラメータでUint16
を用いるのは避けましょう - 利用者の直感に反するので触り心地に大きく影響します
- たとえば、Process パラメータというのに
- true/false をとるパラメータは避けて、Switch Parameter を用いましょう
- Switch Parameter は、もし利用していれば
true
、なければfalse
とみなします - もし 3値 (true, false, Unspecified) が必要な場合は、
Nullable<bool>
が適切でしょう- 個人的に、Unspecified と null を合わせるのが適切なのかは一考の余地があります
- 可能であれば、パラメータに配列を許容しましょう
- 例えば
Get-Process
は Name に String配列を許容します - 利用者の使い心地として、複数回 Cmdlet を実行するより、1回で済む方がうれしいことは多いでしょう
- 例えば
PassThru
パラメータのサポートを検討しましょうStop-Process
のような値を返さない Cmdlet (Void型) であっても、時に結果オブジェクトが必要です- こういった場合に、
PassThru
パラメータを与えることで、結果オブジェクトを返すオプションを提供しましょう - Add, Set, New といった Verb の Cmdlet はサポートしているものが多いです
- ParameterSet のサポート
- Cmdlet は1つの目的のために作ります
- が、時に1つの操作を複数の表現で呼べることがあるでしょう。つまり、パラメータの組み合わせということです
- このパラメータの組み合わせの表現に、ParameterSet を用いることが多いです
- ParameterSet を用いる場合、
DefaultParameterSet
をCmdlet
属性に指定しましょう
(Design Guidelines) Provide Feedback to the User (SD04)
ユーザーは実行中ただ待つのは苦痛です。実行に対して何かしらのフィードバックを返しましょう。
WriteWarning, WriteVerbose, WriteDebug メソッドのサポート
- もし意図しない結果が起こった場合は、
WriteWarning
メソッドで結果をユーザーに伝えましょう - もしユーザーがさらなる詳細情報を求める場合、
WriteVerbose
で結果を返しましょう。例えば、実行シナリオが意図した状態になっているかを伝えることもいいでしょう - 開発者がプロダクトサポートのために必要とする情報は、
WriteDebug
メソッドで返すといいでしょう
- もし意図しない結果が起こった場合は、
長時間実行時の WriteProgress サポート
- 長時間実行する場合、進捗を
WriteProgress
メソッドで表示するといいでしょう
- 長時間実行する場合、進捗を
Host Interface を用いた対話実行
- 時に ShouldProcess 以外に、Host を通してユーザーとやり取りをする必要に迫られます。そんなときに Host プロパティを用いましょう
- たとえば、
PromptForChoice
やWriteLine
/ReadLine
などです - もしCmdlet が GUI を生成しないなら、
Out-GridView
Cmdlet の利用も検討できます - また Cmdlet は、Console API は利用すべきではありません
Cmdlet ヘルプファイルの生成
- Help.xml ファイルで、Cmdlet のヘルプを伝えることができます
(Design Guidelines) Advisory Development Guidelines
アドバイスとしてのガイドラインです。
Design Guidelines と Coding Guidelines がありますが、Design Guidelinesのみ触れます。
適用時は、Code Guideline も参考にしてください。
(Design Guidelines) Support an InputObject Parameter (AD01)
- 特定の操作で良く用いられる名前があります。
InputObject
です - パイプラインからの入力をサポートしてプロセッシングするパラメータ名によく用いられ、.NET Framework のオブジェクトを取り扱います
(Design Guidelines) Support the Force Parameter (AD02)
Force
パラメータを用いたユーザーの権限処理や対話を操作できるようにしましょうRemove-Item
Cmdlet の場合、通常は readonlyファイルを消せません。しかしForce
パラメータを用いることで消すことができます- しかし、もしユーザーがそもそもそのファイルにアクセスする権限がない場合、
Force
をつけても何ら変わらず「失敗」します
- しかし、もしユーザーがそもそもそのファイルにアクセスする権限がない場合、
(Design Guidelines) Handle Credentials Through Windows PowerShell (AD03)
Credential
パラメータをサポートし魔装。このパラメータはPSCredential
型を受け認証を処理することが期待されます- このサポートにより、ユーザーに対して自動的にポップアップを表示し、ユーザー名やパスワード入力ができるようになります
- Credential パラメータには、Credential属性をあてます
Support Encoding Parameters (AD04)
- テキストやバイナリを扱うときは、
Encoding
パラメータをサポートします
Test Cmdlets Should Return a Boolean (AD05)
Test-
とつく Cmdlet は Boolean を返すことが期待されます
PowerShell のデザインガイド
実は、コーディングスタイルに含まれてしまっている部分が強いので、API デザインとしては存在しません。
ただし、Best Practice が存在します。
一度目を通してみると面白いのではないでしょうか?
- Naming Conventions
- Building Reusable Tools
- Output and Formatting
- Error Handling
- Performance
- Security
- Language, Interop and .Net
- Metadata, Versioning, and Packaging
ざくっと上げます。PURE とあるものは、議論の余地があるため記載しません。
Building Reusable Tools
再利用性に注目しています。
TOOL-01 Decide whether you're coding a 'tool' or a 'controller' script
- 自分がツールを作ろうとしているのか、ツールの操作を作ろうとしているのか意識しましょう
- なにかをするためのツールとして書かれている場合、re-usable でしょう
- ツールをビジネスロジックに合わせて「操作」するために書かれている場合、re-usable ではないと考えられます
TOOL-02 Make your code modular
- 処理を、関数にすることで、re-usable になります
TOOL-03 Make tools as re-usable as possible
- 入力をパラメータで受け取り、パイプラインに出力する
- この仕組みは re-usable さが最大限高まります
TOOL-04 Use PowerShell standard cmdlet naming
- PowerShell の標準のネーミングをしましょう
Verb-Noun
大事。Get-Verb
Cmdlet で標準の Verb 一覧が見れます
TOOL-05 Use PowerShell standard parameter naming
- 標準のパラメータ名を用いましょう
TOOL-06 Tools should output raw data
- ツールの場合、Cmdlet の処理中に、データをなるべく触らず生で出力することをコミュニティとしては期待することが多いです
- もし出力データを操作する場合でも、最小限にとどめましょう。そうすることで、多くのシーンで re-usable になります
TOOL-07 Controllers should typically output formatted data
- 操作する場合、re-usable は主眼ではないので適切にわかるデータにフォーマットして返しましょう
WAST-01 Don't re-invent the wheel
- 車輪の再発明だめ
- 下の例は、
Test-Connection $computername -Quiet
で表現できます
function Ping-Computer ($computername) { $ping = Get-WmiObject Win32_PingStatus -filter "Address='$computername'" if ($ping.StatusCode -eq 0) { return $true } else { return $false } }
WAST-02 Report bugs to Microsoft
- バグは共有しよう
Output and Formatting
出力に関してです。
Don't use Write-Host unless you really mean it
- Write-Host だめ。良く言われますね。Host にしか出力しないので、「見せるためだけ」「フォーマットするだけ」に利用しましょう
- 特に
Show
Verb を使っていたり、Format
Verb を使っている関数を書いた時にしか、使わないぐらいがいいです - なるべく他の
Write-*
Cmdlet の利用を検討してください
Use Write-Progress to give progress information to someone running your script
- ユーザーに何かしら進捗を示すとき
Write-Progress
が最適です - ただし、パイプライン上のなんでも流せばいいというものではありません。伝えたいことにしぼりましょう
Use Write-Debug to give information to someone maintaining your script
- スクリプトのメンテナンスをする人に向けて、
Write-Debug
でメッセージを送ってください $DebugPreference = "Continue"
とすることで、Breakpoint で止まらず結果をみることもできます
Use CmdletBinding if you are using output streams
[CmdletBinding()]
を使うだけで、出力ストリームを操作する-Verbose
などが利用できるようになります
Use Format Files for your custom objects
- カスタム型を使う場合は、
modulename.format.ps1xml
を使ってフォーマットを検討してください
Only output one "kind" of thing at a time
- 1つの関数で、複数の型を返すことを避けてください
[OutputType()]
で伝える型とのずれが生じるのは相当なコストをユーザーに強いることになります
Two important exceptions to the single-type rule
- もし内部関数の場合は、複数の型を返すのはありです
$user, $group, $org = Get-UserGroupOrg
のように分けて受け取れます
- もし複数の型を返す場合、個別に
Out-Default
に包んで返すことでフォーマットが混在することを避けられます
Error Handling
エラー処理です。
ERR-01 Use -ErrorAction Stop when calling cmdlets
- Cmdlet の呼び出し時は、
-ErrorAction Stop
をつけてエラー時に捕まえましょう
ERR-02 Use $ErrorActionPreference='Stop' or 'Continue' when calling non-cmdlets
- Cmdlet ではない場合、呼び出し前に
$ErrorActionPreference='Stop'
を実行し、呼び出し後に$ErrorActionPreference='Continue'
に戻しましょう - 特に自動化時に適切にエラーで止めることは重要です
ERR-03 Avoid using flags to handle errors
- フラグで失敗制御はしないでください
try { $continue = $true Do-Something -ErrorAction Stop } catch { $continue = $false } if ($continue) { Do-This Set-That Get-Those }
- try, catch で制御しましょう
Do-Something -ErrorAction Stop Do-This Set-That Get-Those } catch { Handle-Error }
ERR-04 Avoid using $?
$?
の利用は避けましょう- これはエラーが前回のコマンドで発生したか示すものではなく、前回のコマンドが成功したかみるだけです。この結果に関しては、ほぼ意味がないでしょう
ERR-05 Avoid testing for a null variable as an error condition
- null チェックを全部いれるとかやめましょう
ERR-06 Copy $Error[0] to your own variable
- 直前のエラーが
$Error[0]
に収められています。catch
句の$_
も同様です - ただ、次のエラーですぐに上書きされるので必要なら変数にいれてください
$Error
配列に過去のものは入っています
Performance
PERF-01 If performance matters, test it
- PowerShell のパフォーマンスは、妙なくせだらけです
- パフォーマンスかな、とおもったらテストしましょう
- たとえば、以下の例なら2つ目が早いです
[void]Do-Something Do-Something | Out-Null
- いくつか方法が思いつく場合、計測しましょう
PERF-02 Consider trade-offs between performance and readability
- パフォーマンスと読みやすさはトレードオフな場合があることを考慮してください
- 例えば、式で表現とパイプラインで表現でも変わります
$content = Get-Content file.txt ForEach ($line in $content) { Do-Something -input $line }
Get-Content file.txt | ForEach-Object -Process { Do-Something -input $\_ }
あるいは、.NET Framework を直接触ることでも変わります。
$sr = New-Object -Type System.IO.StreamReader -Arg file.txt while ($sr.Peek() -ge 0) { $line = $sr.ReadLine() Do-Something -input $line }
さらにこんな書き方もあるでしょう。
$handle = Open-TextFile file.txt while (-not Test-TextFile -handle $handle) { Do-Something -input (Read-TextFile -handle $handle) }
- どれがいいかといえば、なるべく PowerShell に沿った書き方が読みやすいでしょう。が、基本的には .NET Framework のラッパーにすぎません
- いくつものパターンがある中から、パフォーマンスとご自身の美学に沿って選択してください
Security
Always use PSCredential for credentials/passwords
- Credential や パスワードには、
PSCredentail
を使います - SecureString でパスワードが保持されるため、基本的にこれを使いましょう
param ( [System.Management.Automation.PSCredential] [System.Management.Automation.Credential()] $Credentials )
- どうしても生パスワードを、そこから拾う必要がある場合、メソッドから取得しましょう。なるべくさけてください
$Credentials.GetNetworkCredential().Password
Other Secure Strings
- 他にも、
Read-Host -AsSecureString
でも SecureString を受け取ることgあできます - 万が一 SecureString を String にする必要があるなら、
ZeroFreeBSTRE
を用いてメモリリークを抑えてください
# Decrypt a secure string. $BSTR = [System.Runtime.InteropServices.marshal]::SecureStringToBSTR($this); $plaintext = [System.Runtime.InteropServices.marshal]::PtrToStringAuto($BSTR); [System.Runtime.InteropServices.Marshal]::ZeroFreeBSTR($BSTR); return $plaintext
- もしディスクに認証を保持する必要がある場合、
Export-CliXml
を使ってパスワードを守ってください
# Save a credential to disk Get-Credential | Export-CliXml -Path c:\creds\credential.xml # Import the previously saved credential $Credential = Import-CliXml -Path c:\creds\credential.xml
- さらにもし、String がセンシティブでディスクに保持する必要がある場合は、
ConvertFrom-SecureString
で暗号化してください。ConvertTo-SecureString
で戻すことができます。Windows Data Protection API (DPAPI)をつかっているため、同一Windowsマシンの同一ユーザーでのみ Decrypt できるので注意です。処理として、AES 共通鍵での暗号化もサポートしています
# Prompt for a Secure String (in automation, just accept it as a parameter) $Secure = Read-Host -Prompt "Enter the Secure String" -AsSecureString # Encrypt to Standard String and store on disk ConvertFrom-SecureString -SecureString $Secure | Out-File -Path "${Env:AppData}\Sec.bin" # Read the Standard String from disk and convert to a SecureString $Secure = Get-Content -Path "${Env:AppData}\Sec.bin" | ConvertTo-SecureString
Language, Interop and .Net
VER-01 Write for the lowest version of PowerShell that you can
- サポートする、もっとも低い PowerShell バージョンのために書いてください
- ただし、新しいほどパフォーマンスメリットがあったりします
- たとえば、PoweShell v3 では2番目の書き方のほうがかなり高速化されます
Get-Service | Where-Object -FilterScript { $\_.Status -eq 'Running' }
Get-Service | Where Status -eq Running
VER-02 Document the version of PowerShell the script was written for
#requires -version 3.0
といった形でサポートしているバージョンを明記してください- Module の場合、
PowerShellVersion = '3.0'
とマニフェストの psd1 に設定することで表明できます
余談 : 個人的に注意していること
私が特に多くの人から苦しいと耳にすることで、個人的に気を付けているものは次のものです。だいたい記事にしていたので参考にしていただけると幸いです。
- [Object]型デフォルトに起因する型をつかった操作が影響受けやすいこと
- $null の扱い
- パイプラインを通したときの実行速度と式の違い
- 型の明示をしない場合の暗黙の型変換 (左辺合わせ)
- 単一要素配列が返却時に自動的なアンラップがかかる
- より安全に書くためには StrictMode の利用がいいでしょう
まとめ
PowerShell Script で書く場合も、C# で書く場合と同じように気を付ければ問題なさそうです。
特に、パラメータ入力、パイプラインが最も入り組んでいる印象が強いです。独自の構文$?
はコンソールでの入力以外は使わないんですよねぇ。実際、私はほぼ使わないです。
PowerShell も .NET に限らず、一般的なプログラミング言語のやり方が生きます。言語自体の構文サポートの弱さやなど癖がありますが、ゆるく付き合うといいでしょう。