【进阶】学习 WMI 从转换代码开始

作者:amnesiac 首发:官方论坛中文版

导言:WMI 功能强大,尤其在系统管理方面,即使你不打算使用它,你也几乎一定会遇到使用它的代码。但由于 WMI 体系结构庞大,因此初学者学习 WMI 的难点在于如何找到适合的命名空间、类和相应的属性、方法或事件来实现我们需要的功能。不过很容易注意到,使用 WMI 的代码结构异常简单且网上(尤其是 MSDN)有大量现成的实现各种各样功能的 WMI 代码,所以如果能找到使用其他脚本语言的 WMI 代码并将其转换为 AutoHotkey,那么就绕过了这个难点。

目前关于 Windows 系统管理的脚本中,以 VBScript 居多(例如 MSDN 中的 WMI 代码都是使用这种脚本语言),所以本文以 VBScript 代码的 WMI 脚本为例说明转换为 AutoHotkey 代码的过程,如果看到其他语言的代码也可以参照这个过程,例如对于 VB、JScript、AutoIt 等。本文根据 WMI 代码的不同实现方式把它们分成三类:查看 WMI 属性、执行 WMI 方法和接收 WMI 事件。

注:本文重点说明转换过程中的一些情况,但不会解释 WMI 代码的含义,如果您尚不了解 WMI 基础知识,请先参阅 WMI 脚本第一阶系列教程。

查看属性

分析脚本

下面这个 VBScript 脚本显示操作系统的名称:

strComputer = "." 
Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\CIMV2") 
Set colItems = objWMIService.ExecQuery( _
    "SELECT * FROM Win32_OperatingSystem",,48) 
For Each objItem in colItems 
    Wscript.Echo "-----------------------------------"
    Wscript.Echo "Win32_OperatingSystem instance"
    Wscript.Echo "-----------------------------------"
    Wscript.Echo "Caption: " & objItem.Caption
Next

先分析一下这段 VBScript 代码:

  • 第一行赋值,这里的一个句点表示本地计算机。
  • 第二行连接到 WMI 服务。
  • 第三、四行查询 WMI 服务。
  • 第五行至代码末尾为 For 循环,并在循环中显示内容。

转换脚本

现在直接看看转换过来的 AutoHotkey 脚本:

strComputer := "." 
objWMIService := ComObjGet("winmgmts:\\" . strComputer . "\root\CIMV2") 
colItems := objWMIService.ExecQuery(""
    . "SELECT * FROM Win32_OperatingSystem", ComObjMissing(), 48) 
For objItem in colItems {
    MsgBox, %  "-----------------------------------"
    MsgBox, %  "Win32_OperatingSystem instance"
    MsgBox, %  "-----------------------------------"
    MsgBox, %  "Caption: " . objItem.Caption
}
return

对比这两段代码,简单说说基本的转换方法:

  • 第一行,修改赋值语法:AutoHotkey 中的表达式赋值使用 :=,所以这里把 = 修改为 :=。
  • 第二行,修改赋值语法、对应函数和字符串连接符:去除行首的 Set,在 AutoHotkey 中连接 WMI 服务使用 ComObjGet()函数,并且字符串连接符为句点。
  • 第三、四行,修改续行方式:去除前一行末尾的下划线并在下一行开始处加上一个句点及空格(这里用于连接字符串),由于前一行末尾没有需要连接的变量或字符串,所以另加一对空字符串。
  • 第五行至代码末尾,修改 For 循环语法:去除 For 的 Each 关键字,并在末尾加上左大括号(注意它的前面至少需要一个空格或 TAB),并把循环后面的 Next 替换为右大括号。
  • 在循环中,修改显示命令:把 Wscript.Echo 替换为 MsgBox, %,因为前者直接接表达式,所以 MsgBox 后需加百分号。

调整转换的脚本

这样的转换比较直接,除了个别部分其他都可以用脚本实现转换。这里做些简单的修改, ExecQuery() 的参数不长就不用续行了,循环中的几个消息框相互关联所以放在一起显示(至于字符串连接符——句点,可以保留,也可以去除,这里不管它了):

strComputer := "." 
objWMIService := ComObjGet("winmgmts:\\" . strComputer . "\root\CIMV2") 
colItems := objWMIService.ExecQuery("SELECT * FROM Win32_OperatingSystem", ComObjMissing(), 48) 
For objItem in colItems {
    MsgBox, %  "-----------------------------------"
      . "Win32_OperatingSystem instance"
      .  "-----------------------------------"
      .  "Caption: " . objItem.Caption
}
return

可选参数的默认值

前面代码的差异中,有重要的一点没有提到:在 VBScript 代码中 ExecQuery() 方法省略了第二个参数表示这个参数是可选的并且使用它的默认值,但在 AutoHotkey 中在调用 COM 的方法时若可选参数后还有其他参数,那么这个参数不能省略,可以使用默认值或者用 ComObjMissing() 代替,所以下面两者效果一样(这个方法的这个参数的默认值是字符串 WQL):

