您好,脚本专家!来自 Grave 之外的桌面管理

Microsoft 脚本专家

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

应大多数用户的要求 我们认为本月最好来点变化:我们打算以讲述一个恐怖故事(插入恐怖音乐)作为开始,而不是以谈论系统管理脚本开始!

注意:是的,从技术上说,如果我们确实要在本月来点变化,应该以实际谈论更改系统管理脚本开始。但还是与我们一起开始吧,好吗?谢了!

许多年前,有位老奶奶(当然是虚构的)去世了。将这位老奶奶入土为安后,不久她的老伴 — 老爷爷就开始做可怕的恶梦,在恶梦中爱妻拼命地想从坟墓中爬出来。恶梦不断重复,哀求也在不断重复,最终老爷爷说服了当地机构开棺查看。打开棺材后,每个人震惊地看到,老奶奶的指甲已经弯曲,棺材内部有抓痕!

故事可能并非完全真实,实际上,我们越想越认为它没有一点真实性。不过,这个故事给我们上了重要的一课。有些事情尽管我们不知道,但它们的确存在着。

稍等一下,让我们做点解释。棺材最初旨在保护死者不受自然环境的影响,并防止尸体分解。遗憾的是,棺材导致了一个出乎意料的结果:理论上,它也可以埋葬一个活人并让他无法出来。这个虚构的故事给我们以启示,计划再缜密也可能会导致灾难,人们也可能被活埋(再次插入恐怖音乐)!

注意:当然,如果采用十八世纪六十年代由 Franz Vester 发明的改进的埋葬制度,就可以避免这种情况。棺材上有一个细绳,连接到地面上的响铃;如果误埋了活人,“死者”通过拉动响铃即可获得帮助。改进的埋葬制度还包括一个折叠式梯子,尽管我们并不完全清楚折叠梯子如何帮助您从深埋地下的棺材中逃脱。当然,如果您被埋在车库顶部,折叠梯子将有所帮助。否则....

Internet 防火墙也会出现这种完美计划导致灾难后果的情况(当然,是在某种程度上)。防火墙最初旨在防止破坏分子进入:这些防火墙阻止传入的网络通信,有助于计算机远离黑客和入侵者。初衷很好,但就像活埋问题一样,它同样会产生出乎意料的结果:防火墙也阻止好人进入。这个问题在使用 Windows® 管理规范 (WMI) 时尤其突出,因为 WMI 依赖 DCOM 在远程计算机上执行管理任务,而防火墙往往会阻止所有传入的 DCOM 通信,这使 WMI 很难(如果并非完全不可能)通过 Internet 以编程方式管理计算机。实际上,如果不打开防火墙上的其他端口(这又会使您更容易受到黑客和解密高手的攻击),这是完全不可能的。当然,除非您选择 WinRM:Windows 远程管理(不包含折叠梯子)。

什么是 Windows 远程管理?

根据 WinRM SDK (msdn2.microsoft.com/aa384426),Windows 远程管理是“WS 管理协议的 Microsoft 实施,该协议是基于标准 SOAP、不受防火墙影响的协议,允许不同供应商的硬件和操作系统相互操作。”哈!印象深刻吧?我们不会讨论本月专栏中 WS 管理协议的详细信息,因此建议阅读 WinRM SDK 以获取详细信息。现在,我们关心的是 WinRM 可用于 Windows Server® 2003 R2、Windows Vista® 和 Windows Server 2008,而且 WinRM 使您可以通过 Internet 管理计算机。WinRM 使用端口 80 完成此操作,端口 80 是一种标准的 Internet 服务端口,大多数防火墙都打开此端口(不过,可根据需要更改 WinRM 和默认传输机制 HTTP 使用的端口)。

本月专栏中我们不会讨论如何安装和配置 WinRM。已有足够的信息可帮助您执行此操作 (msdn2.microsoft.com/aa384372)。但是,稍后我们将花点时间强调重要的一点:如果要使用 WinRM 从远程计算机检索信息(当然,这是首先使用 WinRM 的主要原因),那么本地计算机和远程计算机都必须运行 WinRM。

