Search code examples
pythonwmi

Python wmi parameters reversed


Using python's wmi module to create a vss snapshot, I've found that the parameters don't work unless I reverse them:

import wmi

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

In the msdn docs, the function is instead supposed to be used this way:

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

Why is this the case, and is there a way to use the intended order?


Solution

  • Summary

    It looks like the parameter ordering for wmi object's methods is reversed from normal by the PyWin32 layer, and this behaviour has been present for at least five years. The relevant wmi spec says that a wmi client can pass the parameters in any order, so PyWin32 is not 'wrong' to do this, although I can't tell if it's deliberate or accident. I speculate that it's unlikely to change, for backwards compatibility reasons, but you can work around this and put the parameters in the order you want by specifying them as keyword parameters: Create(Volume=, Context=).

    Details

    NB. In the below details, I'm trying to go down in layers from the Python WMI module code to WMI objects accessed by COM in PyWin32 code, to WMI objects as documented and used in other languages, to WMI object specification by MOF files, to specification documents. There's several layers and I write "WMI" a lot, meaning different things at different layers.

    When you say "Python's wmi module" do you mean Tim Golden's Python WMI module (link to source code) that builds on PyWin32?

    When you get a Python WMI object from the wmi module, the initialization steps it goes through are inside the class _wmi_object, and include querying the underlying wmi object for its available methods:

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

    I'm going to skip beneath Python's wmi module, and use PyWin32 directly to look at what you get when querying a WMI COM object for its available methods:

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

    And we see the Win32_ShadowCopy object has the methods Create and Revert. So that's where the Python wmi wrapper first learns about the Create method you are using.


    From there, the Python WMI wrapper class does some setup work which I haven't traced through fully, but it seems to initialize class _wmi_method once for each available method of the COM object. This class includes these initialization steps:

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

    A list comprehension to get the available parameters for each method. Going back to my testing to explore that without the Python WMI layer, it gives output like this:

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

    This example test shows the PyWin32 later, the COM object for Win32_ShadowCopy, the Create method - lists its available parameters in the order you are seeing - the "wrong" order. The Python WMI layer is picking up that ordering.


    When you call the Win32_ShadowCopy object's Create() method through Python WMI's wrapper, the _wmi_method does this:

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

    In other words; it pairs up the parameters you pass in (*args) with the stored parameter list, one by one, taking the parameters in the order you pass them, and pairing them with the method parameters in the order WMI returned them - i.e. it's not intelligent, it just links the first parameter you enter with 'Context' and the second with 'Volume' and gets them backwards, and your code crashes.


    The call method also includes Python's **kwargs parameter which takes all given keywords, suggesting you can do

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

    and put them in the order you want by using them as keyword arguments. (I haven't tried).


    I have tried tracing the .Properties_ lookup through PyWin32com to try and identify where the ordering comes from at the lower layers, and it goes through a long chain of dynamic and cached lookups. I can't see what happens and I don't understand enough COM or PyWin32 to know what kinds of things to be looking for, so that's a dead end for me.


    Taking a different approach and trying to find out from the WMI object setup files where the ordering comes from: run mofcomp.exe which ships with Windows and processes Managed Object Format (MOF) files... click Connect, Create Class "Win32_ShadowCopy"; Click the "Create" method in the methods list, then click the "Edit Method" button; then click "Edit Input Arguments" then click "Show MOF", and get this result:

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

    That's the "correct" order of the parameters coming out of the Windows MOF files, with numeric IDs for the parameters - implying they have a correct ordering 0, 1, etc.

    c:\windows\system32\wbem\vss.mof, the MOF file which appears to cover the Volume Shadow Copy objects contains this:

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

    and the PowerShell example in the comments at this MSDN link includes $class.create("C:\", "ClientAccessible").

    So those three things all tie up with the same ordering and implies there is a correct or standard ordering.

    That leaves me thinking of these possibilities:

    • There is ordering information coming out of PythonCOM and the wmi module should look at it, but doesn't. - I have looked around quickly, and can't find ID / ordering data with the parameters list, so that seems unlikely.
    • There is ordering information somewhere unknown to me which the PyWin32 COM layer should be looking at but doesn't. - Not sure here.
    • There is no official ordering. Trying to confirm this point, we get a fun chain:

      1. What is WMI? Microsoft's implementation of standard management frameworks WBEM and CIM, specified by the DTMF. (DTMF = Distributed Management Task Force, WBEM is Web Based Enterprise Management and CIM is the Common Information Model).
      2. MOF is the Managed Object Format, a text representation of CIM

    This document: http://www.dmtf.org/sites/default/files/standards/documents/DSP0221_3.0.0.pdf appears to be the MOF specification. Check section 7.3.3 Class Declaration, from page 18:

    line 570:

    "A method can have zero or more parameters".

    lines 626 through 628:

    Method parameters are identified by name and not by position and clients invoking a method can pass the corresponding arguments in any order. Therefore parameters with default values can be added to the method signature at any position.

    I don't know for sure if that's an authoritative and current specification, nor have I read all of it looking for exceptions, but it sounds like you should use named parameters.


    The WMI objects and methods have a MOF definition, and the MOF specification says you shouldn't rely on the parameter ordering; however, accessing the WMI objects via COM via PyWin32 is showing a different ordering to (MSDN docs, the MOF file and the PowerShell examples). I still don't know why.

    And Googling that leads me to this mailing list post by Tim Golden, author of the Python wmi module, saying basically the same thing as I've just found, except five years ago:

    the method definition picks up the parameters in the order in which WMI returns them [..] I've got no idea if there is any guarantee about the order of parameters [..] Glancing at a few other method definitions, it does seem as though WMI is consistently returning params in the reverse order of their definition in the MOF.

    At this point, it looks like PyWin32 is returning a reversed list to the typical Windows parameter order, but is that a bug if the CIM managed object method parameter list specification document explicitly says don't rely on the parameter ordering?