告別 Write-Host ── PowerShell 的輸出資料流與日誌設計

· · PowerShell, Windows, 日誌, 維運改善, 自動化, 腳本, 設計, 可維護性

「腳本能正常運作,但一旦失敗,卻不知道發生了什麼事」── 這是在已上線運行的 PowerShell 腳本中,最常見的諮詢內容。而追查原因時,幾乎都會碰到同一種結構:處理狀況只透過 Write-Host 呈現,而在沒有人盯著畫面的夜間執行中,什麼都沒有留下

PowerShell 有六種輸出資料流,各自「面向的對象」不同:是要傳回值、給人閱讀,還是只有在調查時才需要。若能意識到這一點並分開書寫,同一支腳本就能在互動執行時表現得親切易懂,在無人執行時則能被機器讀取。反過來說,若把所有內容全部塞進 Write-Host,只會不斷累積既無法作為值使用、也不會留在日誌中的資訊。

本文將整理六個資料流各自的角色、Write-Host 的正確定位、函式傳回值被污染的機制、由呼叫端控制詳細程度的方法,以及可用於實際維運的結構化日誌格式。

1. 先講結論

  • PowerShell 的輸出資料流共有 6 個。分別是成功(1)、錯誤(2)、警告(3)、詳細(4)、偵錯(5)、資訊(6),各自都能用對應的編號重新導向。*> 代表全部資料流。1
  • PowerShell 5.0 以後,Write-Host 會寫入資訊資料流。因此變得可以用 6> 重新導向,或以 -InformationVariable 擷取。在此之前既無法擷取,也無法抑制。2
  • Write-Host 專門用於「顯示給人看」。無法用於傳回值的用途。要傳給管線的值,應該用 Write-Output(或直接輸出)。23
  • 函式會傳回內部所輸出過的所有物件。與是否有 return 無關。捨棄不需要的輸出時,慣用做法是用 $null = ...4
  • 進度用 Write-Progress,處理的過程用 Write-Verbose進度顯示並不是可以重新導向的資料流。5
  • 加上 [CmdletBinding()] 的函式,會自動具備 -Verbose -Debug -InformationAction 等共通參數。讓呼叫端能自行決定詳細程度,才是正確的做法。67
  • 預設值值得記住。$VerbosePreference $DebugPreference $InformationPreference 是 SilentlyContinue,$WarningPreference $ErrorActionPreference 則是 Continue。8
  • 想事後解析的日誌,要做成結構化(一行一個 JSON)。若想重現畫面的外觀,則並用 Start-Transcript9

本文的前提版本,以及 5.1 中的差異

在資訊系統的實務現場,Windows PowerShell 5.1 依然是主流。以下先整理本文的敘述各自以哪個版本為前提。

敘述內容 前提版本 在 Windows PowerShell 5.1 中
六個輸出資料流與編號重新導向、*> 5.1 與 PowerShell 7 皆相同 直接適用1
Write-Host 寫入資訊資料流(6 號)(可用 6>-InformationVariable 擷取) PowerShell 5.0 以後(第 3 章) 5.1 屬於 5.0 以後的版本,因此直接適用2
Write-Information-InformationAction / -InformationVariable PowerShell 5.0 以後(第 4 章) 直接適用7
函式會傳回內部的所有輸出、用 $null = ... 抑制 不受版本影響(第 5 章) 直接適用4
外部命令(原生命令)的 2>&1 處理方式 本文以 PowerShell 7.4 以後的行為進行說明(第 6 章) 不適用。透過型別區分外部命令輸出的寫法,請務必在實際執行的版本上確認
-ProgressAction 共通參數控制 Write-Progress PowerShell 7.4 以後5 無法使用。改用 $ProgressPreference 控制(第 8 章即以此方式撰寫)
文末發布的範例程式碼 PowerShell 7.6 執行驗證(14 項 Pester 測試)

簡言之,第 2 章到第 5 章,以及第 7 章,在 5.1 上也能直接沿用。需要留意版本差異的,只有外部命令的重新導向(第 6 章)與進度顯示的控制方式(第 8 章)這兩處。

2. 六個資料流,以及各自的對象