这意味着什么?这意味着如果您未将客户端计算机升级到 Windows Vista(假设没有升级!),或未将服务器升级到 Windows Server 2003 R2 或 Windows Server 2008,您不会发现 WinRM 非常有用,至少现在不会。但是,不用说将来可能不会是这种情况(当然,假定防火墙允许,您始终可以使用 WMI 和 DCOM 管理远程计算机)。

返回类的所有属性和实例

但是,谁在乎中止和拒绝呢,对吗?与其做这些无意义的操作,不如看看我们是否能够了解编写利用 WinRM 的脚本的方法。凑巧的是,我们刚好有一个使用 HTTP 协议和端口 80 的简易脚本,可以连接到名为 atl-fs-01.fabrikam.com 的计算机然后返回有关该计算机上安装的所有服务的完整信息。请参阅图 1 以查看完整的脚本。

Figure 1 Silverlight 项目组件

文件 说明
CreateSilverlight.js 这是一个 JScript 脚本(在其最初版本中,Silverlight 仅支持 JScript 脚本编写),它用于指定最初的 Silverlight 启动设置,包括用于配置用户界面和图形对象的 XAML 文件。
SampleProject.js 这只是一个空白文件,您可以在其中添加 JScript 函数。
Silverlight.js 此文件用于初始化 Silverlight 控件。
SampleProject.html 所有的乐趣都在这里了。SampleProject.html 仅仅是一个 HTML 文件,其中包含用于读入三个 .js 文件的代码。它还包括用于实例化 Silverlight 控件的代码。
SampleProject.xaml 它有什么用途?要想找到答案,您必须返回到本专栏的主文本部分。
   

Figure 1 远程计算机上的列表服务

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

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

strResource = "https://schemas.microsoft.com/wbem/wsman/1/wmi/root/cimv2/Win32_Service"

Set objResponse = objSession.Enumerate(strResource)

Do Until objResponse.AtEndOfStream
    DisplayOutput(objResponse.ReadItem)
Loop

Sub DisplayOutput(strWinRMXml)
    Set xmlFile = CreateObject("MSXml2.DOMDocument.3.0")    
    Set xslFile = CreateObject("MSXml2.DOMDocument.3.0")
    xmlFile.LoadXml(strWinRMXml)
    xslFile.Load("WsmTxt.xsl")
    Wscript.Echo xmlFile.TransformNode(xslFile)
End Sub

您可以看到,我们首先将计算机 (atl-fs-01.fabrikam.com) 的 DNS 名称分配给变量 strComputer。另外,我们可以使用该计算机的 IP 地址(或者 IPv6 地址)进行连接。例如:

strComputer = "192.168.1.1"

将值分配给 strComputer 之后,接下来创建 WSMan.Automation 对象的实例,然后调用 CreateSession 方法以连接到远程计算机,在这种情况下使用 HTTP 协议(按照我们的计划执行):

Set objSession = objWRM.CreateSession _
    ("http://" & strComputer)

正如我们说过的,我们希望返回有关远程计算机上安装的服务的信息。此外,至少在第一个示例中,我们需要有关所有服务的所有属性的信息。这一切都意味着什么?意味着我们需要指定 URI 资源,将我们绑定到远程计算机上的 Win32_Service 类:

strResource = _
  "https://schemas.microsoft.com" & _
  "/wbem/wsman/1/wmi/root/cimv2" & _
  "/Win32_Service"

当然,这不是至今为止我们看到过的最好的 URI(不过,稍微想想,我们也不确信曾看到过好的 URI)。尽管如此,幸运的是大部分的 URI 是样本;您只需关心末尾的 WMI 路径:

https://schemas.microsoft.com/wbem/wsman/1/wmi/root/cimv2/Win32_Service

这就相当简单了。如果要连接到 root/cimv2/Win32_Process 类,将会怎么样?接下来,只需相应地修改 URI 路径:

https://schemas.microsoft.com/wbem/wsman/1/wmi/root/cimv2/Win32_Process

您对 root/default/SystemRestore 类感兴趣吗?继续只修改 URI 类,小心地指定默认的命名空间(而不是 cimv2 命名空间):

https://schemas.microsoft.com/wbem/wsman/1/wmi/root/default/SystemRestore

等等....不过,您需要包括 URI 的 https://schemas.microsoft.com/wbem/wsman/1/wmi 部分,但是....

