Windows PowerShell筛选的力量

Don Jones

在上个月的专栏中,我论述了 Windows PowerShell 管道的强大功能和灵活性,它可以使您将一个数据集(更准确地说,是一组对象流)从一个 cmdlet 传递到另一个 cmdlet,进一步优化该数据集直到它完全符合您的需要。在我的论述中,我提到您自己的脚本

(不只是 cmdlet)也可以利用管道。本月我要详细论述该主题。

我在 Windows PowerShell™ 中最常做的事情之一是编写通常通过 Windows® Management Instrumentation (WMI) 对多台远程计算机执行操作的脚本。对于任何处理远程计算机的任务,在脚本运行时,这些计算机中的一台或多台总是有可能不可用。因此,我需要我的脚本能够解决这个问题。

当然,我可以使用许多方法使脚本具备处理 WMI 连接超时的能力,但是我特别不喜欢该方法,因为超时期限本身太长 — 默认情况下大约为 30 秒。这样,如果我的脚本必须等待多次超时发生,它的运行将更加缓慢。相反,我要脚本执行一次快速检查,以查看在尝试连接到给定计算机前该计算机是否实际上处于联机状态。

Windows PowerShell 范例

在其他脚本编写语言(如 VBScript)中,我通常一次处理一台计算机。也就是说,我将检索一个计算机名称 — 可能从存储在文本文件中的名称列表中检索 — 并 ping 该系统以查看其是否可用。如果可用,我将建立 WMI 连接并执行我需要完成的所有其他工作。这是一种常见的脚本式方法。实际上,我可能要在一个循环中编写所有我的代码,然后对我需要连接的每台计算机重复执行一次该循环。

然而,由于 Windows PowerShell 基于对象并且能够直接处理对象组或集合,它更适用于批处理操作。Windows PowerShell 中的范例不是要处理单一的对象或数据段,而是要处理整个组,一点点优化该组,直到完成您打算做的任何事情。例如,我想立刻读取整个名称集,而不是一次检索列表中的一个计算机名称。我要编写单一例程,该例程接受名称集,ping 这些计算机,然后输出可以连接的计算机名称,而不是在循环中 ping 每台计算机。我的过程中的下一步是建立到其余名称(即可通过 ping 访问的名称)的 WMI 连接。

Windows PowerShell 对多项任务使用这种方法。例如,要获得正在运行的服务的列表,我可以使用与以下类似的内容:

Get-Service | Where-Object { $_.Status –eq "Running" }

图 1 显示了我的计算机上显示的输出情况。我使用 Get-Service 检索所有服务,通过管道将其输送到 Where-Object,然后筛选出所有未运行的服务;而不是一次检查一项服务。这差不多就是我用脚本要做的事情:获得计算机名称列表,筛选出所有不可 ping 的名称,然后将可 ping 的计算机列表传送到下一步。

图 1 获得可 ping 的计算机列表

图 1** 获得可 ping 的计算机列表 **(单击该图像获得较大视图)

筛选功能

尽管我可以编写自己的 cmdlet 来实现此功能,但是我不想这样做。编写 cmdlet 需要 Visual Basic® 或 C# 以及大量的 Microsoft® .NET Framework 开发专业知识。更重要的是,在该任务中它要求更多的参与,而我不想投入这么多。幸运的是,Windows PowerShell 允许我编写一种叫做筛选的特殊功能,它可以在管道内完美地发挥作用。筛选功能的基本轮廓如下所示:

function <name> {
  BEGIN {
    #<code>
  }
  PROCESS {
    #<code>
  }
  END {
    #<code>
  }
}

正如您所看到的,该功能包含三个独立的脚本块,分别命名为 BEGIN、PROCESS 和 END。筛选功能 — 即旨在在管道内工作以筛选对象的功能 — 可以拥有这三个脚本块的任何组合,具体取决于您要做什么。它们的工作方式如下:

  • 在首次调用您的功能时,BEGIN 块执行一次。您可以用它来执行设置工作,如果需要的话。
  • PROCESS 块对传送到功能的每个管道对象执行一次。$_ 变量代表当前的管道输入对象。筛选功能中需要 PROCESS 块。
  • 在处理完所有管道对象后,END 块执行一次。这可用于任何收尾工作(如果需要)。

在我的示例中,我要生成一个筛选功能,该功能接受名称集合作为输入对象,然后尝试 ping 每个对象。可以成功地 ping 的每个对象将输出到管道;不可以 ping 的系统将被删除。由于 ping 功能不需要任何特殊的设置或收尾工作,我将仅使用 PROCESS 脚本块。图 2 中的代码提供了完整的脚本。

