以下の内容はhttps://tech.guitarrapc.com/entry/2017/12/06/000000より取得しました。


PowerShellのAPIデザインガイドライン

この記事は、PowerShell Advent Calendar 2017 5日目の記事です。

https://qiita.com/advent-calendar/2017/powershell

昨日は @atworksさんのPSRemotingを用いたリモートプロセス実行でした。

https://qiita.com/atworks/items/b2dbaeb485139b1f9b69

3日目の前回PowerShellのコーディングスタイルについて触れました。

http://tech.guitarrapc.com/entry/2017/12/03/230119

次は、残りのAPIデザインを見てみましょう。

※ 2回に分けて書きましたが個人的には個人/チームが書きやすいようにすればいい話だと思っています。しかし、「曖昧だった基本的な指針がわからず困ってた方」にとって、ベストプラクティスはいい材料です。PowerShellコミュニティは結構活発なので、コミュニティの中で皆様がよいPowerShell生活を送られることを祈っています。

C#のデザインガイド

Required Development Guidelines

https://msdn.microsoft.com/en-us/library/dd878238.aspx

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

次は強く要請するガイドラインです。

https://msdn.microsoft.com/en-us/library/dd878270.aspx

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のような複数名を用いる場合は、そパラメータがいつでも複数要素を受け入れる場合にしましょう

  • パラメータはPascalCaseで。C# であればPropertyを用いるので、C# デザインガイドと同じで違和感はないでしょう

errorActionやerroractionより、ErrorActionが好ましいです

  • パラメータの組み合わせで操作が変わるCmdletを提供する場合2つの方法がある

enumを用いて、enumごとに操作を分岐しパラメータを処理する方法 ValudateSet属性.aspx)を用いて、パラメータの入力を制約する方法 経験上、複数の操作を提供するCmdletは作りたくなります。パラメータ1つだけが特定のパラメータ時に用いないようなの「単純な組み合わせ」であれば、ValidateSetが楽でしょう。が、複数のパラメータの組み合わせをParameterSetで表現するのはおススメしません。Cmdletを分離することを検討するといいでしょう

あらかじめ、どんな用途(アクティビティ) にどんなパラメータが期待されるのかリストされています

Appendなど利用者が直感的に利用しやすいAPIデザインとして一貫性を保つため、該当するものを利用するといいでしょう

  • 強く型付けされた .NET Framework型を利用する

Objectを利用するということは、型に対する意識が強いということです。 .NET Frameworkの型を意識して利用すると型を使って値が保障できます。たとえば、URIにはUri型を用いれば、String型が入ってこないことを保証できます。

  • パラメータの型に首尾一貫性をもたせる

たとえば、ProcessパラメータというのにInt16型を当てた場合、他のCmdletのProcessパラメータでUint16を用いるのは避けましょう。 利用者の直感に反するので触り心地に大きく影響します。

  • true/falseをとるパラメータは避けて、Switch Parameterを用いる
  • Switch Parameterは、もし利用していればtrue、なければfalseとみなす
  • もし3値 (true, false, Unspecified) が必要な場合は、Nullable<bool>が適切でしょう

Unspecifiednullを合わせるかは一考の余地があります。

  • 可能であれば、パラメータに配列を許容しましょう

例えばGet-ProcessはNameにString配列を許容します。 利用者の使い心地として、複数回Cmdletを実行するより、1回で済む方がうれしいことは多いでしょう。

  • PassThruパラメータのサポートを検討しましょう

Stop-Processのような値を返さないCmdlet (Void型) であっても、時に結果オブジェクトが必要です。 こういった場合に、PassThruパラメータを与えることで、結果オブジェクトを返すオプションを提供しましょう。 Add, Set, NewといったVerbのCmdletはサポートしているものが多いです。

  • ParameterSetのサポート

Cmdletは1つの目的のために作ります。 が、時に1つの操作を複数の表現で呼べることがあるでしょう。つまり、パラメータの組み合わせということです。 このパラメータの組み合わせの表現に、ParameterSetを用いることが多いです。 ParameterSetを用いる場合、DefaultParameterSetCmdlet属性に指定しましょう。

(Design Guidelines) Provide Feedback to the User (SD04)

実行中ただ待つのは苦痛です。実行に対して何かしらのフィードバックを返しましょう。

  • WriteWarning, WriteVerbose, WriteDebugメソッドのサポート

もし意図しない結果が起こった場合は、WriteWarningメソッドで結果をユーザーに伝えましょう もしユーザーがさらなる詳細情報を求める場合、WriteVerboseで結果を返しましょう。例えば、実行シナリオが意図した状態になっているかを伝えることもいいでしょう 開発者がプロダクトサポートのために必要とする情報は、WriteDebugメソッドで返すといいでしょう

  • 長時間実行時のWriteProgressサポート

長時間実行する場合、進捗をWriteProgressメソッドで表示するといいでしょう

  • Host Interfaceを用いた対話実行

時にShouldProcess以外に、Hostを通してユーザーとやり取りをする必要に迫られます。そんなときにHostプロパティを用いましょう たとえば、PromptForChoiceWriteLine/ReadLineなどです もしCmdletがGUIを生成しないなら、Out-GridView Cmdletの利用も検討できます またCmdletは、Console APIは利用すべきじゃない。

  • Cmdletヘルプファイルの生成

Help.xmlファイルで、Cmdletのヘルプを伝えられます。

(Design Guidelines) Advisory Development Guidelines

アドバイスとしてのガイドラインです。

https://msdn.microsoft.com/en-us/library/dd878291.aspx

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が存在します。

https://github.com/PoshCode/PowerShellPracticeAndStyle/blob/master/Best-Practices/Introduction.md

一度目を通してみると面白いのではないでしょうか?

  • 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で制御しましょう
try {
    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 -AsSecureStringSecureStringを受け取られる
  • 万が一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]型デフォルトに起因する型をつかった操作が影響受けやすいこと

http://tech.guitarrapc.com/entry/2015/01/20/034358

  • $nullの扱い

http://tech.guitarrapc.com/entry/2012/11/14/071151

http://winscript.jp/powershell/222

  • パイプラインを通したときの実行速度と式の違い

http://tech.guitarrapc.com/entry/2013/03/12/080349

http://tech.guitarrapc.com/entry/2013/03/09/210303

  • 型の明示をしない場合の暗黙の型変換 (左辺合わせ)

http://tech.guitarrapc.com/entry/2013/09/22/122454

  • 単一要素配列が返却時に自動的なアンラップがかかる

http://tech.guitarrapc.com/entry/2015/09/05/012733

  • より安全に書くためにはStrictModeの利用がいいでしょう

http://blog.shibata.tech/entry/2015/11/10/233858

まとめ

PowerShell Scriptで書く場合も、C# で書く場合と同じように気を付ければ問題なさそうです。

特に、パラメータ入力、パイプラインが最も入り組んでいる印象が強いです。独自の構文$?はコンソールでの入力以外は使わないんですよねぇ。実際、私はほぼ使わないです。

PowerShellも .NETに限らず、一般的なプログラミング言語のやり方が生きます。言語自体の構文サポートの弱さやなど癖がありますが、ゆるく付き合うといいでしょう。




以上の内容はhttps://tech.guitarrapc.com/entry/2017/12/06/000000より取得しました。
このページはhttp://font.textar.tv/のウェブフォントを使用してます

不具合報告/要望等はこちらへお願いします。
モバイルやる夫Viewer Ver0.14