Windows PowerShell无脚本运行

Don Jones

每次准备和大家探讨 Windows PowerShell 时(无论在会议上、公共 Windows PowerShell 新闻组还是在我的网站),总会有管理员告诉我他们“打算等等”再学习 Windows PowerShell。为什么呢?他们给出的最常见的理由是

“现在没有时间学习如何编写脚本”。

这种情绪在管理员中很普遍。我们管理员群体的工作很忙,所以实在没有太多空闲时间学习新的技术。但是,我们必须不断努力向前,学习 Windows Vista®、Windows Server® 2008 和 Exchange Server 2007 等新软件,这样才能站在对工作至关重要的技术的前沿。Windows PowerShell™ 是这些技术中举足轻重的一部分。

我常听到的管理员拖延学习在 Windows PowerShell 中编写脚本的另一个理由是,他们“担心成为程序员”(一位管理员同行曾这样说)。我可以在这方面帮上点忙。如果您发现不用编写一丁点代码就能使用 Windows PowerShell 完成一些真正令人诧异的任务,可能会感到吃惊。

准备好进行管道传输

我以前写过有关 Windows PowerShell 所使用的极其灵活、强大且面向对象的管道的文章。(虽然“面向对象”这个词可能使您怀疑我要针对程序员领域进行讲述,但是请继续往下看。)我喜欢管道的一个原因就是交互使用管道的方式。它可以提供即时结果并能使您轻松精简这些结果,以便使所获结果确实是您所需的内容。例如,假设我希望获得多台远程计算机的可用磁盘空间清单,并且现在已将那些计算机的名称列在名为 C:\Computers.txt 的文本文件中。这仅是一个纯文本文件,每行包含一个计算机名称,不像数据库那样复杂。

首先,我会查看是否能从本地计算机上检索到此信息。毕竟,如果可以在一台计算机上搞清楚,那么在多台计算机上执行此任务就应当简单多了。还要记住,此处不会涉及到脚本编写!我将在外壳程序中以交互方式执行所有操作,按 Enter 键即可获得结果。

以下是有关此类任务的提示。每次要清点远程计算机上的管理信息时,可能都要使用 Windows® 管理规范 (WMI)。根据此示例的目的,假设现在我只知道需要使用 WMI。所以我跳到 Live Search,然后输入“windows wmi 可用磁盘空间”作为我的搜索项。将显示其摘录中列出“Win32_LogicalDisk”短语的若干条结果。这看起来像 WMI 类,而且可能正是我要的结果。甚至不必费心单击其中任一搜索结果。改为输入“Win32_LogicalDisk”作为新的搜索项,第一条结果是 MSDN® 站点上的 Win32_LogicalDisk 文档页面 (msdn2.microsoft.com/aa394173.aspx)。所以,我跳到这一页,发现此类的一个属性是 FreeSpace,看起来大有希望。

查找 Cmdlet

下一步,需要查找将为我检索 WMI 类属性的 cmdlet。Windows PowerShell 引人注目的一点是它具有极妙的内置自我发现功能,也就是说,外壳可帮助您查找它自己的功能。因此我输入 Help *wmi* 来查找外壳程序对使用 WMI 的了解。如图 1 所示,该结果为别名 Gwmi 及其指向的 cmdlet(即 Get-WMIObject)。基本上,可以通过两种方法访问同一个功能,这意味着我决定使用哪种方法并不是很困难。既然这两种方法都可以达到我的目的,而 Gwmi 字符较少,易于输入,所以我使用这种方法:

图 1 外壳程序对使用 WMI 的了解

图 1** 外壳程序对使用 WMI 的了解 **(单击该图像获得较大视图)

PS C:\> gwmi win32_logicaldisk

请注意 Windows PowerShell 不区分大小写,因为它知道管理员没有时间注意诸如按 Shift 键之类的繁琐细节。

图 2 所示,此命令的结果包含我的全部 5 个本地驱动器及其看起来以字节形式列出的 FreeSpace 属性。同时还列出了每个驱动器的 DriveType 属性,计算机上显示值 2、3 和 5。

图 2 所有的本地驱动器及其 FreeSpace 属性

图 2** 所有的本地驱动器及其 FreeSpace 属性 **(单击该图像获得较大视图)

本月 cmdlet

您也许用过 Get-Content 读取文本文件的内容。它将文件的每一行作为一个 [string] 对象,并将这些对象传入管道中供其他 cmdlet 使用。但是,Get-Content 拥有许多选项,使其更具灵活性:例如,–readCount 参数允许指定可一次向管道传入 [string] 对象的数量(默认情况下为全部传送),而 –totalCount 参数控制从文件读取的总行数。对于实在非常大的文件,为了提高性能,您可能不希望一次处理整个文件。这两个参数对处理这种大文件非常有用。

下面是另一个用起来很方便的参数:–encoding 参数,它用于正确读取各种文件编码类型,包括 Unicode、ASCII、UTF7、UTF8 和许多其他编码类型。若要查看受支持的编码类型的完整列表,请运行 help gc(gc 是 Get-Content 的别名)。

精简数据

