PowerShell関数のマスタリング:ステップバイステップガイド

PowerShellスクリプトの作成に慣れてくると、コードのモジュール化について学ぶ必要があります。モジュール化とは、コードをビルディングブロックで作成することを指す一般的な用語です。PowerShellの世界では、PowerShell関数がこれを行うための最良の方法の一つです。

PowerShellスクリプトを書く際には、コードを書く方法には多くのオプションがあります。単一の、途切れないコードブロックで何百ものタスクを実行するために、何千行ものコードを書くこともできますが、それは災害となるでしょう。代わりに、関数を書くべきです。

関数は、コードの使いやすさと読みやすさを大幅に向上させ、作業がはるかに容易になります。このチュートリアルでは、関数の書き方、関数のパラメーターの追加と管理、およびパイプライン入力の受け入れ設定について学びます。しかし、まずは用語について見てみましょう。

この記事は私の書籍「PowerShell for Sysadmins: Workflow Automation Made Easy」から抜粋されました。このチュートリアルで何か学んだ場合は、ぜひ書籍をチェックして、関数や他の多くのPowerShellの便利な機能についてもっと学んでください。

関数とコマンドレット

関数の概念はおそらくおなじみのものであるかもしれません。おそらくすでに使用しているStart-ServiceWrite-Hostなどのコマンドは、関数と似ています。これらは、単一の問題を解決する名前付きのコードです。関数とコマンドレットの違いは、それぞれの構造がどのように作成されるかです。

A cmdlet isn’t written with PowerShell. It’s written in another language, typically something like C#. The cmdlet is then compiled and made available inside PowerShell.

一方、関数はPowerShellで書かれています。他の言語ではありません。

以下のように、Get-CommandコマンドレットとそのCommandTypeパラメータを使用して、コマンドがコマンドレットか関数かを確認できます。

Get-Command –CommandType Function

上記のコマンドは、PowerShellセッションに現在ロードされているすべての関数、またはPowerShellで利用可能なモジュール内の関数を返します。

関連記事: PowerShellモジュールの理解と構築

前提条件

すべての例に従いたい場合は、PowerShellのバージョンが利用可能であることを確認してください。このチュートリアルには特定のバージョン要件はありません。また、コードスニペットをコピー、貼り付け、実行するための良いコードエディタ(例:Visual Studio Code)を使用できるようにしてください。

シンプルな関数の作成

関数を使用する前に、それを定義する必要があります。関数を定義するには、関数のキーワードを使用し、その後に記述的なユーザー定義名を続け、中括弧のセットを続けます。中括弧の内側には、PowerShellが実行するスクリプトブロックがあります。

以下に基本的な関数とその関数を実行する例が示されています。この関数はInstall-Softwareという名前で呼ばれ、Write-Hostを使用してコンソールにメッセージを表示します。定義されると、この関数の名前を使用して、スクリプトブロック内のコードを実行できます。

PS> function Install-Software { Write-Host 'I installed some software. Yippee!' }
PS> Install-Software
I installed some software, Yippee!

動詞-名詞で命名する

A function’s name is important. You can name your functions whatever you want, but the name should always describe what the function does. The function-naming convention in PowerShell is the Verb-Noun syntax.

関数名は常に動詞で始め、ダッシュと名詞が続くようにします。”承認済み”の動詞のリストを見つけるには、Get-Verbコマンドレットを使用します。

関数の動作の変更

関数の動作を変更したい場合は、以下のように関数が実行するコードを変更するだけです。

PS> function Install-Software { Write-Host 'You installed some software, Yay!' }
PS> Install-Software
You installed some software, Yay!

関数内のコードを変更したため、表示されるメッセージが若干異なるようになりました。

高度な関数の定義

関数はさまざまな場所で定義することができます。上記のセクションでは、チュートリアルではコードをPowerShellコンソールに直接コピーして貼り付けたものとしています。しかし、これは唯一の方法ではありません。スクリプトでも関数を定義できます。

前のセクションでは、小さな関数を扱っていたので、コンソールに定義することはそれほど問題ではありませんでした。しかし、ほとんどの場合、より大きな関数を持つことになります。そのような関数をスクリプトまたはモジュールに定義し、そのスクリプトまたはモジュールを呼び出して関数をメモリにロードする方が簡単です。

