本文同步发布于Prodesire博客。前言最近在网上搜索Python和WMI的相关资料时,发现大部分文章大同小异,基本上都只讲了很基本的用法,并没有深入讲解如何使用WMI。本文打算更进一步,让我们用Python来玩转WMI。什么是WMI,请参考微软官网关于WMI的介绍。这里简单说明一下,WMI的全称是WindowsManagementInstrumentation,即WindowsManagementSpecification。它是在Windows操作系统上管理数据和操作的基础结构。我们可以使用WMI脚本或应用程序来自动化管理任务等。从使用WMI可以知道WMI支持如下语:用MicrosoftActiveX脚本托管编写的应用程序语言TopicScripts,包括VisualBasicSc??riptingEdition(VBScript)和WMI的PerlScriptingAPI。
从创建WMI脚本开始。
对于脚本代码示例,请参阅脚本和应用程序的WMI任务和TechNetScriptCenter脚本存储库。WindowsPowerShellWindowsPowerShell入门
WMIPowerShellCmdlet,例如Get-WmiObject。VisualBasic应用程序WMI的脚本API。ActiveServerPagesWMI的脚本API.
开始为WMI.C++应用程序创建活动服务器页面COMAPIforWMI。
开始使用C++和WMIC++应用程序示例创建WMI应用程序(包含示例)..NETFramework应用程序用C#编写,Microsoft.Management.Infrastructure命名空间中的VisualBasic.NET或J#Classes。(不再支持System.Management命名空间)。更多有关信息,请参阅WMI.NET概述。可惜WMI本身不支持Python,不过没关系,它支持VB,而且Python中的两个第三方库wmi和win32com可以和VB类似的方式使用。那么接下来,我们就来说说如何使用吧。使用WMI使用wmi库来操作WMI。下面是遍历所有进程和服务的例子:importwmic=wmi.WMI()#遍历进程forprocessinc.Win32_Process():printprocess.ProcessId,process.Name#遍历服务forserviceinc.Win32_Service():printservice.ProcessId,service.Name可以看到,使用起来很简单。但是有两个问题:一个是wmi库太慢了,能不能快点?二是如何知道示例中的进程和服务有哪些属性(如ProcessId等)?由于wmi库是动态生成底层执行语句的,所以无法通过dir(process)获取属性ProcessId。对于第一个问题,我们可以使用win32com库来解决,比wmi快很多。至于第二个问题,先保密,待会再介绍。使用win32com库操作WMIwin32com可以模仿VB的行为。如果你想知道如何使用win32com来操作WMI,最直接的方法就是了解如何使用VB来操作WMI。微软官网提供了很多现成的例子:WMITasks:Processes、WMITasks:Services。关于进程的一个例子是:strComputer="."SetobjWMIService=GetObject("winmgmts:"&"{impersonationLevel=impersonate}!\\"&strComputer&"\root\cimv2")SetcolProcesses=objWMIService.ExecQuery("Select*fromWin32_Process")ForEachobjProcessincolProcessesWscript.Echo"Process:"&objProcess.NamesngProcessTime=(CSng(objProcess.KernelModeTime)+CSng(objProcess.UserModeTime))/10000000Wscript.Echo"处理器时间:"&sngProcessTimeWscript.Echo"进程ID:"&objProcess.ProcessIDWscript.Echo"工作集大小:"&objProcess.WorkingSetSizeWscript.Echo"页面文件大小:"&objProcess.PageFileUsageWscript.Echo"页面错误:"&objProcess.PageFaultsNext做了这样一件事情:首先通过GetObject连接到Win32_Process所在的命名空间,然后执行WQL语句(类似SQL查询语句)找到所有的进程,然后打印出每个进程的相关信息。WQL的具体用法可以参考官网,这里不做详细介绍。然后用win32com这样写(为了简单,例子中打印的属性没有上面那么多):fromwin32com.clientimportGetObjectwmi=GetObject('winmgmts:/root/cimv2')#wmi=GetObject('winmgmts:')#Easierwayofwriteprocesses=wmi.ExecQuery('Select*fromWin32_Process')forprocessinprocesses:print(process.ProcessID,process.Name)看来VB和win32com的用法很接近啊!那么当我们要使用win32com来操作WMI时,可以参考微软官网的VB例子,然后编写Python版的代码。在上面的例子中,我们使用了查询函数Exec??Query来查询符合条件的内容,但是如果我们只是想获取所有的数据,没有特定的限制,我们可以使用更简单的方式——InstancesOf,那么可以这样写:fromwin32com.clientimportGetObjectwmi=GetObject('winmgmts:/root/cimv2')processes=wmi.InstancesOf('Win32_Process')forprocessinprocesses:print(process.ProcessID,process.Name)有的读者可能会问,我们如何知道我们想知道的内容在哪个命名空间,应该获取哪个实例,应该获取实例中的哪些属性?WMI命名空间使用以下脚本获取当前计算机上的命名空间:fromwin32com.clientimportGetObjectimportpywintypesdefenum_namespace(name):try:wmi=GetObject('winmgmts:/'+name)namespaces=wmi.InstancesOf('__Namespace')fornamespaceinnamespaces:enum_namespace('{name}/{subname}'.format(name=name,subname=namespace.Name))exceptpywintypes.com_error:print(name,'limitofauthority')else:内容通过print(name)enum_namespace('root')得到的大概是这样的(...表示省略了一些输出内容):rootroot/subscriptionroot/subscription/ms_409root/DEFAULTroot/DEFAULT/ms_409root/CIMV2root/CIMV2/Security...root/Cliroot/Cli/MS_409root/SECURITY...root/WMIroot/WMI/ms_409root/directoryroot/directory/LDAProot/directory/LDAP/ms_409root/Interoproot/Interop/ms_409root/ServiceModelroot/SecurityCenterroot/MSAPPS12root/Microsoft...通用命名空间简介:root是命名空间层次结构的最高层CIMV2命名空间保存与系统管理域相关的对象(例如计算机及其操作系统)。DEFAULT命名空间包含默认情况下创建的类,无需指定命名空间。directory作为目录服务的通用命名空间,WMI创建了一个名为LDAP的子命名空间。SECURITY名称空间用于在Windows9x机器上支持WMI。WMI使用Windows驱动程序模型提供程序类的命名空间。这是为了避免与CIMV2命名空间中的类名发生冲突。其中root/CIMV2可以说是最基本也是最常用的命名空间了。它的作用主要是提供有关计算机、磁盘、外围设备、文件、文件夹、文件系统、网络组件、操作系统、打印机、进程、安全、服务、共享、SAM用户和组以及更多资源的信息;管理Windows事件日志,例如读取、备份、清除、复制、删除、监视、重命名、压缩、解压缩和更改事件日志设置。Classes/InstancesandAttributes/Values了解了命名空间的获取以及每个命名空间的主要作用,那么如何获取特定命名空间下的所有类,以及它们的属性和值呢?Windows提供了一个WMI测试器,使查询这些东西变得特别容易。按“win+R”并键入wbemtest以打开WMI测试器。打开的界面如下:点击“连接”,输入要查询的命名空间,点击“连接”即可连接到具体的命名空间。然后点击“枚举类”,在弹出的界面中选择“递归”,再点击“确定”,就可以得到这个命名空间下的所有类:从上图可以看出,文中提到的Win32_Process位前面的例子其中,我们不妨双击它看看它的具体内容:我们可以很容易地找到Win32_Process的属性和方法。除了使用wbemtest查看特定命名空间下的所有类,我们还可以找到WMI/MI/OMIProviders中的所有类。我们依次点击这个页面的CIMWin32、Win32、PowerManagementEvents、Win32Provider、OperatingSystemClasses、Win32_Process,最后找到Win32_Process的属性和方法:对比上面两张图,里面的方法是一样的。那么如何获取实例及其值呢?我们在刚刚打开的wbemtest界面中继续点击右侧的“Instance”按钮,会显示所有的流程实例。双击具体的实例,然后在弹出的界面中点击右侧的“显示MOF”按钮,即可显示该实例中具体属性的值。通过以上定位命名空间、类、属性的方法,我们就可以愉快的用Python玩转WMI了。实战中,以IIS为例了解了这么多内容,下面拿一个对象来实践一下。现在有这样一个需求,我们要获取IIS的版本号和它所有的站点名称,怎么办呢?在微软官网上可以比较容易的找到IISWMI的说明。根据直觉,我们要查询的信息可能在类名中包含设置的类中,所以看起来更有可能有IIsSetting(WMI)、IIsWebServerSetting(WMI)、IIsWebInfoSetting(WMI)。分别查看这些类并在IIsSetting中找到示例:o=getobj("winmgmts:/root/microsoftiisv2")nodes=o.ExecQuery("select*fromIIsWebServerSettingwherename='w3svc/1'")e=newEnumerator(nodes)for(;!e.atEnd();e.moveNext()){WScript.Echo(e.item().Name+"("+e.item().Path_.Class+")")}//输出应该是://w3svc/1(IIsWebServerSetting)nodes=o.ExecQuery("select*fromIIsSettingwherename='w3svc/1'")e=newEnumerator(nodes)for(;!e.atEnd();e.moveNext()){WScript.Echo(e.item().Name+"("+e.item().Path_.Class+")")}//输出应该是://w3svc/1(IIsIPSecuritySetting)//w3svc/1(IIsWebServerSetting)从这个例子我们可以知道iis的命名空间是'/root/microsoftiisv2',然后我们可以直接查询各种相关的类,比如说“IIsWebServerSetting”.结合wbemtest和IISManager,我们可以看到IIsWebServerSetting实例中的ServerComment属性值与网站名是一致的:而在类名包含setting的类中找不到版本信息,则进入类名包含的类看到它的信息瞧。果然,在IIsWebInfo(WMI)中找到了MajorIIsVersionNumber和MinorIIsVersionNumber属性,分别表示主要版本和次要版本。然后我们可以很方便的写出下面的Python代码来获取版本和站点名称:#coding:utf-8fromwin32com.clientimportGetObjectwmi=GetObject('winmgmts:/root/microsoftiisv2')#versionwebinfo=wmi.execquery('select*来自IIsWebInfo')[0]version='{major}.{min}'.format(major=webinfo.MajorIIsVersionNumber,min=webinfo.MinorIIsVersionNumber)print(version)#网站名称websettings=wmi.execquery('select*来自IIsWebServerSetting')网站='|'.join(setting.ServerCommentforsettinginwebsettings)print(websites)总结使用Python操作WMI最大的难点不是如何写Python语句,而是如果你知道你想要查询的内容是哪个命名空间in以及相应的类和属性。这些内容需要查阅官方文档,使用wbemtest去探索。获得必要的信息后,编写Python代码就非常容易了。
