Can I use msilib or other Python libraries to extr

2020-08-02 20:05发布

问题:

What I really want to do is determine whether a particular file in the MSI exists and contains a particular string.

My current idea is to run:

 db = msilib.OpenDatabase('c:\Temp\myfile.msi',1)
 query = "select * from File"
 view = db.OpenView(query)
 view.Execute(None)
 cur_record = view.Fetch()     # do this until I get the record I want
 print cur_record.GetString(3) # do stuff with this value

And then if it's there, extract all the files using

msiexec /a c:\Temp\myfile.msi /qn TARGETDIR=c:\foo

and use whatever parser to see whether my string is there. But I'm hoping a less clunky way exists.

回答1:

Note that, as the docs for msilib say, "Support for reading .cab files is currently not implemented". And. more generally, the library is designed for building .msi files, not reading them. And there is nothing else in the stdlib that will do what you want.

So, there are a few possibilities:

  1. Find and install another library, like pycabinet. I know nothing about this particular library; it's just the first search hit I got; you probably want to search on your own. But it claims to provide a zipfile-like API for CAB files, which sounds like exactly the part you're missing.
  2. Use win32com (if you've got pywin32) or ctypes (if you're a masochist) to talk to the underlying COM interfaces and/or the classic Cabinet API (which I think is now deprecated, but still works).
  3. Use IronPython instead of CPython, so you can use the simpler .NET interfaces.

Since I don't have a Windows box here, I can't test this, but here's a sketch of Christopher Painter's .NET solution written in IronPython instead of C#:

import clr
clr.AddReference('Microsoft.Deployment.WindowsInstaller')
clr.AddReference('Microsoft.Deployment.WindowsInstaller.Package')
from Microsoft.Deployment.WindowsInstaller import *
from Microsoft.Deployment.WindowsInstaller.Package import *

def FindAndExtractFiles(packagePath, longFileName):
    with InstallPackage(packagePath, DatabaseOpenMode.ReadOnly) as installPackage:
        if installPackage.FindFiles(longFileName).Count() > 0:
            installPackage.ExtractFiles()


回答2:

Realize that in using Python you have to deal with the Windows Installer (COM) Automation interface. This means you have to do all the database connections, querying and processing yourself.

If you could move to C# ( or say PowerShell ) you could leverage some higher level classes that exist in Windows Installer XML (WiX) Deployment Tools Foundation (DTF).

using Microsoft.Deployment.WindowsInstaller;
using Microsoft.Deployment.WindowsInstaller.Package;

static void FindAndExtractFiles(string packagePath, string longFileName)
{
    using (var installPackage = new InstallPackage(packagePath, DatabaseOpenMode.ReadOnly))
    {
        if(installPackage.FindFiles(longFileName).Count() > 0 )
            installPackage.ExtractFiles();
    }
}

You could also write this as ComVisible(True) and call it from Python.



回答3:

The MSI APIs are inherently clunky, so it's only a matter of where the abstraction lies. Bear in mind that if you just need this a couple times, it may be easier to browse the cab file(s) manually in Explorer. (Files are stored by file key instead of file name).