Share via


Windows PowerShell進度報告

Don Jones

我最近寫了一段又臭又長的 Windows PowerShell 指令碼,它在執行時變得沒什麼回應。我是將它寫成以排程工作的方式執行,所以並沒有產生太多可見的輸出。當我第一次對它進行重要的測試時,卻開始擔心我可能

不小心寫入一個無限迴圈或是其他有問題的指令碼。

當殼層的小游標淒涼地一明一滅時,我不禁自問:「我把它砍掉了嗎?」顯然我對自己沒什麼信心,否則我也不會馬上按下 Ctrl+C 來中斷指令碼。該是時候加入一些進度報告了。

喋喋不休,長篇大論

x

首先我要加入一些狀態訊息,好讓我清楚知道指令碼目前的作業情況。使用 Write-Verbose 指令程式即可在殼層中輕鬆完成這些工作。快在殼層中試試看:

Write-Verbose "Test Message"

如果您試過了,就會發現它沒有什麼作用。這是因為 Write-Verbose 將物件傳送到特殊的 Verbose 管線,而且在預設情況下並不會顯示輸出。此管線是由內建的殼層變數 $VerbosePreference 所控制。這個變數的預設值為 SilentlyContinue,因此會隱藏詳細資訊輸出。不過,如果將值設為 Continue,將可開放此管線:

$VerbosePreference = "Continue"

現在我可以將一些 Write-Verbose 陳述式加入指令碼中,並取得指令碼執行的相關詳細檢視。而且這個技巧的美妙之處在於,當我完成測試及疑難排解之後,只要在指令碼開頭將 $VerbosePreference 再次設為 SilentlyContinue,即可關掉這些額外的訊息,

根本沒有必要移除所有 Write-Verbose 陳述式。事實上,正由於這些陳述式都保留在指令碼中,因此每當我需要手動執行指令碼時,都可以視需要輕易地重新啟動 Verbose 管線。

但我仍然需要知道真正的進度

當我放心地知道指令碼並未陷入無限迴圈,而且實際上執行地很順利時,我關閉 Verbose 管線並再次執行指令碼 — 以確保萬無一失。

但問題來了,雖然知道指令碼運作十分順暢,我還是不能忍受盯著游標閃爍 (我有注意力難以集中的問題;我倒寧願看著油漆等它乾,也好過無事可做)。

我需要大概知道指令碼進行到哪裡了,以及它何時會完成。基本上,我想要有類似進度列的功能。

幸好,Windows PowerShellTM 內含 Write-Progress 指令程式。此指令程式不像 Windows® 一樣提供圖形進度列,但還是顯示不錯的進度列,如 [圖 1] 所示。這個進度列有點類似在 Windows Server® 2003 甚或是 Windows XP 中的安裝程式文字部分所使用的檔案複製進度列。

[圖 1] 指令碼的進度如何?

[圖 1]** 指令碼的進度如何? **(按影像可放大)

Write-Progress 的用法需要稍加說明,其實我覺得使用範例會更好懂。請看以下的指令碼:

for ($a=1; $a -lt 100; $a++) {
  Write-Progress -Activity "Working..." `
   -PercentComplete $a -CurrentOperation
   "$a% complete" `
   -Status "Please wait."
  Start-Sleep 1
}

它是使用 Write-Progress 來顯示進度列。我用了 Start-Sleep 讓指令碼每次通過迴圈時暫停一秒,藉此減緩它的執行速度,好讓我們能實際看到進度情況 — 若未暫停,迴圈會快速地從 0 數到 100,而進度列只會短暫地閃過畫面。

如您所見,Activity — 被我設為 Working — 顯示在進度列上方。Status 顯示在其下,而 CurrentOperation 則顯示在最底下。殼層一次只支援單一的進度列。如果使用 Write-Progress 的當時沒有任何已顯示的進度列,便會建立新的進度列,否則就會更新現有的進度列。

在此我尚未要求進度列在完成時消失。只要在指令碼結尾加入以下內容就可以這麼做:

Write-Progress -Activity "Working..." `
 -Completed -Status "All done."

一般而言,進度列會在指令碼完成時自動消失,但萬一指令碼還要繼續其他工作,您就會希望進度列在完成時隱藏;-Completed 參數會直接從畫面中移除進度列。

滴答、滴答、滴答

Write-Progress 另一個常見用法是建立「倒數秒數」畫面,而非實際的進度列。例子如下:

for ($a=100; $a -gt 1; $a--) {
  Write-Progress -Activity "Working..." `
   -SecondsRemaining $a -CurrentOperation
   "$a% complete" `
   -Status "Please wait."
  Start-Sleep 1
}

在此我將迴圈改成從 100 數到 1,並使用 Write-Progress 的 SecondsRemaining 參數來取代 PercentComplete。結果就如 [圖 2] 所示,如您所見,進度測量表已經消失,由倒數時鐘取而代之。殼層自動將剩餘的總秒數轉換成時、分和秒,以提供更人性化的資訊。在此顯示的完成百分比實際上是從 100 倒數,因為那就是我提供的 CurrentOperation 參數。其中不會實際計算已完成的百分比;Windows PowerShell 只是在字串「% complete」之前顯示 $a 的目前值。

[圖 2] 指令碼還有多久會完成?

[圖 2]** 指令碼還有多久會完成? **(按影像可放大)

善於溝通的指令碼

我特別熱中撰寫善於溝通現況的指令碼。無論是詳細資訊輸出或簡單的進度列都可以達到這種效果。它不僅能彌補我注意力不足的缺點,或許也能裨益幾個月後需要用到我的指令碼的其他工程師。畢竟,顯示某種狀態與進度資訊所能提供的好處多多。

本月 Cmdlet:Tee-Object

在本月,我想展示一下我最喜欢的一个故障排除 cmdlet。以下面的代码为例:

Get-WMIObject Win32_Service | Where { $_.State -ne "Running"
-and $_.StartMode -eq "Automatic" } | ForEach-Object { $_.Start() }

表面上看,它好像要启动所有设置为自动启动的服务,但是由于某种原因还没有启动。它实际上并没有运行,并且要想找到它不运行的原因可能有些困难,因为您不能钻入管道中间分秒不离地查看。也就是说,唯一的办法是使用 Tee-Object。

Tee-Object 将对象重新定向到文件(或变量)并将其沿管道向下传递。例如:

Get-WMIObject Win32_Service | Tee-Object AllServices.csv | Where 
{ $_.State -ne "Running" -and $_.StartMode -eq "Automatic" } | 
Tee-Object FilteredServices.csv | ForEach-Object { $_.Start() }

经此修改后,即可看到执行每个管道命令后发生的情况,很快我发现我的 FilteredServices.csv 文件是空的!难怪这个脚本不能正常运行!通过进一步的研究我发现了导致出现问题的根本原因,StartMode 是 "Auto" 而不是 "Automatic",是 Tee-Object 让我查明了出现问题的准确位置。

Don Jones 是《Technet Magzine》的特約編輯,也是《Windows PowerShell:TFM》(SAPIEN Press,2007 年) 的作者之一。他負責教授 Windows PowerShell (請參閱 www.ScriptingTraining.com),您可以透過 ScriptingAnswers.com 網站與他連絡。

© 2008 Microsoft Corporation and CMP Media, LLC. 保留所有權利;未經允許,嚴禁部分或全部複製.