python, com, and msi (oh my)



  • Since python (3.11.4) is now warning that the msilib module is going to be deprecated in 3.13, I figured "WTH, I can just use the raw IDispatch with the win32com module"

    Almost.

    I'm trying to read a property, but haven't figured out the syntax. In cscript, it's:

    ...
    record = view.Fetch
    prop = record.StringData(1)
    

    It's basically exactly the same in python when using msilib.

    But in python/win32com, it results in an exception - because it's a property, not a method. Hence, I can do:

    prop = record.StringData
    print(prop)
    

    which returns a <bound method StringData of <COMObject <unknown>>>
    (yes, print(record.FieldCount) does return the expected "1")

    The question is: how to turn that indexed property into a string?



  • Hmm...the VB(A) type library viewer shows WindowsInstaller.Record.StringData as a property that takes a value and returns/sets a string vs. one that returns an array or enumeration that you then index off of. Python seems to agree. Maybe the binding doesn't handle this properly? That's what this *sigh* StackOverflow question seems to indicate, along with a workaround using IDispatch.

    Otherwise, if the type library for the Windows Installer COM object includes non-IDispatch versions of everything you could find the underlying get_StringData (or whatever they named it) function and call it directly.



  • @Parody said in python, com, and msi (oh my):

    Otherwise, if the type library for the Windows Installer COM object includes non-IDispatch versions of everything you could find the underlying get_StringData (or whatever they named it) function and call it directly.

    Haven't found anything - all of MS's docs just refer to the automation objects. And, of course, all examples are in vbs.

    I think I'm closer... [fake edit: Ha!!!]

    dispid = record._oleobj_.GetIDsOfNames('StringData')
    data = record._oleobj_.Invoke(dispid, 0, pythoncom.INVOKE_PROPERTYGET, 1, 1)
    

    I'm gonna have to document the 💩 out of that so I can understand it in the future.

    Thx - that link really helped.

    The magic is the last 2 args: the one after 'GET' is 'yes, I want the return value'. Anything after that is args to the "function". It wasn't working at first because I was using the example which ignored the return value.



  • Oh so close. That worked for StringData. Now I can't figure out how to get a writable copy of SummaryInfo. In vbs, you do info = database.SummaryInfo(1)

    Using the above ideas, that returns something. But I can't get the property 'Property' from it then (or the PropertyCount). If I access just as a property via database.SummaryInfo, I can - but then I can't write the property.



  • @dcon said in python, com, and msi (oh my):

    Oh so close. That worked for StringData. Now I can't figure out how to get a writable copy of SummaryInfo. In vbs, you do info = database.SummaryInfo(1)

    Using the above ideas, that returns something. But I can't get the property 'Property' from it then (or the PropertyCount). If I access just as a property via database.SummaryInfo, I can - but then I can't write the property.

    Unfortunately, I'm not sure what exactly you're supposed to do here. VBA and Visual C++ both show it as:

    SummaryInfo Database.SummaryInformation( [long UpdateCount] ) : gets a SummaryInfo object for this database. Optionally takes long UpdateCount: is this the new total number of items? The number of items that will have changed? The online docs mention maxProperties instead but don't define it otherwise.

    long SummaryInfo.PropertyCount : gets the current number of properties in this SummaryInfo that have a value. This is according to the online documentation because God forbid the developers actually use that helpstring property in the type library to tell you anything. 😠

    VARIANT SummaryInfo.Property( long pID ) : gets/sets the value attached to the (pre-defined in the documentation) property ID.

    void SummaryInfo.Persist() : call this when you're done changing things.

    I would have guessed that you get the SummaryInfo, change whatever you want, and call Persist to write it back, but that UpdateCount/maxProperties parameter throws a wrench into everything. I'd assume it needs to be either "This is what PropertyCount will be when you call Persist." or "This is the number of properties you will have changed." but who knows?

    FWIW, Visual C++ doesn't show any other hidden/non-IDispatch compatible functions or anything. I dug the type library out of msi.dll to take a look at it, though you can just use the Object Browser to add a reference to your project instead and get the same thing.



  • @Parody I know in the vbs code passing 1 works. There's just this weird python thing where if I get it as readonly, I can read it, but not update (makes sense, since 0 is the default). But if I get it in the same way as the StringData, I get a python idispatch back, but none of the SummaryInfo methods/properties work.

    I'm guessing the writable object (I assume it is anyways) that I get back via Invoke must be accessed differently from the readonly one since it doesn't even have the _oleobj_ attribute. Just trying to figure out how now...

    Oh :airquotes:interesting:airquotes:... If I access SummaryInfo via Invoke and pass 0 (which should be the same thing as database.SummaryInfo), I run into the same issue. So Invoke is doing something wrong...



  • Of course, if I just say "screw it, the installer is always English", then I don't have to worry about any of this...



  • FWIW, I found the UpdateCount info in the (non-COM) API description:

    uiUpdateCount: Specifies the maximum number of updated values. (Ed: Making space?)

    I can't help much with the Python side, but good luck with it.



  • Maybe dumb suggestion, but if vbscript works, you are tied to a Windows environment anyway, why not shell out to a vbscript from your python script and call it a day? What you are doing sounds like build system type of work, and I have definitely seen worse...



  • @robo2 said in python, com, and msi (oh my):

    Maybe dumb suggestion, but if vbscript works, you are tied to a Windows environment anyway, why not shell out to a vbscript from your python script and call it a day? What you are doing sounds like build system type of work, and I have definitely seen worse...

    That's what I am doing. I just wanted to see if I could remove the fragility of external scripts. Of the 3 scripts, I've killed off 2. Just haven't solved the SummaryInfo yet...



  • Just use PowerShell and accept that you can’t have nice things.



  • @Arantor Someday I'll learn that...



  • @Arantor said in python, com, and msi (oh my):

    Just use PowerShell and accept that you can’t have nice things.

    What does this return? It returns an array... well unless there's only one result, then rather than an array of one object, it just returns an object... unless there are none of the things, then it returns $false.

    I feel like most of my scripting time in PowerShell is spent dealing with crap like this.



  • Hot damn. Figured it out.

    def GetProperty(comobj, prop, index):
    	dispid = comobj.GetIDsOfNames(prop)
    	return comobj.Invoke(dispid, 0, pythoncom.INVOKE_PROPERTYGET, 1, index)
    
    ...
    sumInfo = GetProperty(database._oleobj_, 'SummaryInformation', 1)
    propCount = GetProperty(sumInfo, 'PropertyCount', 0)
    

    Originally, I had the GetProperty method using comobj._oleobj_ since that seemed to work for everything. But the SummaryInfomation, when obtained that way, is an _oleobj_.

    This also means that any SummaryInformation methods have to do something like:

    	dispid = sumInfo.GetIDsOfNames('Persist')
    	sumInfo.Invoke(dispid, 0, pythoncom.INVOKE_FUNC, 0)
    

    I can now modify the table in the msi.


Log in to reply