这里有两个问题。首先,我并不关心其他所有属性,仅需要 FreeSpace。其次,我不想了解光驱、可移动驱动器或网络驱动器的可用空间,我只想了解本地硬盘。也许,DriveType 属性有助于对其进行区分。我返回到 Web 浏览器,发现文档中包含一个介绍不同 DriveType 值所代表的含义的表。我仅需要 DriveType 值为 3 的磁盘,值 3 表示该驱动器为本地磁盘。因为以前没有进行过此类操作,所以我决定看看 Gwmi 命令是否可以为我执行某些筛选操作。运行 Help Gwmi,即列出了有关如何使用该命令的详细信息,还包含一些示例。我找到一个看起来很有用的参数 –filter,所以决定试试运行以下命令:

gwmi win32_logicaldisk -filter "drivetype = 3"

成功了!现在,我的驱动器列表仅包含本地固定磁盘。这样,第二个问题就解决了,现在,我需要解决第一个问题 — 除去我不关心的所有属性。

运行 Get-Command 列出所有的 Windows PowerShell cmdlet,最终我发现了 Select-Object。它的描述为使用它将“选择某个对象或对象集的指定属性”。所以我尝试运行以下命令:

gwmi win32_logicaldisk -filter "drivetype = 3" | select freespace

通过将 Gwmi 的结果传送到 Select(Select-Object 的别名),我可以获得需要的属性。天哪。这些结果并不好,因为我仅得到一个数字列表,而不知道可用空间列表与驱动器的对应关系。所以,我回去查找文档,看到 DeviceID 属性包含驱动器号。我快速修改命令,再次尝试:

gwmi win32_logicaldisk -filter "drivetype = 3" | select deviceid,freespace

好极了!因为仅列出了两个属性,所以外壳程序可将信息置于一个规范的表中。(下个月,我将介绍 Windows PowerShell 何时选择使用列表和表。)

计算

我已经检索了有关可用空间的信息,但是以字节为单位,并不如以兆字节或千兆字节为单位有用。或许,我并不需要 FreeSpace 属性本身,而是需要从 FreeSpace 属性计算得出的值。ForEach-Object cmdlet(或其诸多别名之一,如 %)可帮助解决此问题。此 cmdlet 允许使用特殊的变量 $_ 指代当前的逻辑磁盘,并且允许单独访问每个磁盘属性并进行计算。您可以看到我已修改的命令:

gwmi win32_logicaldisk -filter "drivetype = 3" | % { $_.deviceid; $_.freespace/1GB }

这里我所做的是删除 Select 并使用 ForEach-Object 的别名 (%) 代替它。此 cmdlet 仅需要我告诉它如何处理得到的每个 Win32_LogicalDisk,我已告诉它获取 DeviceID 属性并以 GB 为单位划分 FreeSpace 属性(Windows PowerShell“理解”KB、MB 和 GB 的意思),因此我的输出将以 GB 为单位。

许多计算机

现在我已在一台计算机上取得成功,因此就可在多个系统上进行此操作了。我了解如何获取文本文件的内容。这可通过采用与过去 MS-DOS® 时代相同的方法完成 — 使用 Type 命令,实际上,此命令在 Windows PowerShell 中是 Get-Content 的别名:

Type c:\computers.txt

我现在要做的是使用每台计算机并对每一台计算机执行我成功创建的 WMI 命令。此短语“for each one”(对每一台)应会使您想起一个 cmdlet — 具体来说,就是 ForEach-Object。像那些家居设计电视节目中说的那样,让我们为您揭晓吧,请看最后一个命令:

type c:\computers.txt | % { $_; 
gwmi –computername $_ win32_logicaldisk -filter "drivetype=3" | % { $_.deviceid; $_.freespace/1GB} }

很可怕,对吗?由于我只使用别名而不使用 cmdlet 名称,造成了难以读取的情况。但是,如果一次只浏览一点,解读起来就相当容易了:

  • 首先“键入”文本文件的内容。
  • 将这些内容传送到 ForEach-Object。
  • ForEach-Object 使用 $_ 变量(这是当前的计算机名称)输出当前项目。
  • 然后,ForEach-Object 执行我的 WMI 命令,它有另一个 ForEach-Object 调用。

将别名名称扩展为 cmdlet 名称可能很有帮助。所以下面的命令与之前的相同,但这次我拼写出了完整的 cmdlet 并将命令分行,因而可轻松检查每一节:

Get-Content C:\Computers.txt | 
ForEach-Object { 
 $_; Get-WMIObject –computername $_ 
Win32_LogicalDisk -filter "DriveType=3" | 
 ForEach-Object { 
 $_.DeviceID; $_.FreeSpace/1GB
 }
}

该结果并不吸引人,但能正常发挥作用,如图 3 所示。

图 3 最终结果

图 3** 最终结果 **(单击该图像获得较大视图)

无需脚本

此预排旨在说明在不用编写脚本的情况下,也可以使用 Windows PowerShell。而且,仅使用一些诸如此类的示例,再稍微研究一下 Windows PowerShell 本身,您实际上可以找到完成打算执行的任务所需的一些信息。

关键是现在 Windows PowerShell 已不足不奇。何不现在就开始学习使用它呢?您甚至可能发现自己会想像没有 Windows PowerShell 时是怎么过来的,当然您不应该因脚本编写这个词而因噎废食。

Don Jones是 SAPIEN Technologies 的首席脚本权威,也是 ScriptingTraining.com 的一名讲师。您可以通过网站 ScriptingAnswers.com 与 Don 联系。

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