此时,我们已经获取一些数据。为此,我们只需调用 Enumerate 方法,将变量 strResource 作为唯一的方法参数进行传递:

Set objResponse = _
  objSession.Enumerate(strResource)

该代码行是否会根据有关安装在 atl-fs-01 计算机上的服务的信息正确填写 objResponse?当然会。然而,与标准 WMI 脚本不同,您未获取一系列对象及其属性和属性方法。相反,您将获得一个大而旧的 XML blob,它看起来有点像您在图 2 中看到的内容。

Figure 2 漂亮的颜色

<Canvas
 xmlns="https://schemas.microsoft.com/client/2007"
 xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
 Width="800"
 Height="300">
 
 <Canvas.Background>
 <LinearGradientBrush>
 <GradientStop Color="Blue" Offset="0.0" />
 <GradientStop Color="Black" Offset="1.0" />
 </LinearGradientBrush>
 </Canvas.Background>

</Canvas>

Figure 2 大而旧的 XML blob

<p:Win32_Service xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="
https://schemas.microsoft.com/wbem/wsman/1/wmi/root/cimv2/Win32_Service" xmlns:ci
m="http://schemas.dmtf.org/wbem/wscim/1/common" xsi:type="p:Win32_Service_Type"
xml:lang="en-US"><p:AcceptPause>false</p:AcceptPause><p:AcceptStop>false</p:Acce
ptStop><p:Caption>Windows Media Center Service Launcher</p:Caption><p:CheckPoint
>0</p:CheckPoint><p:CreationClassName>Win32_Service</p:CreationClassName><p:Desc
ription>Starts Windows Media Center Scheduler and Windows Media Center Receiver
services at startup if TV is enabled within Windows Media Center.</p:Description
><p:DesktopInteract>false</p:DesktopInteract><p:DisplayName>Windows Media Center

如果您是一位 XML 高手,则没有什么大问题;任何熟悉 XML 的人都能够解析和输出此信息,而没有太多阻碍(即使按照 WinRM SDK 的说法此信息不是“人类可读的格式”)。但如果您不是 XML 高手呢?在这种情况下,您有两个选择。其一,您可以等到下个月,那时我们将为您提供一些使用 WinRM 的 XML 的技巧。其二,您可以执行我们在示例脚本中进行的操作:利用随 WinRM 一起安装的 XSL 转换。

XSL 是什么格式?

XSL 转换只是一个模板,用来描述如何显示 XML 文件。对 XSL 文件的完整讨论远远超出了本月专栏的范围,对于此问题,即使简要讨论 XSL 文件也远远超出了我们在本月专栏中应有的篇幅。因此,我们不会尝试解释 WsmTxt.xsl(内置转换的名称)的实际工作原理。相反,我们只提供如何在脚本中使用该转换。

调用 Enumerate 方法时,WinRM 发回一个 XML 数据流。使用此数据最简便的方法就是设置 Do Until 循环,在到达数据流的末尾前,它将继续运行。这就是我们要在此处执行的任务:

Do Until objResponse.AtEndOfStream
    DisplayOutput(objResponse.ReadItem)
Loop

您可以看到,我们在循环内部调用了 DisplayOutput 子例程。调用该子例程时,我们将数据流 ReadItem 方法的值作为子例程参数传递(正如整个方法所示,XML 流以单独的数据段返回,而不是以一个大型的数据 blob 返回。我们的脚本按顺序一次读取一段 XML 数据或一个项目)。

同时,DisplayOutput 子例程如下所示:

Sub DisplayOutput(strWinRMXml)
  Set xmlFile = _
    CreateObject("MSXml2.DOMDocument.3.0")    
  Set xslFile = _
    CreateObject("MSXml2.DOMDocument.3.0")
  xmlFile.LoadXml(strWinRMXml)
  xslFile.Load("WsmTxt.xsl")
  Wscript.Echo xmlFile.TransformNode(xslFile)
End Sub

简而言之,我们首先创建 MSXml2.DOMDocument.3.0 对象的两个实例。我们将 XML 数据流 (strWinRMXML) 加载到一个对象中,然后将 XSL 文件 (WsmTxt.xsl) 加载到另一个对象中。此时我们调用 TransformNode 方法,使用 XSL 文件中的信息进行格式化并显示从 XML 数据流中获取的数据。

