您好,脚本专家!深入探索 WMI

Microsoft 脚本专家

目录

命名空间
WMI 类
属性
方法

一位脚本专家在年轻时,曾热衷于两件事。第一是畅饮发酵饮料;第二是冬季露营。他认为两者之间是相互关联的。当时与他同行的一位朋友很喜欢提议大家“继续前行”。本月,我们将采纳那位老朋友的建议,深入探索 WMI(Windows 管理规范)。

令人欣慰的是,这次我们无需进行长途跋涉,可能只是偶而去冲几杯咖啡。实际上,我们打算使用脚本来对 WMI 进行深入探索。

众所周知,脚本专家向来非常注重实用性。因此,我们会尽可能针对实际问题提出解决方案,而非向读者高谈阔论却遗漏一些细节(如何实际完成某项任务)。我们不会染上这种不良风气。虽然本专栏的重点不在于解决特定的系统管理任务,但是,我们有着很实际的目标。主要目的是使您了解 WMI 基础结构,同时,还要向您提供一些实用的试验性脚本。启动记事本 — 让我们进行深入探索!

命名空间

WMI 存储库是一种数据库,用于存储通用信息模型 (CIM)。此模型是面向对象的,这意味着它包含一组描述(WMI 类),它们代表 WMI 可以管理的内容。例如,Win32_Process WMI 类代表进程。WMI 类存储于不同的 WMI 存储库部分。各个 WMI 存储库部分就是命名空间。如果您刚刚接触 WMI 存储库,可能首先会注意到它分为许多高层命名空间。图 1 中所示的脚本

图 1 显示命名空间

strComputer = "."
Call EnumNameSpaces("root")

