更新履歴(5件・最終更新 2026年08月02日)
この記事に加えた変更の記録です。アーカイブした更新前のバージョンは、DOI付きの固定URLから読めます。
- 記事の冒頭に「この記事の知識マップ」節を追加しました。本文で扱っている概念とその関係を、要約・図・詳細ページへのリンクにまとめたものです。本文の主張は変えていません。
- 外部レビュー(1283件)への対応として本文を更新しました。個々の変更内容は、この下の履歴を参照してください。
- 前提とするバージョンと、Windows PowerShell 5.1で注意が要る箇所を冒頭の表に整理しました。あわせて6つのストリームがどこへ流れるかの図と、リダイレクトが効いたかを確かめる手順を追加しています。
- Write-Host が -InformationAction の影響を受けないという説明に、唯一の例外である -InformationAction Ignore の但し書きを補いました。あわせて関連記事へのリンクの文言を現在のタイトルに揃えました。
- 情報ストリームの既定値と「Write-Host は既定で画面に出る」という説明が矛盾して読める点について、Write-Host だけが例外である理由の補足を加えました。
- 初版公開
この記事を引用する(DOI(登録済みアーカイブ): 10.5281/zenodo.21547443)
以下のDOIは過去に登録されたアーカイブを指しており、現在の本文とは一致しない場合があります。現在の本文を参照するときは、このページのURLを使用してください。
小村 豪(2026)「Write-Hostをやめる ── PowerShellの出力ストリームとログ設計」合同会社小村ソフト. https://comcomponent.com/blog/powershell-output-streams-logging/
- DOI(登録済みアーカイブ)
- 10.5281/zenodo.21547443
- DOI(前回登録した版)
- 10.5281/zenodo.21733153
「夜間のバッチ処理が失敗した。でも、何が起きたのか分からない」── PowerShellスクリプトを運用に載せると、この問題にぶつかります。処理の状況を Write-Host で表示していても、画面を見ていない無人実行で、その情報をどこへ保存するかまで決めていなければ、調査に使える記録は残りません。
整理の出発点は、出力を「誰が、何のために使うか」で分けることです。後続の処理へ渡す値、人間に見せるメッセージ、後から調べるためのログは、同じものではありません。
PowerShellには6種類の出力ストリームがあります。この記事では、まずそれぞれの役割と Write-Host の位置づけを押さえ、戻り値を汚さずに表示を制御する方法、必要な出力の捕捉、構造化ログの設計へ進みます。目指すのは、同じスクリプトを対話実行でも無人実行でも使える形にすることです。
1. まず結論
1つめは、処理結果とメッセージを分けることです。 後続のパイプラインへ渡す値は Write-Output、または裸の出力で成功ストリームへ送ります。関数は return で指定した値だけでなく、内部で成功ストリームへ出力したオブジェクトを返すため、不要な出力は $null = ... で捨てます。12
2つめは、表示の詳細度を呼び出し側へ渡すことです。 [CmdletBinding()] を付け、経過は Write-Verbose、記録したい情報は Write-Information に分けます。Write-Host は対話ツールの装飾表示に使うもので、値を返す手段ではありません。PowerShell 5.0以降は情報ストリームへ書くため捕捉できますが、「捕捉できる」と「表示を自由に切り替えられる」は別の話です。345
3つめは、保存する目的に合わせてログの形を選ぶことです。 成功(1)・エラー(2)・警告(3)・詳細(4)・デバッグ(5)・情報(6)は番号でリダイレクトでき、*> でまとめて保存できます。後から解析するなら1行1JSONの構造化ログ、セッションのコマンドや表示の証跡には Start-Transcript を使います。Write-Progress の進捗表示はリダイレクトできないので、記録したい経過とは分けて扱います。678
目的別の読み方
| いま困っていること | 先に読む章 |
|---|---|
Write-Host と Write-Output の使い分けが分からない |
第2章・第3章 |
| 詳細表示を切り替えたい、戻り値に余計な値が混ざる | 第4章・第5章 |
| 無人実行の出力を保存し、後から調べたい | 第6章・第7章 |
| 進捗表示を止めたい、用途に合うコマンドを選びたい | 第8章・第9章 |
この記事の前提バージョンと、5.1での差異
Windows PowerShell 5.1を使う現場でも読めるように、共通する仕組みとバージョン差を先に分けます。
| 記述 | 前提バージョン | Windows PowerShell 5.1では |
|---|---|---|
6つの出力ストリームと番号付きリダイレクト、*> |
5.1でもPowerShell 7でも同じ | そのまま当てはまります6 |
Write-Host が情報ストリーム(6番)に書き込む(6> や -InformationVariable で捕捉できる) |
PowerShell 5.0以降(第3章) | 5.1は5.0以降なので、そのまま当てはまります5 |
Write-Information と -InformationAction / -InformationVariable |
PowerShell 5.0以降(第4章) | そのまま当てはまります4 |
関数が内部の出力をすべて返す、$null = ... での抑止 |
バージョンに依存しません(第5章) | そのまま当てはまります2 |
| 外部コマンドのstdoutをファイルへ直接リダイレクトしたときのバイト列保持 | PowerShell 7.4以降(第6章) | バイト列保持を前提にできません。2>&1 後の型は、バージョンだけでなく変数・パイプライン・ファイルのどこで受けるかも確認してください |
Write-Progress を -ProgressAction 共通パラメーターで制御する |
PowerShell 7.4以降8 | 使えません。$ProgressPreference で制御します(第8章はこちらで書いています) |
| 記事末尾で配布しているサンプルコード | PowerShell 7.6 で実行して検証(Pester 14件) | ─ |
基本となる第2章〜第5章と第7章の設計は、5.1でも使えます。特に確認が必要なのは、外部コマンドのリダイレクト(第6章)と、進捗表示の制御方法(第8章)です。サンプルの検証環境と、実際に運用する環境は分けて確認してください。
図の実線は常に成り立つ関係、破線は条件付きの関係です(成立条件は詳細ページの各関係の説明に記載)。関係すべての一覧(全20件、根拠・確度つき)と主要概念の定義は知識マップ詳細ページにまとめています。データ: JSON-LD / Turtle
2. 6つのストリームと、その相手
まず、データとメッセージの流れを分ける
どの書き込みコマンドがどこへ届くのかを、1枚にします。
flowchart LR
O["Write-Output / 裸の出力"]
OTH["Write-Error / Write-Warning<br/>Write-Verbose / Write-Debug<br/>Write-Information / Write-Host"]
S1["1 成功ストリーム"]
S26["2 エラー / 3 警告 / 4 詳細<br/>5 デバッグ / 6 情報"]
PIPE["後続の処理へ渡る<br/>パイプライン・変数への代入"]
HOST["画面に出る<br/>既定で出るかは環境設定変数しだい"]
FILE["ファイルに残せる<br/>番号付きリダイレクト・捕捉用の変数"]
O --> S1
OTH --> S26
S1 --> PIPE
S1 --> FILE
S26 --> HOST
S26 --> FILE
合流させていない通常のパイプラインで、後続の処理へ渡るのは成功ストリームです。 残りの5つは、人間への表示や、番号付きリダイレクト・捕捉用の変数で扱います。第6章のように成功ストリームへ合流させることもできますが、初めから混ぜて出すこととは分けて考えます。6
まずは、番号と想定読者を対応させてください。3> warnings.log の「3」は、警告ストリームを指定する番号です。
| # | ストリーム | 書き込むコマンド | 想定読者 | 既定の環境設定 |
|---|---|---|---|---|
| 1 | 成功(Success) | Write-Output / 裸の出力 |
後続の処理(パイプライン) | ─ |
| 2 | エラー(Error) | Write-Error / スロー |
人間 + 監視 | Continue |
| 3 | 警告(Warning) | Write-Warning |
人間 | Continue |
| 4 | 詳細(Verbose) | Write-Verbose |
調査中の人間 | SilentlyContinue |
| 5 | デバッグ(Debug) | Write-Debug |
開発者 | SilentlyContinue |
| 6 | 情報(Information) | Write-Information / Write-Host |
人間 + 記録 | SilentlyContinue |
$VerbosePreference・$DebugPreference・$InformationPreference の既定値は SilentlyContinue、$WarningPreference・$ErrorActionPreference は Continue です。表示されるかどうかは、書き込む先だけでなく、この設定にも関係します。9
情報ストリームの中でも、Write-Hostは表示の扱いが違う
情報ストリームの既定が SilentlyContinue なのに Write-Host が画面に出るのは、矛盾ではありません。Write-Host だけは表示の扱いが例外だからです。5
| コマンド | 既定の表示と制御 |
|---|---|
Write-Information |
既定では表示されず、-InformationAction などで制御する |
Write-Host |
既定でも表示され、通常の $InformationPreference・-InformationAction の指定では消えない |
Write-Host を抑止する場合 |
例外の -InformationAction Ignore、または 6> によるリダイレクトを使う |
捕捉と表示を分けて考える理由は、第3章で整理します。
成功ストリームに経過報告を混ぜない
表の中で、設計上いちばん重要なのは1番です。成功ストリームを「人間向けのメッセージ」の置き場にすると、後続処理へ想定外の文字列が流れます。
function Get-KsTargetFile {
Write-Output "対象を検索しています..." # 【NG】戻り値に混ざる
Get-ChildItem -Path $path -Filter '*.csv'
}
# 呼び出し側は FileInfo の配列を期待しているのに、先頭に文字列が入ってくる
$files = Get-KsTargetFile
$files[0].FullName # → 空(先頭は文字列だから)
呼び出し側は FileInfo を期待しているのに、最初に「対象を検索しています…」という文字列を受け取ります。対処は、経過報告を詳細ストリームか情報ストリームへ移すことです。
function Get-KsTargetFile {
[CmdletBinding()]
param([string] $Path)
Write-Verbose "対象を検索しています: $Path" # -Verbose 指定時だけ表示される
Get-ChildItem -Path $Path -Filter '*.csv' # 戻り値は FileInfo だけ
}
この形なら、処理結果は FileInfo のまま、経過だけを -Verbose で表示できます。
3. Write-Hostは「悪」なのか
「捕捉できるか」と「表示を制御できるか」は別
かつての「Write-Hostは使うな」という主張は、画面にしか出せず、後から捕捉も抑止もできなかった時代の事情を含んでいます。PowerShell 5.0以降は情報ストリーム(6番)への書き込みとして実装され、6> でのリダイレクトや -InformationVariable での捕捉が可能になりました。5
ただし、捕捉できるようになっても、既定で画面に出る性質は残っています。$InformationPreference と -InformationAction は通常、Write-Host のメッセージに影響しません。唯一の例外が -InformationAction Ignoreで、これを指定すれば出力を抑止できます。5
つまり、禁止するかどうかではなく、表示そのものが目的か、データや記録として使いたいのかで選びます。
Write-Hostが向いている場面
- 対話的に使うツールで、色付きの見出しや区切りを出したいとき(
-ForegroundColor) - 「このスクリプトはこれから何をするか」をユーザーに伝えるとき
- 処理の値ではなく、装飾された表示そのものが目的のとき
Write-Hostを使ってはいけない場面
- 関数の戻り値として値を渡したいとき(→
Write-Output) - 後で解析する運用ログを残したいとき(→ 構造化ログ、または
Write-Information) - 呼び出し側で表示・非表示を切り替えたいとき(→
Write-Verbose)
無人実行では、その場で表示を読む人がいません。タスクスケジューラから動かすスクリプトを Write-Host による表示だけで済ませず、保存先と捕捉方法まで設計する。これが、この記事のタイトルで伝えたいことです。
4. 呼び出し側に制御を渡す ── [CmdletBinding()]と共通パラメーター
関数は出力先を決め、呼び出し側は詳細度を選ぶ
Write-Verbose の利点は、表示するかどうかを呼び出し側が決められることです。関数に [CmdletBinding()] を付けると、-Verbose・-Debug・-WarningAction・-InformationAction・-ErrorAction などの共通パラメーターが自動で使えるようになります。34
次の例では、処理結果、経過、記録用の情報、注意事項を、それぞれ別の出口へ送ります。
function Invoke-KsImport {
[CmdletBinding()]
param(
[Parameter(Mandatory)] [string] $CsvPath
)
Write-Verbose "取り込み開始: $CsvPath" # 既定では出ない
Write-Information "取り込み: $CsvPath" -Tags 'KsImport' # 既定では出ない(捕捉は可能)
$rows = Import-Csv -Path $CsvPath
if ($rows.Count -eq 0) {
Write-Warning "$CsvPath に取り込み対象がありません" # 既定で出る
return
}
Write-Verbose "$($rows.Count) 件を処理します"
$rows | ForEach-Object { ConvertTo-KsRecord $_ } # 戻り値はこれだけ
}
# 通常実行: 警告のみ表示され、戻り値はレコード
$records = Invoke-KsImport -CsvPath 'D:\in\orders.csv'
# 調査時: 経過も見たい
$records = Invoke-KsImport -CsvPath 'D:\in\orders.csv' -Verbose
# 情報ストリームだけを変数に捕まえて、あとでログファイルへ
$records = Invoke-KsImport -CsvPath 'D:\in\orders.csv' -InformationVariable info
$info | ForEach-Object { $_.MessageData } | Add-Content -Path $logPath
同じ関数でも、呼び出し方で使い分けられます。
| 呼び出し方 | 得られるもの |
|---|---|
| 通常実行 | 戻り値はレコード。警告があれば表示する |
-Verbose を指定 |
戻り値はそのままで、処理の経過も表示する |
-InformationVariable info を指定 |
戻り値とは別に情報を捕捉し、MessageData を取り出して保存できる |
表示のためだけに自前の $LogLevel とif文を増やすより、標準の仕組みに乗せたほうが短く、他人にも意図が伝わります。-Verbose で詳細を出す、という共通の操作に揃えられるからです。
詳細表示は、内側のコマンドにも影響する
$VerbosePreference などの環境設定変数は、スコープと子スコープに効きます。関数を -Verbose 付きで呼ぶと、その中から呼ぶコマンドレットも詳細出力を始めるため、自分が書いたメッセージ以外も増えることがある点に注意してください。9
5. 関数の戻り値が汚れる ── PowerShell特有の落とし穴
returnに書いた値だけが戻るわけではない
第2章では、人間向けの文字列が戻り値に混ざる例を見ました。同じ問題は、値を返すコマンドや.NETメソッドを呼び捨てにしたときにも起きます。PowerShellの関数は、明示的な return の有無にかかわらず、内部で成功ストリームへ出力したオブジェクトを呼び出し元へ返すためです。2
function New-KsWorkFolder {
param([string] $Path)
New-Item -Path $Path -ItemType Directory # 【罠】DirectoryInfo が戻り値に混ざる
$list = [System.Collections.Generic.List[string]]::new()
$list.Add('log') # 【罠2】.Add() は void なので実害なし
$sb = [System.Text.StringBuilder]::new()
$sb.Append('x') # 【罠3】StringBuilder 自身が返る
return $Path
}
$p = New-KsWorkFolder -Path 'D:\work' # $p は3要素の配列になる(DirectoryInfo, StringBuilder, string)
この例で、何が戻り値へ流れるかを分けると、次のようになります。
| 処理 | 戻り値への影響 |
|---|---|
New-Item |
作成したディレクトリの DirectoryInfo が混ざる |
List[string].Add() |
戻り値が void なので、何も混ざらない |
StringBuilder.Append() |
StringBuilder 自身が混ざる |
return $Path |
最後にパスの文字列も返す。先に出たオブジェクトは消えない |
したがって、$p はパス1件ではなく、3要素の配列になります。return を書いただけでは、それ以前の出力をなかったことにはできません。
要らない出力は、その場で捨てる
対処は、不要な出力を捨てることです。書き方は3通りあり、$null = ... を基本にすると軽量です。
$null = New-Item -Path $Path -ItemType Directory # 推奨
New-Item -Path $Path -ItemType Directory | Out-Null # パイプの分だけ遅い
[void] $sb.Append('x') # .NETメソッド向けによく使われる
「実行は成功するが、戻り値の形だけが違う」という不具合は、戻り値の型や件数を固定するテストで見つけられます。詳しくは「PesterによるPowerShellのテスト整備」で扱っています。
6. リダイレクトと捕捉
保存するストリームと、合流させるストリームを選ぶ
ストリームは番号で個別にリダイレクトできます。> は上書き、>> は追記、n>&1 は指定したストリームを成功ストリームへ合流させる指定です。6
.\Invoke-NightlyExport.ps1 3> warnings.log # 警告だけ別ファイルへ
.\Invoke-NightlyExport.ps1 4>&1 | Tee-Object -FilePath run.log # 詳細を成功ストリームに合流
.\Invoke-NightlyExport.ps1 *> all.log # 全ストリームを1ファイルへ
.\Invoke-NightlyExport.ps1 2>&1 | Where-Object { $_ -is [System.Management.Automation.ErrorRecord] }
ここでは、警告の分離、詳細の合流、全ストリームの保存、エラーの型による抽出を並べています。4>&1 は詳細出力の行き先を変える指定であり、詳細表示を有効にする指定ではありません。必要な詳細出力は、呼び出し側で -Verbose などにより有効にしてから捕捉します。46
ファイルへの保存と、PowerShell内での受け取りを分ける
合流させると、成功ストリームに異なる型が混ざります。 PowerShellスクリプトや関数のエラーストリームを合流させた場合、エラーの要素は ErrorRecord のままです。そのため、上の例では型で仕分けられます。
外部プログラム(ネイティブコマンド)については、ファイルへのリダイレクトと、PowerShell内でオブジェクトとして受け取る経路を分けてください。PowerShell 7.4以降でバイト列が保持されるのは、外部コマンドのstdoutをファイルへ直接リダイレクトする場合などです。stderrをstdoutへ合流させて保存する場合は、このバイト列保持の対象外で、テキストとして扱われます。6
一方、2>&1 の結果を変数やPowerShellコマンドレットで受け取ると、stdoutの文字列にstderr由来の ErrorRecord が混ざる経路があります。PowerShell 7.6.0の実装でも、通常のstderrの行を ErrorRecord に包んでいます。「7.4以降なら、合流後は常に文字列だけになり、型による区別はできない」とは一括りにできません。
ただし、外部コマンドのstdout/stderrを確実に区別して記録したいなら、後から型だけに頼って仕分けるより、最初から別々に受け取る設計が分かりやすくなります。実行バージョンと受け取り方を組にして確認してください。外部exeの呼び出し全般は「PowerShellから外部exeを正しく呼ぶ」で扱っています。
短い出力で、保存先を確かめる
リダイレクトが意図どおりかは、成功ストリームと警告ストリームを出す短いコードで確認できます。
# 警告だけを別ファイルへ。画面には 'データ' だけが残る
& { Write-Output 'データ'; Write-Warning '注意事項' } 3> warnings.log
Get-Content warnings.log # 警告のメッセージが入っている。'データ' は入らない
# 全ストリームを1ファイルへ
& { Write-Output 'データ'; Write-Warning '注意事項'; Write-Verbose '詳細' -Verbose } *> all.log
Get-Content all.log # 3つの出力がまとめて入る
3> を付けたのに期待したメッセージが入らないときは、まずそのメッセージを書いたストリームを確認します。Write-Host なら6番、Write-Warning なら3番です。「ログに出したはずなのに出ない」という問題は、出力先と捕捉先の対応から調べます。
*> は手軽ですが、全ストリームをまとめたテキストから、後で元のストリームごとに仕分け直すのは難しくなります。機械的に集計したいなら、別ファイルへ分けるか、第7章の構造化ログを使います。
トランスクリプトは実行の証跡として併用する
Start-Transcript は、セッションのコマンドとコンソール出力をテキストに記録します。「そのとき何を実行し、何が表示されたか」を後から確認するための手軽な手段です。7
Start-Transcript -Path "C:\Logs\export_$(Get-Date -f yyyyMMdd_HHmmss).log" -Append
try { Invoke-KsExport }
finally { Stop-Transcript }
表示の証跡と、項目を指定して集計できるログは役割が違います。後者が必要なら、トランスクリプトだけで済ませず、構造化ログと併用します。
7. 後から解析できるログにする ── 構造化ログ
1行1JSONで、調べたい項目を固定する
「先月、このエラーが何件出たか」を知りたいとき、自由書式のテキストではメッセージを探し続けることになります。1行1JSON(JSON Lines) にしておけば、時刻・重大度・メッセージなどの項目を使い、PowerShellだけで集計できます。
次のログ関数は、ファイルへの記録と人間向けの出力を分けています。ログを書いても、成功ストリームへ余計な値を返さないことがポイントです。
function Write-KsLog {
[CmdletBinding()]
param(
[Parameter(Mandatory)] [ValidateSet('INFO','WARN','ERROR')] [string] $Level,
[Parameter(Mandatory)] [string] $Message,
[hashtable] $Data,
[string] $Path = $script:KsLogPath
)
$entry = [ordered]@{
ts = (Get-Date).ToString('o') # ISO 8601。ソートも突合もしやすい
level = $Level
message = $Message
script = $MyInvocation.ScriptName
host = $env:COMPUTERNAME
user = $env:USERNAME
}
# 追加情報は data 配下に入れる。トップレベルに混ぜると、呼び出し側が
# level や user といったキーを渡したときに基本項目を上書きしてしまう
if ($Data) { $entry['data'] = $Data }
# -Compress で1行にする。追記は Add-Content(UTF-8)
$entry | ConvertTo-Json -Compress -Depth 5 | Add-Content -Path $Path -Encoding utf8
# 人間向けの表示は標準の仕組みに乗せる(画面には出しても値は返さない)
switch ($Level) {
# -ErrorAction は指定しない。ここで固定すると、呼び出し側が
# -ErrorAction Stop で終了エラーにしたくてもできなくなる
'ERROR' { Write-Error $Message }
'WARN' { Write-Warning $Message }
default { Write-Verbose $Message }
}
}
# 使用例
Write-KsLog -Level INFO -Message '取込完了' -Data @{ rows = 1250; file = 'orders.csv'; ms = 4210 }
# → {"ts":"...","level":"INFO","message":"取込完了","script":"...","host":"...","user":"...",
# "data":{"rows":1250,"file":"orders.csv","ms":4210}}
基本項目を守り、表示とエラー処理を固定しない
この例で押さえるのは、次の3点です。
| 設計 | 理由 |
|---|---|
時刻は ToString('o')、出力は ConvertTo-Json -Compress |
ISO 8601の時刻と1行1JSONに揃え、並べ替えや突き合わせをしやすくする |
追加情報は data 配下へ入れる |
呼び出し側が渡した level や user で基本項目を上書きさせない |
記録とは別に Write-Error・Write-Warning・Write-Verbose へ送る |
成功ストリームを汚さず、呼び出し側の表示・エラー処理の選択を残す |
特に、Write-Error の -ErrorAction をログ関数内で固定していません。呼び出し側が -ErrorAction Stop を指定して終了エラーとして扱いたい場合に、その選択を妨げないためです。
記録した項目を、そのまま集計に使う
過去30日分のエラーをメッセージ別に数える例です。自由書式の文字列を解析するのではなく、level・ts・message を使って抽出します。
Get-Content 'C:\Logs\ks.log' |
ForEach-Object { $_ | ConvertFrom-Json } |
Where-Object { $_.level -eq 'ERROR' -and [datetime]$_.ts -ge (Get-Date).AddDays(-30) } |
Group-Object message | Sort-Object Count -Descending | Select-Object Count, Name
複数台からの収集や監視ツールとの連携を考えるなら、Windowsのイベントログ・ETWに載せる選択肢もあります。設計の比較は「Windowsのイベントログ・ETWと構造化ログ」、ログファイルの世代管理は「PowerShellスクリプト応用 ── ログ調査・アーカイブ・レポート化」にまとめています。
8. 進捗表示の扱い
画面の進捗表示は、保存するログではない
Write-Progress はホストの進捗表示機能を使います。リダイレクト可能なデータストリームではないため、*> で進捗表示までログに残せるわけではありません。8
無人実行では表示を読む人がいないうえ、環境によっては進捗更新のコストも無視できません。表示が不要なら、次のように止めます。この書き方はWindows PowerShell 5.1でも使えます。
# 無人実行スクリプトの冒頭で進捗表示を止める
$ProgressPreference = 'SilentlyContinue'
-ProgressAction 共通パラメーターでの制御はPowerShell 7.4以降です。5.1と共通のスクリプトでは、この例の $ProgressPreference と区別してください。8
記録したい経過は、節目を別に書く
進捗を運用ログに残したいなら、毎回の画面更新を保存しようとせず、節目だけを詳細ストリームへ送るのが実用的です。
$i = 0
foreach ($row in $rows) {
$i++
if ($i % 100 -eq 0) { Write-Verbose "$i / $($rows.Count) 件完了" }
...
}
この例では100件ごとに経過を書きます。無人実行で保存する場合は、詳細出力を有効にし、第6章のリダイレクトと組み合わせます。
9. 実務の定石(判断表)
迷ったときは、「何を表示したいか」ではなく、その情報を誰が使うか、保存後に何をしたいかから選びます。
| 出したい情報 | 使うもの | 理由 |
|---|---|---|
| 後続処理に渡す値 | Write-Output / 裸の出力 |
成功ストリームはデータ専用1 |
| 処理の経過(調査時だけ見たい) | Write-Verbose |
呼び出し側が -Verbose で制御4 |
| 運用として記録したい出来事 | Write-Information + 構造化ログ |
-InformationVariable で捕捉可能5 |
| 対話ツールの装飾表示 | Write-Host |
情報ストリーム経由なので捕捉も可能5 |
| 想定内だが注意すべき事象 | Write-Warning |
既定で表示され、-WarningVariable で捕捉9 |
| 失敗 | Write-Error / throw |
エラー処理は専用記事を参照 |
| 開発中の内部状態 | Write-Debug |
-Debug を付けたときだけ4 |
| 進捗 | Write-Progress(対話時のみ) |
ログには残らない。無人実行では止める8 |
| 実行証跡をまるごと | Start-Transcript |
独自ログの保険として併用7 |
10. まとめ
まず、処理結果は成功ストリーム、経過や注意は別のストリームに分けます。関数は return に書いた値だけを返すわけではないので、不要な戻り値を $null = ... で捨て、後続処理が期待する型と件数を守ります。
次に、表示の詳細度は呼び出し側へ渡します。[CmdletBinding()] と Write-Verbose・Write-Information を使えば、標準の共通パラメーターで切り替えられます。PowerShell 5.0以降の Write-Host は捕捉できますが、対話ツールの装飾表示という用途に留め、値や運用ログの代わりにしないことが大切です。
最後に、解析用の記録と表示の証跡を分けます。集計するログは1行1JSONに揃え、セッションの確認には Start-Transcript を併用します。Write-Progress は保存用ではないため、無人実行では表示を止め、必要な節目だけを別途記録してください。
サンプルコードのダウンロード
この記事で扱ったコードは、そのまま動かせる形にまとめて配布しています。1行1JSONの構造化ログと、戻り値を汚さない関数の書き方が入っています。
この記事のサンプルは、PowerShell 7.6 で実際に実行して検証しています(Pester 14件)。zipに含まれる Invoke-SampleTests.ps1 を実行すれば、お手元でも同じ検証を再現できます。
# 構文解析 + 静的解析 + Pesterテスト
./Invoke-SampleTests.ps1
設定値(パス、サーバー名、テナントIDなど)は例です。そのまま本番環境で実行せず、自社の環境に合わせて読み替えてください。
関連記事
- PowerShellのエラー処理と再実行設計 ── try/catchが効かない罠からexit code・リトライの定石まで
- PowerShellスクリプトの引数設計とモジュール化 ── 「動くスクリプト」から「人に渡せるスクリプト」へ
- PowerShellスクリプト応用 ── ログ調査・アーカイブ・レポート化を安全に自動化する
- Windowsのイベントログ・ETWと構造化ログ
- PesterによるPowerShellのテスト整備 ── 運用スクリプトを壊しにくくする実務の型
- カスタムロガーの最低要件と結合テストのチェックリスト
関連する相談領域
合同会社小村ソフトでは、運用スクリプトのログ設計の見直し、「失敗した理由が分からない」状態の解消、監視・集計につながるログ基盤の整備を扱っています。
参考リンク
-
Microsoft Learn, Write-Output. オブジェクトを成功ストリーム(パイプライン)へ送ること、明示的に呼ばなくても式の結果が同じように出力されることについて。 ↩ ↩2
-
Microsoft Learn, about_Return. PowerShellの関数がreturnの有無にかかわらず、関数内で出力されたすべてのオブジェクトを呼び出し元へ返すこと、returnは値を返しつつ現在のスコープを抜けるための構文であることについて。 ↩ ↩2 ↩3
-
Microsoft Learn, about_Functions_CmdletBindingAttribute. [CmdletBinding()]属性を付けた高度な関数がコンパイル済みコマンドレットと同様に動作し、共通パラメーターが自動的に利用可能になることについて。 ↩ ↩2
-
Microsoft Learn, about_CommonParameters. -Verbose / -Debug / -WarningAction / -InformationAction / -ErrorAction および対応する -*Variable パラメーターの動作、環境設定変数との関係について。 ↩ ↩2 ↩3 ↩4 ↩5 ↩6
-
Microsoft Learn, Write-Host. PowerShell 5.0以降のWrite-HostがWrite-Informationのラッパーとして情報ストリームへ出力するようになり、6>によるリダイレクトが可能になったこと、$InformationPreference環境設定変数と-InformationAction共通パラメーターがWrite-Hostのメッセージには影響せず(例外は-InformationAction Ignore)、そのため既定でも画面に表示されること、-ForegroundColor / -BackgroundColorによる装飾、出力がパイプラインに渡されないことについて。関連してWrite-Informationは情報ストリームへの明示的な書き込みと-Tagsによる分類について。 ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7
-
Microsoft Learn, about_Redirection. PowerShellが成功・エラー・警告・詳細・デバッグ・情報の各ストリームを持ちそれぞれ番号で識別されること、
>>>によるファイルへのリダイレクト、n>&1による他ストリームへの合流、*>による全ストリームのリダイレクトについて。 ↩ ↩2 ↩3 ↩4 ↩5 ↩6 -
Microsoft Learn, Start-Transcript. セッションのコマンドとコンソール出力をテキストファイルに記録すること、-Appendによる追記、Stop-Transcriptによる停止について。 ↩ ↩2 ↩3
-
Microsoft Learn, Write-Progress. コマンドの進行状況をホストの進捗表示として出力すること、$ProgressPreferenceで表示を制御できること、PowerShell 7.4以降は-ProgressAction共通パラメーターでも制御できることについて。 ↩ ↩2 ↩3 ↩4 ↩5
-
Microsoft Learn, about_Preference_Variables. $VerbosePreference・$DebugPreference・$InformationPreferenceの既定値がSilentlyContinue、$WarningPreferenceと$ErrorActionPreferenceの既定値がContinueであること、$ProgressPreferenceによる進捗表示の制御、これらがスコープと子スコープに適用されることについて。 ↩ ↩2 ↩3
関連する記事
同じタグを共有する最新の記事です。さらに近い話題で知識を深められます。
PowerShellスクリプトが遅いときに見るところ ── 配列・パイプライン・突合の勘所
PowerShellスクリプトが遅い原因の定番を整理します。配列の+=がO(n^2)になる理由、パイプラインとforeachの差、突合のハッシュテーブル化、ファイルI/Oの改善、そして正しい測り方までを実務目線で解説します。
PowerShellの並列処理 ── ForEach-Object -Parallelとジョブの使い分け
ForEach-Object -Parallel・Start-ThreadJob・Start-Jobの違いと使い分け、$using:とスレッド安全性、ThrottleLimitの決め方、かえって遅くなるケースまでを実務目線で整理します。
PowerShellから外部exeを正しく呼ぶ ── 引数のクォート・終了コード・文字化けの落とし穴
PowerShellからrobocopyや社内EXEを呼ぶと引数が壊れる、終了コードが取れない、出力が文字化けする。PowerShell 7.3の引数渡し変更、停止解析トークン--%、Start-Processの使い分けまでを実務目線で整理します。
PowerShellでの資格情報の安全な扱い ── 平文パスワードをスクリプトから追放する
PowerShellスクリプトの平文パスワードを安全な保管へ移行する手順を整理します。SecureStringの実像と限界、Export-ClixmlによるDPAPI保存の仕組み、SecretManagement/SecretStoreの使いどころまで解説します。
PowerShellスクリプトの引数設計とモジュール化 ── 「動くスクリプト」から「人に渡せるスクリプト」へ
PowerShellスクリプトを他人に渡せる品質に引き上げる手順を整理します。paramブロックと[CmdletBinding()]、入力検証、パイプライン入力、-WhatIf対応、.psm1モジュール化、社内共有とGit管理の勘所まで解説します。
関連トピック
このテーマと近いトピックページです。記事を起点に、関連するサービスや他の記事へ進めます。
Windows技術トピック
Windows 開発、不具合調査、既存資産活用の技術トピックをまとめた入口です。
このテーマがつながるサービス
この記事は次のサービスページにつながります。近い入口からご覧ください。
Windowsアプリ開発
業務アプリ、装置連携、通信ツールなどの Windows ソフト開発を支援します。
技術相談・設計レビュー
改修方針、設計レビュー、既存資産の扱いを整理するための技術相談です。
よくある質問
この記事のテーマについて、相談時によくある質問をまとめています。
- Write-Hostは使ってはいけないのですか?
- 禁止ではなく、用途が限られるという理解が正確です。PowerShell 5.0以降のWrite-Hostは情報ストリーム(6番)に書き込むため、6>によるリダイレクトや-InformationVariableでの捕捉ができるようになりました。ただし既定では常に画面に表示される前提のコマンドであり、値をパイプラインに流したいときには使えません。対話的なツールで人間に読ませる装飾表示ならWrite-Host、処理の進捗や補足情報ならWrite-VerboseやWrite-Information、後続処理に渡す値ならWrite-Output(または裸の出力)、という使い分けになります。
- 関数の戻り値に、意図しない値が混ざってしまいます。
- PowerShellの関数は、明示的なreturnがなくても、関数内で出力されたすべてのオブジェクトを返すためです。New-ItemやStringBuilderの.Append()など、値を返すコマンドやメソッドを呼び捨てにすると、その戻り値が成功ストリームに流れて呼び出し元に届きます。逆に、List[T]の.Add()のように戻り値がvoidのメソッドは何も出力しないので、抑止は不要です。対処は、不要な出力を$null = ...で捨てるか、| Out-Nullを付けるか、[void]でキャストすることです。性能面では$null = ...が最も軽い書き方になります。
- スクリプトの詳細ログを、実行時に切り替えられるようにしたいです。
- 処理の途中経過をWrite-Verboseで書き、関数に[CmdletBinding()]を付けてください。それだけで呼び出し側が-Verboseを指定したときだけ表示されるようになります。恒常的に出したい場合は$VerbosePreference = 'Continue'をスクリプト冒頭で設定します。同様に、Write-Debugは-Debug、Write-Warningは-WarningActionで呼び出し側から制御できます。自前のログレベル変数を作るより、PowerShellの標準の仕組みに乗せたほうが、他人が読んだときに意図が伝わります。
- 詳細ログや警告も含めて、まるごとファイルに残すにはどうすればよいですか?
- 用途によって3通りあります。単純に全ストリームをファイルに落とすなら*> でリダイレクトします。実行の証跡として画面に出たものを丸ごと残すならStart-Transcriptが手軽です。プログラムから後で解析したいなら、1行1JSONの構造化ログを自分のログ関数で書き出すのが確実で、この場合でも保険としてトランスクリプトを併用する価値があります。
- Write-Progressの表示は、ログファイルに残せますか?
- 残せません。進捗表示はホストの表示機能であり、リダイレクト可能なデータストリームとは別の扱いだからです。無人実行では表示先がないため、進捗はログに残す情報とは分けて考えてください。無人実行では$ProgressPreference = 'SilentlyContinue'にして進捗表示自体を止めると、環境によっては目に見えて速くなることもあります。進捗をログに残したいなら、Write-Verboseで「100件中50件完了」といった節目だけを書くほうが実用的です。