您好,脚本专家!比赛已经开始了!对了,还有一些 XML 也登场了。

The Microsoft Scripting Guys

下载这篇文章的代码: HeyScriptingGuy2008_02.exe (151KB)

您知道, 每当人们谈论体坛传奇人物时,肯定都会提到以下名字:Babe Ruth、Pele、Muhammad Ali、Walter Payton、ScooterK 和 MrRat、Johnny—

哈,太有趣了,您...哦,您是认真的?您真的不知道 ScooterK 和 MrRat 是谁?好吧,显然不关注冬季脚本编写比赛(2 月 15 日到 3 月 3 日在 TechNet 脚本中心举办,请访问 microsoft.com/technet/scriptcenter/funzone/games)的人们,ScooterK 和 MrRat 是真正的传奇人物,他们是 2007 冬季脚本编写比赛的参赛选手,并在至少一个小组夺得了完美分数。

并且非常酷的是:您是否真的期望成为像 Pele 那样的足坛传奇人物?可能不会。您是否曾经期望成为世界重量级拳击冠军?是的,一些脚本专家似乎已接近重量级,但距离拳击冠军的宝座还相去甚远。但是您 — 对,就是您 — 可以非常轻松地成为下一个 ScooterK 或下一个 MrRat。

注意:从理论上来讲,您也可能成为下一个 Babe Ruth。只需能在棒球连赛的两场比赛之间一口气吃下 24 支热狗即可。

请放宽心;我们将告诉您如何能成为下一个 ScooterK 或 MrRat(甚至可能是下一个 Bizzy 或 H2Data)。您所需要做的只是现身脚本中心并参加脚本编写比赛。在 2 月 15 日,我们将公布 10 个不同的比赛项目(10 项脚本编写挑战),并且要求您完成其中的任意或所有项目。编写脚本来解决我们所提出的问题,并通过电子邮件将它发送给脚本专家。(可在脚本编写比赛主页找到完整的说明。)我们将对您的脚本进行测试,如果它确实起作用,我们会奖励您积分。如果成功地完成所有 10 个比赛项目,您也将成为体坛传奇人物。或者至少是脚本编写领域中的传奇人物,这也差不多是一回事。

脚本编写比赛(从 2 月 15 日到 3 月 3 日)趣味横生且极具挑战性。其中最大的好处是,所有人都可以参加脚本编写比赛。您在某种程度上仍是系统管理脚本编写方面的新手吗?那么,请进入初学者组;我们将提供单独针对 VBScript、Windows PowerShellTM 和(本年度新增比赛项目)Perl 初学者竞赛题目。您认为初学者赛区的题目有些简单?那么就试试高级组,其中的竞赛题目同样涉及 VBScript、Windows PowerShell 和 Perl。

比赛(我们是否提到过举办时间是从 2 月 15 日到 3 月 3 日?)的中心内容是脚本编写,您肯定不想错过它们。现在,请访问脚本中心主页了解有关竞赛培训的提示和技巧,然后在 2 月 15 日我们正式开始比赛时再回来。

再重复一遍时间:2 月 15 日到 3 月 3 日。切勿错过。

那是什么?实际上,我们同意您的观点:还有整整一个月的时间,现在就推出新的“您好,脚本专家!”专栏来重点宣告脚本编写比赛可能有些气势过胜。然而,为了令 TechNet 杂志的忠诚读者高兴(我们从不动摇的主要目标),我们决定无论如何顶着风险推出新专栏。

正好在一年前(哇,真的有那么久了吗?),我们发布了一个专栏来说明如何使用脚本读取 XML 文件。该专栏并未告知您如何使用脚本来创建、编写和修改 XML 文件。本月,我们将补上这部分内容。您说希望了解如何编写可创建 XML 文件的脚本?只需提问即可。嗯,提问然后等一年,以便我们解决此问题。图 1 显示了该脚本。无可否认,它可能看起来比较复杂,但我们会解释其原理。

Figure 1 创建 XML 文件

Set xmlDoc = _
  CreateObject("Microsoft.XMLDOM")  
  
Set objRoot = _
  xmlDoc.createElement("ITChecklist")  
xmlDoc.appendChild objRoot  

Set objRecord = _
  xmlDoc.createElement("ComputerAudit") 
objRoot.appendChild objRecord 
  
Set objName = _
  xmlDoc.createElement("ComputerName")  