Sub EnumNameSpaces(strNameSpace)
    On Error Resume Next
    WScript.Echo strNameSpace
    Set objWMIService=GetObject _
        ("winmgmts:{impersonationLevel=impersonate}\\" & _ 
            strComputer & "\" & strNameSpace)

    Set colNameSpaces = objWMIService.InstancesOf("__NAMESPACE")

    For Each objNameSpace in colNameSpaces
        Call EnumNameSpaces(strNameSpace & "\" & objNameSpace.Name)
    Next
End Sub

会显示 strComputer 指定的计算机上的 WMI 存储库里的所有命名空间的名称(圆点表示本地计算机)。

命名空间可以包含子命名空间。您可以将整个命名空间视为一个目录结构。因此,如果只是要查看所有命名空间(从顶层根命名空间开始),我们可能只是返回第一层命名空间,这样不会显示任何子命名空间。实际上,我们可以使用递归方法来解决这一问题。我们创建了 EnumNameSpaces 子例程,它将一个命名空间作为参数,并返回其所有子命名空间。

我们首先调用 EnumNameSpaces,并使用根目录作为参数。这将返回根命名空间内的所有命名空间。然后,我们执行递归操作。请注意,EnumNameSpaces 实际上是在调用其自身,并传递它确定的每个子命名空间。喝一大口咖啡,好好想一想。结果是:每个命名空间都将得到处理,如果其中包含子命名空间,也都会显示出来。

请注意,我们在子例程的开头加入了 On Error Resume Next 语句。这是为了防止在安全环境下,即使您并非具有对所有命名空间的访问权限,仍可以运行脚本。如果事实如此,脚本仍会运行,不过因为要等待超时,所以运行速度会比较慢。

没错,您可以直接使用 wbemtest.exe(安装 WMI 的任何计算机上都有此文件)或 Scriptomatic (go.microsoft.com/fwlink/?LinkId=125976) 来执行此任务。不过,借助一个启动脚本以及您卓越的脚本编写技能,您可以筛选这些命名空间或将它们输出至 Excel,还可以比较两台计算机上的命名空间。

现在我们已经了解了划分存储库的方式,那么,现在就来开发一个脚本,以便查看各个部分都包含哪些内容。我们知道 WMI 类存储在各个命名空间中,因此,我们先从列出这些命名空间着手。

WMI 类

还记得我们提到 WMI 存储库是用来存储通用信息模型 (CIM) 的吗?通常情况下,此模型存储在 CIMV2(V2 表示版本 2)命名空间中。如果查看 CIMV2 命名空间,您应该会看到组成此模型的所有 WMI 类。以下脚本将执行此任务:

strComputer = "."
Set objWMIService=GetObject("winmgmts: _
    {impersonationLevel=impersonate}!\\" & _
    strComputer & "\root\cimv2")

For Each objclass in 
    objWMIService.SubclassesOf()
    Wscript.Echo objClass.Path_.Class
Next

我们先看一下此脚本究竟如何运行。调用 GetObject 会返回 SWbemServices 对象。GetObject 是一个 VBScript 函数,可返回对可编写脚本的 COM 对象的引用。在本例中,由于我们为其传递了“winmgmts:…”字符串,因此,GetObject 从 WMI 脚本库返回了一个对象。请注意,我们传递给 GetObject 的字符串包含要连接的命名空间,在本例中为 \root\cimv2。这样一来,我们要使用的 SWbemServices 对象会绑定到指定的特定命名空间。您可以参阅 SWbemServices 文档,以了解在脚本中可以执行的操作。

ExecQuery 可能是一个您比较熟悉的操作。使用此方法,您可以对要连接的命名空间运行 WQL(Windows 管理规范查询语言)查询。但是,将 SWbemServices 对象与命名空间相关联之后,还可以执行许多其他操作。

我们希望查看命名空间中的所有 WMI 类,使用 SubClassesOf 可以实现此目的。文档中指出,它会返回 SWbemObjectSet。它并不是特别难理解!只需注意最后三个字母即可 — 这是一个集合。所有 WMI 脚本专家都知道使用 For Each 可以遍历集合。

顾名思义,SWbemObjectSet 的每个成员都是一个 SWbemObject。其中,每个 SWbemObject 都代表 CIMV2 命名空间中的一个 WMI 类。请参阅 SWbemObject 文档,其中提供了目前我们掌握的有关这些类的所有信息。

在我们的脚本中,我们选择只显示类的名称。为此,我们使用了 Path_ 属性。实际上,Path_ 属性本身就是一个对象,它是 SWbemObjectPath,而且,作为一个对象,它自身带有许多属性。我们使用的是 Class,它代表类的名称。

现在,您不仅拥有了可以显示命名空间中所有 WMI 类的脚本,还拥有了可以轻松更新以便显示这些类相关信息的脚本。例如,WMI 类可以是其他 WMI 类的扩展。设想一下,您有一个汽车模型 (Win32_Car),但实际上您需要管理旅行车。汽车模型中的所有内容都适用于旅行车。

但是,您还需要一些其他内容,例如,指示是否存在高雅的木质镶板的布尔值。您不希望重新创建所有 Win32_Car 功能。您希望使用一个机制来扩展 Win32_Car 类,以包含所有新的属性。WMI 正好包含此类机制。

要查看 WMI 类是否继承了另一个 WMI 类的属性,可以查看与此 WMI 类相关联的 SWbemObject 类的 Derivation_ 属性。图 2 中的脚本显示了 CIMV2 命名空间中的 WMI 类,并提供了派生这些类的类列表。

图 2 CIMV2 类派生

strComputer = "."
Set objWMIService=GetObject("winmgmts:{impersonationLevel=impersonate}!\\" & _
    strComputer & "\root\cimv2")

For Each objclass in objWMIService.SubclassesOf()
    WScript.StdOut.Write objclass.Path_.Class
    arrDerivativeClasses = objClass.Derivation_ 
    For Each strDerivativeClass in arrDerivativeClasses 
       WScript.StdOut.Write " <- " & strDerivativeClass
    Next
    WScript.StdOut.Write vbNewLine
Next

    For Each objNameSpace in colNameSpaces
        Call EnumNameSpaces(strNameSpace & "\" & objNameSpace.Name)
    Next
End Sub

此脚本的开头与上一个脚本类似。它使用的是 WScript.StdOut.Write,而非 WScript.Echo,以防止在显示的字符串后面自动添加换行符(注意:您必须使用 Cscript.exe(而非 Wscript.exe)运行脚本,才能使 StdOut.Write 正常运行)。

在 SWbemObject 文档中,您会发现它包含一个 Derivation_ 属性。此属性是一个字符串数组,其中包含派生当前类的类名称。在我们的脚本中,我们使用 For Each 遍历此数组,并显示所有类(以 ascii 箭头分隔)。习惯从 GetObject 返回的 SWbemServices 对象着手,并了解 WMI 脚本库文档中介绍的可能操作后,您可以测试这些名字古怪的对象的属性和方法以查看可能会发生什么情况。

了解您的脚本中使用的 WMI 脚本库对象之后,您可以继续下一级别的 WMI 脚本编写。您不但可以使用我们的脚本,还可以了解我们能够调用 ExecQuery 或引用 Properties_ 属性的原因。现在,您可以更进一步。

Scriptomatic 工具无法满足您的期望?没关系,继续修改此工具,以满足您的要求。或许其他人还会购买您的作品。如果真是这样,请将相关说明寄至 scripter@microsoft.com,详述我们如何收到我们的版税支票。

属性

每个 WMI 类都会使用一组属性和方法来模拟您可以管理的实体,属性就是实体的特征。例如,进程包含 ID 和优先级,并使用一定量的内存。这些属性都包含在 Win32_Process WMI 类中。

确定管理实体的类后,请查看可用属性,以确定管理模型中是否包含您要管理的内容。SWbemObject 类包含一个名为 Properties_ 的属性。有趣吧?此属性的值是一个包含 SWbemProperty 对象集合的 SWbemPropertySet 对象。其中,每个 SWbemProperty 对象分别对应于与 SWbemObject 相关的 WMI 类中的属性。我知道,所有这些 SWbem* 名称听起来似乎都很复杂,但实际上并没想象得那么难。请看一下图 3

fig03.gif

图 3 SWbemObject 公开绑定到的 WMI 类的属性(单击可获得大图)

请记住,以 SWbem* 开头的类是 WMI 脚本对象库的成员。您可以通过这些对象来使用 WMI。但是,它们并不是您可以管理的 WMI 模型的一部分。

图 3 中,SWbemObject 代表一个 WMI 类 Win32_SomeClass,该类包含以下属性:Property_1、Property_2 和 Property_3。它通过其自身的 Properties_ 属性公开这些属性。当然,如果它绑定到另一 WMI 类 Win32_SomeOtherClass,其属性的名称并不会发生更改,仍然是 Properties_。但是,其绑定的类的属性可能会有所不同。

实际上,SWbemObject 会公开其绑定到的特定 WMI 类的属性,但是,您可以使用相同的 Properties_ 机制来获取不同的属性。明白了吗?再喝一大口咖啡,仔细考虑一下这个图表。一切就都清楚了。

图 4 中的脚本利用 SWbemObject 及其 Properties_ 属性来检索并显示 Win32_Service WMI 类的所有属性。您应该非常熟悉此脚本的开头。主要不同在于我们分解了命名空间和 WMI 类,以便更轻松地对其进行更改。例如,您可以将 strClass 的值直接更改为 Win32_BIOS,以查看此类的属性,而非 Win32_Service 的属性。在 For Each 循环中,我们遍历了 SWbemPropertySet 集合(objClass.Properties 属性)并显示了每个 SWbemProperty 的 Name。

图 4 获取 Win32_Service 的属性

strComputer = "."
strNameSpace = "root\cimv2"
strClass = "Win32_Service"

Set objClass = GetObject("winmgmts:{impersonationLevel=impersonate}!\\" & _ 
    strComputer & "\" & strNameSpace & ":" & strClass)

WScript.Echo strClass & " Class Properties"
WScript.Echo "------------------------------"

For Each objClassProperty in objClass.Properties_
    WScript.Echo objClassProperty.Name
Next

方法

最后,有些 WMI 类不只是模拟可管理实体的属性或特征,还包含一些方法,用于访问实体可以采取的行为或操作,或者可以针对实体采取的行为或操作。

返回 WMI 类的所有方法的脚本格式(如图 5 所示)与返回属性的脚本极其相似。

图 5 获取 WMI 类的方法

strComputer = "."
strNameSpace = "root\cimv2"
strClass = "Win32_Service"

Set objClass = GetObject("winmgmts:{impersonationLevel=impersonate}!\\" & _ 
    strComputer & "\" & strNameSpace & ":" & strClass)

WScript.Echo strClass & " Class Methods"
WScript.Echo "---------------------------"

For Each objClassMethod in objClass.Methods_
    WScript.Echo objClassMethod.Name
Next

当然,区别就是将 Properties_ 属性替换成了 Methods_ 属性。现在,我想了解是否还可以显示这些方法使用的参数的类型?如何只显示确实包含方法的 WMI 类?读完本专栏后,您应该尝试编写脚本来解决这类问题。

希望我们已经为您提供了一些有用的信息,帮助您开始了解如何深入探索 WMI。的确,您必须要靠自己的努力来逐渐掌握 SWbem*。但是,脚本提供了可以深入探索的良好轻型机制。不过那两位冬季露营朋友的运气就没这么好了。事实上,他们不可能走得太远。因为在隆冬季节将足够的发酵饮料必需品运送到太远的地方不是很容易的事。

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