首先用一張圖來整理,各個寫入命令會送達到哪裡。

Write-Output / 直接輸出Write-Error / Write-WarningWrite-Verbose / Write-DebugWrite-Information / Write-Host1 成功資料流2 錯誤 / 3 警告 / 4 詳細5 偵錯 / 6 資訊傳遞給後續處理管線・賦值給變數顯示在畫面上是否為預設顯示,取決於環境設定變數可保留在檔案中編號重新導向・用於擷取的變數

只有成功資料流會傳遞給後續處理。其餘 5 個資料流,不是顯示在畫面上,就是要靠重新導向或 -*Variable 才能擷取到,不會進入管線。如果左側的分支(用哪個命令書寫)選錯了,一定會以「值送不到」或「沒留在日誌裡」的形式浮現出來。編號則用於像 3> warnings.log 這樣指定重新導向的時候(第 6 章)。

# 資料流 寫入命令 預期讀者 預設環境設定
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

資訊資料流的預設值是 SilentlyContinue,但 Write-Host 卻會顯示在畫面上,這並不矛盾。唯獨 Write-Host 是例外,Microsoft Learn 明確指出「$InformationPreference 環境設定變數與 -InformationAction 共通參數不會影響 Write-Host 的訊息」。2 也就是說,Write-Information 預設不會顯示,而 Write-Host 即使在預設情況下也會顯示,能夠抑制它的只有 -InformationAction Ignore6> 重新導向這兩種方式。

這張表格中最重要的是第一行。成功資料流不是用來寫「給人看的訊息」的地方。若在這裡寫入給人看的字串,那麼在用 | 連接該函式的瞬間,非預期的字串就會流入後續處理。

function Get-KsTargetFile {
    Write-Output "正在搜尋目標..."   # 【NG】混入傳回值
    Get-ChildItem -Path $path -Filter '*.csv'
}

# 呼叫端原本預期的是 FileInfo 陣列,結果開頭卻混入了字串
$files = Get-KsTargetFile
$files[0].FullName    # → 空的(因為開頭是字串)

正確做法是把過程報告送到詳細資料流或資訊資料流。

function Get-KsTargetFile {
    [CmdletBinding()]
    param([string] $Path)

    Write-Verbose "正在搜尋目標: $Path"   # 只有在指定 -Verbose 時才會顯示
    Get-ChildItem -Path $Path -Filter '*.csv'      # 傳回值只有 FileInfo
}

3. Write-Host 是「壞習慣」嗎

過去曾廣泛流傳「不要使用 Write-Host」的主張,但在現今的 PowerShell 中,情況已經不同了。PowerShell 5.0 以後,Write-Host 被實作為寫入資訊資料流(6 號),可以用 6> 重新導向,或用 -InformationVariable 擷取。2 「只能顯示在畫面上,事後完全無法擷取」這種當年的批評,如今已不再成立。不過,正如第 2 章提到的,即使資訊資料流的預設值是 SilentlyContinue,Write-Host 的顯示卻不會因此消失,因為它不受 $InformationPreference-InformationAction 影響(唯一的例外是 -InformationAction Ignore,只有它能抑制 Write-Host 的輸出)。「變得可以擷取」與「預設會顯示在畫面上」這兩者是可以並存的。2

話雖如此,它的適用場合仍然有限。

適合使用 Write-Host 的場合

  • 在互動式使用的工具中,想顯示帶顏色的標題或分隔線時(-ForegroundColor)
  • 想告訴使用者「這支腳本接下來要做什麼」時
  • 目的不是處理的值,而是裝飾性顯示本身時

不應該使用 Write-Host 的場合

  • 想以函式傳回值的形式傳遞值時(→ Write-Output)
  • 想留下事後要解析的維運日誌時(→ 結構化日誌,或 Write-Information)
  • 想由呼叫端切換顯示・不顯示時(→ Write-Verbose)

在無人執行的腳本中,原本就沒有顯示對象。只靠 Write-Host 來表現狀況的腳本,一旦從工作排程器執行,瞬間就會變成一支「什麼都看不出來的腳本」。這正是本文標題的含意所在。

4. 把控制權交給呼叫端 ── [CmdletBinding()] 與共通參數

