[PowerShell]Begin, Process, Endブロックでより高度な関数にしよう
PowerShellで関数(Function)を自作する際に「引数で配列を渡し、関数内でforeach
する」というシチュエーションが頻繁にあります。
function Invoke-GreetingWithForeach {
param(
# 引数として、文字列配列を受け取る
[Parameter(ValueFromPipeline=$true)]
[string[]]$NameList
)
# 1. さいしょに1度だけ実行
Write-Host "<--- 挨拶処理の開始 --->"
# 2. 繰り返し処理
foreach ($personName in $NameList) {
Write-Host ("こんにちは、" + $personName + "さん!")
}
# 3. さいごに1度だけ実行
Write-Host "<--- 挨拶処理が終了 --->"
}
この関数に対し、Begin
やProcess
、End
ブロックを使って高度な関数にすることで、
- 処理性能の向上(大量データで処理する時)
- PowerShellのコマンドレットとの連携(パイプライン連携など)
- 呼び出し元でエラー制御が可能
というような恩恵があるらしい……
今回は、高度な関数にするための「Begin
/ Process
/ End
ブロック」について検証・調べたことを共有します!
この記事のターゲット
- PowerShellでエコシステム(再利用)を意識した自作関数を作成したい方
- 大容量データの処理を自作関数で行う方
- PowerShellの真骨頂?を味わいたい方
foreachではなくBeginやProcess、Endブロックする場合のコード
まず、パイプライン実行の検証用とするため、引数を文字列配列($NameList
)から文字列変数($Name
)に変更。
1の最初に1度だけ実行する箇所を Begin
ブロック で囲み、2の繰り返し実行する処理を Process
ブロック で囲む。さいごに 3で最後に1度だけ実行する箇所を End
ブロック で囲む という変更になります。
BeginやProcess、Endブロックのコード例
function Invoke-GreetingEx {
[CmdletBinding()]
param(
# パイプラインからの入力を受け付けるための属性
[Parameter(ValueFromPipeline=$true)]
[string]$Name
)
Begin {
# 1. さいしょに1度だけ実行
Write-Host "<--- 挨拶処理の開始 --->"
}
Process {
# 2. 繰り返し処理
Write-Host ("こんにちは、" + $Name + "さん!")
}
End {
# 3. さいごに1度だけ実行
Write-Host "<--- 挨拶処理が終了 --->"
}
}
これで各ブロックごとに分離した関数になりました!つぎはこの関数の実行方法です。
使い方と実行結果
Begin
/Process
/End
ブロックで定義した関数は、基本的にパイプラインを使って実行します。
-
実行例
例)パイプラインで高度な関数を実行"Taro", "Ichiro", "Jiro" | Invoke-GreetingEx
-
実行結果
実行結果<--- 挨拶処理の開始 ---> こんにちは、Taroさん! こんにちは、Ichiroさん! こんにちは、Jiroさん! <--- 挨拶処理が終了 --->
Begin
とEnd
がそれぞれ1回だけ、Process
は名前の数分(3回)が実行されています。
結果だけみるとforeachの処理との違いがわかりにくいと思うので、内部的な流れの例を交えて関数を高度化するメリットについて触れていきます。
BeginやProcess、Endブロックで関数を高度化するメリット
冒頭にも紹介していた下記3つのメリットについてまとめます。
- 処理性能の向上(大量データで処理する時)
- PowerShellコマンドレットとの連携(パイプライン連携)
- 呼び出し元でエラー制御(引数による柔軟な制御)
メリット1. 処理性能の向上
下記が冒頭にも紹介していたforeach
を使ったコード。
function Invoke-GreetingWithForeach {
[CmdletBinding()] # 関数の高度な機能(共通パラメータなど)を使用
param(
# 引数として、文字列配列を受け取る
[Parameter(ValueFromPipeline=$true)]
[string[]]$NameList
)
# 1. さいしょに1度だけ実行
Write-Host "<--- 挨拶処理の開始 --->"
# 2. 繰り返し処理
foreach ($personName in $NameList) {
Write-Host ("こんにちは、" + $personName + "さん!")
}
# 3. さいごに1度だけ実行
Write-Host "<--- 挨拶処理が終了 --->"
}
繰り返しになりますが、下記がBegin
, Process
, End
ブロックを使ったコード。
function Invoke-GreetingEx {
[CmdletBinding()]
param(
# パイプラインからの入力を受け付けるための属性
[Parameter(ValueFromPipeline=$true)]
[string]$Name
)
Begin {
# 1. さいしょに1度だけ実行
Write-Host "<--- 挨拶処理の開始 --->"
}
Process {
# 2. 繰り返し処理
Write-Host ("こんにちは、" + $Name + "さん!")
}
End {
# 3. さいごに1度だけ実行
Write-Host "<--- 挨拶処理が終了 --->"
}
}
これら2つのコードを比較すると「繰り返し処理がProcess
ブロックなのか、foreach
なのかの違いで本質的に同じではないか?」という疑問が生まれます。
結果だけを見れば間違いはないですが、結果を実行するまでの過程、PowerShellの内部的な動作に大きな違いがあり大容量データを取り扱うとき、パフォーマンスに反映されるとのこと。
つまりは2つの関数を比較すると、
という関係性となります。
つぎは、Processブロックを実装することで実現できる「本質的なストリーム処理」について触れていきます。
本質的なストリーム処理とは
はい、承知いたしました。
これまでの議論を踏まえ、ご提示いただいた元のMarkdownの文章を、より正確で誤解のない表現に修正し、全体を一つにまとめた完成版として出力します。
Process
ブロックを使った関数の場合(本質的なストリーム処理)
特徴: データが1つ来るたびに、Process
ブロックがその都度実行されます。メモリには常に一度に1つのデータしかロードされないため、巨大なデータセットでも効率良く処理できます。
下記を実行した際の内部的な動作を見ていきます。
"データA", "データB", "データC" | Invoke-GreetingEx
内部的な動きをシーケンス図に表すと下記のとおり。
シーケンス図を箇条書きにすると……
-
"データA"
がパイプラインに投入される。 -
Process
ブロックが起動し、"データA"
を処理する。 -
"データB"
がパイプラインに投入される。 -
Process
ブロックが再び起動し、"データB"
を処理する。 -
"データC"
がパイプラインに投入される。 -
Process
ブロックが再び起動し、"データC"
を処理する。
上記のとおりProcess
ブロックを使う事で、データを1つずつメモリ効率良く、逐次的に処理することが可能です。
foreach
を使った関数の場合
特徴: すべてのデータを一度にメモリ上に展開してから処理を開始。そのため、扱うデータセットが非常に大きい場合(ファイルサイズが数GBのデータなど)は、メモリを大量に消費する可能性があります。
下記を実行した際の内部的な動作を見ていきます。
# 処理対象のデータをすべて配列としてメモリ上に用意する
$nameListData = @(
"データA",
"データB",
"データC"
)
# 関数に配列変数を直接渡す
Invoke-GreetingWithForeach $nameListData
この内部的な動きもシーケンス図に表すと下記のとおり。
このシーケンス図も箇条書きにすると……
-
【準備】変数の定義
PowerShellは、$nameListData
という変数に@("データA", "データB", "データC")
という3つの要素を持つ配列をメモリ上に格納。 -
【呼び出し】関数に配列データを一括で渡す
Invoke-GreetingWithForeach $nameListData
の行が実行。
PowerShellは、$nameListData
変数が保持している配列全体を、関数のパラメーター($InputData
)に一括で渡す。 -
【実行】関数内の処理
関数Invoke-GreetingWithForeach
が起動する。
関数は、受け取った配列に対してforeach
ループを開始し、各要素を順番に処理する。
このように、foreach
パターンでは、処理を開始する前にすべてのデータをメモリ上に用意する必要があるという点が、ストリーム処理との大きな違いです。
補足情報:foreachの関数をパイプラインで実行すると期待する動きになりません。
ちなみにforeachを使った関数を実行する際にパイプラインを配列で渡して実行すると期待する動きになりません。
(PowerShellの不具合ということではなく、パイプラインの使い方を間違えているため、このような挙動に。)
$nameListData = @(
"データA",
"データB",
"データC"
)
$nameListData | Invoke-GreetingWithForeach
これで実行すると、下記のとおり データC
のみが出力するのみとなります。
<--- 挨拶処理の開始 --->
こんにちは、データCさん!
<--- 挨拶処理が終了 --->
高度化していない関数にパイプラインでデータを渡すと、PowerShellは関数全体を「End
ブロック」のような扱いになります。つまり、「パイプラインにすべての配列データが流れ終わった後に、最後の要素だけが実行される」という流れになってしまいます。
シーケンス図で説明すると下記のとおり。
下記がシーケンス図を箇条書きにしたもの。
-
【関数実行前】パイプラインからのデータ投入(上書きされて最後の要素だけが有効)
-
"データA"
の処理-
"データA"
がパイプラインに投入され、関数の引数$NameList
の値が"データA"
となる - 関数はまだ実行されない
-
-
"データB"
の処理- つぎに
"データB"
が投入されるが、関数の引数$NameList
の値が"データB"
で上書きされてしまう - 関数は実行されない
- つぎに
-
"データC"
の処理- 最後に
"データC"
が投入されて上記と同様に関数の引数$NameList
の値が"データC"
でさらに上書きされてしまう - 関数がやっと実行される
- 最後に
-
-
【関数実行】引数を元に関数内の処理が実行(最後の要素だけが実行)
-
パイプラインのデータがすべて流れ終わった時点で、関数本体が1度だけ実行。
-
foreach ($personName in $NameList)
が実行されるが、$NameList
には"データC"
のみのためループしない
-
このとおりパイプラインで複数の要素を渡し実行する場合は、Processブロックは必須となります。
つぎは2つ目のメリットであるPowerShellコマンドレットとの連携について触れていきます。
メリット2. PowerShellコマンドレットとの連携
Process
ブロックを使った関数は、PowerShellコマンドレットとパイプラインを使うことで柔軟な連携が可能です。
実行例
実行する前に氏名が入ったテキストファイルを作っておき、Get-Content
で参照したデータをパイプラインで渡して、
高度化している関数Invoke-GreetingEx
に渡すという流れです。
$nameListData = @(
"Saburo",
"Shiro",
"Goro",
"Rokuro",
"Shichiro"
)
# 配列の内容をテキストファイルに書き出す
$filePath = "D:\Downloads\NameList.txt"
Set-Content -Path $filePath -Value $nameListData -Encoding UTF8
# テキストファイルを読み込んだ後、パイプラインで実行
Get-Content -Path $filePath | Invoke-GreetingEx
実行結果
期待通りの動きをしてくれました。
# テキストファイルの準備
PS C:\Users\XXXX> $nameListData = @(
>> "Saburo",
>> "Shiro",
>> "Goro",
>> "Rokuro",
>> "Shichiro"
>> )
>>
>> # 配列の内容をテキストファイルに書き出す
>> $filePath = "D:\Downloads\NameList.txt"
>> Set-Content -Path $filePath -Value $nameListData -Encoding UTF8
PS C:\Users\XXXX>
# テキストファイル確認
PS C:\Users\XXXX> Get-Content -Path $filePath
Saburo
Shiro
Goro
Rokuro
Shichiro
PS C:\Users\XXXX>
# 実行
PS C:\Users\XXXX> Get-Content -Path $filePath | Invoke-GreetingEx
<--- 挨拶処理の開始 --->
こんにちは、Saburoさん!
こんにちは、Shiroさん!
こんにちは、Goroさん!
こんにちは、Rokuroさん!
こんにちは、Shichiroさん!
<--- 挨拶処理が終了 --->
PS C:\Users\XXXX>
このように高度な関数は、パイプライン連携が可能、かつ本質的なストリーム処理が可能という点でさまざまなシチュエーションで活躍してくれるでしょう。
メリット3.呼び出し元でエラー制御
関数の利用者が、その場の状況に応じて-ErrorAction
パラメーターを渡すだけで、「停止(Stop
)」「続行(Continue
)」「無視して続行(SilentlyContinue
)」などの挙動を自由に選択できます。これにより、関数が非常に柔軟で再利用性の高いツールとなります。
ここではErrorAction
による挙動の違いを、よりわかりやすく検証するために使用するコードを変更します。
function Get-FirstLineEx {
[CmdletBinding()] # ErrorActionなどの共通パラメータを使用できるように
param(
[Parameter(Mandatory = $true, ValueFromPipeline = $true)]
[string]$Path
)
process {
# Get-Contentでファイルの先頭1行だけを取得
# → ここでファイルが存在しない場合、「非終了エラー」が発生
$firstLine = (Get-Content -Path $Path -TotalCount 1)
# 結果を整形して出力
# (ファイルが存在しない、または空の場合は、$firstLineは$nullになる)
Write-Output "[$Path] -> $firstLine"
}
}
このようにパイプラインでファイルパスを渡し、対象ファイルの先頭行を表示する関数を使っていきます。
つぎは、検証する入力ファイルを準備。下記がそのコードです。
# --- 実行前の準備 ---
# 1. テスト用のファイルを作成
Set-Location "D:\Downloads"
"1件目となるFILE-Aファイルの先頭、1行目です。" | Out-File ./FILE-A.txt -Encoding utf8
"2件目となるFILE-Bファイルの先頭、1行目です。" | Out-File ./FILE-B.txt -Encoding utf8
# 2. 処理対象のファイルパスを【配列】に格納
# 存在しないファイルもリストに含める
$filePathList = @(
"./FILE-A.txt", # 存在する1件目のファイル
"./non_existent_file.txt", # 存在しないファイル
"./FILE-B.txt" # 存在する2件目のファイル
)
以降の検証では下記のようなイメージで実行します。
# 配列変数をパイプラインで関数に渡す
$filePathList | Get-FirstLineEx -ErrorAction (Stop / Continue / SilentlyContinue)
呼び出し側でエラー時に中断(Stop)するよう指定して実行
呼び出し側でエラーが発生した時に処理を中断するよう、-ErrorAction Stop
を指定し実行してみます。
$filePathList | Get-FirstLineEx -ErrorAction Stop
PS D:\Downloads> $filePathList | Get-FirstLineEx -ErrorAction Stop
[./FILE-A.txt] -> 1件目となるFILE-Aファイルの先頭、1行目です。
Get-Content : パス 'D:\Downloads\non_existent_file.txt' が存在しないため検出できません。
発生場所 行:11 文字:22
+ $firstLine = Get-Content -Path $Path -TotalCount 1
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (D:\Downloads\non_existent_file.txt:String) [Get
-Content], ItemNotFoundException
+ FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.GetContentCommand
PS D:\Downloads>
結果、呼び出し側の指定方法によりエラー発生時に停止することができました。
呼び出し側でエラー時に続行(Continue)するよう指定して実行
つづいて呼び出し側でエラーが発生しても続行するようにしてみます。
$filePathList | Get-FirstLineEx -ErrorAction Continue
下記が実行結果。
PS D:\Downloads> $filePathList | Get-FirstLineEx -ErrorAction Continue
[./FILE-A.txt] -> 1件目となるFILE-Aファイルの先頭、1行目です。
Get-Content : パス 'D:\Downloads\non_existent_file.txt' が存在しないため検出できません。
発生場所 行:11 文字:22
+ $firstLine = Get-Content -Path $Path -TotalCount 1
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (D:\Downloads\non_existent_file.txt:String) [Get
-Content], ItemNotFoundException
+ FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.GetContentCommand
[./non_existent_file.txt] ->
[./FILE-B.txt] -> 2件目となるFILE-Bファイルの先頭、1行目です。
PS D:\Downloads>
上記のとおり、エラーメッセージを表示しつつ最後まで処理が完走しました。
ちなみに-ErrorAction
を引数で指定していなくても、Continue
で処理されます。これは Continue
が規定値であるため、引数で指定していなくても続行するからです。
補足情報:ErrorActionの規定値(デフォルトの値)を厳密に解説すると……
より厳密に解説します。
じつは-ErrorAction
を指定していない場合は、自動変数「$ErrorActionPreference
」を参照しています。
その自動変数のデフォルトの値が Continue
であるため、-ErrorAction
が指定されていない場合は Continue
と同じ挙動となり続行されるというのが厳密な解説でした。
ErrorAction パラメーターは、現在のコマンドの$ErrorActionPreference変数の値をオーバーライドします。
$ErrorActionPreference変数の既定値は Continue であるため、ErrorAction パラメーターを使用しない限り、エラー メッセージが表示され、実行が続行されます。
呼び出し側でエラー時に無視して続行(SilentlyContinue)するよう指定して実行
つづいてエラーを無視して続行したい場合は……
$filePathList | Get-FirstLineEx -ErrorAction SilentlyContinue
結果、エラーメッセージすら表示されず正常に処理できるものだけが表示。
PS D:\Downloads> $filePathList | Get-FirstLineEx -ErrorAction SilentlyContinue
[./FILE-A.txt] -> 1件目となるFILE-Aファイルの先頭、1行目です。
[./non_existent_file.txt] ->
[./FILE-B.txt] -> 2件目となるFILE-Bファイルの先頭、1行目です。
PS D:\Downloads>
補足情報:ErrorActionパラメーターとは?
-ErrorAction
は、[CmdletBinding()]
を追加した高度な関数や標準コマンドレットで使える共通パラメーター。
コマンド実行中に「非終了エラー(Non-Terminating Error)」が発生した際にPowerShellの挙動を指定できます。
-
重要な前提:エラーの2種類
PowerShellには大きく2種類のエラーがあります。-
終了エラー (Terminating Error)
例: 存在しないコマンドの実行、構文エラー。- スクリプトの続行が不可能な深刻なエラーを意味する。
- デフォルトでスクリプトを即座に停止させる。
-
非終了エラー (Non-Terminating Error)
例:Get-Content
でファイルが見つからない、Remove-Item
で削除対象が存在しない。- 処理の続行が可能な、比較的軽微なエラー。
- デフォルトではエラーメッセージを表示するだけで、スクリプトは続行する。
-
繰り返しになりますが、この2種類のうち-ErrorAction
で制御できるのが「非終了エラー (Non-Terminating Error)」です。
-ErrorAction
オプション一覧表
パラメーター値 | 動作 (Action) | 主な使用用途・シナリオ |
---|---|---|
Stop |
🔴 停止 ・非終了エラーを終了エラーに格上げし、スクリプトの実行を即座に停止させる。 ・ try-catch ブロックでエラーを捕捉できるようになる。・エラー記録は、自動変数 $Error に追加。 |
・そのエラーが処理全体にとって致命的な場合。 ・ try-catch を使って、特定のエラー発生時に回復処理や代替処理を実装したい場合。 |
Continue |
🟡 続行(報告あり) ・デフォルトの動作。 ・エラーメッセージをコンソールに出力する。 ・スクリプトの実行は続行される。 ・エラー記録は、自動変数 $Error に追加。 |
・エラーの発生は把握したいが、処理全体は止めずに最後まで実行したい場合。 ・対話的な操作や、エラーをログに記録したい場合。 |
SilentlyContinue |
🟢 続行(報告なし) ・エラーメッセージを抑制し、画面には何も表示させない。 ・スクリプトの実行は続行される。 ・エラー記録は、自動変数 $Error に追加。 |
・エラーが発生することが想定済みで、それが問題ではない場合。 例:「ファイルがあれば削除する(なければ何もしないでOK)」というような処理。 |
Inquire |
❓ 問い合わせ ・エラー発生時に処理を中断し、ユーザーにどうするかを尋ねるプロンプトを表示する。 ・ [Y] はい(Y) [A] すべて続行(A) [H] コマンドの中止(H) [S] 中断(S) [?] ヘルプ (既定値は "Y"): などの選択肢が表示。・ユーザーの選択にかかわらず、エラー記録は自動変数 $Error に追加。 |
・対話的なスクリプトで、実行者がその場で判断を下せるようにしたい場合。 ・不向きなのは自動化されたバッチ処理など、無人実行されるスクリプト。 |
Ignore |
⚫ 完全に無視 ・エラーメッセージを抑制し、スクリプトの実行を続行する。 ・ SilentlyContinue との違いは、$Error 変数にすらエラー記録を追加しない点。 |
・非常に稀なケース。エラーが発生したという事実を、ログを含めどこにも残したくない場合。 |
※ Suspend
は、PowerShell 6 以降でサポートされていないワークフロー機能に関連したオプションのため、一覧表から除外。
これで、ひととおり関数を高度化するメリットについて解説しました。
つぎは、もうすこし実践的な例を題材に検証していきましょう。
もう少し実践的な例で検証
ここまではシンプルな例で検証してきましたが、Begin
/Process
/End
ブロックは、より複雑なタスクで真価を発揮するでしょう。
すこし実践的な例として、「複数のサーバーに接続し、それぞれのサーバーで特定のサービスの状態を確認して、結果をCSVファイルに出力する」というシナリオを想定します。
実践的なコード
# PowerShellのパイプライン処理の思想に沿った、より実践的な関数
function Get-ServiceStatusOnServers {
[CmdletBinding()] # これにより -ErrorAction, -Verbose などの共通パラメータが使用可能となる
param(
[Parameter(ValueFromPipeline=$true)]
[string]$ComputerName,
[string]$ServiceName = "BITS",
[string]$OutputPath
)
Begin {
# [1.Begin] 最初に一度だけ実行
$results = [System.Collections.Generic.List[object]]::new()
Write-Verbose "[Begin] 処理を開始します。"
# 実践的な例として、CSV出力先の親ディレクトリが存在するかチェック
if ($OutputPath) {
$parentDir = Split-Path -Path $OutputPath -Parent
if (-not (Test-Path -Path $parentDir)) {
# ★ 呼び出し元のErrorActionにより動作が変化する箇所
# CSV出力先の親ディレクトリが存在しなかった場合にWrite-Errorで「終了しないエラー」を発生させる。
Write-Error "出力先の親ディレクトリが存在しません: $parentDir"
}
}
}
Process {
# [2.Process] パイプラインから渡されたサーバー1台ずつ処理を繰り返す
Write-Verbose "[Process] サーバー '$ComputerName' を処理中..."
# 呼び出し元の-ErrorActionに関係なく、サービス取得の失敗は必ずcatchに飛んで結果を記録するロジック
try {
$service = Get-Service -Name $ServiceName -ComputerName $ComputerName -ErrorAction Stop
$statusObject = [PSCustomObject]@{
Computer = $ComputerName
Service = $service.Name
Status = $service.Status
Message = "成功"
}
}
catch {
# エラーメッセージから改行を削除して整形
$errorMessage = $_.Exception.Message -replace "(`r`n|`n|`r)"," "
$statusObject = [PSCustomObject]@{
Computer = $ComputerName
Service = $ServiceName
Status = "Unknown"
Message = "エラー: $errorMessage"
}
}
$results.Add($statusObject)
}
End {
# [3.End] すべてのProcessブロックの処理が終了後に一度だけ実行
Write-Verbose "[End] 最終処理を開始します。"
if ($results.Count -gt 0) {
if ($OutputPath) {
try {
$results | Export-Csv -Path $OutputPath -NoTypeInformation -Encoding UTF8 -ErrorAction Stop
Write-Host "✅ 結果をCSVファイルに出力しました: $OutputPath" -ForegroundColor Green
}
catch {
# CSV出力に失敗した場合の代替え処理(フォールバック)
Write-Warning "CSVファイルへの出力に失敗しました。エラー: $($_.Exception.Message)"
Write-Host "`n"
Write-Host "--- CSV出力が失敗したため、結果をコンソールに表示します ---"
$results | Format-Table
}
} else {
# 出力パスが指定されていなければ、結果をコンソールに表示
$results | Format-Table
}
}
# ★ 呼び出し元のErrorActionにより動作が変化する箇所
# 実践的な例として、外部システムに処理完了を通知する関数が失敗したと仮定してエラーを発生させる
Write-Error "完了通知をログサーバーに送信できませんでした。(テスト用のエラー)"
Write-Verbose "[End] すべての処理が完了しました。"
}
}
実践的なコードの説明
この関数は、前述したとおり「複数のサーバーに接続し、それぞれのサーバーで特定のサービスの状態を確認して、結果をCSVファイルに出力する」という内容でそれぞれの役割をブロックでわけて定義。
-
Begin
ブロック
最初の1度だけ、全サーバーの結果をまとめるための「空のリスト」を準備します。
なお、呼び出し側のErrorActionを検証するため、出力先の親ディレクトリーを対象に存在チェックを行っています。 -
Process
ブロック
サーバー1台ずつに接続を試み、その結果を整形して$results
リストに溜めていきます。 -
End
ブロック
最後に一度だけ、まとまった結果をCSVファイルに出力(Export-Csv
を使って書き出し)。
なお、呼び出し側のErrorActionを検証するため、ログサーバーに接続できなかったものとして意図的にエラー(非終了エラー)を発生させています。
以上のようにBegin
やProcess
、End
のそれぞれがしっかりと役割を果たしています。
$serverList = "localhost", "127.0.0.1", "8.8.8.8", "Dev-WinOS", "169.254.130.39"
$serverList | Get-ServiceStatusOnServers -ServiceName "TermService" -OutputPath "D:\Downloads\ServiceStatus_TermService.csv" -Verbose
PS D:\Downloads> $serverList = "localhost", "127.0.0.1", "8.8.8.8", "Dev-WinOS", "169.254.130.39"
PS D:\Downloads>
PS D:\Downloads> $serverList | Get-ServiceStatusOnServers -ServiceName "TermService" -OutputPath "D:\Downloads\ServiceStatus_TermService.csv" -Verbose
詳細: [Begin] 処理を開始します。
詳細: [Process] サーバー 'localhost' を処理中...
詳細: [Process] サーバー '127.0.0.1' を処理中...
詳細: [Process] サーバー '8.8.8.8' を処理中...
詳細: [Process] サーバー 'Dev-WinOS' を処理中...
詳細: [Process] サーバー '169.254.130.39' を処理中...
詳細: [End] 最終処理を開始します。
✅ 結果をCSVファイルに出力しました: D:\Downloads\ServiceStatus_TermService.csv
# ★意図的にエラーを発生させているので、実際には下記のエラーが表示されるが無視。
# Get-ServiceStatusOnServers : 完了通知をログサーバーに送信できませんでした。(テスト用のエラー)
# 発生場所 行:1 文字:15
# + ... erverList | Get-ServiceStatusOnServers -ServiceName "TermService" -Ou ...
# + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# + CategoryInfo : NotSpecified: (:) [Write-Error], WriteErrorException
# + FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException,Get-ServiceSta
# tusOnServers
詳細: [End] すべての処理が完了しました。
下記が出力されたCSVファイル(ServiceStatus_TermService.csv
)の内容。
Server | Service | Status | Message |
---|---|---|---|
localhost | TermService | Running | 成功 |
127.0.0.1 | TermService | Running | 成功 |
8.8.8.8 | TermService | Unknown | エラー: サービス名 'TermService' のサービスが見つかりません。 |
Dev-WinOS | TermService | Running | 成功 |
169.254.130.39 | TermService | Running | 成功 |
このような構成にすることで「準備 → 各個処理と結果収集 → 最終的な出力」という流れが明確に分離できていることがわかります。
呼び出し元でErrorAtionを指定して実行してみる
-
エラーで即停止(ErrorAction:
Stop
)
実行する際に-ErrorAction
で「Stop
」を指定して実行。コピー用$serverList = "localhost", "127.0.0.1", "8.8.8.8", "Dev-WinOS", "169.254.130.39" # ErrorAction「Stop」を指定 $serverList | Get-ServiceStatusOnServers -ServiceName "TermService" -OutputPath "X:\NO_EXISTS_FOLDER\ServiceStatus_TermService.csv" -Verbose -ErrorAction Stop
実行結果PS D:\Downloads> $serverList | Get-ServiceStatusOnServers -ServiceName "TermService" -OutputPath "X:\NO_EXISTS_FOLDER\ServiceStatus_TermService.csv" -Verbose -ErrorAction Stop 詳細: [Begin] 処理を開始します。 Get-ServiceStatusOnServers : 出力先の親ディレクトリが存在しません: X:\NO_EXITS_FOLDER 発生場所 行:1 文字:15 + ... erverList | Get-ServiceStatusOnServers -ServiceName "TermService" -Ou ... + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + CategoryInfo : NotSpecified: (:) [Write-Error], WriteErrorException + FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException,Get-ServiceSta tusOnServers PS D:\Downloads>
想定通り
Begin
ブロック内で発生したエラーで処理が中断されました。
なお、途中で処理がとまったのでCSVファイル(ServiceStatus_TermService.csv
)は出力されていません。 -
初期値の動作(ErrorAction:
Continue
)
$ErrorActionPreference
の規定値がContinue
のため、-ErrorAction
で何も指定せずに実行。コピー用$serverList = "localhost", "127.0.0.1", "8.8.8.8", "Dev-WinOS", "169.254.130.39" # ErrorActionの指定なしで規定値の「Continue」で動く $serverList | Get-ServiceStatusOnServers -ServiceName "TermService" -OutputPath "X:\NO_EXISTS_FOLDER\ServiceStatus_TermService.csv" -Verbose
実行結果PS D:\Downloads> $ErrorActionPreference Continue PS D:\Downloads> PS D:\Downloads> $serverList | Get-ServiceStatusOnServers -ServiceName "TermService" -OutputPath "X:\NO_EXISTS_FOLDER\ServiceStatus_TermService.csv" -Verbose 詳細: [Begin] 処理を開始します。 Get-ServiceStatusOnServers : 出力先の親ディレクトリが存在しません: X:\NO_EXITS_FOLDER 発生場所 行:1 文字:15 + ... erverList | Get-ServiceStatusOnServers -ServiceName "TermService" -Ou ... + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + CategoryInfo : NotSpecified: (:) [Write-Error], WriteErrorException + FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException,Get-ServiceSta tusOnServers 詳細: [Process] サーバー 'localhost' を処理中... 詳細: [Process] サーバー '127.0.0.1' を処理中... 詳細: [Process] サーバー '8.8.8.8' を処理中... 詳細: [Process] サーバー 'Dev-WinOS' を処理中... 詳細: [Process] サーバー '169.254.130.39' を処理中... 詳細: [End] 最終処理を開始します。 警告: CSVファイルへの出力に失敗しました。エラー: ドライブが見つかりません。名前 'X' のドライブが存在しません。 --- CSV出力が失敗したため、結果をコンソールに表示します --- Computer Service Status Message -------- ------- ------ ------- localhost TermService Running 成功 127.0.0.1 TermService Running 成功 8.8.8.8 TermService Unknown エラー: サービス名 'TermService' のサービスが見つかりませ... Dev-WinOS TermService Running 成功 169.254.130.39 TermService Running 成功 Get-ServiceStatusOnServers : 完了通知をログサーバーに送信できませんでした。(テスト用のエラー) 発生場所 行:1 文字:15 + ... erverList | Get-ServiceStatusOnServers -ServiceName "TermService" -Ou ... + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + CategoryInfo : NotSpecified: (:) [Write-Error], WriteErrorException + FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException,Get-ServiceSta tusOnServers 詳細: [End] すべての処理が完了しました。 PS D:\Downloads>
途中で処理は止まっていないですが、親フォルダーが存在しなかったため、結果はコンソールに出力されました。
CSVファイル(ServiceStatus_TermService.csv
)は出力されていません。コンソール出力結果Computer Service Status Message -------- ------- ------ ------- localhost TermService Running 成功 127.0.0.1 TermService Running 成功 8.8.8.8 TermService Unknown エラー: サービス名 'TermService' のサービスが見つかりませ... Dev-WinOS TermService Running 成功 169.254.130.39 TermService Running 成功
補足情報:Get-Serviceで確認する主要なサービス一覧
表示名 (DisplayName) | サービス名 (Name) | チェックする理由・役割 |
---|---|---|
Background Intelligent Transfer Service | BITS |
【ネットワーク/安定性】 Windows Updateなどで使用される、ネットワークのアイドル帯域を利用してファイルを賢く転送するサービス。これが停止していると、Windows Updateなどが失敗する原因になる。 ※サンプルコードの規定値として使用。 |
Windows Update | wuauserv |
【セキュリティ/安定性】 Windowsのセキュリティ更新プログラムや機能更新を管理する最重要サービス。これが停止していると、PCが脆弱な状態のままとなる。BITSと密接に関連している。 |
DNS Client | Dnscache |
【ネットワーク】 Webサイトのドメイン名をIPアドレスに変換した結果を一時保存(キャッシュ)する。「インターネットに繋がらない」「特定のサイトが開けない」といった問題の際に確認。 |
DHCP Client | Dhcp |
【ネットワーク】 ルーターなどからIPアドレスを自動的に取得・管理。これが停止しているとIPアドレスが取得できないため、ネットワークに接続できなくなる。 |
Print Spooler | Spooler |
【基本機能】 印刷ジョブを管理。「印刷ができない」「印刷キューからジョブが消えない」といった印刷関連のトラブルの際に、まず確認・再起動する対象。 |
Windows Time | W32Time |
【安定性/セキュリティ】 コンピューターの時刻をインターネット上の時刻サーバーと同期させる。時刻のズレは、Webサイトの証明書エラーや、企業環境での認証エラーの原因となる。 |
Remote Procedure Call (RPC) | RpcSs |
【システム基盤】 多くのWindowsサービスやアプリケーションが互いに通信するための基盤となり、非常に重要なサービス。これが停止すると、Windows自体が正常に動作しない。依存関係が極めて多いのが特徴。 |
Windows Audio | AudioSrv |
【基本機能】 Windowsの音声機能を管理。「PCから音が出ない」という問題の際に確認。 AudioEndpointBuilder という関連サービスも重要。 |
Windows Defender Firewall | MpsSvc |
【セキュリティ/ネットワーク】 Windows標準のファイアウォール機能。「特定のアプリケーションが通信できない」「ネットワーク共有にアクセスできない」といった問題が、この設定に起因することがある。 |
Remote Desktop Services | TermService |
【リモート接続】 他のコンピューターからリモートデスクトップ接続を受け付けるために必要。「リモートデスクトップで接続できない」という場合に確認。 ※サンプルコードを実行する際の引数として実際に使用。 |
Cryptographic Services | CryptSvc |
【セキュリティ/安定性】 ファイルの署名検証や証明書の管理など、Windowsのセキュリティの根幹を支えるサービスの一つ。Windows Updateが正常に動作するためにも必要。 |
これで具体的な活用方法のイメージができたのではないでしょうか。ツール化やシステム化する対象が大規模であればあるほど、関数の高度化の恩恵を受けるでしょう。
まとめ
この記事では、PowerShellの関数をより強力で再利用性の高いツールにするため、Begin
, Process
, End
ブロックを使った高度な関数について検証・解説しました。
最後に、foreach
を使った関数と比較し重要なポイントをまとめてみます。
-
foreach
関数は「バッチ処理(一括実行)」:- シンプルで直感的だが、最初に全データをメモリに読み込むため、巨大なデータには不向き。
- パイプラインで渡すと、最後のデータしか処理できない。
-
Begin
/Process
/End
関数は「ストリーム処理(逐次実行)」:- データを1つずつ効率的に処理するため、メモリ効率が非常に高い。
-
Get-Content
などの標準コマンドレットとパイプライン(|
)でシームレスな連携が可能。 -
-ErrorAction
パラメーターとの連携により、呼び出し側で柔軟にエラー制御が可能。
「PowerShellらしい」堅牢で効率的なスクリプトを目指すなら、パイプラインを意識し、Begin
/Process
/End
ブロックを使いこなすことが重要そうです。
実行するときにパイプラインを使用するというのが、最初は慣れなそうですが今後は意識的に使っていこうと思います!
参考文献
関連記事
Discussion