您好,脚本专家!不太辛苦的工作照样也有回报

The Microsoft Scripting Guys

下载本文中所用的代码: HeyScriptingGuy2008_06.exe (150KB)

数百年来, 人们一直深信辛勤劳动终有回报;真正的满足来自勤恳的工作;真正的快乐源自结果,而不是过程;对,您能明白就好。任劳任怨地工作(像灰姑娘一样),终有一天会获得回报。

备注:这是真的吗?如果辛勤工作,不仅会更加快乐,而且总有一天会因为自己的努力而得到回报吗?为什么要问这种问题?

当然,灰姑娘非常幸运;毕竟,与邪恶的继母和两个坏心眼的异母姐姐生活在一起,肯定会遇到各种各样的艰辛。但如果您不像她那么“幸运”呢?如果您没有与邪恶的继母和两个坏心眼的异母姐姐生活在一起,又该怎么办呢?假设您面对毫无尽头、令人精疲力尽又吃力不讨好的工作,该如何度日呢?您将如何寻找真正的快乐?

如果您终日淹没在无穷无尽的工作中,脚本专家建议您尝试编写一个脚本,使其以精致、简洁的表格格式输出数据,如图 1 所示。

图 1 表格输出

图 1** 表格输出 **(单击该图像获得较大视图)

我们说过,灰姑娘非常幸运:她必须将房子打扫得一尘不染,完成后还要坐在充满烟尘的壁炉边。但是,说到编写以表格格式显示数据的脚本,即使是灰姑娘也会望而怯步。坐在煤灰中是一回事,而编写以表格格式输出数据的脚本是另一回事。

备注:除非您必须坐在壁炉旁边编写脚本,它们才是一回事,呵呵。

为什么这项任务会让灰姑娘望而怯步?因为它相当困难。在 VBScript 中以表格格式显示数据的唯一方法是执行如下操作:

  • 首先,确定每列的最大大小。例如,您可能希望服务的显示名称最多占据 52 个字符空间。
  • 据此,可以计算一段数据的字符数。例如,显示名称 Adobe LM Service 有 16 个字符。
  • 如果显示名称超出 52 个字符的限制,要确定需要从字符串末尾去除多少个字符,才能适合分配的空间。如果显示名称不足 52 个字符,要确定必须在此字符串末尾添加多少个空格,才能使其长度正好等于 52 个字符。
  • 对下一组数据重复以上步骤,然后是再下一组,依此类推。

如果您熟悉灰姑娘的故事,应该知道神仙教母可能会突然现身,将老鼠变成系统管理员(神仙嘛,自然能千变万化),然后那只老鼠会为您编写这个脚本。不过,这种幸运不会砸在您头上,只能全靠自己了。

不过从好的方面来看,您将是世界上最充实的人,因为您也将成为世界上最辛苦工作的人。到目前为止还没有其他工作比它更艰难。

当然,有些人比较懒,可能不愿意这样“以苦为乐”。如果您是这样一个人,很可能会想:“谢天谢地,我们有脚本专家;他们会告诉我如何将老鼠变成系统管理员,然后告诉我如何使老鼠将输出转化为表格形式。”那么,很遗憾地告诉您一个坏消息:脚本专家并不知道如何将老鼠变成系统管理员。(但我们可以将系统管理员变成老鼠,如果这样对您有所帮助。)我们并不知道如何让别人为您编写脚本,更别说编写以表格格式输出数据的脚本。

但是没关系,只要您运行的是 Windows® XP 或 Windows Server® 2003(大多数人如此),便不需要老鼠为您编写脚本。(不过,很遗憾,这并不适用于 Windows Vista®。)此时,您可以利用 Microsoft.CmdLib 对象自己轻松编写脚本。请参见图 2 中的示例脚本。

Figure 2 创建表格显示

Dim arrResultsArray()
i = 0

Set objCmdLib = _
  CreateObject("Microsoft.CmdLib")
