二、VB中使用WMI的基本方法
    上一讲我们初步了解了一下WMI,并在VB中使用WMI对象做了一个进程管理器,为加深各位的对WMI对象引用方法的程序结构印象和增加兴趣,我们再举一个例子,就是枚举当前系统所有服务的名称、状态和启动类型等。与例程1一样,首先建立一个新工程具有Form1窗体,在菜单中的【工程】—【部件】下,添加“Microsoft Windows Common Controls 6.0”,在菜单中的【工程】—【引用】下,添加“Microsoft WMI Scripting V1.1 Library”,然后在Form1窗体上添加1个ListView1,在代码窗口添加如下代码(例程2):

Option Explicit
Dim objSWbemLocator As SWbemLocator
Dim objSWbemServices As SWbemServices
Dim objSWbemObjectSet As SWbemObjectSet
Dim objSWbemObject As SWbemObject
Dim strComputer As String, strNameSpace As String, strClass As String

Private Sub Form_Load()
Dim i As Long
    Me.Caption = "服务"
    ListView1.ColumnHeaders.Clear
    ListView1.ColumnHeaders.Add , , "名称", 2600
    ListView1.ColumnHeaders.Add , , "状态", 1000
    ListView1.ColumnHeaders.Add , , "启动类型", 1000
    ListView1.ColumnHeaders.Add , , "路径", 2600
    ListView1.ColumnHeaders.Add , , "登录身份", 1400
    ListView1.View = lvwReport
           
    strComputer = "."           '计算机名,.为本机
    strNameSpace = "root\cimv2" '指定命名空间为root\cimv2
    strClass = "Win32_Service"  '指定类为Win32_Service
   
    Set objSWbemLocator = CreateObject("WbemScripting.SWbemLocator")    '建立1个WBEM对象的引用指针
    Set objSWbemServices = objSWbemLocator.ConnectServer(strComputer, strNameSpace)  '连接到指定计算机、命名空间的WMI,返回一个对 SWbemServices 对象的引用
    Set objSWbemObjectSet = objSWbemServices.ExecQuery("SELECT * FROM " & strClass)  '通过WQL查询,返回指定类的所有实例

    For Each objSWbemObject In objSWbemObjectSet
        ListView1.ListItems.Add , "a" & i, objSWbemObject.DisplayName '将服务名称添加到ListView1第一列
        ListView1.ListItems("a" & i).SubItems(1) = objSWbemObject.State '将服务的状态添加到ListView1第二列
        ListView1.ListItems("a" & i).SubItems(2) = objSWbemObject.StartMode '将服务的启动方式添加到ListView1第三列
        ListView1.ListItems("a" & i).SubItems(3) = objSWbemObject.PathName '将服务程序的路径添加到ListView1第四列
        ListView1.ListItems("a" & i).SubItems(4) = objSWbemObject.StartName '将服务的登录身份添加到ListView1第五列
        i = i + 1
    Next
    Set objSWbemObject = Nothing
    Set objSWbemObjectSet = Nothing
End Sub

    嘿,我们运行这个例程后,果然把自己计算机上所有的服务都列了出来,并且还知道这些服务目前的运行状态以及服务程序所在的目录和执行文件名。美中不足的是似乎我们不能停止或启动某个服务,不要急,其实可以很方便的就增加停止或启动某个服务的功能,但因为本节主要是要讨论VB中使用WMI的基本方法,因此故意省略了其它一些功能的实现。vb listview控件
    闲话少说言规正传,现在我们就开始讨论一下VB中使用WMI的基本方法。例程2与例程1
相比,除了功能不一样外,精简了程序结构,因此我们就以例程2的结构为主进行讨论。
    我们先来观察一下例程1和例程2,可以看到它们都添加了“Microsoft WMI Scripting V1.1 Library”的引用,这个对象库(以下简称WMI脚本对象库)就是我们在VB的WMI编程中所要依赖的。再看一下程序的定义部分和整个程序的首次执行过程,它们基本相同:都定义了SwbemLocator、SwbemServices、SwbemObjectSet、SwbemObject对象;创建了SwbemLocator指针实例;通过SwbemLocator指针连接到WMI服务;检索一个 WMI 托管资源;枚举托管资源中的每个实例;显示各实例的一些属性。其实这些步骤对于任何用于检索 WMI 托管资源信息的程序来说都是共同的,下面我们就详细的分析一下各步骤。
    1)创建SwbemLocator指针
    创建SwbemLocator指针的目的是为了建立一个引用WMI对象的实例,然后用这个实例操作WMI。有没有注意到,例程1和例程2创建SwbemLocator指针稍有不同,例程1是在定义处用Dim objSWbemLocator As New SwbemLocator定义语句直接创建了,而例程2是在执行过程Form_Load中用Set objSWbemLocator = CreateObject("WbemScripting.SWbemLocator")语句创建的,其实可以将这条语句简化为Set objSWbemLocator = New SwbemLocator来创建。不管用哪种方式创建SwbemLocator