colItems := objWMIService.ExecQuery("SELECT * FROM Win32_OperatingSystem", "WQL", 48)
colItems := objWMIService.ExecQuery("SELECT * FROM Win32_OperatingSystem", ComObjMissing(), 48)

这点也适用于转换 VB 家族其他语言的情况。

补充自定义函数

在一些情况下会遇到 AutoHotkey 中没有与 VBScript 中相对应的命令或函数,此时要考虑自定义函数了。在下面这个例子中,由于这个属性的值是数组,所以需要先进行处理才能显示出来(原来的 VBScript 代码中使用 Join(),不过这是它的内置函数)。

strComputer := "." 
objWMIService := ComObjGet("winmgmts:\\" . strComputer . "\root\CIMV2") 
colItems := objWMIService.ExecQuery("SELECT * FROM Win32_BIOS", ComObjMissing(), 48) 
For objItem in colItems {
    MsgBox, % "-----------------------------------"
      . "`nWin32_BIOS instance"
      . "`n-----------------------------------"
      . "`nBiosCharacteristics: " . Join(objItem.BiosCharacteristics, ",")
}
return

Join(arrList, strDelimiter)
{
    For strItem in arrList
    {
        If (A_Index = 1)
          strList := strItem
        strList .= strDelimiter . strItem
    }
    Return, strList
}

在 WMI 中有许多属性的值是数组,很可能需要这个函数。另外,许多 WMI 属性的值是时间:

strComputer := "." 
objWMIService := ComObjGet("winmgmts:\\" . strComputer . "\root\CIMV2") 
colItems := objWMIService.ExecQuery("SELECT * FROM Win32_OperatingSystem", ComObjMissing(), 48) 
For objItem in colItems {
    MsgBox, % "-----------------------------------"
      . "`nWin32_OperatingSystem instance"
      . "`n-----------------------------------"
      . "`nInstallDate: " . objItem.InstallDate
}
return

WMIDateStringToDate(dtmDate)
{
    WMIDateStringToDate := SubStr(dtmDate, 5, 2) . "/"
    . SubStr(dtmDate, 7, 2) . "/" . SubStr(dtmDate, 1, 4) 
    . " " . SubStr(dtmDate, 9, 2) . ":" . SubStr(dtmDate, 11, 2) . ":" . SubStr(dtmDate,13, 2)
    Return, WMIDateStringToDate
}

WMI 中时间格式类似于 20101220164120.000000+480,看起来不太方便,这时一般需要进行适当的处理。

执行方法

下面这个 VBScript 脚本把计算机名称从 MS-201012201636 修改为 NewComputerName:

strComputer = "." 
Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\CIMV2") 
' Obtain an instance of the the class 
' using a key property value.
Set objShare = objWMIService.Get("Win32_ComputerSystem.Name='MS-201012201636'")

' Obtain an InParameters object specific
' to the method.
Set objInParam = objShare.Methods_("Rename"). _
    inParameters.SpawnInstance_()

' Add the input parameters.
objInParam.Properties_.Item("Name") =  "NewComputerName"
objInParam.Properties_.Item("Password") =  ""
objInParam.Properties_.Item("UserName") =  "Administrator"

' Execute the method and obtain the return status.
' The OutParameters object in objOutParams
' is created by the provider.
Set objOutParams = objWMIService.ExecMethod("Win32_ComputerSystem.Name='MS-201012201636'", "Rename", objInParam)

' List OutParams
Wscript.Echo "ReturnValue: " & objOutParams.ReturnValue

转换并进行简单调整后的 AutoHotkey 脚本:

strComputer := "." 
objWMIService := ComObjGet("winmgmts:\\" . strComputer . "\root\CIMV2") 
; Obtain an instance of the the class 
; using a key property value.
objShare := objWMIService.Get("Win32_ComputerSystem.Name='MS-201012201636'")

; Obtain an InParameters object specific
; to the method.
objInParam := objShare.Methods_("Rename").inParameters.SpawnInstance_()

; Add the input parameters.
objInParam.Properties_.Item("Name") := "NewComputerName"
objInParam.Properties_.Item("Password") := ""
objInParam.Properties_.Item("UserName") := "Administrator"

; Execute the method and obtain the return status.
; The OutParameters object in objOutParams
; is created by the provider.
objOutParams := objWMIService.ExecMethod("Win32_ComputerSystem.Name='MS-201012201636'", "Rename", objInParam)

; List OutParams
MsgBox, % "ReturnValue: " . objOutParams.ReturnValue
return

除了把行注释符替换为分号,这里不进行更多的解释了。把其中的两处 MS-201012201636 替换为您计算机的名称(请在[系统属性]对话框中查看计算机名),就可以执行这个代码来修改计算机名称了(其中输入参数中的用户名和密码可能需要根据具体情况进行调整)。

接收事件

同步监听

