Python wmi 参数反转

2 投票
1 回答
1042 浏览
提问于 2025-04-18 17:04

我在使用Python的wmi模块创建VSS快照时发现,参数的顺序不对就不管用,必须把它们反过来才能正常工作:

import wmi

def vss_create():
    shadow_copy_service = wmi.WMI(moniker='winmgmts:\\\\.\\root\\cimv2:Win32_ShadowCopy')
    res = shadow_copy_service.Create('ClientAccessible', 'C:\\')

msdn文档中,这个函数应该这样使用:

Win32_ShadowCopy.Create("C:\\", "ClientAccessible");

为什么会这样呢?有没有办法按照预期的顺序来使用呢?

1 个回答

4

总结

看起来,PyWin32层对wmi对象的方法参数顺序进行了反转,这种情况至少已经存在了五年。相关的wmi规范说明,wmi客户端可以以任何顺序传递参数,因此PyWin32这样做并不是“错误”的,虽然我无法判断这是故意的还是意外的。我猜这种情况不太可能改变,因为需要向后兼容,但你可以通过将参数指定为关键字参数来解决这个问题,像这样:Create(Volume=, Context=)

详细信息

注意:在下面的详细信息中,我试图从Python WMI模块代码逐层深入,了解通过PyWin32代码访问的WMI对象,再到其他语言中使用的WMI对象,最后到通过MOF文件定义的WMI对象,甚至是相关的规范文档。这里有好几个层次,我会多次提到“WMI”,在不同层次上它的意思也不同。

当你提到“Python的wmi模块”时,是指Tim Golden的Python WMI模块(链接到源代码),它是基于PyWin32构建的吗?

当你从wmi模块获取一个Python WMI对象时,它的初始化步骤是在_wmi_object类内部进行的,包括查询底层wmi对象的可用方法:

for m in ole_object.Methods_:
    self.methods[m.Name] = None

我将跳过Python的wmi模块,直接使用PyWin32来查看查询WMI COM对象时可用的方法:

>>> from win32com.client import GetObject
>>> vss = GetObject('winmgmts:\\\\.\\root\\cimv2:Win32_ShadowCopy')
>>> [method.Name for method in list(vss.Methods_)]
[u'Create', u'Revert']

我们看到Win32_ShadowCopy对象有这两个方法。所以这就是Python wmi包装器首次了解你正在使用的方法的地方。


接下来,Python WMI包装类会进行一些设置工作,我没有完全追踪这些步骤,但似乎它会为COM对象的每个可用方法初始化_wmi_method类。这个类包括以下初始化步骤:

self.method = ole_object.Methods_ (method_name)
self.in_parameter_names = [(i.Name, i.IsArray) for i in self.in_parameters.Properties_]

使用列表推导式获取每个方法的可用参数。回到我的测试,探索没有Python WMI层的情况,它的输出如下:

>>> CreateMethod = vss.Methods_('Create')
>>> [n.Name for n in list(CreateMethod.InParameters.Properties_)]
[u'Context', u'Volume']

这个示例测试展示了PyWin32的后续,Win32_ShadowCopy的COM对象,以及方法 - 列出了你看到的可用参数的顺序 - 也就是“错误”的顺序。Python WMI层正是根据这个顺序来处理的。


当你通过Python WMI的包装器调用Win32_ShadowCopy对象的方法时,_wmi_method会这样做:

def __call__ (self, *args, **kwargs):
    for n_arg in range (len (args)):
      arg = args[n_arg]
      parameter = parameters.Properties_[n_arg]
      parameter.Value = arg

换句话说;它将你传入的参数(*args)与存储的参数列表一一对应,按照你传入的顺序,将参数与WMI返回的参数顺序配对 - 也就是说,它并不聪明,只是将你输入的第一个参数与'Context'配对,第二个与'Volume'配对,结果就反了过来,导致你的代码崩溃。


调用方法还包括Python的**kwargs参数,它可以接收所有给定的关键字,这意味着你可以这样做:

Create(Volume='C:\\', Context="ClientAccessible")

并通过将它们作为关键字参数来按你想要的顺序放置。(我还没有尝试过)。


我尝试追踪.Properties_在PyWin32com中的查找,以确定在更低层次上顺序来自哪里,这经过了一长串动态和缓存的查找。我看不到发生了什么,也不够了解COM或PyWin32,不知道该寻找什么,所以这对我来说是个死胡同。


换个方法,试图从WMI对象的设置文件中找出顺序来源:运行随Windows一起提供的mofcomp.exe,它处理管理对象格式(MOF)文件... 点击连接,创建类“Win32_ShadowCopy”;在方法列表中点击“Create”方法,然后点击“编辑方法”按钮;接着点击“编辑输入参数”,然后点击“显示MOF”,得到这个结果:

[abstract]
class __PARAMETERS
{
    [in, ID(0): DisableOverride ToInstance] string Volume;
    [in, ID(1): DisableOverride ToInstance] string Context = "ClientAccessible";
};

这是从Windows MOF文件中输出的“正确”参数顺序,带有参数的数字ID - 这意味着它们有一个正确的顺序0,1等。

c:\windows\system32\wbem\vss.mof,这个看起来涵盖卷影复制对象的MOF文件包含:

[static,implemented,constructor] uint32 Create([in] string Volume,[in] string Context = "ClientAccessible",[out] string ShadowID);

而评论中的PowerShell示例在这个MSDN链接中包括$class.create("C:\", "ClientAccessible")

所以这三者都与相同的顺序相符,暗示有一个正确或标准的顺序。

这让我想到以下几种可能性:

  • 有顺序信息来自PythonCOM,而wmi模块应该查看,但没有找到。我快速查找了一下,没找到参数列表中的ID/顺序数据,所以这似乎不太可能。
  • 有某个我不知道的顺序信息,PyWin32 COM层应该查看但没有找到。 - 这里不太确定。
  • 没有官方顺序。为了确认这一点,我们得到了一条有趣的链:

    1. 什么是WMI?微软实现的标准管理框架WBEM和CIM,由DTMF指定。(DTMF = 分布式管理任务组,WBEM是基于Web的企业管理,CIM是通用信息模型)。
    2. MOF是管理对象格式,是CIM的文本表示。

这份文档:http://www.dmtf.org/sites/default/files/standards/documents/DSP0221_3.0.0.pdf似乎是MOF规范。查看7.3.3 类声明部分,从第18页开始:

第570行:

“一个方法可以有零个或多个参数。”

第626到628行:

方法参数是通过名称而不是位置来识别的,调用方法的客户端可以以任何顺序传递相应的参数。因此,可以在方法签名的任何位置添加具有默认值的参数。

我不确定这是否是权威和最新的规范,也没有仔细阅读以寻找例外,但听起来你应该使用命名参数。


WMI对象和方法有MOF定义,而MOF规范说明你不应该依赖参数顺序;然而,通过PyWin32访问WMI对象时显示的顺序与(MSDN文档、MOF文件和PowerShell示例)不同。我仍然不知道为什么。

而搜索这个 引导我到这个邮件列表帖子,作者是Python wmi模块的Tim Golden,他基本上说了和我刚刚发现的相同的事情,只不过是五年前:

方法定义按照WMI返回的顺序获取参数[..] 我不知道参数顺序是否有任何保证[..] 快速浏览几个其他方法定义,似乎WMI始终以与MOF中定义的顺序相反的顺序返回参数。

此时,看起来PyWin32返回的参数列表与典型的Windows参数顺序相反,但如果CIM管理对象方法参数列表规范文档明确说明不要依赖参数顺序,这算不算一个bug呢?

撰写回答