Figure 2 Ping-Address 和 Restart-Computer

1  function Ping-Address {
2    PROCESS {
3      $ping = $false
4      $results = Get-WmiObject -query `
5      "SELECT * FROM Win32_PingStatus WHERE Address = '$_'"
6      foreach ($result in $results) {
7        if ($results.StatusCode -eq 0) {
8          $ping = $true
9        }
10      }
11      if ($ping -eq $true) {
12        Write-Output $_
13      }
14    }
15  }
16
17  function Restart-Computer {
18    PROCESS {
19      $computer = Get-WmiObject Win32_OperatingSystem -computer $_
20      $computer.Reboot()
21    }
22  }
23
24  Get-Content c:\computers.txt | Ping-Address | Restart-Computer

请注意,我已定义了两个功能:Ping-Address 和 Restart-Computer。在 Windows PowerShell 中,调用功能之前必须对其进行定义。因此,第一个可执行的脚本行是行 24 ,它使用 Get-Content cmdlet 来从文件中获得计算机名称列表(每行显示一个计算机名称)。该列表(从技术角度上说,是字符串对象集合)将通过管道传递到 Ping-Address 功能,它会筛选出不能 ping 的任何计算机。结果将通过管道传递到 Restart-Computer,该功能使用 WMI 来远程重新启动可以 ping 的每台计算机。

Ping-Address 功能执行 PROCESS 脚本块,这意味着功能需要输入对象集合。PROCESS 脚本块自动处理该输入 — 我不必定义任何输入参数来包含该输入。我通过将变量 $ping 设置为 $false 来启动行 3 上的过程,$false 是代表布尔值 False 的内置 Windows PowerShell 变量。

然后,我使用本地 Win32_PingStatus WMI 类来 ping 指定的计算机。请注意,行 5 上代表当前管道对象的 $_ 变量将嵌入 WMI 查询字符串内。当字符串包含在双引号中时,Windows PowerShell 总是尝试用内容替换变量(如 $_),因此您不必浪费时间考虑字符串连接。我正在行 5 上使用该功能。

行 6 是检查 ping 结果的循环。如果这些结果中的任意一个成功返回(也就是说,StatusCode 为零),我会设置 $ping 等于 $true 来指示成功。在行 11 上,我检查 $ping 是否已设置为 $true。如果是,我会将原始输入对象输出到默认输出流。Windows PowerShell 自动管理默认输出流。如果该功能位于管道的末端,则输出流将转换为文本表示形式。如果管道中有更多的命令,则输出流的对象 — 在本例中,是包含计算机名称的字符串对象 — 将传递到下一个管道命令。

Restart-Computer 功能稍微简单一些,但是它也使用 PROCESS 块以便轻松地加入到管道中。在行 19 上,该功能连接到指定的计算机并检索其 Win32_OperatingSystem WMI 类。行 20 执行类的 Reboot 方法来重新启动远程计算机。

同样,行 24 是实际执行所有操作的位置。当然,运行该脚本时您应该非常小心 — 它旨在重新启动在 c:\computers.txt 中指定的每台计算机,如果您不特别注意该文本文件中的名称,肯定会导致灾难性的后果!

后续步骤

该脚本并非完全无懈可击。我还应该意识到 WMI 错误可能与基本连接不相关,如防火墙阻止远程计算机上的必要端口或 WMI 安全错误。我可以通过实施陷阱处理程序处理这些问题 — 这听起来像未来专栏的理想主题。

此外,该脚本正在执行一个相当严重的操作 — 重新启动远程计算机。任何尝试这种潜在的危险操作的脚本都应该实施两个常见的 Windows PowerShell 参数,–Confirm 和 –WhatIf。解释如何进行此操作在这里一言难尽,但可以在未来专栏中进行专题论述。同时,请访问 Windows PowerShell 团队博客;设计师 Jeffrey Snover 会介绍该主题

即使不了解这些特性,您也可以很好地理解筛选功能的作用。我将在随后的专栏中完善该技术,向您展示这些功能如何很好地模块化可重复使用的代码以及从总体上增强脚本的功能。

Don Jones是 SAPIEN Technologies 的首席脚本权威和 Windows PowerShell:TFM (SAPIEN Press, 2007) 的合著者。可通过 www.ScriptingAnswers.com 与他联系。

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