objName.Text = "atl-ws-001"
objRecord.appendChild objName  

Set objDate = _
  xmlDoc.createElement("AuditDate")  
objDate.Text = Date  
objRecord.appendChild objDate  

Set objIntro = _
  xmlDoc.createProcessingInstruction _
  ("xml","version='1.0'")  
xmlDoc.insertBefore _
  objIntro,xmlDoc.childNodes(0)  

xmlDoc.Save "C:\Scripts\Audits.xml"  

首先,创建一个 Microsoft.XMLDOM 对象实例。您可能已经猜到,这就是使我们能够使用 XML 文件的对象。我们的目标是生成一个如图 2 所示的简单的 XML 文件。

图 2 我们的目标:一个简单的 XML 文件

图 2** 我们的目标:一个简单的 XML 文件 **

要创建该 XML 文件,我们首先要做的就是创建根节点 (ITChecklist)。如何实现呢?如下所示:

Set objRoot = _
  xmlDoc.createElement("ITChecklist")  
xmlDoc.appendChild objRoot  

非常简单,不是吗?我们仅需调用 createElement 方法,将希望指定给根节点的名称传递给 createElement。然后,只需调用 appendChild 方法,将对新元素 (objRoot) 的对象引用作为唯一的方法参数进行传递。此时,我们已有了自己的根节点。

不过,请等一下,还有其他内容。接下来创建 ComputerAudit 节点,它是 ITChecklist 节点的子节点,代表单台计算机的信息。正如您所看到的,创建该节点的代码与创建根节点的代码类似:

Set objRecord = _
  xmlDoc.createElement("ComputerAudit") 
objRoot.appendChild objRecord 

唯一的区别在于:创建根节点时,是在 XML 文档自身上调用的 appendChild。(请注意对象引用 xmlDoc。)为向根添加新的子节点,我们要在根节点 (objRoot) 而非 XML 文档上调用 appendChild。就是这么简单。

将 ComputerName 和 AuditDate 节点添加为 ComputerAudit 的子节点同样简单。为此,我们将执行一个类似的过程:调用 createElement 创建新节点,然后调用 appendChild 将此新节点添加至文件。

(快速问答:这次,我们从哪里调用 appendChild?没错,是这样的:从 objRecord,我们刚花时间创建的父 (ComputerAudit) 节点。)

另外,由于 ComputerName 和 AuditDate 节点需包含值,因此还必须在将它们添加到文档前为其中每个节点的 Text 属性指定值。

只要浏览实际创建 ComputerName 节点的代码就会清楚:

Set objName = _
  xmlDoc.createElement("ComputerName")  
objName.Text = "atl-ws-001"
objRecord.appendChild objName  

正如您所料,创建 AuditDate 的代码几乎相同。我们只需在调用 createElement 时指定另一节点名称,并为 Text 属性指定另一个值即可。

老兄,此过程对于每个人而言都已经非常简单了,对吧?

创建完第一个(并且在本例中是唯一的)记录的节点后,我们来执行这一小段可爱的代码:

Set objIntro = _
 xmlDoc.createProcessingInstruction _
  ("xml","version='1.0'")  
xmlDoc.insertBefore _
  objIntro,xmlDoc.childNodes(0)  

它只是在文件开头处插入 <?xml version="1.0" ?> 标记,以确保得到格式标准的 XML 文档。

到此为止,剩下的工作就是调用 Save 方法并将新文件保存为 C:\Scripts\Audits.xml:

xmlDoc.Save "C:\Scripts\Audits.xml"  

这样,我们就拥有了全新的 XML 文档。

现在,它实际上已经非常有用了:您已知晓了如何使用脚本创建全新的 XML 文件。当然,这只是特例,大部分时间都不需要实际创建全新的 XML 文件;而只需将新数据添加到现有文件即可。那么,脚本专家是否会为每个人展示如何操作呢?

您知道,我们最初决定对该问题回答不,我们不想为每个人展示如何将数据添加到现有的 XML 文件。随后,由于我们的仁慈和大方,因此决定与 TechNet 杂志的读者达成协议:如果阅读该杂志的所有人均同意参加 2008 年冬季脚本编写比赛,那么,作为回报,我们将向所有人展示如何使用脚本来将数据添加到 XML 文件。那么,是否所有人均同意参加脚本编写比赛呢?

