Windows PowerShell进度报告

Don Jones

我最近正在编写一个冗长而复杂的 Windows PowerShell 脚本,它在运行时经常会失去响应。我编写了这个程序后打算把它作为计划任务运行,因此它实际上不会产生很多见输出。当运行它进行第一次全面测试时,我很紧张甚至有些担心,

恐怕程序会出现死循环或其他脚本问题。

当外壳停止运行并无助地闪烁着它的小光标时,我就问自己“要终止它吗?”显然我对自己没有信心,因为我很快就按下 Ctrl+C 中断了脚本。现在该是添加一些进度报告的时候了。

重复,重复,重复

我要做的第一件事是添加一组状态消息,让我能够确切知道脚本正在做什么。利用 Get-Help cmdlet,外壳可以很容易地实现此功能。继续操作并在外壳中试一试:

Write-Verbose "Test Message"

如果您刚刚试过,就会注意到它其实什么也没做。这是因为 Write-Verbose 将对象发送到特殊的 Verbose 管道,默认情况下,该管道不显示其输出。内置的外壳变量 $VerbosePreference 控制此管道。此变量的默认值是 SilentlyContinue,它会抑制 verbose 输出。将其设置为 Continue,打开管道:

$VerbosePreference = "Continue"

现在可以将一组 Write-Verbose 声明添加到我的脚本中,并看一看运行时所发生的详细情况。此方法的吸引人之处在于,在完成测试和故障排除后,我可以通过在脚本开头将 $VerbosePreference 设回 SilentlyContinue 来关闭所有多余的聊天。

没有必要前去删除所有 Write-Verbose 声明。实际上,由于它们就在脚本中,因此只要我需要手动运行脚本,我就可以随时很容易地将 Verbose 管道切换回来。

但是我需要真正的进度

如果脚本没有陷入死循环并且能够完美运行,我就会关闭 Verbose 管道然后重新运行一次 — 只是为了确认。

现在的问题是,我知道脚本在功能方面没有任何问题,但我就是无法面对闪烁着的光标。(我面对的就是注意力集中问题。在百无聊赖的情况下,我只好想尽一切办法来打发时间。)

我需要了解的是有关脚本进展程度的一般指示,以及它的预计完成时间。基本上来说,我希望有一个类似于进度条的东西。

幸运的是,Windows PowerShellTM 包括了 Write-Progress cmdlet。这个 cmdlet 并不提供与 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 会非常快,进度条在屏幕上只是一闪而过。

正如您所见,被我设置为 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 只显示当前值 $a,后跟字符串 "% complete"。

图 2 完成脚本要多长时间?

图 2** 完成脚本要多长时间? **(单击该图像获得较大视图)

脚本通信

我非常热衷于编写脚本来显示脚本执行进展情况。它可以采用 verbose 输出的形式,也可以只是一个简单的进度条。它可以弥补我注意力不足的问题,也可以帮助以后需要运行我的脚本的其他用户。最后我要说的是,以某种形式显示状态和进度信息是非常有用的。

本月 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 杂志》**的特约编辑,并且是《Windows PowerShell:**TFM》(SAPIEN Press, 2007) 的合著者。他主要讲授 Windows PowerShell(请参阅 www.ScriptingTraining.com),可通过 ScriptingAnswers.com 网站与其联系。

© 2008 Microsoft Corporation 与 CMP Media, LLC.保留所有权利;不得对全文或部分内容进行复制.