大きな関数を調整するたびに再入力するのは少しイライラするかもしれません。

I suggest you now open your favorite editor and store the function in a .ps1 file as you work through the rest of the tutorial.

関数にパラメータを追加する

PowerShell関数には任意の数のパラメータを持たせることができます。独自の関数を作成する際には、パラメータを含めるかどうかを選択し、それらのパラメータがどのように機能するかを決定することができます。パラメータは必須またはオプションであり、任意のものを受け入れるか、可能な引数のリストの中から強制的に1つを受け入れるかのいずれかです。

関連記事:PowerShellパラメータについて知りたかったすべて

たとえば、以前のInstall-Software関数でインストールしている架空のソフトウェアには多くのバージョンがあるかもしれません。しかし、現在のInstall-Software関数ではユーザーがどのバージョンをインストールしたいかを指定する方法はありません。

もし関数を使用するのがあなただけなら、特定のバージョンが必要なたびにコードを変更することができますが、それは時間の無駄です。このプロセスは潜在的なエラーの原因にもなりますし、他の人があなたのコードを使用できるようにしたいですよね。

関数にパラメータを導入することで、その関数に変動性を持たせることができます。変数が同じ状況の多くのバージョンを処理できるスクリプトを書くことを可能にしたように、パラメータは1つのことを多くの方法で行う単一の関数を書くことができます。

この場合、同じソフトウェアのバージョンをインストールし、多くのコンピュータ上で実行したいと考えています。

まず、関数にパラメータを追加し、インストールするバージョンを指定できるようにします。

シンプルなパラメータの作成

関数にパラメータを作成するには、paramブロックが必要です。 paramブロックには、関数のすべてのパラメータが含まれます。 以下に示すように、paramキーワードの後にカッコを付けてparamブロックを定義します。

function Install-Software {
	[CmdletBinding()]
	param()

	Write-Host 'I installed software version 2. Yippee!'
}

この時点では、関数の実際の機能はまったく変わりません。 パラメータを準備するために、配管をインストールしただけです。

現時点では、関数は実際にソフトウェアをインストールしていません。 ソフトウェアのインストールをシミュレートするために、単にWrite-Hostコマンドレットを使用しています。

paramブロックを追加したら、以下のようにparamブロックのカッコ内にパラメータを作成できます。

function Install-Software {
	[CmdletBinding()]
	param(
		[Parameter()]
		[string] $Version
	)
	
	Write-Host "I installed software version $Version. Yippee!"

}

上記のparamブロックの内部では、まずパラメータブロックを定義します。 ここで使用しているParameter()ブロックを使用すると、「高度なパラメータ」に変換されます。 ここでの空のパラメータブロックは何もしませんが、必要です。次のセクションで使用方法を説明します。

代わりに、パラメータ名の前にある[string]型に焦点を当てましょう。パラメータの値を特定の型にキャストするために、パラメータ変数名の前に角括弧でパラメータの型を指定します。PowerShellは、このパラメータに渡される値を常に文字列に変換しようとします – もしそれが既に文字列でない場合は。上記のコードでは、$Versionとして渡されるものは常に文字列として扱われます。

パラメータを型にキャストすることは必須ではありませんが、強くお勧めします。それは明示的に型を定義し、将来のエラーを大幅に減らすでしょう。

また、$VersionWrite-Hostステートメントに追加します。これにより、Install-Software関数をVersionパラメータで実行し、バージョン番号を渡すと、以下に示すようなステートメントが表示されるはずです。

PS> Install-Software -Version 2
I installed software version 2. Yippee!

では、このパラメータで何ができるか見てみましょう。

必須パラメータ属性

パラメータブロックを使用して、パラメータの動作を変更するためのさまざまなパラメータ属性を制御することができます。たとえば、関数を呼び出す際に特定のパラメータを渡す必要があるようにしたい場合、そのパラメータを必須として定義することができます。

デフォルトでは、パラメータはオプションです。以下のように、パラメータブロック内にMandatoryキーワードを使用して、ユーザーにバージョンの指定を強制しましょう。