指针,都不影响下面的步骤。SwbemLocator指针对象只有1个只读属性Security_(其实也是一个对象)和1个方法ConnectServer,第二步中我们就会讨论用此方法连接到WMI服务。
    2)连接到WMI服务
    要用WMI对象编程,必须连接到目标计算机的WMI服务,然后返回一个SwbemServices对象。比较一下例程1和例程2的连接方法,它们都用SwbemLocator指针对象的ConnectServer方法实现,不过例程1没有用任何参数,例程2中增加了strComputer和strNameSpace二个参数。ConnectServer方法共有8个参数,所有参数都是可选的,因此我们看到例程1中没有用任何参数,即全部使用缺省参数。下面我们看一下这8个参数:
    strServer 计算机名字,如果这台远程计算机与你不是同一个主域,需要完整的带域名的全称,例如:””。缺省为本机,本机也可以用”.”
    strNamespace 需要登录的CIM命名空间,例如:"root\CIMV2"。缺省为"root\CIMV2"。(CIM就是一个存储库(架构),包括模型化托管环境和定义每个由 WMI 公开的数据块的对象储存库或类存储,WMI所有的类被分组到命名空间中,命名空间是表示一个特定的管理区域的类逻辑组,在这里就不深入分析了)
    strUser 用户名,一般必须为指定计算机上管理员帐号,也可以是指定主域的用户名,例
如:"DomainName\UserName"。缺省为当前登录系统帐号的用户名。
    strPassword 口令(密码),对应用户名的口令
    strLocale 本地化代码,通常为空。
    StrAuthority 验证字符串,通常为空,如果不为空,则只能用于“Kerberos”或“NTLMDomain”。
    iSecurityFlags 延迟时间(注:中文解释为本人理解,似乎和英文意思不一样),当为0时,直到连接成功为止,这就有可能因为连接不成功时长时间等待。当为wbemConnectFlagUseMaxWait (数值为128),则确保不超过2分钟返回。
    ObjwbemNamedValueSet 为wbemNamedValueSet对象,在这里就不展开了。
    如果连接到本机,通常情况下只需要设置strNamespace参数,其它参数都可以省略,但如果连接到远程计算机,一般需要对前4个参数进行设置。
    这里要注意的是,连接到WMI服务是指连接到一个指定的CIM命名空间(如果还不能理解什么是CIM命名空间,也没关系,在后面的章节中还会讨论CIM命名空间),所以也可以认为本步骤就是:连接到一个指定的CIM命名空间,即使你不设置strNamespace参数,那也是连接到缺省的CIM命名空间,通常缺省的CIM命名空间为"root\CIMV2",你可以通过你计算
