开始之前
写这个系列教程是为了让大家在不熟悉 WMI 相关知识的情况下在 AutoHotkey 中如何操作 WMI,利用它编写脚本来管理和自动化企业系统、应用程序和网络,
- 如果您想了解 AutoHotkey 中操作 COM 的相关函数和语法,请参见 ComObjGet 命令 及其他相关部分。
- 如果您想学习 WMI 的相关知识,请参见 Windows Management Instrumentation。在后面的部分,为了描述方便会使用一些与 WMI 相关的名词,但不会进行详细解释。
- 如果您在其他语言或脚本中使用过 WMI,那么只需了解 AutoHotkey 中操作 COM 的相关函数和语法就行了,如果愿意继续阅读并不吝赐教,不胜感激。
WMI 最初于 1998 年作为一个附加组件与 Windows NT 4.0 Service Pack 4 一起发行,是内置在Windows 2000、Windows XP 和 Windows Server 2003 系列操作系统中核心的管理支持技术。基于由 Distributed Management Task Force(DMTF)所监督的业界标准,WMI 是一种规范和基础结构,通过它可以管理(访问、配置和监视)所有的 — 几乎所有的 Windows 系统资源 — 例如磁盘、事件日志、文件、文件夹、文件系统、网络组件、操作系统设置、性能数据、打印机、进程、注册表设置、安全性、服务、共享、用户、组等等。关于WMI 的详细描述,请参阅维基百科条目 Windows Management Instrumentation(英文)。
为什么需要 WMI?
简单说来,在 WMI 之前,如果需要管理 Windows 中各种各样的资源,除了在图形界面操作外,唯一可以使用的只有 Windows API,虽然命令行工具或 COM 在某些时候也是一种选择,不过命令行工具功能较为有限,而 COM 则局限在实现了对象模型的那些组件和应用程序。这里说明了两点,一个是 WMI 适用于系统管理(这是它的目标,实际上还包括微软众多的服务器程序,如 IIS),二是使用 WMI 比 Windows API 方便(尤其对于之前没接触过的人),此外,前面说的系统管理不仅包括本地,还包括远程计算机。
最后
在刚接触 WMI 时,可能感觉它非常复杂,不过其中的对象(以及集合)如同上面描述的那样,只有这么有限的几个。如果您认真看完了上面部分,却觉得什么都没学会,那么请别担心,这只是第一部分。学习后续部分后,您就可以从生疏逐渐熟练起来。对于这个教程,其实是我想把自己学习 WMI 的一些经历和大家分享,如果发现其中的错误疏漏之处,敬请批评指正,谢谢。
WMI 对象
脚本 API 对象模型
下图显示了 WMI 脚本对象之间的关系。 脚本 API 对象简述
WMI 参考中的脚本 API 描述了使用特定语法的每个脚本对象。有关此语法的说明,请参阅适用于此脚本 API 的文档约定。
下表列出了 WMI 脚本对象以及它们的使用说明。
[table][tr][td]对象[/td][td]描述[/td][/tr]
[tr][td]SWbemDateTime[/td][td]创建和解析 CIM 日期时间 值. 这是个在 Windows XP 中可用的帮助器对象.[/td][/tr]
[tr][td]SWbemEventSource[/td][td]与 SWbemServices.ExecNotificationQuery 结合使用来检索事件.[/td][/tr]
[tr][td]SWbemLastError[/td][td]在错误发生时提供扩展错误信息.[/td][/tr]
[tr][td]SWbemLocator[/td][td]获取能访问特殊宿主计算机上 WMI 的 SWbemServices 对象.[/td][/tr]
[tr][td]SWbemMethod[/td][td]包含单个 WMI 方法定义.[/td][/tr]
[tr][td]SWbemMethodSet[/td][td]取得 SWbemMethod 对象集合.[/td][/tr]
[tr][td]SWbemNamedValue[/td][td]包含单个命名值.[/td][/tr]
[tr][td]SWbemNamedValueSet[/td][td]获取对 SWbemNamedValue 对象集合的访问.[/td][/tr]
[tr][td]SWbemObject[/td][td]包含和操作单个 WMI 对象类或实例.[/td][/tr]
[tr][td]SWbemObjectEx[/td][td]扩展 SWbemObject 的功能. 此对象为 SWbemRefresher 对象添加了Refresh 方法. [indent]Windows 2000, Windows NT 4.0 和 Windows Me/98/95: 此对象不可用。[/indent][/td][/tr]
[tr][td]SWbemObjectPath[/td][td]生成并验证对象路径.[/td][/tr]
[tr][td]SWbemObjectSet[/td][td]获取对 SWbemObject 对象集合的访问.[/td][/tr]
[tr][td]SWbemPrivilege[/td][td]设置或清楚权限.[/td][/tr]
[tr][td]SWbemPrivilegeSet[/td][td]获取对 SWbemPrivilege 对象集合的访问.[/td][/tr]
[tr][td]SWbemProperty[/td][td]包含单个 WMI 属性.[/td][/tr]
[tr][td]SWbemPropertySet[/td][td]获取对 SWbemProperty 对象集合的访问.[/td][/tr]
[tr][td]SWbemQualifier[/td][td]包含单个属性限定符.[/td][/tr]
[tr][td]SWbemQualifierSet[/td][td]获取对 SWbemQualifier 对象集合的访问.[/td][/tr]
[tr][td]SWbemRefresher[/td][td]在一次操作中收集和更新对象属性值.[indent]Windows 2000, Windows NT 4.0 和 Windows Me/98/95: 此对象不可用。[/indent][/td][/tr]
[tr][td]SWbemRefreshableItem[/td][td]表示 SWbemRefresher 对象中单个可刷新元素, 例如属性. [indent]Windows 2000, Windows NT 4.0 和 Windows Me/98/95: 此对象不可用。[/indent][/td][/tr]
[tr][td]SWbemSecurity[/td][td]管理安全设置, 例如组件对象模型(COM)Privileges, AuthenticationLevel 和 ImpersonationLevel.[/td][/tr]
[tr][td]SWbemServices[/td][td]创建, 更新以及获取类或实例.[/td][/tr]
[tr][td]SWbemServicesEx[/td][td]扩展 SWbemServices 的功能. 此对象添加 Put 和 PutAsync 方法以允许保存类或实例到多个命名空间. [indent] Windows 2000, Windows NT 4.0 和 Windows Me/98/95: 此对象不可用。[/indent][/td][/tr]
[tr][td]SWbemSink[/td][td]接收异步操作和事件通知的结果, 此对象用于客户端应用程序.[/td][/tr][/table]
资料来源: WMI 脚本实例
检索信息
注:如未特别说明,以下示例脚本均使用带 BOM 的 UTF-8 编码,并在带 SP3 的 Windows XP 系统,使用 AutoHotkey_L 1.0.90.00 Unicode 32 位版本测试通过。
示例 1:检索每个服务的状态和启动类型
Code: Select all
wbemServices := ComObjGet("winmgmts:\\.")
wbemObjectSet := wbemServices.InstancesOf("Win32_Service")
For wbemObject In wbemObjectSet
MsgBox, % "Display Name: " wbemObject.DisplayName "`n"
. " State: " wbemObject.State "`n"
. " Start Mode: " wbemObject.StartMode
return
Code: Select all
wbemServices := ComObjGet("winmgmts:\\.\root\cimv2") ; 连接目标电脑的 WMI 服务,[c]\root\cimv2[/c]为命名空间
wbemObjectSet := wbemServices.InstancesOf("Win32_Process") ; 获取Win32_Service类的实例集合
For wbemObject In wbemObjectSet ; 从实例集中枚举单个实例(尽管这里也可以用 while,不过建议用 for)
MsgBox, % "Process: " wbemObject.Name "`n"
. "CommandLine: " wbemObject.CommandLine "`n" .
. "Working Set Size: " wbemObject.WorkingSetSize
return
实例分析
脚本中使用的三个步骤,对于任何用于检索 WMI 托管资源信息的 WMI 脚本来说是共同的。下面让我们来仔细分析这两个脚本:
步骤 1:连接到 WMI 服务
在 WMI 脚本中,第一个步骤都是建立一个到目标计算机上的 Windows 管理服务的连接。ComObjGet()函数的参数(我称其为 WMI 路径)中 winmgmts: 为 WMI 脚本库名字对象的名称,接着的句点在WMI中表示本地计算机。把句点换成远程计算机的名称如 remote-computer,那么您还可以检索远程计算机的服务和进程信息(前提是远程计算机安装了 WMI服务并且您在那台计算机上拥有管理员的访问权限)。
用这种方法连接到 WMI,返回一个对 SWbemServices 对象的引用,在示例 1 和 2 中我们使用名为 wbemServices的变量来引用该对象。SWbemServices 是在 WMI 脚本库中定义的一打左右的对象中的一个。WMI脚本库提供一组用于访问 WMI 基础结构的通用对象脚本。有了一个对 SWbemServices 对象的引用,您就可以调用任何 SWbemServices提供的方法;InstancesOf就是此种方法中的一个。
步骤 2:检索 WMI 托管资源的实例
第二个步骤调用 SWbemServices 对象的 InstancesOf 方法,此方法返回由资源的类名标识的托管资源的所有实例的集合。InstancesOf 以一个 SWbemObjectSet集合的形式返回所需的资源,在示例 1 和 2 中我们使用名为 wbemObjectSet的变量引用它。SWbemObjectSet是 WMI 脚本库中定义的另一个脚本对象。
步骤 3:显示 WMI 托管资源的属性
最后一个步骤是枚举 SWbemObjectSet 集合的内容。SWbemObjectSet中的每个项都是一个 SWbemObject(WMI 脚本库中的另外一个对象) —表示所需资源的一个单个实例。使用 SWbemObject来访问托管资源类定义中定义的方法和属性。
上面对这些 WMI 对象进行了很详细的介绍,因为它们很重要,很大一部分的脚本都需要使用它们,如果您忘了它们的功能,请回到上一篇重新认识一下。
其他例子 脚本模板
在前面部分中,列举了两个获取属性的例子并进行详细的分析,这是必要的,因为在 WMI 的几百个类,这些类的绝大多数属性都是只读的,换句话说,我们能对它们进行的操作只有获取属性的值。而获取这些属性值的过程都可以使用几乎一样的模板,大多数时候我们需要修改的仅仅是其中的类名和相应的属性名,这篇中除了再添加一个获取属性的例子,还提供了执行类方法和监视类事件的模板。
不可否认,WMI 有非常难于学习而且更难于使用的名声。在许多方面,与其说得到这个名声是因为 WMI 的确很难,还不如说只是因为它太大了。WMI 可以用于管理计算机硬件、计算机软件和几乎这二者范围内的任何事。有人会想当然地认为,任何包含了如此众多不同元素的技术一定是很难的。
事实上,按照众多标准方法,可以用 WMI 执行很多任务。例如,您已经看到了一个模板是如何作为脚本的基础的,这些脚本返回关于几乎任何托管资源的信息。在本系列的前面部分中,相同的基本脚本(有一两个较小的改动)被用于返回像服务的状态和进程命令行那样完全不同的内容。
下列主题提供了基本的 WMI 脚本模板,可以用于:
- 检索托管资源属性。
- 修改托管资源属性。
- 调用托管资源方法。
- 订阅事件以监控托管资源的创建、修改和/或删除。
strComputer = "remote-computer"
检索托管资源实例
到现在为止,我们一直使用 SWbemServices InstancesOf 方法检索托管资源的属性,下面的模板中我们将使用 ExecQuery 方法,关于两者的区别,我可能在以后说明。
代码段 03-01. 用于检索托管资源实例的模板
Code: Select all
strComputer := "."
strNamespace := "\root\cimv2"
strClass := "Win32_Service"
objSWbemServices := ComObjGet("winmgmts:\\" strComputer strNamespace)
colSWbemObjectSet := objSWbemServices.ExecQuery("SELECT * FROM " strClass)
For objSWbemObject In colSWbemObjectSet
{
MsgBox, % "Display Name: " objSWbemObject.DisplayName
MsgBox, % "State: " objSWbemObject.State
MsgBox, % "Start Mode: " objSWbemObject.StartMode
}
- 将 strClass 的值设置为目标托管资源的相应的 WMI 类。
- 如必要,将 strNamespace 的值设置为目标类的 WMI 命名空间。
- 替换显示属性及其值的 For 循环中的语句。删除下列行,并用在显示的属性值的相应的代码行替换它们。
Code: Select all
MsgBox, % "Display Name: " objSWbemObject.DisplayName MsgBox, % "State: " objSWbemObject.State MsgBox, % "Start Mode: " objSWbemObject.StartMode
在 Windows 2000 中,WMI 主要是只读技术。在 Windows 2000 root\cimv2 命名空间中定义的 4,395 个属性中,只有 39 个属性是可写的。这些数字在 Microsoft WindowsXP 中又增加了,在大约 6560 个属性中有 145 个是可写的。这个数字在 Windows Server 2003 中甚至更大了。
代码段 03-02 中的模板演示了如何修改一个可写的属性。这个脚本检索由 Win32_OSRecoveryConfiguration 类模拟的托管资源的所有实例。(在这种情况下,这个类只包含单个实例。)这个脚本为三个属性(DebugInfoType、DebugFilePath 和 OverWriteExistingDebugFile)提供了新的值并使用 SWbemObject Put_ 方法提交了改变(因此配置操作系统恢复选项)。如果您忘记调用 Put_ 方法,这些更改将不会被应用。
注 此模板只对可写的属性起作用。试图更改只读的属性将导致错误。要确定一个属性是否为可写的,检查此属性的“Write”限定符。
代码段 03-02. 用于修改托管资源的可写属性的模板
Code: Select all
strComputer := "."
strNamespace := "\root\cimv2"
strClass := "Win32_OSRecoveryConfiguration"
objSWbemServices := ComObjGet("winmgmts:\\" strComputer strNamespace)
colSWbemObjectSet := objSWbemServices.ExecQuery("SELECT * FROM " strClass)
For objSWbemObject In colSWbemObjectSet
{
objSWbemObject.DebugInfoType := 1
objSWbemObject.DebugFilePath := "c:\tmp\memory.dmp"
objSWbemObject.OverWriteExistingDebugFile := False
objSWbemObject.Put_
}
- 直接访问与修改由 Win32_OSRecoveryConfiguration 类定义的属性;
- 调用其本身的 Put_ 方法来提交所作更改。
- 将 strClass 的值设定为目标托管资源的相应 WMI 类。
- 如必要,将 strNamespace 的值设定为目标类的 WMI 命名空间。
- 替换配置新属性值的 For 循环中的语句。删除下列行,并用正在被修改的属性的相应的代码行替换它们:
Code: Select all
objSWbemObject.DebugInfoType := 1 objSWbemObject.DebugFilePath := "c:\tmp\memory.dmp" objSWbemObject.OverWriteExistingDebugFile := False
在托管资源的类定义中定义的方法允许您在托管资源上执行操作。例如,Win32_Service 类包含让您执行诸如启动与停止服务的方法;Win32_NTEventlogFile 包含备份和清除事件日志的方法;Win32_OperatingSystem 包含重新启动或关闭计算机的方法。
代码段 03-03 提供了一个模板,能够用来编写调用 WMI 托管资源方法的脚本。这个特定的脚本使用 Win32_Service 类的 StopService 方法来停止本地计算机上的“警报器”服务。
注 在调用在托管资源的类定义中定义的方法之前,该方法必须实现。如何确定一个方法是否实现?检查此方法的实现限定符。TRUE 值表明一个方法有由提供程序提供的实现。已经说过,应注意有些方法不定义实现限定符,即使方法已经实现。下面显示的 Win32_Service StopService 方法就是这样一个例子。确定一个方法是否被实现的结果也会卷入一些麻烦和错误。如我们前面所提到的,我们被告知此解决方案正在进行更正。
代码段 03-03. 调用托管资源方法的模板
Code: Select all
strComputer := "."
strNamespace := "\root\cimv2"
strClass := "Win32_Service"
strKey := "Name"
strKeyValue := "Alerter"
objSWbemServices := ComObjGet("winmgmts:\\" strComputer strNamespace)
colSWbemObjectSet := objSWbemServices.ExecQuery("SELECT * FROM " strClass " WHERE " strKey "='" strKeyValue "'")
For objSWbemObject in colSWbemObjectSet
{
objSWbemObject.StopService()
}
- 将 strClass 的值设置为目标托管资源的相应的 WMI 类。
- 如必要,将 strNamespace 的值设置为目标类的 WMI 命名空间。
- 将 strKey 的值设置为组成 WHERE 子句基本的属性的名称。
- 将 strKeyValue 的值设置为 strKey 的相应值。
- 替换调用方法的 For 循环中的语句。删除下列行,并用正在被调用的方法的相应代码行替换它们:如必要,还必须加入相应的方法参数。
objSWbemObject.StopService()
使用查询 Win32_VolumeChangeEvent 类的监视脚本,在连接或移除可移动磁盘时发出通知,根据 EventType 可以判断是连接还是移除。[indent]注:Windows 2000 中 Win32_VolumeChangeEvent 不可用。
[/indent]代码段 03-04. 订阅事件的模板
Code: Select all
strComputer = "."
strNamespace := "\root\cimv2"
strClass := "Win32_VolumeChangeEvent"
objWMIService = ComObjGet("winmgmts:" "{impersonationLevel=impersonate}!\\" strComputer strNamespace)
colMonitoredEvents = objWMIService.ExecNotificationQuery("Select * from " strClass)
Loop
{
objLatestEvent = colMonitoredEvents.NextEvent
MsgBox, % objLatestEvent.DriveName
MsgBox, % objLatestEvent.EventType
MsgBox, % objLatestEvent.Time_Created
}
- 将 strClass 的值设置为目标托管资源的相应的 WMI 类。
- 如必要,将 strNamespace 的值设置为目标类的 WMI 命名空间。
- 替换显示属性及其值的 For 循环中的语句。删除下列行,并用在显示的属性值的相应的代码行替换它们。
Code: Select all
MsgBox, % objLatestEvent.DriveName MsgBox, % objLatestEvent.EventType MsgBox, % objLatestEvent.Time_Created
这部分也许是多余的,不过当时的这种经历在我接触 WMI 并开始学习后,对于我能逐步学习到现在起了非常重要的作用。因为我没有学习过专业编程,只是个脚本爱好者,如果一开始学习 WMI 时需要经常去查看 MSDN 上的 WMI 参考,即使我可以坚持下来,学习的过程也肯定充满了很大的痛苦。而从微软网站的 TechNet 脚本中心的许多有趣的例子转换开始,则会有趣得多。所以我特意在这里介绍这种经历,如果您之前接触过 WMI,却望 MSDN 上的 WMI 参考而却步,那么也许这部分会让您较轻松的度过刚开始学习时迷糊的时期。如果您除了 AutoHotkey 没有其他脚本语言的使用经历,那么这部分是为您量身定做的。
检索事件日志
我们先看看下面这个例子,使用 VBS 通过 WMI 读取 Windows 事件日志记录(由于微软网站上许多用于系统管理的脚本使用 VBS,所以这里以 VBS 为例):
Code: Select all
Set wbemServices = Getobject("winmgmts:\\.")
Set wbemObjectSet =
wbemServices.InstancesOf("Win32_NTLogEvent")
For Each wbemObject In wbemObjectSet
WScript.Echo "Log File: " & wbemObject.LogFile & vbCrLf & _
"Record Number: " & wbemObject.RecordNumber & vbCrLf & _
"Type: " & wbemObject.Type & vbCrLf & _
"Time Generated: " & wbemObject.TimeGenerated & vbCrLf & _
"Source: " & wbemObject.SourceName & vbCrLf & _
"Category: " & wbemObject.Category & vbCrLf & _
"Category String: " & wbemObject.CategoryString & vbCrLf & _
"Event: " & wbemObject.EventCode & vbCrLf & _
"User: " & wbemObject.User & vbCrLf & _
"Computer: " & wbemObject.ComputerName & vbCrLf & _
"Message: " & wbemObject.Message & vbCrLf
Next
现在我们动手一步步把这段代码转换成 AutoHotkey 的语法:
[table=98%][tr][td]首先把第一行赋值中的等号替换为冒号等号(即把 = 替换成 :=),并去除开始的Set[/td][/tr]
[tr][td]把连接对象的函数 Getobject() 替换成 ComObjGet(),这样第一行变成:[/td][/tr]
[tr][td]wbemServices := ComObjGet("winmgmts:\\.")[/td][/tr]
[tr][td]对第二行执行与第一步相同的操作后变成:[/td][/tr]
[tr][td]wbemObjectSet := wbemServices.InstancesOf("Win32_NTLogEvent")[/td][/tr]
[tr][td]在 For 循环中,把 Each 和 Next 关键字去除,同时在循环体前后加上大括号,变成:[/td][/tr]
[tr][td]For wbemObject In wbemObjectSet
{}
[/td][/tr]
[tr][td]把显示文本命令 WScript.Echo 替换为 MsgBox,并在后面加上一个百分号[/td][/tr]
[tr][td]把循环体中的换行符 vbCrLf 替换成 `n,把字符串连接符 & 替换成句点或去除,把其中每行末尾的续行符 _ 替换成句点并放在后一行的行首[/td][/tr][/table]
合并为脚本
经过刚才的替换后,我们看看努力的成果:
Code: Select all
wbemServices := ComObjGet("winmgmts:\\.")
wbemObjectSet :=
wbemServices.InstancesOf("Win32_NTLogEvent")
For wbemObject In wbemObjectSet
{
MsgBox, % "Log File: " wbemObject.LogFile "`n"
. "Record Number: " wbemObject.RecordNumber "`n"
. "Type: " wbemObject.Type "`n"
. "Time Generated: " wbemObject.TimeGenerated "`n"
. "Source: " wbemObject.SourceName "`n"
. "Category: " wbemObject.Category "`n"
. "Category String: " wbemObject.CategoryString "`n"
. "Event: " wbemObject.EventCode "`n"
. "User: " wbemObject.User "`n"
. "Computer: " wbemObject.ComputerName "`n"
. "Message: " wbemObject.Message "`n"
}
这里介绍的是使用 WMI 的一个简单的例子,在实际中,可能会遇到一些复杂的情况,不过只要掌握了相应的方法和一些技巧后,这些都不是太大的问题,相应的我们还可以把 JS 等其他脚本的代码也转换过来,虽然在 AutoHotkey 中可以嵌入 VBS/JS 的代码,不过在一些时候这样的转换可能是必须的,或者可以带来一些好处。(在我最开始看到一段 WMI 的 VBS 脚本时我没有任何的 VBS 语法知识,经过几次失败的尝试后,我成功了,所以感受较深。之后逐渐明白了 WMI 是独立于语言的 Windows 系统管理技术。)
巨大的宝藏
介绍前面这个例子,其实是为了介绍 TechNet 脚本中心脚本库,原因我想您已经想到了,对,因为 WMI 是用于系统管理的技术,而微软网站上的几乎也都是系统管理的,所以如果我们想实现某个功能,那么很可能在这里找到我们需要的脚本,我们所需要做的只是如同上面那样简单的转换语法即可以在 AutoHotkey 脚本中使用了。打开脚本库页面后,在主体部分可以看到根据任务分类的脚本类别,选择某个类别进入后即可以看到许多属于这个类别的脚本了。
如果您没有在这里找到刚好适用的脚本,请别灰心,也许这里的示例已经给了您某些提示。例如假设您想获取某个进程的父进程 ID,而在 [进程类别] 中没有找到这样的例子,那么我们看看其他例子,会发现那些例子中都使用 Win32_Process 类,那么我们想实现的功能很可能也需要使用这个类(也许您会说看到进程就会想到 process 单词了,确实如此,然而在某些时候这种对应关系并不那么明显),这样我们去看看这个类的方法和属性,经过一番努力后,终于找到了 ParentProcessID 属性,正是我们所需要的。
如果前面的努力还没有找到适合的类及其方法/属性该怎么办呢? 别急,我们还有其他方法,尽管可能希望会小一些。其中的一种是搜索微软网站(此时我一般使用 [微软网站集成的搜索引擎],个人习惯吧),使用适当的关键词,中文搜索不到时可能要考虑用英文的,因为除了上面介绍的脚本库,微软网站上的其他地方还有大量的系统管理脚本,只是并没有按任务进行分类,且使用的语言可能不是 VBS(不过我想这个已经不重要了)。这里巨大的宝藏即是指微软网站,而不仅是脚本中心。
还有其他的方法,我将在以后介绍。
这里提供下载自微软的离线脚本库文件,左侧目录是根据系统管理的任务分类的,VBS 代码,大家可以根据自己的需要转换一些有用的脚本(我曾再次寻找下载的微软网址,很纳闷没有找到,如果谁找到了请帮忙提供)。[attach]1747[/attach]
就到这里吧
在本系列前面部分中,我不曾提到要多写 WMI 脚本,到这里我有个小小的想法,希望大家能多转换几个自己感兴趣的例子,我想编写脚本->遇到问题->解决问题->小结的这个过程中获得经验是个硬道理吧。要不可能教程完了,也不清楚自己会了什么。
如果您在看过教程之前的部分后已经开始学习 MSDN 上的 WMI 参考(不是专业人员能这么做真让我敬佩)而不感到迷茫,那么我想这部分对您可能是多余的,并且希望您能和大家分享学习 WMI 参考的一些感受,我可能也会在本系列之后的某一时间中专门说说我阅读 WMI参考的方法。
WMI 查询语言
为什么需要 WQL?
在本系列开始部分的几个例子都使用 InstancesOf 方法获取属性,不过在 03-01 代码段中我们使用了 ExecQuery 方法,在对比中发现这两种方法除了参数不同外似乎没有什么区别。这里再看看使用这两种方法实现相同功能的一个例子,显示所有来自 Application 日志文件中类型为错误的事件:
代码段 05-01. 使用 InstancesOf 方法
Code: Select all
strComputer := "."
strNamespace := "\root\cimv2"
strClass := "Win32_NTLogEvent"
wbemServices := ComObjGet("winmgmts:\\" strComputer strNamespace)
wbemObjectSet := wbemServices.InstancesOf(strClass)
For wbemObject In wbemObjectSet
{
if (wbemObject.LogFile = "Application" andwbemObject.EventType = 1)
MsgBox, % "LogFile: "wbemObject.LogFile "`n"
. "Record Number: "wbemObject.RecordNumber "`n"
. "Type: "wbemObject.Type "`n"
. "EventType: "wbemObject.EventType "`n"
. "Message: "wbemObject.Message
}
Code: Select all
strComputer := "."
strNamespace := "\root\cimv2"
strClass := "Win32_NTLogEvent"
wbemServices := ComObjGet("winmgmts:\\" strComputer strNamespace)
wbemObjectSet := wbemServices.ExecQuery("SELECT * FROM " strClass "WHERE LogFile = 'Application' and EventType = 1")
For wbemObject In wbemObjectSet
{
MsgBox, % "LogFile: "wbemObject.LogFile "`n"
. "Record Number: " wbemObject.RecordNumber "`n"
. "Type: "wbemObject.Type "`n"
. "EventType: "wbemObject.EventType "`n"
. "Message: "wbemObject.Message
}
查询 WMI 是向匹配一些预定义条件的托管资源发出请求的过程。例如代码段 05-02 中,WMI 查询可以只请求那些在Application 日志文件中且事件类型为 1(即错误)的事件。
由于 InstancesOf 总是返回一个指定资源的所有实例以及每个实例的所有属性,而 WMI查询只返回那些匹配查询的实例和属性,所以 WMI 查询为检索托管资源实例及其属性提供了比 InstancesOf方法更有效率的机制。所以使用 WMI 查询比使用 InstancesOf 方法具有下列一些好处:
- 创建有目标的查询有时会显著地提高数据返回的速度。
- 有目标的查询也让对返回的数据的工作变得更轻松。相反,InstancesOf 将返回所有事件,因此您将不得不单独地检查每个事件并确定它是否:1. 来自应用程序事件日志并且,2. 带有 EventType1。虽然这可以完成,但是这太低效了而且对您来说需要额外的工作(尤其是日志文件中含有大量的事件时)。
- 有目标的查询还可以减少返回的数据的数量,对在网络上运行的脚本来说这是个重要的考虑。
要查询 WMI,使用 WMI 查询语言(WQL)构造一个查询字符串。查询字符串定义了必须被满足的条件来获得成功匹配的结果。在查询字符串定义后,查询使用 SWbemServices ExecQuery 方法提交到 WMI服务。满足查询的托管资源实例以 SWbemObjectSet 集合的形式返回到脚本中。WMI 查询语言(WQL)是经过语义上的微小改动以支持 WMI 的一个 ANSI SQL 子集。
使用 WQL 可以访问三种 WMI 信息:数据、事件和架构。一般 WQL 查询是使用 SWbemServices对象的方法执行的。ExecQuery 和它的异步等价方法 ExecQueryAsync 用于数据和 Schema 查询,ExecNotificationQuery 和 ExecNotificationQueryAsync 用于捕获事件通知。在每种类型的查询中,WQL 语法都有一些独特的特征,接着将进行详细讨论。
想了解受支持的 WQL 关键字的完整列表,请参阅 WQL(适用于 WMI 的 SQL)。在对象或属性名中使用 SQL 关键字可以限定需要解析的查询。后面的 SQL 关键字是受限制的:NULL、TRUE 和 FALSE。
WQL 支持下列三种查询类型:
- 数据查询 数据查询用于获取类实例和数据关联。它们是在 WMI 脚本和应用程序中最常见的查询类型。在进行数据查询时,较常用的有三种语句:SELECT、ASSOCIATORS OF 和 REFERENCES OF。
注 WQL 不支持数组数据类型的查询。- SELECT 指定在查询中需要返回的属性,例如我们想获取所有逻辑磁盘的名称和可用空间,可以使用类似这样的查询语句:
SELECT Name,FreeSpace FROM Win32_LogicalDisk
注 WQL 不支持数组数据类型的查询。 - ASSOCIATORS OF 返回与特定源实例关联的所有实例,例如后面的查询返回与 C 盘关联的所有实例:
ASSOCIATORS OF{Win32_LogicalDisk.DeviceID="C:"} - REFERENCES OF 返回引用特定源实例的所有实例,此语句具有与 ASSOCIATORS OF 类似的语法:
REFERENCES OF{Win32_Service.Name="lanmanworkstation"}
- SELECT 指定在查询中需要返回的属性,例如我们想获取所有逻辑磁盘的名称和可用空间,可以使用类似这样的查询语句:
- 事件查询 用户注册事件查询来接收事件通知。事件提供者注册事件查询以支持事件。下面的这个事件查询例子请求在 Win32_NTLogEvent 类创建新实例时接收到通知。
关于事件查询的更多细节,请参阅 接收事件通知。
Code: Select all
strComputer := "." objWMIService := ComObjGet("winmgmts:\\" strComputer "\root\CIMV2") objEvents := objWMIService.ExecNotificationQuery("SELECT * FROM __InstanceModificationEvent WITHIN 10 WHERE " . "TargetInstance ISA 'Win32_Service'" . " AND TargetInstance._Class = 'win32_TerminalService'") Loop { strReceivedEvent := objEvents.NextEvent MsgBox, % "An event has occurred." }
- 架构查询 架构查询用于获取类定义和架构关联,这里不进行介绍,若有兴趣请参阅 获取类定义。
- 在 WQL 查询中可以使用的 AND 和 OR关键字的数目是有限制的。在复杂查询中使用的大量 WQL 关键字可能导致 WMI 返回 HRESULT 值类型的WBEM_E_QUOTA_VIOLATION 错误码。WQL 关键字的数目限制取决于查询的复杂程度。
- 在查询的扩展和定制部分可以使用 WHERE 子句,不过这不是必要的。WHERE子句由属性或关键字,运算符和常量组成。所有的 WHERE 子句必须指定包含在 WQL中的预定义运算符的其中一个。想了解语法的更多细节,请参阅 WHERE子句。想了解有效的 WQL 运算符的更多细节,请参阅 WQL运算符。
- WQL 不支持跨命名空间的查询或关联。您无法查询特定类在目标计算机上所有命名空间中的所有实例。
由于我对某些部分使用的不很熟练,且行文匆匆,所以本文可能艰涩难懂,以后我将根据自己的认识和大家的反馈对本篇和本系统进行适当的更新。
本教程改编自:WMI 脚本入门第一部分、第二部分、第三部分