function Install-Software {
	[CmdletBinding()]
	param(
		[Parameter(Mandatory)]
		[string]$Version
	)

	Write-Host "I installed software version $Version. Yippee!"

}

上記の関数を実行すると、以下のプロンプトが表示されるはずです。

Prompting for a mandatory parameter

Mandatory属性を設定した後、パラメーターなしで関数を実行すると、ユーザーが値を入力するまで実行が停止します。関数は、ユーザーがVersionパラメーターに値を指定するまで待機します。入力されると、PowerShellは関数を実行します。

強制パラメータープロンプトを回避するには、以下のように関数を呼び出す際にパラメーターに値を渡すだけです。

Install-Software -Version 2

デフォルトパラメーター値

たとえば、あるパラメーターに同じ値を何度も渡す場合、デフォルトパラメーター値を定義できます。デフォルトパラメーターは、ほとんどの場合に特定の値を期待する場合に便利です。

例えば、このソフトウェアのバージョン2を90%の場合にインストールしたい場合、毎回値を設定する必要がないように、$Versionパラメーターにデフォルト値2を割り当てることができます。以下にこの例を示します。

function Install-Software {
	[CmdletBinding()]
	param(
		[Parameter()]
		[string]$Version = 2
	)

	Write-Host "I installed software version $Version. Yippee!"

}

デフォルトパラメーターを持つことは、渡すパラメーターを防ぐものではありません。渡された値はデフォルト値を上書きします。

パラメーターの検証属性の追加

関数を介してパラメーターに渡すことができる値を制限することは常に良い考えです。そのために最適な方法は、パラメーターの検証属性を使用することです。

ユーザー(あるいは自分自身)が関数やスクリプトに渡すことができる情報を制限することは、関数内の不要なコードを排除するために役立ちます。例えば、既存のバージョンであるバージョン3の値をInstall-Software関数に渡す場合を考えてみましょう。

あなたの機能は、すべてのユーザーが存在するバージョンを知っていることを前提としているため、バージョン4を指定しようとした場合にどうなるかを考慮していません。その場合、フォルダが存在しないため、適切なフォルダを見つけることができません。

パラメータの検証がない場合の問題

以下の例を確認してください。ファイルパスで$Version文字列を使用する場合、既存のフォルダ名を完了しない値(例:SoftwareV3やSoftwareV4)を渡すと、コードが失敗します。

function Install-Software {
	param(
		[Parameter(Mandatory)]
		[string]$Version
	)

	Get-ChildItem -Path \\SRV1\Installers\SoftwareV$Version

}
Failing on unexpected parameter values

この問題に対処するためには、エラーハンドリングコードを書くか、ユーザーに既存のソフトウェアのバージョンのみを渡すように要求することで問題を事前に防ぐことができます。ユーザーの入力を制限するために、パラメータの検証を追加します。

パラメータの検証の追加