不错,还是有点乱。但是至少输出的可读性更强,虽然算不上完美(请参阅图 3)。

Figure 3 为文本着色

<TextBlock 
 Name="Test"
 FontSize="40"
 FontFamily="Georgia"
 FontWeight="Bold"
 Canvas.Top="20" 
 Canvas.Left="20"
 Text="The TechNet Script Center">

 <TextBlock.Foreground>
 <SolidColorBrush Name="test_brush" Color="red"/>
 </TextBlock.Foreground>

</TextBlock>

Figure 3 整齐的 XML 版本

Win32_Service
    AcceptPause = false
    AcceptStop = true
    Caption = User Profile Service
    CheckPoint = 0
    CreationClassName = Win32_Service
    Description = This service is responsible for loading and unloading user profiles. If this service is stopped or disabled, users will no longer be able to successfully logon or logoff, applications may have problems getting to users' data, and components registered to receive profile event notifications will not receive them.

如我们所述,这虽然不错,但不够理想,所以我们完全有理由在下个月进行相应调整,届时我们为您提供一些独立操作 XML 输出的方法。

返回类的所选实例和属性

不用说,这非常酷,除了一点:它可能未完全反映您通常的工作方法。是的,有时您希望返回类的所属性和实例;但是有时(可能大多时候)您希望只返回类的所选属性或实例。例如,您可能希望只返回有关正在运行的服务的信息,在常规 WMI 脚本中使用类似以下内容的代码运行的服务:

Set colItems = objWMIService.ExecQuery _
  ("Select * From Win32_Service " & _
   "Where State = 'Running'")

这很好。但是,您如何实际修改 Resource 字符串使其等效于该内容呢?

坦白地讲,您不会修改 Resource 字符串使其等效于 ExecQuery 语句。您确实需要修改 Resource 字符串,但是您同样需要完成另外一些事。

了解这一点后,请看一下图 4。这是一个 WinRM 脚本,返回有关在计算机上运行的服务的信息(相对于安装在计算机上的所有服务)。

Figure 4 TextBlock 技巧

<TextBlock.Triggers>
 <EventTrigger RoutedEvent=
     "TextBlock.Loaded">
 <BeginStoryboard>
 <Storyboard>
 <DoubleAnimation
 Storyboard.TargetName="Test"
 Storyboard.TargetProperty="Opacity"
 From="0.0" To="1.0" 
 Duration="0:0:5" />
 </Storyboard>
 </BeginStoryboard>
 </EventTrigger>
</TextBlock.Triggers>

Figure 4 查找运行服务

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

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

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

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

Do Until objResponse.AtEndOfStream
    DisplayOutput(objResponse.ReadItem)
Loop

Sub DisplayOutput(strWinRMXml)
    Set xmlFile = CreateObject("MSXml2.DOMDocument.3.0")    
    Set xslFile = CreateObject("MSXml2.DOMDocument.3.0")
    xmlFile.LoadXml(strWinRMXml)
    xslFile.Load("WsmTxt.xsl")
    Wscript.Echo xmlFile.TransformNode(xslFile)
End Sub

乍一看,可能觉得与向您显示的第一个 WinRM 脚本一样;然而,它存在一些非常重要的差异。首先,看一下分配给 Resource 字符串的值:

strResource = _
  "https://schemas.microsoft.com" & _
  "/wbem/wsman/1/wmi/root/cimv2/*"

注意,编写筛选的查询时,我们不会为要使用的类指定实际名称 (Win32_Service),只会连接到类所在的命名空间 (root/cimv2)。请记住将星号 (*) 添加到末尾。否则,此脚本将失败并返回消息“... 此类名称必须添加 '*'(星号)”,这只表示您需要设置类名称 *。

此外,我们需要定义 Filter 和 Dialect:

strFilter = _
  "Select * From Win32_Service " & _
  "Where State = 'Running'"
strDialect = _
  "https://schemas.microsoft.com" & _
  "/wbem/wsman/1/WQL"

Filter 应易于确定;它是我们放置 Windows Management Instrumentation 查询语言 (WQL) 查询的位置 ("Select * From Win32_Service Where State = 'Running'")。同时,Dialect 是创建 Filter 时使用的查询语言。此时,仅允许使用一种查询语言:WQL。不过,必须指定 Dialect,否则脚本将失败并警告“不支持...筛选器语言。”