Write-Verbose 真正的價值在於能由呼叫端決定是否要顯示。只要在函式上加上 [CmdletBinding()],就能自動使用 -Verbose -Debug -WarningAction -InformationAction -ErrorAction 等共通參數。67

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

常常會看到自行建立 $LogLevel 變數、再用 if 陳述式分支的實作方式,但搭上標準機制會更簡潔,也更容易讓別人理解意圖。因為「加上 -Verbose 就會顯示詳細內容」,是所有使用 PowerShell 的人的共同認知。

另外,$VerbosePreference 等環境設定變數會作用於當前範圍與子範圍。8 若以 -Verbose 呼叫函式,函式內部呼叫的 Cmdlet 也會開始輸出詳細資訊,因此輸出量有時會比預期多。

5. 函式的傳回值被污染 ── PowerShell 特有的陷阱

PowerShell 的函式,即使沒有明確的 return,也會傳回內部輸出過的所有物件4 這是一個強大的規格,但同時也是最容易出事故的部分。

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)

對策就是「捨棄不需要的輸出」。寫法有三種,但$null = ... 是最輕量的

$null = New-Item -Path $Path -ItemType Directory   # 建議做法
New-Item -Path $Path -ItemType Directory | Out-Null # 因為多了一段管線而較慢
[void] $sb.Append('x')                             # 常用於 .NET 方法

只要寫測試,就能一眼發現這種行為。「固定傳回值形狀」的測試有多重要,在《用 Pester 整備 PowerShell 測試》中有詳細討論。

6. 重新導向與擷取

資料流可以用編號個別重新導向。1

.\Invoke-NightlyExport.ps1 3> warnings.log            # 只把警告送到另一個檔案
.\Invoke-NightlyExport.ps1 4>&1 | Tee-Object -FilePath run.log   # 把詳細資料流合流進成功資料流
.\Invoke-NightlyExport.ps1 *> all.log                 # 把所有資料流送到同一個檔案
.\Invoke-NightlyExport.ps1 2>&1 | Where-Object { $_ -is [System.Management.Automation.ErrorRecord] }

> 是覆寫,>> 是附加。不過請留意:用重新導向合流時,型別會混在一起。像上面的例子一樣,把 PowerShell 腳本或函式的錯誤資料流合流時,該元素仍然保持 ErrorRecord 型別,因此可以像上面那樣依型別區分。

另一方面,外部程式(原生命令)的 2>&1 情況不同。PowerShell 7.4 以後,重新導向的輸出會被當作位元組資料流處理,合流後會變成字串,因此無法再用 ErrorRecord 來區分。若想區分外部命令的 stdout/stderr,請不要讓它們合流,分開接收(參見《正確地從 PowerShell 呼叫外部 exe》)。

是否真的如所寫的那樣被分開,親自動手試一次最為可靠。可以用一段同時輸出成功資料流與警告資料流的簡短程式碼來確認。

# 只把警告送到另一個檔案。畫面上只會留下 '資料'
& { Write-Output '資料'; Write-Warning '注意事項' } 3> warnings.log
Get-Content warnings.log     # 裡面是警告訊息。不會包含 '資料'

# 把所有資料流送到同一個檔案
& { Write-Output '資料'; Write-Warning '注意事項'; Write-Verbose '詳細' -Verbose } *> all.log
Get-Content all.log          # 3 個輸出會一起放進去

如果加了 3>,但 warnings.log 卻是空的,就代表那則訊息並不是寫進警告資料流(如果是用 Write-Host 寫的,就要用 6>)。要排查「明明應該出現在日誌裡,卻沒有出現」的問題,從這兩行程式碼開始,是最快的方式

另外 *> 雖然方便,但一旦全部混在一起,事後要再依資料流重新分類就會變得困難。若想用機器來彙整,請依資料流分別輸出到不同檔案,或改用下一章的結構化日誌。

若想把執行證據整個保留下來,Start-Transcript 是很方便的方式。它會把工作階段(session)的命令與輸出記錄成文字,因此之後可以重現「當時畫面上顯示了什麼」。9

Start-Transcript -Path "C:\Logs\export_$(Get-Date -f yyyyMMdd_HHmmss).log" -Append
try   { Invoke-KsExport }
finally { Stop-Transcript }