下面这个 VBScript 脚本监听进程创建、关闭事件:

strComputer = "." 
Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\CIMV2") 
Set objEvents = objWMIService.ExecNotificationQuery _
("SELECT * FROM Win32_ProcessTrace")

Wscript.Echo "Waiting for events ..."
Do While(True)
    Set objReceivedEvent = objEvents.NextEvent

    'report an event
    Wscript.Echo "Win32_ProcessTrace event has occurred."
Loop

转换并进行简单调整后的 AutoHotkey 脚本:

strComputer := "." 
objWMIService := ComObjGet("winmgmts:\\" . strComputer . "\root\CIMV2") 
objEvents := objWMIService.ExecNotificationQuery("SELECT * FROM Win32_ProcessTrace")

MsgBox, % "Waiting for events ..."
Loop {
    objReceivedEvent := objEvents.NextEvent

    ; report an event
    ToolTip, % "Win32_ProcessTrace event has occurred."
}
return

为什么这里把 Wscript.Echo 替换为 ToolTip 而不是 MsgBox 呢?因为 MsgBox 是阻塞的,在显示对话框时可能会错过其他事件。里面包含的无限循环用来持续进行监视,对于所发生事件的相关信息可以从接收到的事件对象中获取(即 objReceivedEvent)。使用同步方法监听事件这个脚本就没办法做别的事情了,请看后面的解决方法。

异步监听

下面这个 VBScript 脚本与前一个的用途相同,也是监听进程的创建和关闭事件,不过这里使用异步方法:

strComputer = "." 
Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\CIMV2") 
Set MySink = WScript.CreateObject( _
    "WbemScripting.SWbemSink","SINK_")

objWMIservice.ExecNotificationQueryAsync MySink, _
    "SELECT * FROM Win32_ProcessTrace"

WScript.Echo "Waiting for events..."

While (True)
    Wscript.Sleep(1000)
Wend

Sub SINK_OnObjectReady(objObject, objAsyncContext)
    Wscript.Echo "Win32_ProcessTrace event has occurred."
End Sub

Sub SINK_OnCompleted(objObject, objAsyncContext)
    WScript.Echo "Event call complete."
End Sub

转换并进行简单调整后的 AutoHotkey 脚本:

#Persistent

strComputer := "." 
objWMIService := ComObjGet("winmgmts:\\" . strComputer . "\root\CIMV2") 
MySink := ComObjCreate("WbemScripting.SWbemSink")
ComObjConnect(MySink, "SINK_")

objWMIservice.ExecNotificationQueryAsync(MySink, "SELECT * FROM Win32_ProcessTrace")

MsgBox, % "Waiting for events..."
return

SINK_OnObjectReady(objObject, objAsyncContext) {
    ToolTip, % "Win32_ProcessTrace event has occurred."
}

SINK_OnCompleted(objObject, objAsyncContext) {
    ToolTip, % "Event call complete."
}

其中需要重点注意的是把

Set MySink = WScript.CreateObject( _
    "WbemScripting.SWbemSink","SINK_")

替换为了:

MySink := ComObjCreate("WbemScripting.SWbemSink")

ComObjConnect(MySink, "SINK_") 还有原来 VBScript 脚本中的无限空循环被替换为 #Persistent 指令,这样可以在脚本中执行其他操作。从这里可以看出,在具体情况中需要进行灵活的替换,不应该拘泥于某种固定的模式。

对于 WMI 事件,建议采用后面这种异步方式,这样一个脚本中可以同时监听多个事件,还可以在监听事件的同时执行其他操作(虽然使用多个脚本或多线程也可以实现,然而会复杂多了)。

小结

从前面的例子可以看出,转换过程中语法的转换比较简单,在实际情况中应根据需要灵活进行转换。此外,WMI 是 COM 的一个部分,所以这样的转换方法也可以扩展到使用 COM 的脚本。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,588评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,456评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,146评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,387评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,481评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,510评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,522评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,296评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,745评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,039评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,202评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,901评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,538评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,165评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,415评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,081评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,085评论 2 352

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,646评论 18 139
  • ¥开启¥ 【iAPP实现进入界面执行逐一显】 〖2017-08-25 15:22:14〗 《//首先开一个线程,因...
    小菜c阅读 6,383评论 0 17
  • 真的是天才与庸人的区别。30几年前的读书方法,用在如今还是那么实用。那么在只读了一遍的情况下,看看自己...
    小朋友嘀嗒阅读 196评论 0 0
  • 人生啦,总有那么顺或不顺,我不能因为某些小事情影响我一直的心情,这世上除了生死,哪件事不是小事?虽然,我老是当垃圾...
    1大太阳阅读 187评论 0 0
  • 梦中的你依旧温柔,确再也找不到相守的理由, 红尘痴恋,皆化做祭奠, 说好的再也不见,我在梦中食言, 多少细水流年,...
    浅岁桉年阅读 1,633评论 0 1