注意:有趣的是,此错误消息建议您调用 Enumerate 方法时删除 Dialect。您不应听从该建议。执行筛选的查询时,必须指定 Dialect 而且必须是 WQL。没有了。

仅当我们调用 Enumerate 方法时,才需要进行另一更改。在那时,我们需要传递表示 Filter 的变量 (strFilter)、表示 Dialect 的变量 (strDialect) 以及表示 Resource 的变量 (strResource):

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

试一试,看看会发生什么情况。

现在,只返回类的所选属性如何?例如,假设您仅需要返回在计算机上运行的所有服务的 Name 和 DisplayName。然后呢?

在这种情况下,您可以尝试操作 XML,以便只显示 Name 和 DisplayName。这可以实现,但确实有点棘手。执行此操作的较简单方法是分配 Filter 时仅指定这些属性:

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

执行以上操作,您将获取每个服务的 Name 和 DisplayName,如下所示:

XmlFragment
    DisplayName = Windows Event Log
    Name = EventLog

XmlFragment
    DisplayName = COM+ Event System
    Name = EventSystem

当然,这种格式有点可笑。(如何处理 XmlFragment 内容?)这也是我们下个月进行调整的原因。

等待

幸运的是,这就足以让您探索 WinRM 的奇妙世界。当然,还没有彻底解决“防止假死”问题,我们不能结束关于 WinRM 的专栏。在实行“防止假死”的城市里,死者不会立即下葬,而是被放在暖房中,并用一些绳和线栓住了手脚。只要他们稍有移动,即会触发警报并获得帮助。对采取“防止假死”措施的尸体,没有明显希望就不会受到任何处理。

现在您注意到,“防止假死”与分配给脚本专家组非常相似,难道不是吗?当然,采取“防止假死”措施的死者还没有死而复生的,而人们将被分配给脚本专家组...

Scripto 博士的脚本求助者

2007 年 6 月,脚本专家组在佛罗里达州奥兰多市参加了 Tech•Ed 2007 会议。因为除了参加次会议没有其他工作,因此我们决定找一些乐子。不仅如此,我们还认为其他人也能享受这些乐趣。明确这一点后,我们想到了 Scripto 博士的 Fun Book,这个小册子包含与脚本相关的迷题及其他各种信息。我们和《TechNet 杂志》合作,这意味着我们劝其放弃 Expo Hall 中的一角并且将 Fun Book 分发给需要它的人。

事实证明,Fun Book 很受欢迎(可能不像 Scripto 博士的玩具那么受欢迎,但也差不太多)。《TechNet 杂志》的投机者看到了将脚本专家组的成功作为资本的更多方法(脚本专家看起来无法将其成功转化为资本),要求我们为其设置一些迷题。脚本专家 Jean Ross 刚刚转过头不久,脚本专家 Greg Stemp 说:“当然,我们会那样做!”并且我们正好在此:Scripto 博士的脚本求助者。尽情享受吧。

顺便编写脚本

在此迷题中,上半部分的字母破译后可创建脚本(在 VBScript 中)。不过不要担心,您不必猜到全部内容;您一次只需破译一列。在上半部分每列中的字母也将填写到下半部分同一列的空白处。示例如下:

您可以看到,第一列中填写了字母 S、C 和 T。那三个字母以某种未知顺序处于下面的网格中。但是当所有的字母都处于正确的顺序(向左或向右读取)时,就会形成一种逻辑。看一看解决方案:

您能看到第一列中字母 S、C 和 T 的移动顺序为 T、S 和 C。它们是“脚本中心”中每个单词的首字母。因为实际的迷题更长而最终结果是完整的脚本,所以难度更大。

提示:最终脚本以文件的完整路径开始,然后分析并显示此文件名称。

祝您好运!

ANSWER:

Scripto 博士的脚本求助者

答案:顺便编写脚本,2007 年 11 月

在此迷题中,您需要将列内的字符放到下面的正确框中,以使底部行创建一个脚本。独立的脚本如下所示:

name = "C:\Scripts\Test.txt"
arr = Split(name, "\")
index = Ubound(arr)
Wscript.Echo "Filename: " _
& arr(index)
        

迷题网格如下所示:

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

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