7. 讓日誌事後可供解析 ── 結構化日誌

給人閱讀的日誌,和給機器彙整的日誌,是兩回事。當你想知道「上個月這個錯誤出現了幾次」時,自由格式的文字就會變成與 grep 的耐力賽。若事先用一行一個 JSON(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 壓成一行。附加寫入用 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}}

彙整方式如下。

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 使用的是主控台(host)的進度顯示功能,並不是可以重新導向的資料流5 也就是說無法保留到日誌中。在無人執行時既沒有顯示對象,而且依環境不同,進度更新的成本有時也不容忽視。

# 在無人執行腳本的開頭停止進度顯示
$ProgressPreference = 'SilentlyContinue'

若想把進度保留到維運日誌中,只在節點時寫入詳細資料流,是比較實用的做法。

$i = 0
foreach ($row in $rows) {
    $i++
    if ($i % 100 -eq 0) { Write-Verbose "$i / $($rows.Count) 筆已完成" }
    ...
}

9. 實務上的固定做法(判斷表)

想輸出的資訊 使用的工具 理由
傳給後續處理的值 Write-Output / 直接輸出 成功資料流專用於資料3
處理的過程(只在調查時想看) Write-Verbose 由呼叫端以 -Verbose 控制7
想以維運角度記錄的事件 Write-Information + 結構化日誌 可用 -InformationVariable 擷取2
互動式工具的裝飾性顯示 Write-Host 透過資訊資料流,因此也可以擷取2
在預期範圍內,但需要留意的事件 Write-Warning 預設會顯示,可用 -WarningVariable 擷取8
失敗 Write-Error / throw 錯誤處理請參閱專文
開發中的內部狀態 Write-Debug 只有加上 -Debug7
進度 Write-Progress(僅限互動執行時) 不會保留在日誌中。無人執行時應停止5
執行證據整個保留 Start-Transcript 作為自訂日誌的保險並用9

10. 總結

  • PowerShell 的輸出分成 6 個資料流。成功資料流專用於資料,若混入給人看的訊息,傳回值就會壞掉。
  • PowerShell 5.0 以後的 Write-Host 會寫入資訊資料流,因此可以擷取,但不能用於傳回值或維運日誌的用途。
  • 函式會傳回內部輸出過的所有內容。捨棄不需要的輸出時,慣用做法是使用 $null = ...
  • 加上 [CmdletBinding()] 並使用 Write-Verbose / Write-Information,就能把詳細程度的控制權交給呼叫端。比自行建立日誌等級變數更簡潔,也更容易傳達意圖。
  • 事後要彙整的日誌,應做成一行一個 JSON 的結構化日誌。若目的是重現畫面,則並用 Start-Transcript
  • 進度顯示不是資料流,因此不會保留在日誌中。無人執行時,用 $ProgressPreference = 'SilentlyContinue' 直接停止,是比較實用的做法。

範例程式碼下載

本文所涉及的程式碼,已整理成可以直接執行的形式提供下載。內容包含一行一個 JSON 的結構化日誌,以及不污染傳回值的函式寫法。

下載範例程式碼(zip)

本文的範例,已實際在 PowerShell 7.6 上執行並驗證過(14 項 Pester 測試)。只要執行 zip 中所附的 Invoke-SampleTests.ps1,您也能在自己的環境中重現相同的驗證。

# 語法解析 + 靜態分析 + Pester 測試
./Invoke-SampleTests.ps1

設定值(路徑、伺服器名稱、租用戶 ID 等)僅為範例。請勿直接在正式環境中執行,務必配合貴公司的環境進行調整。

相關文章

相關諮詢領域

合同會社小村軟體承接維運腳本的日誌設計檢討、消除「不知道失敗原因」的狀態,以及整備能串接監控與彙整的日誌基礎設施。