Set objCmdLib.ScriptingHost = _
  WScript.Application

arrHeader = Array("Display Name", _
  "State", "StartMode")
arrMaxLength = Array(52, 12, 12)
strFormat = "Table"
blnPrintHeader = True
arrBlnHide = Array(False, False, False)

strComputer = "."

Set objWMIService = GetObject("winmgmts:\\" _
  & strComputer & "\root\cimv2")

Set colServices = objWMIService.ExecQuery _
  ("Select * FROM Win32_Service")

For Each objService In colServices
  ReDim Preserve arrResultsArray(i)
  arrResultsArray(i) = _
  Array(objService.DisplayName, _
    objService.State,objService.StartMode)
  i = i + 1
Next
objCmdLib.ShowResults arrHeader, _
  arrResultsArray, arrMaxLength, _
  strFormat, blnPrintHeader, arrBlnHide

稍微解释一下,Microsoft.CmdLib 是 Windows XP 和 Windows Server 2003 附带的 COM 对象。此对象有许多不同的功能;有关详细信息,请在命令提示符下键入 cmdlib.wsc /?,并阅读屏幕上弹出的脚本文件中的注释。今天,我们只介绍 Microsoft.CmdLib 的一项功能:以表格格式显示数据的功能。

这显然会产生一个问题:如何以表格格式显示数据?让我们看看是否可以找到解决办法。您可以看到,我们的示例脚本首先会定义名为 arrResultsArray 的动态数组:

Dim arrResultsArray()

在典型脚本中,一旦检索到数据,它们就会立即显示到屏幕上。但是,脚本专家怎么会采用这么平凡的做法呢?在此脚本中,我们将不显示检索到的数据,而是在数组中存储所有返回的数据,然后利用 Microsoft.CmdLib 设置数据格式并显示数据。

换句话说,这正是我们要先创建动态数组(即可在编写脚本期间调整大小的数组)的原因。定义数组后,将名为 i 的计数器变量的值设置为 0;我们将使用此变量来跟踪该数组的当前大小。

稍微解释一下,我们将 i 的值设置为 0,是因为数组第一项的索引编号始终指定为 0。因此,将第一项添加到数组时,添加的是项目 0 而不是项目 1。

接下来,我们需要初始化 Microsoft.CmdLib 对象的实例,这是以下两行代码的用途:

Set objCmdLib = _
CreateObject("Microsoft.CmdLib")
Set objCmdLib.ScriptingHost = WScript.Application

这将为我们生成一个小代码块:

arrHeader = Array("Display Name", "State", "StartMode")
arrMaxLength = Array(52, 12, 12)
strFormat = "Table"
blnPrintHeader = True
arrBlnHide = Array(False, False, False)

在此,我们要设置几个参数以配置输出。在第一行中,将这些值指派给名为 arrHeader 的数组;您可以看到,它们正是输出表格中各列的标题。对于此示例脚本,我们要检索的是有关计算机上运行的服务的信息,然后显示每项服务的 DisplayName、State 和 StartMode 属性值。

为了顾名思义,我们为数组指派了列名称 Display Name、State 和 StartMode(当然,我们完全可以简单地为列名称指定 A、B 和 C;Larry、Moe 和 Curly;或者其他任何名称。列名称不必与属性名称相同)。

备注:灰姑娘的故事有许多版本,包括她异母姐姐的名字。在迪斯尼版本的灰姑娘故事中,两位姐妹的名字是 Drizzella 和 Anastasia,这正好是我们的脚本编辑的姓氏和中间名!

在第二行代码中,将这些值指派给另一个名为 arrMaxLength 的数组。此数组保存该表格中每列的大小。在输出中,我们希望为第 1 列(服务显示名称)分配 52 个字符空间;然后分别为每个服务状态和启动模式分配 12 个字符空间。(Microsoft.CmdLib 会自动在各列之间插入空格。)如果希望列的大小分别为 52、12、12 个字符空间(实际上也是如此),则可以使用如下所示的代码:

arrMaxLength = Array(52, 12, 12)

第三行指定数据的输出格式。我们希望以表格形式显示数据;因此可将 strFormat(保存输出类型的变量)的值设置为 Table。假设我们要改用逗号分隔值列表格式显示数据。在这种情况下,可以将格式设置为 CSV,如下所示:

strFormat = "CSV"

这样,我们就会得到类似以下内容的输出:

"Display Name","State","StartMode"
"Adobe LM Service","Stopped","Manual"
"Adobe Active File Monitor V4","Stopped","Manual"
"Alerter","Stopped","Manual"
"Application Layer Gateway Service","Running","Manual"
"Apple Mobile Device","Running","Auto"
"Application Management","Stopped","Manual"

请注意,Microsoft.CmdLib 不仅会在各项之间添加逗号,还会将各个值用双引号引起来。这个功能比您最初设想的还棒。毕竟,要手动执行操作,您必须使用以下代码:

Wscript.Echo Chr(34) & objService.DisplayName & Chr(34) & "," & Chr(34) & objService.State & Chr(34) & "," & Chr(34) & objService.StartMode & Chr(34)

看起来不怎么样啊!

怎么?您不喜欢表格,也不喜欢 CSV 格式吗?那么,可以尝试将格式设置为 List,就会得到类似以下内容的输出:

Display Name: Adobe LM Service
State:        Stopped
StartMode:   Manual
Display Name: Adobe Active File Monitor V4
State:        Stopped
StartMode:   Manual

但这有点跑题了。(有时我们容易跑题。)定义输出格式后,将名为 blnPrintHeader 的变量的值设置为 True:

blnPrintHeader = True

我们将使用 blnPrintHeader 告知 Microsoft.CmdLib 打印列标题。若不需要打印列标题呢?没关系;如果不需要,就将 blnPrintHeader 设置为 False:

blnPrintHeader = False

最后,使用下面的这行代码:

arrBlnHide = Array(False, False, False)

要显示数据时,Microsoft.CmdLib 会提示您选择显示或隐藏列。要隐藏列(即隐藏特定属性的数据),请将该属性值设置为 True;要显示列,请将此值设置为 False。

在输出中,我们将按顺序显示 DisplayName、State 和 StartMode 属性的值,因此在数组中使用的值分别是 False、False、False。如果要显示 DisplayName、隐藏 State、显示 StartMode,该怎么办?在这种情况下,可使用下面的这行代码:

arrBlnHide = Array(False, True, False)

请记住,使用 False 会显示列,使用 True 会隐藏列,不要搞混了。

此时,我们已准备好输出某些数据 — 如果我们实际提供了一些数据,就会输出数据。(脚本专家是否曾编写过无法输出数据的脚本,然后花费大量时间调试该脚本,结果却发现仅仅因为最初没有实际检索任何数据?呃,当然没有这种事,您为什么会这么想?)

知道这一点后,我们下一步就是绑定到本地计算机上的 Windows Management Instrumentation (WMI) 服务,然后使用 ExecQuery 方法检索有关该计算机上安装的所有服务的信息:

strComputer = "."
Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")

Set colServices = objWMIService.ExecQuery ("Select * FROM Win32_Service")

我们可能需要提醒一下,在此您并非只能使用 WMI 数据。实际上,Microsoft.CmdLib 可使用任何类型的数据。WMI 只是检索一组数据的简便方法。

一旦我们有了与服务相关的数据集合,便可以设置一个 For Each 循环,遍历集合中的所有服务。不过,我们不再显示每项服务的属性值,而是执行以下代码:

ReDim Preserve arrResultsArray(i)
arrResultsArray(i) = Array(objService.DisplayName, objService.State,objService.StartMode)
i = i + 1