嗯,我们正在等待您的参与。对,就是您 — 明尼苏达州罗切斯特的专家。

情况就象刚才我们介绍的那样。并且相信我们,脚本编写比赛过程会是一段美好的时光;每个人都会有这种感觉。我们保证。

现在,如之前说好的,我们将展示可将数据添加到现有 XML 文件的脚本。看一看图 3

Figure 3 添加到 XML 文件

Set xmlDoc = _
  CreateObject("Microsoft.XMLDOM")

xmlDoc.Async = "False"
xmlDoc.Load("C:\Scripts\Audits.xml")

Set objRoot = xmlDoc.documentElement
  
Set objRecord = _
  xmlDoc.createElement("ComputerAudit")
objRoot.appendChild objRecord

Set objFieldValue = _
  xmlDoc.createElement("ComputerName")
objFieldValue.Text = "atl-ws-100"
objRecord.appendChild objFieldValue

Set objFieldValue = _
  xmlDoc.createElement("AuditDate")
objFieldValue.Text = Date
objRecord.appendChild objFieldValue
  
xmlDoc.Save "C:\Scripts\Audits.xml"  

您会看到,它与创建 XML 文件的脚本并没有太大差别。首先,创建一个 Microsoft.XMLDOM 对象实例,然后将 Async 属性设置为 False,即告知脚本我们希望同步而非异步加载文档。这中间有什么区别呢?是这样,如果异步加载文档,脚本会随心所欲地继续执行,即使文档并未完全加载也是一样。毫无疑问,如果尝试在尚不存在的文档上执行任务,会马上产生问题。通过确保同步加载 XML 文件,还可确保在完全加载文件后脚本才继续执行。

说到完全加载 — 没关系,不用管它。我们转而调用 Load 方法来打开文件 C:\Scripts\Audits.xml。打开文件后,我们立即使用此行代码来创建 documentElement 类的实例,其实际作用是将我们绑定到文档根。当然,在本示例中,是 ITChecklist 节点:

Set objRoot = xmlDoc.documentElement

之后的事情就轻而易举了。通过使用 appendChild 方法将该新节点添加到文件来创建 ComputerAudit 节点的新实例。然后,创建 ComputerName 和 AuditDate 节点的新实例,为每个新节点指定适当的值(分别是 atl-ws-100 和当前日期)。将这两个项目放入刚创建的新 ComputerAudit 节点中,调用 Save 方法保存文件,然后高兴地回来研究脚本以迎接即将开始的脚本编写比赛。

再次提醒,比赛计划于 2 月 15 日至 3 月 3 日在 TechNet 脚本中心举行。

到目前为止一切顺利。我们可以创建新的 XML 文件,并且可像该文件添加新记录。所有这些都很棒,但是我们仍有许多内容需要了解;例如,如何修改文件中的现有记录?嗯,至少有一种方法可以做到这一点(如图 4 所示)。

Figure 4 修改 XML

Set xmlDoc = _
  CreateObject("Microsoft.XMLDOM")

xmlDoc.Async = "False"
xmlDoc.Load("C:\Scripts\Audits.xml")

Set colNodes=xmlDoc.selectNodes _
  ("/ITChecklist/ComputerAudit " & _
   "[ComputerName = 'atl-ws-100']/AuditDate")

For Each objNode in colNodes
   objNode.Text = Date
Next
  
xmlDoc.Save "C:\Scripts\Audits.xml"  

调用 selectNodes 方法(此方法用于确定查询将返回哪些记录)时会出现该脚本的重要部分。您会看到,在调用 selectNodes 时,我们谨慎地指定了两个标准:我们只需要 ComputerName 属性等于 atl-ws-100 的记录,并且我们仅希望得到 AuditDate 属性。

注意:您说无法明白其工作原理?那么,请参阅我们的第一篇有关使用 XML 文件的文章 (technetmagazine.com/issues/2007/02/HeyScriptingGuy);此处更加详细地解释了 selectNodes 查询语法。

按照老习惯,selectNodes 会返回满足指定标准的所有 XML 记录的集合。这样,仅需通过设置 For Each 循环遍历集合中的所有项目,然后在该循环中为 AuditDate 指定新值,即可更新 AuditDate 属性(我们所需要的唯一属性)的值:

For Each objNode in colNodes
   objNode.Text = Date
Next