參考連結

  1. Microsoft Learn, about_Redirection。關於 PowerShell 擁有成功・錯誤・警告・詳細・偵錯・資訊等各個資料流,並各自以編號識別;> >> 重新導向到檔案;n>&1 合流到其他資料流;以及 *> 重新導向所有資料流的說明。  2 3

  2. Microsoft Learn, Write-Host。關於 PowerShell 5.0 以後,Write-Host 成為 Write-Information 的包裝(wrapper),輸出到資訊資料流,因此可以用 6> 重新導向;$InformationPreference 環境設定變數與 -InformationAction 共通參數不會影響 Write-Host 的訊息(例外是 -InformationAction Ignore),因此即使預設情況下也會顯示在畫面上;透過 -ForegroundColor / -BackgroundColor 進行裝飾;以及輸出不會傳給管線的說明。相關地,Write-Information 則說明了明確寫入資訊資料流,以及透過 -Tags 進行分類。  2 3 4 5 6 7 8

  3. Microsoft Learn, Write-Output。關於將物件送往成功資料流(管線),以及即使不明確呼叫,運算式的結果也會以相同方式輸出的說明。  2

  4. Microsoft Learn, about_Return。關於 PowerShell 的函式無論有無 return,都會把函式內輸出過的所有物件傳回給呼叫端,以及 return 是一種在傳回值的同時跳出目前範圍的語法的說明。  2 3

  5. Microsoft Learn, Write-Progress。關於將命令的進行狀況以主控台(host)的進度顯示形式輸出、可用 $ProgressPreference 控制顯示,以及 PowerShell 7.4 以後也可以用 -ProgressAction 共通參數控制的說明。  2 3 4

  6. Microsoft Learn, about_Functions_CmdletBindingAttribute。關於加上 [CmdletBinding()] 屬性的進階函式,其行為會與已編譯的 Cmdlet 相同,並自動可以使用共通參數的說明。  2

  7. Microsoft Learn, about_CommonParameters。關於 -Verbose / -Debug / -WarningAction / -InformationAction / -ErrorAction,以及對應的 -*Variable 參數的行為,還有與環境設定變數之間關係的說明。  2 3 4 5

  8. Microsoft Learn, about_Preference_Variables。關於 $VerbosePreference、$DebugPreference、$InformationPreference 的預設值為 SilentlyContinue,$WarningPreference 與 $ErrorActionPreference 的預設值為 Continue,透過 $ProgressPreference 控制進度顯示,以及這些設定會套用於當前範圍與子範圍的說明。  2 3

  9. Microsoft Learn, Start-Transcript。關於將工作階段(session)的命令與主控台輸出記錄到文字檔案、用 -Append 附加寫入,以及用 Stop-Transcript 停止記錄的說明。  2 3

共用相同標籤的最新文章。能以相近的主題延伸理解。

與本文相近的主題頁面。以本文為起點,可進一步連到相關服務與其他文章。

本文連結到以下服務頁面,歡迎從最接近的入口查看。

常見問題

整理諮詢這個主題時常見的問題。

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 的標準機制,這樣別人閱讀程式碼時也更容易理解意圖。
包含詳細日誌與警告在內,該如何把所有內容整個保留到檔案裡?
依用途不同,有三種做法。若只是單純想把所有資料流都存進檔案,用 *> 重新導向即可。若想把畫面上出現過的內容作為執行證據整個保留下來,Start-Transcript 是最省事的方式。若之後要用程式進行解析,用自訂的日誌函式輸出一行一個 JSON 的結構化日誌最為可靠,即使在這種情況下,並用 Transcript 作為保險也有其價值。
Write-Progress 顯示的內容可以保留到日誌檔案裡嗎?
無法保留。因為進度顯示是主控台(host)的顯示功能,與可重新導向的資料流是分開處理的。無人執行時沒有顯示對象,因此請把進度與想留在日誌中的資訊分開考慮。在無人執行的情況下,把 $ProgressPreference 設為 'SilentlyContinue' 以直接停止進度顯示,依環境不同,有時甚至能明顯提升速度。若想把進度保留到日誌中,用 Write-Verbose 只記錄「100 件中已完成 50 件」這類節點,會更為實用。

作者檔案

本文作者的個人檔案頁面。

Go Komura

小村軟體有限公司 代表

以 Windows 軟體開發、技術諮詢與故障調查為中心,在難以重現的故障調查與既有資產仍在運作的專案上具有優勢。

回到部落格一覽