您好,脚本专家!WinRM 系列文章第二部分发布了

The Microsoft Scripting Guys

当脚本 专家开始着手创建关于 Windows® 远程管理 (WinRM) 的由两部分组成的系列文章时,一个非常重要的问题摆在了眼前:安全性。毕竟,我们脚本专家非常了解哈里波特系列的最后一部发行时随之产生的问题:因一时疏忽,导致该书的预订副本提早交付,并且其发行日期早于官方发行日期。(这是否会产生问题呢?当然不会;出版社只是建议读者在发行日期前不要阅读这本书。)

但这也仅是冰山一角。本书上市之前,该书的内容和系列的结局就已经泄露了。(也许您尚未阅读过本书,其内容如下:哈里波特实际上就是某类巫师或具有类似身份的人!)同样,有时作者 J. K. Rowling 甚至尚未交稿,读者就可以通过 Internet 立即获得草稿的扫描副本。总而言之,这是一种轻微的安全崩溃,脚本专家决定要坚决制止这种情况发生在自己身上。毕竟,如果人们如此顽固地泄露哈里波特系列的结局,那么可以想象,假以时日,他们可能就会将触角伸向脚本专家按两部分撰写的 WinRM 系列文章的完结篇。

幸运的是,脚本专家能够抵制这种情况的发生,保守自己的秘密。当然,这主要是因为直到交稿截止日期的前一刻,他们才真正写完该系列的第二部分。但是,八月份是撰写此专栏的脚本专家休假的日子,和绝大多数 Microsoft 员工不同的是,他从来不在休假期间看电脑,更别提使用电脑了。

情况确实如此,照这样看来:即使是休假以外的时间,他也很少看电脑,更别提使用电脑了。但这是另外一回事。

不管怎样,我们知道你们许多人在过去的四个星期中都是辗转反侧,彻夜难眠,一直担心 WinRM 传奇将以何种方式结束。值得高兴的是,在漫长痛苦的期待中饱受煎熬的不眠之夜终于结束了。这里,脚本专家撰写的 WinRM 系列文章(共两部分)激动人心的尾声终于第一次揭晓了。实际上,图 1 包含了上述所有信息。

Figure 1 The denouement

strComputer = "atl-fs-01.fabrikam.com"

Set objWRM = CreateObject("WSMan.Automation") Set objSession = objWRM.CreateSession("http://" & strComputer)

strDialect = "http://schemas.microsoft.com/wbem/wsman/1/WQL" strResource = "http://schemas.microsoft.com/wbem/wsman/1/wmi/root/cimv2/*" strFilter = "Select Name, DisplayName From Win32_Service Where State = 'Running'"

Set objResponse = objSession.Enumerate(strResource, strFilter, strDialect)

Do Until objResponse.AtEndOfStream strXML = objResponse.ReadItem

    Set objXMLDoc = CreateObject("Microsoft.XMLDom") objXMLDoc.async=False objXMLDoc.loadXML(strXML)

    Set objElement = objXMLDoc.documentElement Wscript.Echo "Name:" & objElement.ChildNodes(0).ChildNodes(0).NodeValue Wscript.Echo "Display Name:" &  objElement.ChildNodes(1).ChildNodes(0).NodeValue Wscript.Echo Loop

是的,我们知道:故事情节的跌宕起伏也会让我们的心跟着七上八下。objElement.ChildNodes(0).ChildNodes(0).NodeValue!谁知道接下来会发生什么事?当然,因为这在我们的安全锁定范围内,即使脚本专家自己也不知道该系列将如何结束。但是现在秘密公开了。

在进一步探讨之前,我们应将该系列作为一个整体快速回顾一下,以防有人因为不理解而无法读懂第 1 部分(该部分已在 technetmagazine.com/issues/2007/11/HeyScriptingGuy 上提供)。 现在回到第 1 部分,我们在该部分中引入了 WinRM,Windows Server® 2003 R2、Windows Vista® 和 Windows Server 2008 中都提供了这项新技术,使用该技术可更轻松地通过 Internet 甚至防火墙管理计算机。当然,Windows 管理规范 (WMI) 始终能够远程管理计算机;但是 WMI 依赖分布式 COM (DCOM) 作为其远程管理技术。这并没有什么问题,只是在默认情况下,许多防火墙都会阻止 DCOM 通信。的确,您可以打开相应的端口并允许 DCOM 通信,但许多网络管理员并不愿意这样做,他们很担心为 DCOM 放行的同时还会带来各种类型的恶意损害。