什么问题?您想知道我们能否一次修改多个属性?当然能。但遗憾的是,不是现在;在下一专栏中,还有一些必须解决的问题。

您说您还有一个问题:如何更新文件中所有计算机的审核日期?这很简单;使用图 5 中所示的脚本。

Figure 5 更改审核日期

Set xmlDoc = _
  CreateObject("Microsoft.XMLDOM")

xmlDoc.Async = "False"
xmlDoc.Load("C:\Scripts\Audits.xml")

Set colNodes=xmlDoc.selectNodes _
  ("/ITChecklist/ComputerAudit/AuditDate")

For Each objNode in colNodes
   objNode.Text = Date
Next
  
xmlDoc.Save "C:\Scripts\Audits.xml"

此脚本与之前的 XML 修改脚本之间的唯一区别是什么?在此脚本中,我们并未指定我们仅希望查看 ComputerName 等于 atl-ws-100 的那些记录。省去该标准,我们默认会得到所有记录。

结束前,让我们再来看最后一个脚本。假设我们的老 atl-ws-100 已履行了最后的义务并且升入了计算机的天堂。

神学注释:那么,计算机寿命终结后会去何处?事实证明,它们通常被提供给某位脚本专家。例如,撰写本专栏的脚本专家就曾获赠一台笔记本电脑,原因是“您为 Microsoft 做出了很大的贡献,因此您确实应拥有一台笔记本电脑。”这件慷慨礼物的唯一缺点是什么?计算机已损坏并且实际上已无法开机。这可能还不算糟糕。事实证明,它还没有硬盘。

为结束硅元素生命的巨大循环,需从 XML 文件中删除 atl-ws-100。我们将如何做呢?请参见图 6

Figure 6 从 XML 文件删除

Set xmlDoc = _
  CreateObject("Microsoft.XMLDOM")

xmlDoc.Async = "False"
xmlDoc.Load("C:\Scripts\Audits.xml")

Set colNodes=xmlDoc.selectNodes _
  ("/ITChecklist/ComputerAudit " & _
   "[ComputerName = 'atl-ws-100']")

For Each objNode in colNodes
  xmlDoc.documentElement.removeChild _
    (objNode)
Next
  
xmlDoc.Save "C:\Scripts\Audits.xml"  

您会看到,它类似于之前修改 AuditDate 属性的那个脚本。实际上只有两点不同。首先,请注意我们并未麻烦地在 selectNodes 查询中指定任何属性值;在执行查询时,我们会得到 atl-ws-100 的所有 XML 记录:

Set colNodes=xmlDoc.selectNodes _
  ("/ITChecklist/ComputerAudit " & _
   "[ComputerName = 'atl-ws-100']")

接下来,在 For Each 循环中,只需调用 removeChild 方法来删除 ComputerName 等于 atl-ws-100 的所有记录即可:

xmlDoc.documentElement.removeChild _
  (objNode)

就是这样。再见,atl-ws-100;旧梦重温。

好的,有没有**《TechNet 杂志》的编辑仍在阅读本月的专栏?您说他们都去参加每月脚本测试题了?非常好。我们现在就是权威了,我们可以告诉您:放下您手上的事情(比如阅读本杂志),赶快到脚本中心并开始准备参加脚本编写比赛。毕竟,**您有很多时间来阅读《TechNet 杂志》。而脚本编写比赛一年才一次,并且时间有限(具体地说,从 2 月 15 日到 3 月 3 日,您知道只有这点时间)。切勿错失良机!

注意:您知道,既然您提到了,我们确实是等到您读完整个专栏后才告诉您停止阅读**《TechNet 杂志》的,对吧?请想想原因是什么吧...

Scripto 博士的脚本谜题

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

2008 年 2 月:神秘的符号

计算机键盘包含各种怪模怪样的字符;更怪的是其中的大部分字符在脚本编写语言 VBScript 或 Windows PowerShell 中均有特定用途。看看您能否将每个符号与其脚本编写用途相匹配。第一个已完成。

ANSWER:

Scripto 博士的脚本谜题

答案:神秘的符号,2008 年 2 月

  

The Microsoft Scripting Guys 为 Microsoft 工作,也就是受雇于 Microsoft。在玩、教或看棒球(以及各种其他活动)的闲暇之余,他们还负责维护 TechNet 脚本中心。要查看相关信息,请登录 www.scriptingguys.com

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