さまざまな種類のパラメータの検証が存在しますが、Install-Software関数に関しては、[ValidateSet属性](https://adamtheautomator.com/powershell-validateset/)が最適です。ValidateSet検証属性を使用すると、パラメータに許可される値のリストを指定できます。たとえば、文字列1または2のみを考慮している場合、ユーザーはこれらの値のみを指定できるようにする必要があります。そうでない場合、関数は直ちに失敗し、ユーザーにその理由を通知します。

パラメータの検証属性を、以下に示すように、元のParameterブロックのすぐ下に追加します。

function Install-Software {
	param(
		[Parameter(Mandatory)]
		[ValidateSet('1','2')]
		[string]$Version
	)

	Get-ChildItem -Path \\SRV1\Installers\SoftwareV$Version

}

ValidateSet属性の括弧内に項目(1および2)を追加することで、PowerShellに対してVersionの有効な値が1または2であることを伝えます。セット内の値以外を指定しようとすると、以下のエラーメッセージが表示され、利用可能なオプションの数が特定されることをユーザーに通知します。

Parameter validation stopping PowerShell function execution

ValidateSet属性は一般的なバリデーション属性ですが、他にも利用可能なものがあります。パラメータの値を制限する方法の詳細については、Microsoftのドキュメントをご覧ください。

パイプライン入力の受け入れ

これまで、通常の-ParameterName 構文を使用してパラメータを渡すことができる関数を作成しました。これは機能しますが、PowerShellのパイプラインを使用してパラメータに値を渡す別のオプションもあります。Install-Software関数にパイプラインの機能を追加しましょう。

関連記事:ATA PowerShellパラメータのパイプライン入力の受け入れ

最初に、コードに別のパラメータを追加して、ソフトウェアをインストールするコンピュータを指定します。また、Write-Host の参照にもそのパラメータを追加して、インストールをシミュレートします。

function Install-Software {
	param(
		[Parameter(Mandatory)]
		[string]$Version
		[ValidateSet('1','2')],
		
		[Parameter(Mandatory)]
		[string]$ComputerName
	)

	Write-Host "I installed software version $Version on $ComputerName. Yippee!"

}

関数にComputerNameパラメータを追加したら、以下のようにコンピュータ名とバージョンの値を渡してInstall-Software関数を繰り返し実行できます。

$computers = @("SRV1", "SRV2", "SRV3")
foreach ($pc in $computers) {
	Install-Software -Version 2 -ComputerName $pc
}

これらすべてを行う代わりに、パイプラインを使用する方法について学ぶべきです。

関数をパイプラインに対応させるための準備

残念ながら、以前に作成した単純な関数ではPowerShellのパイプラインを活用することはできません。関数が受け入れるパイプライン入力のタイプを決定し、実装する必要があります。

A PowerShell function uses two kinds of pipeline input: ByValue (entire object) and ByPropertyName (a single object property). Here, because the $computers array contains only strings, you’ll pass those strings via ByValue.

パイプラインサポートを追加するには、以下のようにパラメータにパラメータ属性を追加します。ValueFromPipelineまたはValueFromPipelineByPropertyNameのキーワードを使用します。

function Install-Software {
	param(
		[Parameter(Mandatory)]
		[ValidateSet('1','2')]
		[string]$Version,

		[Parameter(Mandatory, ValueFromPipeline)]
		[string]$ComputerName
	)

	Write-Host "I installed software version $Version on $ComputerName. Yippee!"

}

Instal-Software関数を更新したら、次のように呼び出します。

$computers = @("SRV1", "SRV2", "SRV3")
$computers | Install-Software -Version 2

スクリプトを再実行すると、以下のような結果が得られます。

I installed software version 2 on SRV3. Yippee!

配列内の最後の文字列だけがInstall-Softwareが実行されることに注意してください。次のセクションでこれを修正する方法について説明します。

processブロックの追加

PowerShellにこの関数を実行するよう指示するには、プロセスブロックを含める必要があります。プロセスブロックの中には、関数がパイプライン入力を受け取るたびに実行したいコードを記述します。以下のようにスクリプトにプロセスブロックを追加してください。

function Install-Software {
	param(
		[Parameter(Mandatory)]
		[ValidateSet('1','2')]
		[string]$Version,

		[Parameter(Mandatory, ValueFromPipeline)]
		[string]$ComputerName
	)

	process {
		Write-Host "I installed software version $Version on $ComputerName. Yippee!"
	}
}

前と同様に関数を呼び出してください。今回は、Install-Software関数は3つの行(各オブジェクトごとに1つ)を返します。

I installed software version 2 on SRV1. Yippee!
I installed software version 2 on SRV2. Yippee!
I installed software version 2 on SRV3. Yippee!

プロセスブロックには、実行したいメインのコードを記述します。また、関数呼び出しの最初と最後に実行されるコードには、beginブロックとendブロックを使用することもできます。Microsoftのドキュメントbeginprocessendブロックについて詳しく学ぶことができます。

次のステップ

関数を使用すると、コードを独立したビルディングブロックに分割することができます。これにより、作業をより小さな、管理しやすいチャンクに分割するだけでなく、読みやすくテスト可能なコードを書くことを強制されます。

I now challenge you to look through some old scripts and see where you can add a PowerShell function. Look for patterns in code. Build a function from those patterns. Notice where you’re copying/pasting code snippets and turn those into PowerShell functions!

Source:
https://adamtheautomator.com/powershell-functions/