最近在网上搜索Python和WMI相关资料时,发现大部分文章都千篇一律,并且基本上只说了很基础的使用,并未深入说明如何使用WMI。本文打算更进一步,让我们使用Python玩转WMI。
具体请看微软官网对WMI的介绍。这里简单说明下,WMI的全称是Windows Management Instrumentation,即Windows管理规范。它是Windows操作系统上管理数据和操作的基础设施。我们可以使用WMI脚本或者应用自动化管理任务等。
从Using WMI可以知道WMI支持如下语言:
Application language | Topic |
---|---|
Scripts written in Microsoft ActiveX script hosting, including Visual Basic Scripting Edition (VBScript) and Perl | Scripting API for WMI.
Start with Creating a WMI Script. For script code examples, see WMI Tasks for Scripts and Applications and the TechNet ScriptCenterScript Repository. |
Windows PowerShell | Getting Started with Windows PowerShell
WMI PowerShell Cmdlets, such as Get-WmiObject. |
Visual Basic applications | Scripting API for WMI. |
Active Server Pages | Scripting API for WMI.
Start with Creating Active Server Pages for WMI. |
C++ applications | COM API for WMI.
Start with Creating a WMI Application Using C++ and WMI C++ Application Examples (contains examples). |
.NET Framework applications written in C#, Visual Basic .NET, or J# | Classes in the Microsoft.Management.Infrastructure namespace. (The System.Management namespace is no longer supported). For more information, see WMI .NET Overview. |
很遗憾,WMI并不原生支持Python。不过没有关系,它支持VB,而Python中的两个第三方库wmi和win32com,均能以类似VB的用法来使用。那么接下来,我们来讲讲如何使用。
以下是一个遍历所有进程,所有服务的示例:
1
2
3
4
5
6
7
8
9
|
import
wmi
c
=
wmi
.
WMI
(
)
# 遍历进程
for
process
in
c
.
Win32_Process
(
)
:
print
process
.
ProcessId
,
process
.
Name
# 遍历服务
for
service
in
c
.
Win32_Service
(
)
:
print
service
.
ProcessId
,
service
.
Name
|
可以看到,使用起来非常简单。但是有两个问题:一是wmi库实在是太慢了,能不能快点?二是如何知道例子中process和service有哪些属性(比如ProcessId等)?由于wmi库是动态生成底层执行语句,用dir(process)这种方式是获取不到ProcessId这种属性的。
针对第一个问题,我们可以使用win32com这个库来解决,它相较于wmi的速度快了很多。而第二个问题,先卖个关子,后文会有介绍。
win32com能模仿VB的行为,想了解如何使用win32com来操作WMI,最直接的方式是了解如何使用VB来操作WMI。在微软的官网上提供了很多现成的例子:WMI Tasks: Processes, WMI Tasks: Services。
其中一个例子关于进程是这样的:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
strComputer
=
"."
Set
objWMIService
=
GetObject
(
"winmgmts:"
&
"{impersonationLevel=impersonate}!\\"
&
strComputer
&
"\root\cimv2"
)
Set
colProcesses
=
objWMIService
.
ExecQuery
(
"Select * from Win32_Process"
)
For
Each
objProcess
in
colProcesses
Wscript
.
Echo
"Process: "
&
objProcess
.
Name
sngProcessTime
=
(
CSng
(
objProcess
.
KernelModeTime
)
+
CSng
(
objProcess
.
UserModeTime
)
)
/
10000000
Wscript
.
Echo
"Processor Time: "
&
sngProcessTime
Wscript
.
Echo
"Process ID: "
&
objProcess
.
ProcessID
Wscript
.
Echo
"Working Set Size: "
&
objProcess
.
WorkingSetSize
Wscript
.
Echo
"Page File Size: "
&
objProcess
.
PageFileUsage
Wscript
.
Echo
"Page Faults: "
&
objProcess
.
PageFaults
Next
|
它做了这样一件事:首先通过GetObject连接到Win32_Process所在的名称空间,然后执行WQL语句(类似SQL的查询语句)查到所有的进程,再把每一个进程的相关信息打印出来。WQL的具体用法请见官网,这里不详细介绍。
那么用win32com就可以这么写(例子中打印的属性为了简便,就不像上面那么多啦):
1
2
3
4
5
6
7
|
from
win32com
.
client
import
GetObject
wmi
=
GetObject
(
'winmgmts:/root/cimv2'
)
# wmi = GetObject('winmgmts:') #更简单的写法
processes
=
wmi
.
ExecQuery
(
'Select * from Win32_Process'
)
for
process
in
processes
:
print
(
process
.
ProcessID
,
process
.
Name
)
|
看上去,VB和win32com的用法非常接近!那么当我们想要使用win32com对WMI进行操作时,就可以参考微软官网上VB的例子,然后比葫芦画瓢写出Python版的代码。
上例中,我们使用了查询函数ExecQuery来查询符合条件的内容,不过如果我们仅仅是想要获得所有的数据,而没有特定的限定条件,就可以使用更简单的方式——InstancesOf,那么就可以写成下面这样:
1
2
3
4
5
6
|
from
win32com
.
client
import
GetObject
wmi
=
GetObject
(
'winmgmts:/root/cimv2'
)
processes
=
wmi
.
InstancesOf
(
'Win32_Process'
)
for
process
in
processes
:
print
(
process
.
ProcessID
,
process
.
Name
)
|
有读者可能会问,我们怎么知道自己想要了解的内容在哪个名称空间,我们应该获取哪个实例,又该获取实例中的哪些属性呢?
使用下面的脚本可以获得当前计算机上的名称空间:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
from
win32com
.
client
import
GetObject
import
pywintypes
def
enum_namespace
(
name
)
:
try
:
wmi
=
GetObject
(
'winmgmts:/'
+
name
)
namespaces
=
wmi
.
InstancesOf
(
'__Namespace'
)
for
namespace
in
namespaces
:
enum_namespace
(
'{name}/{subname}'
.
format
(
name
=
name
,
subname
=
namespace
.
Name
)
)
except
pywintypes
.
com_error
:
print
(
name
,
'limit of authority'
)
else
:
print
(
name
)
enum_namespace
(
'root'
)
|
获得的内容大概是这样的(…表示省略了一些输出内容):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
root
root
/
subscription
root
/
subscription
/
ms_409
root
/
DEFAULT
root
/
DEFAULT
/
ms_409
root
/
CIMV2
root
/
CIMV2
/
Security
.
.
.
root
/
Cli
root
/
Cli
/
MS_409
root
/
SECURITY
.
.
.
root
/
WMI
root
/
WMI
/
ms_409
root
/
directory
root
/
directory
/
LDAP
root
/
directory
/
LDAP
/
ms_409
root
/
Interop
root
/
Interop
/
ms_409
root
/
ServiceModel
root
/
SecurityCenter
root
/
MSAPPS12
root
/
Microsoft
.
.
.
|
通用的名称空间的简单介绍:
root 是名称空间层次结构的最高级。
CIMV2 名称空间存放着和系统管理域相关(比如计算机以及它们的操作系统)的对象。
DEFAULT 名称空间存放着默认被创建而不指定名称空间的类。
directory 目录服务的通用名称空间,WMI 创建了名为LDAP的子名称空间。
SECURITY 用来支持Windows 9x计算机上的WMI的名称空间。
WMI 使用Windows Driver Model providers的类所在的名称空间。这是为了避免和CIMV2名称空间中类名冲突。
其中,root/CIMV2可以说是最为基本和常用的名称空间了。它的作用主要是提供关于计算机、磁盘、外围设备、文件、文件夹、文件系统、网络组件、操作系统、打印机、进程、安全性、服务、共享、SAM 用户及组,以及更多资源的信息;管理 Windows 事件日志,如读取、备份、清除、复制、删除、监视、重命名、压缩、解压缩和更改事件日志设置。
了解了名称空间的获取,每个名称空间的主要功能,那么如何获取特定名称空间下所有的类,以及它们的属性和值呢?
Windows提供了一个WMI测试器,使得查询这些内容变得尤为方便。按下”win+R”,输入wbemtest,从而打开WMI测试器。打开后的界面如下:
点击“连接”,输入想要查询的名称空间,再点击“连接”即可连到特定名称空间。
然后点击“枚举类”,在弹出的界面中选择“递归”,然后点击“确定”,就会得到这个名称空间下所有的类:
从上图可以看到,之前举例中提到的Win32_Process位列其中,我们不妨双击它,看看关于它的具体内容:
我们可以很容易地找到Win32_Process的属性和方法。除了使用wbemtest查看特定名称空间下的所有类,我们还可以在WMI/MI/OMI Providers中找到所有的类。我们依次在这个页面中点击CIMWin32, Win32, Power Management Events,Win32 Provider,Operating System Classes,Win32_Process 最终找到Win32_Process的属性和方法:
对比上面两张图,里面的方法都是一致的。
那么如何获得实例和它的值呢?我们继续在刚刚打开的wbemtest界面中点击右边的“实例”按钮,就会显示所有的进程实例。双击某个具体的实例,然后在弹出的界面中点击右侧的“显示MOF”按钮就会显示这个实例中具体属性的值。
通过上述定位名称空间、类、属性的方法,我们就可以愉快地使用Python来玩耍WMI。
了解了这么多内容,咱们就拿个对象练练手。现在有这么个需求,我们想要获取IIS的版本号以及它所有的站点名称,怎么办?
在微软官网上比较容易的找到IIS WMI的说明,根据直觉,我们要查询的信息可能会是在类名中包含setting的类中,那么看起来比较有可能的有IIsSetting (WMI), IIsWebServerSetting (WMI), IIsWebInfoSetting (WMI)。
对这些类都分别看一看,发现IIsSetting中提供了一个例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
o
=
getobj
(
"winmgmts:/root/microsoftiisv2"
)
nodes
=
o
.
ExecQuery
(
"select * from IIsWebServerSetting where name='w3svc/1'"
)
e
=
new
Enumerator
(
nodes
)
for
(
;
!
e
.
atEnd
(
)
;
e
.
moveNext
(
)
)
{
WScript
.
Echo
(
e
.
item
(
)
.
Name
+
" ("
+
e
.
item
(
)
.
Path_
.
Class
+
")"
)
}
// The output should be:
// w3svc/1 (IIsWebServerSetting)
nodes
=
o
.
ExecQuery
(
"select * from
IIsSetting where name='w3svc/1'"
)
e
=
new
Enumerator
(
nodes
)
for
(
;
!
e
.
atEnd
(
)
;
e
.
moveNext
(
)
)
{
WScript
.
Echo
(
e
.
item
(
)
.
Name
+
" ("
+
e
.
item
(
)
.
Path_
.
Class
+
")"
)
}
// The output should be:
// w3svc/1 (IIsIPSecuritySetting)
// w3svc/1 (IIsWebServerSetting)
|
从这个例子中,我们可以知道iis的名称空间是‘/root/microsoftiisv2’,然后我们可以直接在这个空间中查询各种相关类,比如说“IIsWebServerSetting”。
结合wbemtest和IIS管理器,我们可以看出IIsWebServerSetting实例中的ServerComment属性值和网站名称一致:
而版本信息则在类名包含setting的类中无法找到,那再去类名包含info的类中瞧一瞧。果然,在IIsWebInfo (WMI)中找到了MajorIIsVersionNumber和MinorIIsVersionNumber属性,分别表示大版本和小版本。那么我们就能比较轻松地写出下面的Python代码来获得版本和站点名称:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
# coding:utf-8
from
win32com
.
client
import
GetObject
wmi
=
GetObject
(
'winmgmts:/root/microsoftiisv2'
)
# 版本
webinfo
=
wmi
.
execquery
(
'select * from IIsWebInfo '
)
[
0
]
version
=
'{major}.{min}'
.
format
(
major
=
webinfo
.
MajorIIsVersionNumber
,
min
=
webinfo
.
MinorIIsVersionNumber
)
print
(
version
)
# 站点名称
websettings
=
wmi
.
execquery
(
'select * from IIsWebServerSetting '
)
websites
=
' | '
.
join
(
setting
.
ServerComment
for
setting
in
websettings
)
print
(
websites
)
|
使用Python操作WMI,最大的难点并不在于如何编写Python语句,而在于如果获知想要查询的内容在哪个名称空间以及对应的类和属性。而这些内容则需要查阅官方文档以及使用wbemtest进行探索。获得了这些必要的信息后,再去编写Python代码就是一件非常轻松的事情。