机上的注册表查看或修改HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\WBEM\Scripting\Default Namespace项的值。
    3)获得WMI 托管资源(类)的实例集合
    当完成了连接到WMI服务后,我们就需要根据要执行的任务,选择不同的类(这些类都是WMI 的托管资源)。在上一步中,我们已经知道是连接到一个指定的CIM命名空间,而每个命名空间都驻留了许多类,本步骤就是根据要求获得我们所需类的实例集合。
    我们还是通过例程来讨论。在例程1中,我们通过SwbemServices对象的InstancesOf方法,参数为我们所需的"Win32_Process"类,然后返回由指定类名标识("Win32_Process")的托管资源的所有实例。而在实例2中,我们通过SwbemServices对象的ExecQuery方法,参数为"SELECT * FROM Win32_Service"(细心的读者可能发现参数好像与例程2中的并不完全相同,但你再看一下该语句执行前,已经对strClass变量赋予了"Win32_Service"值),然后返回的却是由指定类名标识("Win32_Service")的托管资源的所有实例。2个例程用了2个方法都获得指定类名标识的托管资源的所有实例,那可不可以调换过来用呢?当然可以!在例程1中,我们完全可以用Set objSWbemObjectSet = objSWbemServices.ExecQuery("SELE
CT * FROM Win32_Process")替换原语句,在例程2中用Set objSWbemObjectSet = objSWbemServices.InstancesOf("Win32_Service ")替换原语句。既然可以互相替换,那是不是说这2种方法是一样的呢?实际上这2种方法是有区别的。InstancesOf方法就象其名字所体现的一样,返回所有指定类实例,所以用起来非常方便。但方便往往与灵活和高效是矛盾的,假设您只想要实例或属性的一个子集的情形又怎么样呢?假设您想要优化实例和属性恢复到最小的网络流量。在这种情况下,您会非常高兴听到 WMI 支持大量强大的查询工具。
    查询 WMI 是向匹配一些预定义条件的托管资源发出请求的过程。例如,WMI 查询只要求那些处于 Stopped(停止)状态的服务,则例程2中,我们可以将该条语句改成:Set objSWbemObjectSet = objSWbemServices.ExecQuery("SELECT * FROM Win32_Service  WHERE State='Stopped'")就可以了。
    WMI 查询为检索托管资源实例及其属性提供了比 InstancesOf 方法更有效率的机制。WMI 查询只返回那些匹配查询的实例和属性,但是 InstancesOf 总是返回一个指定资源的所有实例以及每个实例的所有属性。同样,查询是在与对象路径中一致的目标计算机上进行的,而不是在运行的源计算机上。因此,WMI 查询可以显著地减少像 InstancesOf 那样更低效率的
数据检索机制造成的网络流量。
    要查询 WMI,使用 WMI 查询语言(WQL)构造一个查询字符串。查询字符串定义了必须被满足的条件来获得成功匹配的结果。在查询字符串定义后,查询使用 SWbemServicesExecQuery 方法提交到 WMI 服务。满足查询的托管资源实例以 SWbemObjectSet 集合的形式返回。
    使用 WQL 和 ExecQuery 方法(而不是 InstancesOf)为创建只返回您感兴趣的项提供了灵活性。但我目前尚未获得一个完整的关于WQL查询语言的资料,只知道“WQL查询语言是一种类似于SQL查询语言,是SQL语言的一个子集”,但我在实际使用时,发现并不完全是SQL语言的一个子集,WQL语言还能用于建立事件,控制事件的响应速度等,不知道是否因为本人孤陋寡闻,在SQL中是不是有WITHIN、ISA等关键字?那位如有完整的关于WQL查询语言的资料(最好是电子的)能够提供,本人不胜感谢!
    好,讨论过了获得实例集合的方法,我们是不是还有一个疑问?参数中的"Win32_Process"和"Win32_Service"是什么?这些就是我们称作托管资源的类,大约有数千个不同的类,通常分为系统类、核心与公共类以及扩展类,我们作为应用人员角度,所关心的主要是扩展类,它们通常都在root\cimv2 命名空间中,大部分是一些以Win32_开头命名
的类,大约有4、5百个。这些类可以通过微软网站的msdn.microsoft/librar ... i/wmi_reference.asp查看。关于这方面的问题,我们在后面还将做进一步的讨论。
    4)枚举实例并显示属性
    在上一步中我们获得了所需类的实例集合,接着我们就用For Each xxxx In yyyy 循环语句枚举每一个实例对象,要注意的是yyyy是一个集合,而xxxx是集合中的元素,而这个元素也是一个对象。在例程1和例程2中,objSWbemObjectSet对象就是由所有的单个实例对象组成的集合,在例程1中的单个实例对象是进程对象("Win32_Process"),例程2中的单个实例对象是系统服务对象(" Win32_Service ")。
    在循环体中,我们取出了每个实例对象(objSWbemObject)的一些属性添加到了列表控件(ListView1)中,比较例程1和例程2,它们各自的属性名称基本上都不相同,即使意义完全相同属性其名称也可能不同,例如例程1中属性ExecutablePath和例程2中属性PathName中的内容都是程序的路径,但属性名称却不相同。实际上,每个类都有各自的属性,在这里就不做深入的讨论了,在后面我们还将做进一步的讨论,并提供一个能够罗列出所有CIM命名空间、类、属性、方法的程序。
    在例程1中我们还使用了一个方法去终止进程,因为本节主要是讨论VB中使用WMI编程的
基本步骤,所以不对此再进行分析了。大家只要记住VB中使用WMI编程的基本步骤为4步:
    1)创建SwbemLocator指针
    2)连接到WMI服务
    3)获得WMI 托管资源(类)的实例集合
    4)枚举实例并显示属性   
    本讲就到这里,下一讲再来做一个例程,并介绍另一种不完全相同的编程的步骤。由于本人计算机安装的是WIN2000系统,就WMI来说,WINDOWS XP和WIN2003的功能都增强了许多,有些例子在我这里无法实现,所举的例子也仅在WIN2000下做了测试,如执行例程时发生问题请告知。那你们可能会问“你为什么不用WINDOWS XP或WIN2003”?呵呵,因为本人无产阶级也,只能用D版的,而WINDOWS XP或WIN2003需要激活,尽管有解密的,但网上升级的风险很大,不定哪一天升级完后,被告知只能使用XX天必须激活,呜呼哀哉也。下一讲见。