頻繁に修正が必要な PowerShell のワンライナーとスクリプトは、再利用可能な関数に変換するのに適しています。
よりツール指向であるため、可能な限り関数を記述します。 スクリプト モジュールに関数を追加し、そのモジュールを $env:PSModulePath
で定義された場所に配置して、関数を呼び出すことができます。その際、関数を保存した場所を見つける必要はありません。
PowerShellGet モジュールを使用すると、NuGet リポジトリで PowerShell モジュールを簡単に共有できます。
PowerShellGet は、PowerShell バージョン 5.0 以上に付属しています。 PowerShell バージョン 3.0 以上では、個別のダウンロードとしても入手できます。
複雑にしすぎないようにしてください。 シンプルを心がけ、最も簡単にタスクを実行できる方法を使用します。 再利用するコードで、エイリアスと位置指定パラメーターを使用するのは避け、 読みやすさを考えてコードを書式設定してください。 値はハードコーディングせず、パラメーターと変数を使用します。 悪い影響が何もなくても、不要なコードは記述しないでください。 不必要に複雑になってしまいます。 PowerShell コードを記述するときは、細部にまで注意を払うことが大いに役立ちます。
名前付け
PowerShell で関数に名前を付けるときは、承認されている動詞と単数形の名詞を含むパスカル ケース名を使用します。 PowerShell で承認済みの動詞のリストを取得するには、Get-Verb
を実行します。 次の使用例は、Get-Verb
プロパティで の結果を並べ替えます。
Get-Verb | Sort-Object -Property Verb
Group プロパティを使用すると、動詞がどのように使用されるかを把握できます。
Verb Group
---- -----
Add Common
Approve Lifecycle
Assert Lifecycle
Backup Data
Block Security
Checkpoint Data
Clear Common
Close Common
Compare Data
Complete Lifecycle
Compress Data
Confirm Lifecycle
Connect Communications
Convert Data
ConvertFrom Data
ConvertTo Data
Copy Common
Debug Diagnostic
Deny Lifecycle
Disable Lifecycle
Disconnect Communications
Dismount Data
Edit Data
Enable Lifecycle
Enter Common
Exit Common
Expand Data
Export Data
Find Common
Format Common
Get Common
Grant Security
Group Data
Hide Common
Import Data
Initialize Data
Install Lifecycle
Invoke Lifecycle
Join Common
Limit Data
Lock Common
Measure Diagnostic
Merge Data
Mount Data
Move Common
New Common
Open Common
Optimize Common
Out Data
Ping Diagnostic
Pop Common
Protect Security
Publish Data
Push Common
Read Communications
Receive Communications
Redo Common
Register Lifecycle
Remove Common
Rename Common
Repair Diagnostic
Request Lifecycle
Reset Common
Resize Common
Resolve Diagnostic
Restart Lifecycle
Restore Data
Resume Lifecycle
Revoke Security
Save Data
Search Common
Select Common
Send Communications
Set Common
Show Common
Skip Common
Split Common
Start Lifecycle
Step Common
Stop Lifecycle
Submit Lifecycle
Suspend Lifecycle
Switch Common
Sync Data
Test Diagnostic
Trace Diagnostic
Unblock Security
Undo Common
Uninstall Lifecycle
Unlock Common
Unprotect Security
Unpublish Data
Unregister Lifecycle
Update Data
Use Other
Wait Lifecycle
Watch Common
Write Communications
PowerShell 関数では承認されている動詞を使用することが重要です。 未承認の動詞を持つ関数を含んだモジュールは、PowerShell セッションにインポートされると警告メッセージを生成します。 この警告メッセージにより、プロらしくない関数になってしまいます。 また、未承認の動詞があると、関数の検出可能性も制限されます。
単純な関数
PowerShell の関数を宣言する場合、関数キーワード、関数名、左中かっこ、右中かっこの順に指定します ({ }
)。 関数によって実行されるコードは、波括弧内にあります。
function Get-Version {
$PSVersionTable.PSVersion
}
次の例に示す関数は、PowerShell のバージョンを返す簡単な例です。
Get-Version
Major Minor Build Revision
----- ----- ----- --------
5 1 14393 693
関数に Get-Version
などの汎用的な名前を使用すると、名前の競合が生じる可能性があります。 将来追加される既定のコマンドや、他のユーザーが書き込む可能性があるコマンドと競合する可能性があります。 名前の競合を防ぐために、関数名の名詞部分にプレフィックスを付けます。 (例: <ApprovedVerb>-<Prefix><SingularNoun>
)。
次の例では、プレフィックス PS
を使用しています。
function Get-PSVersion {
$PSVersionTable.PSVersion
}
次の関数は、名前以外は、前の関数とまったく同じです。
Get-PSVersion
Major Minor Build Revision
----- ----- ----- --------
5 1 14393 693
名詞にプレフィックスを追加しても、名前の競合が発生する可能性はあります。 私の場合は、関数の名詞の前に自分のイニシャルを付けています。 基準を決めて、それに従ってください。
function Get-MrPSVersion {
$PSVersionTable.PSVersion
}
この関数は、他の PowerShell コマンドとの名前の競合を防ぐためにより独特な名前を使用している点を除けば、前述の 2 つの関数と何ら変わりません。
Get-MrPSVersion
Major Minor Build Revision
----- ----- ----- --------
5 1 14393 693
メモリにロードされると、関数 PSDrive に関数が表示されます。
Get-ChildItem -Path Function:\Get-*Version
CommandType Name Version
----------- ---- -------
Function Get-Version
Function Get-PSVersion
Function Get-MrPSVersion
これらの関数を現在のセッションから削除する場合は、Function PSDrive から削除するか、PowerShell を一度閉じてから再度開いてください。
Get-ChildItem -Path Function:\Get-*Version | Remove-Item
関数が本当に削除されていることを確認します。
Get-ChildItem -Path Function:\Get-*Version
関数がモジュールの一部として読み込まれている場合は、モジュールをアンロードすることで、関数を削除できます。
Remove-Module -Name <ModuleName>
Remove-Module
コマンドレットは、現在の PowerShell セッションのメモリから PowerShell モジュールを削除します。 システムまたはディスクから削除されることはありません。
パラメーター
値を静的に割り当てないでください。 代わりにパラメーターと変数を使用してください。 パラメーターに名前を付ける場合は、可能な限り、パラメーター名の既定のコマンドレットと同じ名前を使用します。
次の関数では、Computer でも、ServerName でも、Host でもなく、ComputerName をパラメーター名に使用します。 ComputerName を使用すると、パラメーター名を、既定のコマンドレットのパラメーター名と大文字小文字の区別に一致するよう標準化します。
function Test-MrParameter {
param (
$ComputerName
)
Write-Output $ComputerName
}
次の関数は、システム上のすべてのコマンドに対してクエリを実行し、特定のパラメーター名を持つ数値を返します。
function Get-MrParameterCount {
param (
[string[]]$ParameterName
)
foreach ($Parameter in $ParameterName) {
$Results = Get-Command -ParameterName $Parameter -ErrorAction SilentlyContinue
[pscustomobject]@{
ParameterName = $Parameter
NumberOfCmdlets = $Results.Count
}
}
}
次の結果でわかるように、ComputerName パラメーターを持つコマンドは 39 個あります。 Computer、ServerName、Host、Machine などのパラメーターを持つコマンドはありません。
Get-MrParameterCount -ParameterName ComputerName, Computer, ServerName,
Host, Machine
ParameterName NumberOfCmdlets
------------- ---------------
ComputerName 39
Computer 0
ServerName 0
Host 0
Machine 0
パラメーター名には、デフォルトのコマンドレットと同じ大文字と小文字の形式を使用してください。 たとえば、ComputerName
ではなく、computername
を使用します。 この名前付けスキームは、PowerShell に慣れているユーザーが関数を検出し、既定のコマンドレットのような外観と操作感を得るのに役立ちます。
param
ステートメントを使用すると、1 つ以上のパラメーターを定義できます。 パラメーター定義はコンマ (,
) で区切ります。 詳細については、about_Functions_Advanced_Parameters を参照してください。
高度な関数
関数を PowerShell の高度な関数に変換するのは簡単です。 関数と高度な関数の違いの 1 つが、高度な関数には、自動的に追加される共通パラメーターがあることです。 一般的なパラメーターには、 Verbose や Debugといったパラメーターが含まれます。
前のセクションで使用した Test-MrParameter
関数から始めます。
function Test-MrParameter {
param (
$ComputerName
)
Write-Output $ComputerName
}
共通パラメーターを表示する方法はいくつかあります。 1 つは Get-Command
を使用して構文を表示する方法です。
Get-Command -Name Test-MrParameter -Syntax
Test-MrParameter
関数に共通のパラメーターがない点に注意してください。
Test-MrParameter [[-ComputerName] <Object>]
もう 1 つは、Get-Command
のパラメーター プロパティをドリルダウンする方法です。
(Get-Command -Name Test-MrParameter).Parameters.Keys
ComputerName
CmdletBinding
属性を追加して、関数を高度な関数に変換します。
function Test-MrCmdletBinding {
[CmdletBinding()] # Turns a regular function into an advanced function
param (
$ComputerName
)
Write-Output $ComputerName
}
CmdletBinding
を指定すると、共通パラメーターが自動的に追加されます。
CmdletBinding
には param
ブロックが必要ですが、param
ブロックは空でもかまいません。
Get-Command -Name Test-MrCmdletBinding -Syntax
Test-MrCmdletBinding [[-ComputerName] <Object>] [<CommonParameters>]
Get-Command
のパラメーター プロパティをドリルダウンすると、共通パラメーター名を含む実際のパラメーター名が表示されます。
(Get-Command -Name Test-MrCmdletBinding).Parameters.Keys
ComputerName
Verbose
Debug
ErrorAction
WarningAction
InformationAction
ErrorVariable
WarningVariable
InformationVariable
OutVariable
OutBuffer
PipelineVariable
SupportsShouldProcess
SupportsShouldProcess
属性は、リスク軽減パラメーターとして WhatIf と Confirm を追加します。 これらのパラメーターは、変更するコマンドにのみ必要です。
function Test-MrSupportsShouldProcess {
[CmdletBinding(SupportsShouldProcess)]
param (
$ComputerName
)
Write-Output $ComputerName
}
新しい WhatIf および Confirm パラメーターに注目してください。
Get-Command -Name Test-MrSupportsShouldProcess -Syntax
Test-MrSupportsShouldProcess [[-ComputerName] <Object>] [-WhatIf] [-Confirm]
[<CommonParameters>]
もう一度、Get-Command
を使って、一般的なものを含む実際のパラメーター名の一覧を返すことができます。さらに、WhatIf や Confirmも含まれます。
(Get-Command -Name Test-MrSupportsShouldProcess).Parameters.Keys
ComputerName
Verbose
Debug
ErrorAction
WarningAction
InformationAction
ErrorVariable
WarningVariable
InformationVariable
OutVariable
OutBuffer
PipelineVariable
WhatIf
Confirm
パラメーターの検証
入力は早い段階で検証してください。 有効な入力がないと完了できないような状況では、コードの処理を進めないでください。
パラメーターに使用する変数のデータ型を常に指定します。 次の例では、ComputerName パラメーターに対して、String がデータ型として指定されています。 この検証では、ComputerName パラメーターに指定できるコンピューター名が 1 つだけに制限されます。
function Test-MrParameterValidation {
[CmdletBinding()]
param (
[string]$ComputerName
)
Write-Output $ComputerName
}
複数のコンピューター名が指定されている場合、エラーが生成されます。
Test-MrParameterValidation -ComputerName Server01, Server02
Test-MrParameterValidation : Cannot process argument transformation on
parameter 'ComputerName'. Cannot convert value to type System.String.
At line:1 char:42
+ Test-MrParameterValidation -ComputerName Server01, Server02
+ ~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidData: (:) [Test-MrParameterValidation]
, ParameterBindingArgumentTransformationException
+ FullyQualifiedErrorId : ParameterArgumentTransformationError,Test-MrP
arameterValidation
現時点での定義の問題は、ComputerName パラメーターの値は省略できるのに、関数が正常に完了するには、その値が必要であることです。 このシナリオは、Mandatory
パラメーター属性が有益な場合です。
次の例で使用している構文は、PowerShell バージョン3.0 以降と互換性があります。
[Parameter(Mandatory=$true)]
を指定すると、関数と PowerShell バージョン 2.0 以降の互換性が確保されます。
function Test-MrParameterValidation {
[CmdletBinding()]
param (
[Parameter(Mandatory)]
[string]$ComputerName
)
Write-Output $ComputerName
}
これで ComputerName が必須になり、指定されていない場合、関数によって入力を求めるプロンプトが表示されます。
Test-MrParameterValidation
cmdlet Test-MrParameterValidation at command pipeline position 1
Supply values for the following parameters:
ComputerName:
ComputerName パラメーターに複数の値を指定できるようにする場合は、String データ型を使用しますが、文字列の配列を許可するためにデータ型に角括弧 ([]
) を追加します。
function Test-MrParameterValidation {
[CmdletBinding()]
param (
[Parameter(Mandatory)]
[string[]]$ComputerName
)
Write-Output $ComputerName
}
ComputerName パラメーターに既定値を指定する必要が出てくることもあるでしょう (指定されていない場合)。
問題は、必須パラメーターでは既定値を使用できないことです。 代わりに、ValidateNotNullOrEmpty
パラメーター検証属性を既定値と共に使用してください。
既定値を設定するときも、静的な値は使用しないでください。 次の例では、$env:COMPUTERNAME
が既定値として使用されています。つまり、値が指定されていない場合、これはローカルコンピューター名に自動的に変換されます。
function Test-MrParameterValidation {
[CmdletBinding()]
param (
[ValidateNotNullOrEmpty()]
[string[]]$ComputerName = $env:COMPUTERNAME
)
Write-Output $ComputerName
}
詳細出力
インライン コメントは、複雑なコードを記述しているが、ユーザーがコードを見ない限り表示されない場合に便利です。
次の例の関数には、foreach
ループにインライン コメントが含まれています。 この特定のコメントを見つけるのはそれほど難しくないでしょう。しかし、関数に数百行のコードが含まれていたらどうでしょうか。
function Test-MrVerboseOutput {
[CmdletBinding()]
param (
[ValidateNotNullOrEmpty()]
[string[]]$ComputerName = $env:COMPUTERNAME
)
foreach ($Computer in $ComputerName) {
#Attempting to perform an action on $Computer <<-- Don't use
#inline comments like this, use write verbose instead.
Write-Output $Computer
}
}
もっと良い方法があります。インライン コメントではなく Write-Verbose
を使用する方法です。
function Test-MrVerboseOutput {
[CmdletBinding()]
param (
[ValidateNotNullOrEmpty()]
[string[]]$ComputerName = $env:COMPUTERNAME
)
foreach ($Computer in $ComputerName) {
Write-Verbose -Message "Attempting to perform an action on $Computer"
Write-Output $Computer
}
}
Verbose パラメーターを指定せずに関数を呼び出した場合、詳細出力は表示されません。
Test-MrVerboseOutput -ComputerName Server01, Server02
Verbose パラメーターを指定して関数を呼び出した場合、詳細出力が表示されます。
Test-MrVerboseOutput -ComputerName Server01, Server02 -Verbose
パイプライン入力
関数でパイプライン入力を受け入れる場合は、追加のコードが必要です。 本書で前述したように、コマンドが受け入れることができるのは、値 (型) またはプロパティ名によるパイプライン入力です。 関数はネイティブ コマンドと同じように記述できるため、これらの入力の種類のいずれか、または両方を受け入れることができます。
値渡しのパイプライン入力を受け入れるには、その特定のパラメーターに対して ValueFromPipeline
パラメーター属性を指定します。
値渡しのパイプライン入力は、各データ型の 1 つのパラメーターからのみ受け入れられます。 文字列入力を受け入れる 2 つのパラメーターがある場合、値渡しのパイプライン入力を受け入れることができるのはそのうちの 1 つのみです。 両方の文字列パラメーターに対して値渡しを指定した場合、入力ではどのパラメーターにバインドするかが判断できません。 このシナリオも、私がこの種類のパイプライン入力を、値渡しではなく型によるパイプライン入力と呼ぶ理由です。
パイプライン入力は、foreach
ループで項目が処理される方法と同様に、一度に1つの項目を受信します。
関数が配列を入力として受け入れる場合、各項目を処理するには process
ブロックが必要です。 関数が入力として 1 つの値のみを受け入れる場合、process
ブロックは必要ありませんが、一貫性を保つため推奨されます。
function Test-MrPipelineInput {
[CmdletBinding()]
param (
[Parameter(Mandatory,
ValueFromPipeline)]
[string[]]$ComputerName
)
process {
Write-Output $ComputerName
}
}
パラメーター属性で指定する点を除き、ValueFromPipelineByPropertyName
パイプライン入力の受け入れも同様です。また、データ型に関係なく、任意の数のパラメーターに対して指定できます。 ポイントは、パイプインされるコマンドの出力に、関数のパラメーター名またはそのエイリアスと一致するプロパティ名が含まれている必要があることです。
function Test-MrPipelineInput {
[CmdletBinding()]
param (
[Parameter(Mandatory,
ValueFromPipelineByPropertyName)]
[string[]]$ComputerName
)
process {
Write-Output $ComputerName
}
}
begin
ブロックと end
ブロックは省略可能です。
begin
は process
ブロックの前に指定され、パイプラインから項目が受信される前の初期作業を実行するときに使用されます。 パイプ処理された値には、begin
ブロックではアクセスできません。
end
ブロックは process
ブロックの後に指定され、すべての項目の処理が完了した後に後処理として使用されます。
エラー処理
次の例に示す関数では、コンピューターに接続できない場合に、ハンドルされない例外が生成されます。
function Test-MrErrorHandling {
[CmdletBinding()]
param (
[Parameter(Mandatory,
ValueFromPipeline,
ValueFromPipelineByPropertyName)]
[string[]]$ComputerName
)
process {
foreach ($Computer in $ComputerName) {
Test-WSMan -ComputerName $Computer
}
}
}
PowerShell でエラーを処理する方法はいくつかあります。
Try/Catch
は、最新のエラー処理方法です。
function Test-MrErrorHandling {
[CmdletBinding()]
param (
[Parameter(Mandatory,
ValueFromPipeline,
ValueFromPipelineByPropertyName)]
[string[]]$ComputerName
)
process {
foreach ($Computer in $ComputerName) {
try {
Test-WSMan -ComputerName $Computer
}
catch {
Write-Warning -Message "Unable to connect to Computer: $Computer"
}
}
}
}
前の例に示した関数はエラー処理を使用していますが、コマンドが終了エラーを生成しないため、未処理の例外が生成されます。 終了エラーのみが捕捉されます。 終了しないエラーを終了エラーに変換するには、ErrorAction パラメータに Stop を値として指定します。
function Test-MrErrorHandling {
[CmdletBinding()]
param (
[Parameter(Mandatory,
ValueFromPipeline,
ValueFromPipelineByPropertyName)]
[string[]]$ComputerName
)
process {
foreach ($Computer in $ComputerName) {
try {
Test-WSMan -ComputerName $Computer -ErrorAction Stop
}
catch {
Write-Warning -Message "Unable to connect to Computer: $Computer"
}
}
}
}
グローバル $ErrorActionPreference
変数は、どうしても必要な場合を除き、変更しないでください。 ローカル スコープで変更を加えた場合、そのスコープを終了すると以前の値に戻ります。
PowerShell 関数内から .NET のような何かを直接使用している場合は、コマンド自体で ErrorAction パラメーターを指定することはできません。 .NET メソッドを呼び出す直前に、$ErrorActionPreference
変数を変更できます。
コメント ベースのヘルプ
関数にヘルプを追加することは、ベスト プラクティスとみなされています。 ヘルプを使うことで、共有された人がそれらの使い方を理解できるようになります。
function Get-MrAutoStoppedService {
<#
.SYNOPSIS
Returns a list of services that are set to start automatically, are not
currently running, excluding the services that are set to delayed start.
.DESCRIPTION
Get-MrAutoStoppedService is a function that returns a list of services
from the specified remote computer(s) that are set to start
automatically, are not currently running, and it excludes the services
that are set to start automatically with a delayed startup.
.PARAMETER ComputerName
The remote computer(s) to check the status of the services on.
.PARAMETER Credential
Specifies a user account that has permission to perform this action. The
default is the current user.
.EXAMPLE
Get-MrAutoStoppedService -ComputerName 'Server1', 'Server2'
.EXAMPLE
'Server1', 'Server2' | Get-MrAutoStoppedService
.EXAMPLE
Get-MrAutoStoppedService -ComputerName 'Server1' -Credential (Get-Credential)
.INPUTS
String
.OUTPUTS
PSCustomObject
.NOTES
Author: Mike F. Robbins
Website: https://mikefrobbins.com
Twitter: @mikefrobbins
#>
[CmdletBinding()]
param (
)
#Function Body
}
関数にコメント ベースのヘルプを追加すると、既定の組み込みコマンドと同様にヘルプを取得できるようになります。
PowerShell で関数を記述するための構文はどれも、なじみのないユーザーにとっては太刀打ちできないように思えるかもしれません。 なんらかの構文を思い出せない場合は、PowerShell Integrated Scripting Environment (ISE) の 2 つ目のインスタンスを別のモニターで開き、関数のコードを入力するときに "コマンドレット (高度な関数) - 完了" スニペットを表示してください。 スニペットにアクセスするには、PowerShell ISE で Ctrl + J キーを使用します。
まとめ
この章では、次の方法を含む、PowerShell での関数の記述の基本について説明しました。
- 高度な関数を作成する
- パラメーター検証を使用する
- 詳細出力を使用する
- パイプライン入力をサポートする
- エラーの処理
- コメント ベースのヘルプを作成する
レビュー
- PowerShell で承認されている動詞の一覧を取得するには、どうすればよいですか。
- PowerShell 関数を高度な関数に変換するには、どうすればよいですか。
- PowerShell 関数に WhatIf および Confirm パラメーターをいつ追加すべきですか?
- 終了しないエラーを終了するエラーに変換するには、どうすればよいですか。
- コメント ベースのヘルプを関数に追加する必要があるのは、なぜですか。
リファレンス
PowerShell