同じ処理を何度も使うなら、関数にまとめると便利です。PowerShellの関数はfunctionで定義しますが、他の言語に慣れていると戸惑う点がいくつかあります。呼び出すときにかっこを付けないこと、引数はparamで受け取ること、そして最大の落とし穴であるreturnを書かなくても、関数内の出力がすべて戻り値になることです。
特に戻り値の挙動は、知らないと「余計な値が混ざる」という分かりにくいバグの原因になります。この記事では、実機で確認しながら、引数の受け取り方・戻り値・検証・パイプライン対応までを整理します。
- 関数は
function 名前 { 処理 }で定義し、かっこなし(名前 -引数 値)で呼び出します。 - 引数は
param([int]$A, [string]$B = "既定")のようにparamで受け取ります。 - 最重要:
returnを書かなくても、関数内で出力された値はすべて戻り値になります。returnは早く抜けるだけです。 - そのため、デバッグの文字列などを途中で出力すると戻り値に混ざります。画面表示には
Write-Hostを使います。 - 引数は
[Parameter(Mandatory)]で必須、[ValidateSet(...)]などで検証できます。 processブロックを使うと、関数をパイプライン対応にできます。
関数の中では、条件分岐やループ、エラー処理をよく使います。比較演算子と条件分岐、ループ完全ガイド、try-catchでエラー処理もあわせて参考になります。
関数の基本(定義とかっこなしの呼び出し)
関数はfunctionに続けて名前と{ }で定義します。呼び出すときは、コマンドと同じようにかっこを付けません。名前の付け方は「動詞-名詞」(Get-Userなど)が慣例です。
# 定義
function Get-Greeting {
"こんにちは"
}
# 呼び出し(かっこは付けない)
Get-Greeting # こんにちは
# NG: かっこを付けると別の意味になる
# Get-Greeting() # 引数として空配列を渡す扱いになり、意図と違う
実機でも、Get-Greetingはそのままこんにちはを返しました。Get-Greeting()のようにかっこを付けると、コマンド呼び出しではなく「空の引数を渡す」など意図しない動きになるため、付けないでください。
paramで引数を受け取る
引数は、関数の先頭にparam( )を書いて受け取ります。型([int]など)や既定値も指定できます。呼び出しは-引数名 値の形(名前指定)か、順番に渡す位置指定です。
function Add-Numbers {
param(
[int]$A,
[int]$B = 10 # 既定値(指定しなければ 10)
)
$A + $B
}
Add-Numbers -A 3 -B 4 # 7(名前で指定)
Add-Numbers 3 4 # 7(位置で指定)
Add-Numbers -A 5 # 15(B は既定の 10)
# 型を指定すると、合わない値は自動変換 or エラーになる
function Greet {
param([string]$Name = "ゲスト")
"ようこそ、$Name さん"
}
Greet -Name "山田" # ようこそ、山田 さん
実機でも、Add-Numbers -A 5はBが既定値10になり15を返しました。型を付けると、想定外の型が渡されたときに変換またはエラーになり、安全になります。
【最重要】returnしなくても出力が戻り値になる
ここがPowerShellの関数で最も重要なポイントです。多くの言語ではreturnで返した値だけが戻り値ですが、PowerShellでは関数内で出力された値がすべて戻り値になります。returnは「そこで関数を抜ける」だけで、それ以前の出力も戻り値に含まれます。
function Get-Multi {
"a"
"b"
return "c"
}
$result = Get-Multi
$result.Count # 3 ← a, b, c がすべて返る!
$result # a, b, c
# return は「値を返して抜ける」とは限らない。早期に抜けるだけ
function Get-First {
param([int]$N)
if ($N -lt 0) { return "負の数です" } # ここで抜ける
"正の数: $N"
}
この挙動でハマりやすいのが、デバッグ用の文字列を途中で出力してしまうケースです。実機で確認したところ、関数内に"処理中..."のような裸の文字列を書くと、本来返したい値と一緒に戻り値に混入しました(件数が2件になった)。画面に表示したいだけなら、戻り値に入らないWrite-Hostを使ってください。
# NG: 裸の文字列は戻り値に混ざる
function Get-CountNG {
$items = 1, 2, 3
"処理中..." # これも戻り値になってしまう
$items.Count
}
# 戻り値: "処理中...", 3 ← 2件混ざる
# OK: 画面表示は Write-Host(戻り値に入らない)
function Get-CountOK {
$items = 1, 2, 3
Write-Host "処理中..." # 画面に出るだけ。戻り値には入らない
$items.Count
}
# 戻り値: 3 だけ
実機でも、Write-Hostで出力したものは戻り値に含まれず、Get-CountOKの戻り値は3だけになりました。「関数の戻り値にしたいもの」と「画面に見せたいだけのもの」を分けて、後者はWrite-Host(またはWrite-Verbose)を使うのが鉄則です。
引数を必須にする・検証する
引数に条件を付けると、不正な値での実行を防げます。[Parameter(Mandatory)]で必須、[ValidateSet(...)]で選択肢の限定、[ValidateRange(...)]で範囲の限定ができます。
function New-Account {
param(
# 必須(指定しないと入力を求められる)
[Parameter(Mandatory)]
[string]$Name,
# 決まった値のどれかだけ許可する
[ValidateSet("admin", "user", "guest")]
[string]$Role = "user",
# 範囲を限定する
[ValidateRange(0, 120)]
[int]$Age
)
"アカウント: $Name ($Role, $Age 歳)"
}
New-Account -Name "山田" -Role "admin" -Age 30 # OK
# New-Account -Name "山田" -Role "xxx" # エラー(xxx は許可外)
# New-Account -Name "山田" -Age 999 # エラー(範囲外)
実機でも、ValidateSetの許可外の値やValidateRangeの範囲外を渡すと、例外(ParameterBindingValidationException)で止まりました。検証を付けておくと、関数の中でifを書かなくても、入口で不正な値をはじけます。ほかに、空を禁止する[ValidateNotNullOrEmpty()]や、正規表現で形式を縛る[ValidatePattern(...)]もあります。
関数をパイプライン対応にする
関数をパイプライン(|)で使えるようにするには、引数にValueFromPipelineを付け、本体をprocessブロックで囲みます。processは、流れてくる要素ごとに実行されます。
function Double-It {
param(
[Parameter(ValueFromPipeline)]
[int]$Num
)
process {
$Num * 2
}
}
# パイプラインで使える
1, 2, 3 | Double-It # 2, 4, 6
# 引数で渡しても使える
Double-It -Num 5 # 10
実機でも、1, 2, 3 | Double-Itは2, 4, 6を返しました。processブロックを使わないと、パイプラインの最後の1件しか処理されないため、流すデータを1件ずつ扱うときは必ずprocessに書きます。前処理・後処理が必要ならbegin・endブロックも使えます。
変数のスコープ(関数内はローカル)
関数の中で作った変数は、その関数の中だけで有効なローカル変数です。関数の外の同名の変数には影響しません。
$message = "外側の値"
function Test-Scope {
$message = "内側の値" # これは関数内だけの別物
Write-Host $message # 内側の値
}
Test-Scope
$message # 外側の値(関数内の代入は影響しない)
# 外の変数を関数内から変えたいときは $script: を付ける(多用は避ける)
$count = 0
function Add-One { $script:count++ }
Add-One
$count # 1
実機でも、関数内で$messageに代入しても、関数の外の$messageは外側の値のままでした。関数は外の変数を読むことはできますが、書き換えたいときは$script:や$global:を付けます。ただし、スコープをまたいだ書き換えは分かりにくくなるため、できるだけ戻り値で値を返す設計にするのがおすすめです。
[CmdletBinding()]で高度な関数にする
関数に[CmdletBinding()]を付けると、-Verboseや-ErrorActionといった共通パラメータが使えるようになり、標準コマンドに近い「高度な関数」になります。
function Get-Report {
[CmdletBinding()]
param(
[Parameter(Mandatory)]
[string]$Path
)
Write-Verbose "処理を開始します: $Path" # -Verbose 指定時だけ表示
"レポート完了: $Path"
}
Get-Report -Path "C:\data" # 通常実行
Get-Report -Path "C:\data" -Verbose # 詳細メッセージも表示される
Write-Verboseは、-Verboseを付けたときだけ表示される詳細メッセージです。戻り値には入らないため、進捗の表示に向いています。本格的な関数を作るなら[CmdletBinding()]を付けておくと、後から機能を足しやすくなります。
よくある失敗
returnしていない値が勝手に返る
関数内で出力した値はすべて戻り値になります。デバッグ用の裸の文字列などが混ざらないよう、画面表示はWrite-Host、戻り値にしたい値だけを出力します。
関数をかっこ付きで呼ぶ
PowerShellの関数はGet-Greeting()ではなくGet-Greetingと呼びます。引数は名前 -引数 値の形です。
パイプライン対応にprocessを書き忘れる
ValueFromPipelineを付けてもprocessブロックがないと、最後の1件しか処理されません。流すデータはprocessで1件ずつ扱います。
関数内の変数の変更が外に反映されないと悩む
関数内の変数はローカルです。外の変数は読めますが書き換えはできません。値を返したいなら戻り値を使います。
引数のチェックをすべてif文で書く
必須・選択肢・範囲のチェックは、MandatoryやValidateSetなどの属性で入口に書けます。関数の中身が読みやすくなります。
よくある質問
returnはその場で関数を抜けるための命令で、書かなくても最後に出力された値(や途中の出力)が返ります。意図しない値が混ざらないよう、戻り値にしたいものだけを出力してください。Write-Hostを使います。Write-Hostの出力は戻り値に含まれません。進捗の詳細ならWrite-Verbose(-Verbose指定時だけ表示)も便利です。[Parameter(Mandatory)]を引数に付けます。指定せずに呼び出すと、PowerShellが入力を求めてきます。さらに[ValidateSet(...)]や[ValidateRange(...)]で、許可する値も縛れます。[Parameter(ValueFromPipeline)]を付け、本体をprocess { }ブロックで囲みます。これで1,2,3 | 関数名のように、流れてくる要素を1件ずつ処理できます。$script:や$global:を付けますが、分かりにくくなるため、基本は戻り値で値を返す設計にしてください。まとめ
- 関数は
function 名前 { }で定義し、かっこなしで呼び出します。 - 引数は
param([型]$名前 = 既定値)で受け取り、名前指定・位置指定の両方で渡せます。 returnを書かなくても、出力された値はすべて戻り値になります。returnは早く抜けるだけです。- 画面表示は
Write-Hostを使い、戻り値を汚さないようにします。 - 引数は
Mandatory・ValidateSetなどで検証でき、processでパイプライン対応にできます。 - 関数内の変数はローカルです。値は戻り値で返すのが基本です。
PowerShellの関数で最初に押さえるべきは、「出力したものが全部戻り値になる」という1点です。ここさえ理解すれば、戻り値の混入で悩むことがなくなり、paramと検証属性を組み合わせて、安全で使い回しやすい関数を書けるようになります。
