Windows PowerShell重新考虑管道

Don Jones

之前关于 Windows PowerShell 是多么得与众不同、多么得新颖、多么得令人兴奋已经谈了很多,尽管它是基于已存在多年的命令行界面概念,而且主要用在基于 UNIX 和 Linux 的操作系统中。但是这个 Windows PowerShell 与其先前版本共享的常见技术很容易

忽视 Windows PowerShell™ 真正的灵活性和独特性,及其卓越的 Windows® 环境适用性。

Windows PowerShell 中最常谈到的一个功能就是管道,不过很遗憾,它也是最容易被误解的功能之一。那是因为它依据的是 19 世纪 70 年代早期定义的术语,曾经代表完全不同且性能较弱的功能。

管道的起源

曾创建过的第一批 UNIX 外壳中,其中一个就是 Thompson 外壳,它非常原始,只具有最基本的脚本编写语言元素功能,没有变量。这个外壳有意设计得很普通,执行程序是其唯一的真实用途。但是,它引入了一种改进当时其他外壳的关键概念,即管道。通过使用 < and > 符号,可以指示该外壳将输入和输出重定向到不同命令,以及重定向来自不同的命令的输入和输出。例如用户现在可以将命令输出重定向到某个文件。

这个语法在后来得到了扩展,可以使一个命令的输出也能被输送到另一个命令的输入,从而使一长串连续的命令可以链接在一起,以完成更多复杂的任务。在外壳的版本 4 之前,都采用竖线字符“|”来进行输送,因此称为管道字符。甚至 MS-DOS® 最早的版本就已通过这些字符实施了基本管道,允许用户将 type 命令的输出输送到更多命令的输入,从而创建针对长文本文件的一次显示一页的方式。

尽管在 1975 年发布 UNIX 版本 6 之前,Thompson 外壳被广泛认为存在不足,但对外壳开发人员和用户来讲,管道的概念还是很深入人心,并且发展成了许多至今还在使用的技术。

管道文本

几乎所有外壳的限制都在于它们内在的基于文本的本质。在基于 UNIX 的操作系统中,这实际上不是限制,而是对操作系统本身如何运行的反映。UNIX 中几乎任何资源都可以以某种类型的文件表示,这意味着可以将文本从一个命令输送到另一个可以提供大量功能和灵活性的命令。

然而一提到管理信息,文本肯定是具有限制性的。例如,如果我要向您提供一个运行在 Windows 计算机上的服务列表,您肯定能明白这个意思。也许我最好是在第一栏输入服务名称,第二栏输入它的启动模式。您强大的大脑会进行透明而即时的分析或转换,文本会显示为您可以理解的有意义的信息。可是计算机没那么聪明:为了让计算机对这个列表执行一些有意义的操作,您必须告诉计算机第一栏由字符 1 到 20 组成,第二栏也许由字符 22 到 40 组成,等等。

多年来,这种文本文件分析一直是管理员必须将多个命令链接在一起的唯一方法。实际上,VBScript 和 Perl 这些脚本编写语言在字符串操作方面占优势,主要是因为它们需要能够接收一个程序或命令的文本输出,然后将该输出分析转换为某种可用于某些后续任务的有用数据。例如,我曾经写过这样一个 VBScript 作业,即接收 Dir 命令的文本输出,分析文件名称和日期的输出,然后将以前未使用的文件移到存档位置。字符串分析非常棘手,因为例外(输入数据中的各种变化)几乎一直发生,这要求您重做脚本中的逻辑以处理所有可能发生的改变。

作为 Windows 环境中管理脚本或自动化的形式,字符串分析并不是很有用。这是因为 Windows 本身不是以容易访问的文本格式存储很多信息。相反,它使用数据中心存储(例如 Active Directory®、Windows 注册表和证书存储),使得脚本专家们必须先使用一个工具来生成某些形式的文本输出,然后才是分析这个文本并使用它执行一些操作的脚本。

对象更简单

Windows 软件开发人员总是会想办法让一切变得更简单。最初,Microsoft 特意开发了 COM,想以更易于使用的方式来表示 Windows 复杂的内部工作机制。现在,Microsoft® .NET Framework 仍执行这个相同的任务——它以标准化的方式表示软件的内部工作机制。

通常,COM 和 .NET 将项目作为对象公开。(软件开发人员可能会反对这种简化,但是出于讨论目的,简单的术语就足够了。)这些对象都具有很多不同类型的成员。对于我们来说,对象的属性和方法是我们最感兴趣的地方。属性基本上在某些方面可以说明一个对象,或者它会修改对象或其行为。例如,服务对象可能会有一个包含服务名称的属性和另一个包含服务启动模式的属性。方法会导致对象执行某个操作。例如,服务对象可能会有名为 Stop、Start、Pause 和 Resume 的方法,表示可以和服务一起执行的不同操作。

从编程或脚本编写的角度来看,对象的成员是指使用点阵符号。对象会经常被分配给变量,从而为您提供物理操作该对象的方法。例如,如果我将一个服务分配给变量 $service,我可以使用语法 $service.Stop 停止该服务。或者我可以通过显示 $service.Name 来检索该服务的显示名称。

管道中的对象