因此,WinRM 是“WS-Management 协议的 Microsoft 实现,该协议是标准的基于 SOAP 的协议,它不受防火墙的影响,允许不同供应商的硬件和操作系统相互操作。”这只是对目前能够使用标准 Internet 协议(如 HTTP 和 HTTPS)执行远程管理的另一种说法。

正如我们上个月所讲的,使用 WinRM 可以轻松地连接到远程计算机并从中检索 WMI 信息。那么,这是否意味着它是一项非常完美的技术呢?当然不是,最起码并不完全是这样。正如我们前面所指出的,当 WinRM 将数据发送回调用脚本时,该数据会以 XML 格式返回。毫无疑问,分析和使用 XML 有点棘手,对于在此领域经验有限的系统管理员而言尤其如此。正因为如此,WinRM 附带了 XSL 转换,以便将返回的数据转换成更具可读性的格式。

太好了,只是这也意味着您的输出将始终如下所示:

Win32_Service AcceptPause = false AcceptStop = true Caption = User Profile Service CheckPoint = 0 CreationClassName = Win32_Service

当然这也未必是坏事,但这同时还意味着您的输出将始终写入到命令窗口;默认情况下,无法轻松地将数据保存到文本文件、将该数据写入到数据库或 Microsoft® Excel® 电子表格,或者对该数据执行任何其他处理,而只能在屏幕上显示这些信息。这远远不够。

除此之外,如果选择只返回 WMI 类的某些选定的属性(可能希望减少网络通信量),问题会变得更糟。如果只使用某个类的几个属性而不是该类的所有属性,输出内容将如下所示:

XmlFragment DisplayName = Windows Event Log Name = EventLog

XmlFragment DisplayName = COM+ Event System Name = EventSystem

这种显示信息的方式很有意思,但却不怎么合乎我们的审美情趣(特别是整个报告中充斥着标题 XmlFragment)。

那么您该如何做呢?编写自定义代码解析此 XML 数据并对其进行格式化?这听起来不像系统管理脚本编写人员要做的事。还是有别的处理办法?

如果想把事情做好,就自己动手

事实证明,处理 WinRM 返回的原始 XML 数据这项任务看似令人生畏,但实际上却没有那么可怕。本月,我们将向您介绍一种解析和格式化 XML 数据的简单方法。我们所用的方法并不是处理 XML 的唯一方法,不过没关系;此处,我们主要是想说明您不必依赖 XSLT 转换。一旦您掌握了这个基本理念,那么,此后处理 WinRM 数据便不会有任何限制。

有一点需要注意:由于本专栏的篇幅要求,我们将跳过刚刚在图 1 中显示的脚本中的大部分内容。不过,这样应该不会造成太大的影响,因为上月专栏中详细介绍了此脚本前面部分的三分之二内容。我们将着重介绍此脚本最后的三分之一内容,而这部分正是我们实际处理返回数据的部分。

您可以看到,这部分 WinRM 内容将承接以下情节:创建 WSMan.Automation 对象实例,查询远程计算机(在本例中为 atl-fs-01.fabrikam.com),以及接收此计算机上正运行的所有服务的相关信息。接下来就转到了该脚本的代码行,您可以在此处构建一个 Do Until 循环,用于读取和处理返回的 XML 数据;直到读取和处理完所有数据后,此循环才会停止。(为便于理解,也可以这样说:当 XML 文件的 AtEndOfStream 属性值为 True 时,此循环才会停止。)

以下是我们要讨论的代码行,同时也是本月故事的真正开始:

Do Until objResponse.AtEndOfStream

在循环内部,我们首先使用 ReadItem 方法读取返回的 XML 数据的第一部分。(因为我们要使用 WinRM 并且要检索服务信息,所以这第一部分将包含集合中第一个服务返回的所有数据。)将此数据(同样为 XML 格式)存储在名为 strXML 的变量中,然后,创建 Microsoft.XMLDom 对象的实例。实际上,这提供了一个可供我们使用的空白 XML 文档:

Set objXMLDoc = _ CreateObject("Microsoft.XMLDom")

空白文档准备就绪后,即可将 Async 属性的值设为 False,然后调用 loadXML 方法。

这又引出了下列问题:您为什么会这么说?为什么要将 Async 属性的值设为 False?为什么要调用 loadXML 方法?这些问题提得很好;要是这些问题都是由我们自己想出来的该有多好!

对于初学者而言,Async 属性表示该脚本是否允许异步下载 XML 信息。如果该属性为 True,将立即开始下载,并将控制权返回脚本,而不论是否已完成下载。当然,这听起来相当不错。但遗憾的是,脚本随后会像已包含需要的所有信息一样继续执行。如果该脚本并不包含需要的所有信息,那么就可能会遇到问题。因此,我们将 Async 属性设为 False。

注意:当然,您可以编写代码以定期监视下载状态,以此确保不会过早执行该脚本。这个方法虽然有效,但是直接将 Async 属性的值设为 False 更简单。执行此操作时,将阻止该脚本运行,直至下载完成;换句话说,一旦下载过程开始,脚本就会耐心地等待下载完成,然后再执行其他操作。

至于 loadXML 方法,顾名思义:此方法将设置好格式的 XML 文档(或文档片段)载入我们的空白文档。我们只需调用 loadXML 方法,将变量 strXML 作为唯一的方法参数进行传递:

objXMLDoc.loadXML(strXML)

最终结果如何呢?现在已将 WinRM 数据转换成了虚拟的 XML 文档。这意味着我们可以使用标准的 XML 方法来分析此虚拟文档了。

为此,我们必须使用下列代码行创建指向 XML 文件中根元素的对象引用:

Set objElement = objXMLDoc.documentElement

此时,我们就可以享受其中的乐趣了。(当然,假设您的乐趣就是分析 XML 文件。这确实是我们脚本专家认为很有趣的一件事。)

您可能已经想起,我们的 WMI 查询语言 (WQL) 查询(用 WinRM 术语称之为筛选)如下所示:

strFilter = "Select Name, DisplayName " & _ "From Win32_Service Where State = 'Running'"

您可以看到,我们已经从 Win32_Service 类中请求了两个属性:Name 和 DisplayName(脚本中还包括一个 Where 子句,限制为正在运行的服务所返回的数据。但在这里我们不必为此事费神)。提前请求两个属性是否很重要?请求的顺序是否很重要?也许是的。我们到底该如何了解这些内容呢?

嗯,这个问题问得好。作为本文的作者,我们可能确实应该知道这些问题的答案。这没有什么不对,实际情况是,我们知道上述两个问题的回答都是肯定的。通过 WQL 查询提前请求两个属性是否很重要?是的,确实重要;毕竟,返回给我们的属性值只有这两个。(执行 Select * From 查询则会返回类中所有属性的值。)

那么,这两个属性的顺序是否也很重要?当然重要。指定属性名称的顺序就是属性值返回的顺序。由于所有属性值都会作为根元素的子节点(或部分)返回,所以这很重要。哪个属性值会作为第一个子节点返回呢?这个问题很简单。在本例中,第一个子节点将是 Name 属性,因为该属性在 WQL 查询中排在首位。哪个属性会作为第二个子节点返回呢?没错,就是 DisplayName 属性,因为该属性在查询中排在第二位。

稍等一下。这个问题是您自己解决的,还是有人提前向您泄露了本专栏的副本?嗯......

不管怎样,这会使回显 Name 属性的值变得更容易。我们只需引用第一个 ChildNodes(项 0)集合中第一项(项 0)的 NodeValue,如下所示:

Wscript.Echo "Name:" & _ objElement.ChildNodes(0).ChildNodes(0).NodeValue

那么我们如何引用 DisplayName 属性的值呢?在本例中,我们引用的是第二个 ChildNodes(项 1)集合中第一项的 NodeValue:

Wscript.Echo "Display Name:" & _ objElement.ChildNodes(1).ChildNodes(0).NodeValue

如果 WQL 查询中还有第三个属性(比如 Status),该如何引用?这时,我们只需引用第三个 ChildNodes(项 2)集合中第一项的 NodeValue:

Wscript.Echo "Status:" & _ objElement.ChildNodes(2).ChildNodes(0).NodeValue

依此类推,直到最后一个属性。

那么,此时我们的脚本输出内容是什么样子的呢?输出类似以下内容:

Display Name:Windows Event Log Name:EventLog

Display Name:COM+ Event System Name:EventSystem

当然,上述输出内容并未显示出与默认的 WinRM 输出的所有差别(尽管我们已去掉了多余的 XmlFragment 标题)。区别在于:通过使用各个属性值,我们已不再受默认格式的限制(请注意:我们使用的是标签 Display Name,而不是 DisplayName),也不再局限于只在命令窗口中显示信息。

那您是否更愿意将此数据改写到 Excel 电子表格呢?该操作很容易实现。首先在调用 Enumerate 方法的 WinRM 脚本代码行后直接插入下列代码块(用于创建和配置新 Excel 电子表格):

Set objExcel = _ CreateObject("Excel.Application") objExcel.Visible = True Set objWorkbook = objExcel.Workbooks.Add() Set objWorksheet = objWorkbook.Worksheets(1)

i = 2 objWorksheet.Cells(1,1) = "Name" objWorksheet.Cells(1,2) = "Display Name"

现在,将原始的 Do Until 循环替换为您在图 2 中看到的循环。试一试,看看会发生什么情况。

Figure 2 New Do Until loop

Do Until objResponse.AtEndOfStream strXML = objResponse.ReadItem

  Set objXMLDoc = CreateObject("Microsoft.XMLDom") objXMLDoc.async=False objXMLDoc.loadXML(strXML)

  Set objElement = objXMLDoc.documentElement objExcel.Cells(i, 1) = objElement.ChildNodes(0).ChildNodes(0).NodeValue objExcel.Cells(i, 2) = objElement.ChildNodes(1).ChildNodes(0).NodeValue i = i + 1 Loop

WinRM,第 3 部分?

在本月专栏和上月专栏之间的间隔,您应该具有足够的时间开始使用 WinRM。我们希望如此;WinRM 是一项颇具诱惑力的新技术,该技术使远程管理计算机(同时还能维护安全和安全保护)变得更加简单。当然,这会让所有人都想到一个问题:这是否意味着 WinRM 传奇还会有第三部?

但是很抱歉,我们不能透露这方面的信息。不是因为安全问题,而是因为这取决于脚本专家通常的规划和决策过程,我们也不知道是否会继续编写关于 WinRM 方面的文章。就像他们说的,请继续关注!

Scripto 博士的脚本谜题

Scripto 博士现在遇到了一个小麻烦。他将其中一个脚本随处乱放(而不是像一个优秀的脚本专家一样将脚本保存好),而且无意中将其打乱了,弄得到处都是。他已经设法找到了所有变量、关键字、符号一类的东西,并将它们按字母顺序排好了,现在他要做的就是将它们重新组合成一个完整的脚本。这可能要占用他一些时间,但是他希望在下个月的**《TechNet 杂志》出版前整理好此脚本。同时,给读者提供一次尝试的机会,看读者是否可以将这组看似随机的脚本片段还原成一个完整的脚本。祝您好运!

提示:好了,下面提供了一些帮助。最终脚本会删除本地计算机中早于指定日期的所有文件。

ANSWER:

Scripto 博士的脚本谜题

答案:粗心的 Scripto 博士,2007 年 12 月

是的,Scripto 博士已经设法将自己的脚本重新组合了。以下就是重建后的有效脚本,可以删除本地计算机中早于指定日期的所有文件:

strDate = "20060102000000.000000+000"

strComputer = "."
Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")
Set colFiles = objWMIService.ExecQuery("Select * From CIM_DataFile Where CreationDate < '" & strDate & "'")
For Each objFile in colFiles
    Wscript.Echo objFile.Name
Next

The Microsoft Scripting Guys 为 Microsoft 工作,也就是受雇于 Microsoft。在玩、教或看棒球(以及各种其他活动)的闲暇之余,他们还负责维护 TechNet 脚本中心。您可以光顾他们的网站 www.scriptingguys.com,看看他们都在做些什么。

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