在此代码块中,我们将执行什么操作?在第 1 行中,调整数组的大小,使用 ReDim Preserve 命令不仅可以调整数组的大小,还可以保存该数组中所有的现有数据。(如果没有 Preserve 关键字,虽然可以调整数组大小,但该数组中所有当前数据都将被清除。)

将数组大小设置为多少?第一次运行循环时,我们将数组大小设置为 0;换句话说,我们将创建包含一个元素的数组。但如何确定我们设置的数组大小为 0 呢?因为我们使用了计数器变量 i,而该变量等于 0。

因此,如果使用变量 i 表示该数组的大小,是否意味着我们设置的大小始终为 0?的确如此,除非每次执行循环时将 i 值递增 1;您可以看到,在代码块的第 3 行将执行此操作。

现在,只剩下一行代码了:

arrResultsArray(i) = Array(objService.DisplayName, objService.State,objService.StartMode)

在此,我们只获取感兴趣的属性值 — DisplayName、State 和 StartMode,并将它们添加到数组 arrResultsArray 中。请注意,我们不是要单个添加属性值,而是将它们作为值数组添加。当然,这有点奇怪:它意味着数组 arrResultsArray 中的每项将是另一个数组;这正是 Microsoft.CmdLib 的工作原理。

当然,这可能又使您开始担心。“数组中的数组?究竟如何才能在充满数组的数组中访问所有各个值?”不要慌,这很容易:Microsoft.CmdLib 会自动帮您处理。

备注:很抱歉,不过访问数组的数组中的所有各个值大概是 Microsoft.CmdLib 唯一能为您执行的操作。不过,下一版对象可能会考虑加入其他功能,如打扫邪恶继母的房间,刷盘子以及为令人讨厌的异母姐姐做衣服等。

实际上,通过调用 ShowResults 方法并将此方法传递给我们以前在脚本中配置的所有数组和变量,就可以使用简洁的表格格式输出数据:

objCmdLib.ShowResults arrHeader, arrResultsArray, arrMaxLength, strFormat, blnPrintHeader, arrBlnHide

所有这些操作将会得到什么样的结果?它看上去就像按所需格式设置的输出,如图 1 所示。

不错,是吧?您获得了美观的输出,而且几乎不需执行任何操作。(而且,下一个脚本甚至更加容易,因为您只需对第一个脚本做一些小的更改。)

诚然,您不像灰姑娘那样日夜操劳,才能以表格形式显示脚本输出,所以您也无法享受像灰姑娘那样的满足感。老实讲,您也不太可能嫁给王子;本文也没想指导您如何嫁给王子。不过,为了从 VBScript 脚本获得这么好的输出,付出一点小代价也是值得的,尤其是想一想当今的王子哪有那么好得到。

而且谁晓得:说不定您自己也能找到一位王子呢。毕竟,就连皇室成员也需要了解他们计算机上安装的所有服务信息,不是吗?

Scripto 博士的脚本谜题

每月挑战不仅测试您解决谜题的技能,还测试您的脚本编写技能。

2008 年 6 月:PowerShell Pathway

在每个谜题中,从水平、垂直或对角方向连接字母以组成 Windows PowerShell cmdlet 的名称。每个字母只能使用一次。如下所示:

此谜题创建的 cmdlet 是 New-Alias。现在该您了。下面还有三个谜题:

ANSWER:

Scripto 博士的脚本谜底

答案:PowerShell Pathway,2008 年 6 月

Read-Host

Set-AuthenticodeSignature

The Microsoft Scripting Guys 为 Microsoft 工作,也就是受雇于 Microsoft。在不玩、不教或不看棒球(以及各种其他活动)的时候,他们负责管理 TechNet 脚本中心。要查看相关信息,请访问 www.scriptingguys.com

© 2008 Microsoft Corporation 和 CMP Media, LLC。保留所有权利;未经允许不得复制本文的部分或全部内容.