因为 Windows 是一个庞大而复杂的操作系统,而且它不会以文本格式存储它的管理数据,所以较早的外壳技术根本不适合它。例如,假设我有一个名为 SvcList.exe 的命令行工具,它可以产生服务的格式化列表及其启动模式。在 Windows 命令行外壳(一种外壳,深深扎根于具有数十年历史的 MS-DOS 外壳)中,我可能会运行以下程序:

SvcList.exe | MyScript.vbs 

此语句检索服务列表并将该列表输送到 VBScript÷ 文件。我必须编写 VBScript 文件来分析这个格式化列表,然后执行所有我想执行的操作——也许会输出任何启动模式为“禁用”的服务。这项任务可能比较耗时。最后,问题是该 SvcList.exe 有一个独特的输出,它不共享其他命令可以轻易用来分析利用其输出的通用格式。

然而对象可以提供这个通用格式,那就是 Windows PowerShell 管道与所有对象(而不仅是文本)一起工作的原因。当您运行 Get-WMIObject 之类的 cmdlet 时,用程序员术语来说,会产生对象组或对象集合。每个对象都包含允许您操作它的所有属性和方法。如果将对象输送到 Where-Object cmdlet,就可以筛选它们,从而只剩下我想要显示的对象。Where-Object 不需要分析任何文本,因为它没有检索任何文本,它在检索对象。例如:

Get-WMIObject Win32_Service | Where-Object {$_.StartMode -eq “Disabled” }

或者,如果您更喜欢通过别名提供的较短语法:

gwmi Win32_Service | where {$_.StartMode -eq “Disabled” }

有趣的是 Windows PowerShell 始终会将对象沿着管道传递。直到管道的末端才停止(当没有其他地方可以传递对象的时候),外壳会使用其内置格式规则生成对象的文本表示形式。例如,请看下面的情况:

Gwmi Win32_Service | where {$_.StartName –eq “LocalSystem” } | select Name,StartMode

这组三个 cmdlet 可以检索我本地计算机中的所有服务,筛选掉不使用 LocalSystem 帐号登录的所有服务,然后将其他服务传递给 Select-Object cmdlet,这样仅输出两个属性:Name 和 StartMode——它们是我之前要求要选择的两个属性。结果是一份以 LocalSystem(也许是出于安全审核目的)身份登录的服务的简单报告。

由于所有的 cmdlet 都共享一种通用数据格式——对象,所以它们可以与另一个不进行复杂字符串分析的 cmdlet 共享数据。同时由于 Windows PowerShell 本来就可以创建对象的文本表示形式,管道的末端就肯定是我,一个人,可以读取的文本输出。图 1 显示了产生的输出示例。

图 1 和对象一起传递的一系列输送的 cmdlet 所产生的文本输出

图 1** 和对象一起传递的一系列输送的 cmdlet 所产生的文本输出 **

管道的亮点

在 Windows PowerShell 中输送的原因非常令人诧异,那就是 Windows PowerShell 中的每个项目都是对象,包括您可以使用的所有属性和方法。从技术角度上来说,甚至文本文件也是字符串对象的集合,文件中的每一行文本都可充当一种独立的独特字符串对象。例如,创建名为 C:\Computers.txt 的文本文件(使用“记事本”)。使用文本填充该文件,然后在 Windows PowerShell 中运行下列命令:

Get-Content C:\Computers.txt | Select-Object Length | Format-List

如果您不太喜欢要进行很多键入操作,也可以使用别名:

gc C:\Computers.txt | select Length | fl

该代码会提供一个列表,指定每个文本行的长度(以字符为单位)。Get-Content 会检索文件中的字符串对象,Select-Object 则会获取每个对象的 Length 属性,而 Format-List 则会为您创建好用的、可以读取的文本输出。虽然这可能不是一个可以使用的实用管理工具,但是它阐明,即使是与文本行一样简单的元素也是 Windows PowerShell 中的对象。

由于能够将对象从 cmdlet 输送到 cmdlet,或者从 cmdlet 输送到脚本,因此可以创建非常强大的“一行式命令”。它们是简单的 cmdlets 字符串,附加在一个较长的管道中,该管道进一步完善了对象集,可以提供您真正需要的一切。几乎不进行任何脚本编写或编程,Windows PowerShell cmdlet(在相应管道中排成一队)也可以实现显著的效果。

支持未来一代

未来的 Microsoft 服务器产品仍会建立在扩展了此功能的 Windows PowerShell 之上。例如,实施新的 Exchange Server 2007 机器时,您可以使用 Windows PowerShell 来检索所有的邮箱,筛选掉所有不在新邮件服务器所在办公室的邮箱,然后将这些邮箱移到新的服务器上——这一切操作都不需要编写脚本,只需单个文本行即可实现。Exchange Server 2007 团队已发布强大的一行式命令的冗长列表。它真正阐述了管道的功能及其能够完成的管理任务。

使用 Windows PowerShell 的技巧是要了解,虽然这个新工具建立在长期存在的原则和 UNIX 世界的原理之上,但它是适合 Windows 管理的唯一工具。不要让术语的通用性愚弄了您,让您误以为 Windows PowerShell 就是 Windows 的 UNIX 外壳翻版。Windows PowerShell 包含全新的概念,那就是利用 Windows 平台,将它与 Windows 执行任务的方式紧密结合起来。

Don Jones是一位 Windows PowerShell MVP 及 Windows PowerShell 101 (ScriptingTraining.com) 的作者。您可以通过 www.ScriptingAnswers.com 联系 Don。

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