开发者

msiexec scripting in python

开发者 https://www.devze.com 2023-02-16 12:48 出处:网络
Most of this is background, skip the next 3 paragraphs for the question: I have developed a tool that calls some installers, changes registry items, and moves files around to help me test a product w

Most of this is background, skip the next 3 paragraphs for the question:

I have developed a tool that calls some installers, changes registry items, and moves files around to help me test a product which has a fairly fast update cycle. So far so good, I have a GUI which runs in a separate process to the business logic to prevent it locking due to the GIL, everything works etc, however I have concerns with a section of my code where I make calls to msiexec.

Specifically it's the uninstall part which gives me concerns. Currently the GUID does not change so I am able to uninstall the product using an os.system(开发者_如何学C'msiexec /x "{GUID}" /passive') sort of thing. It's actually a bit more complicated as I'm using subprocess.Popen and polling it until it finished from within an event loop to allow for concurrency with other steps.

My concern is that should the GUID change, obviously this will not work. I don't want to point msiexec directly at the installation source, as this would mean that it wouldn't work if I were to 'lose' the msi file, which I store in a temporary directory.

What I am looking for, is a way of querying by program name to get the GUID, or even a wrapper for msiexec that would do all of this, including the uninstall, for me. I thought of scanning through the registry, but the _winreg module seems very slow, so I'd prefer to avoid this if at all possible. If there's a better way to scan the registry, I'm all ears, as this would speed up other parts of the tool also.


Update0

Performance on this is critical as one of the design goals is to make the process which the tool follows faster than any other method, manual or otherwise, in order to gain wholesale adoption.


Update1

I have tried a slight variation of the registry version below however it consistently returns None. I'm not quite sure how this is happening - it seems like it is failing to open the appropriate key as I have inserted a breakpoint after the with statement which is never reached...

def get_guid_by_name(name):
  from _winreg import (OpenKey,
                       QueryInfoKey,
                       EnumKey,
                       QueryValueEx,
                       HKEY_LOCAL_MACHINE,
                       )
  with OpenKey(HKEY_LOCAL_MACHINE, 
               r'SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall') as key:
      subkeys, _0, _1 = QueryInfoKey(key) # The breakpoint here is never reached
      del _0, _1
      for i in range(subkeys):
        subkey = EnumKey(key, i)
        if subkey[0] != '{' or subkey[-1] != '}':
          continue
        with OpenKey(key, subkey) as _subkey:
          if name in QueryValueEx(_subkey, 'DisplayName')[0]:
            return subkey
  return None

  print get_guid_by_name('Microsoft Visual Studio')

Update2

Strike that - I'm a fool who doesn't check his indentation thoroughly enough - print get_guid_by_name('Microsoft Visual Studio') was within get_guid_by_name...


I'm not sure about the _winreg module being all that slow. I suppose if you were trying to enumerate the entire registry to find all instances of a string that might take a while, but with a decently targeted query it seems reasonably fast.

Here's an example:

from _winreg import *

def get_guid_by_name(name):
    # Open the uninstaller key
    with OpenKey(HKEY_LOCAL_MACHINE, r'Software\Microsoft\Windows\CurrentVersion\Uninstall') as key:
        # We only care about subkeys of the installer key
        subkeys, _, _ = QueryInfoKey(key)
        for i in range(subkeys):
            subkey = EnumKey(key, i)
            # Since we're looking for uninstallers for MSI products,
            # the key name will always be the GUID. We assume that any
            # key starting with '{' and ending with '}' is a GUID, but
            # if not the name won't match.
            if subkey[0] != '{' or subkey[-1] != '}':
                 continue
            # Query the display name or other property of the key to
            # see if it's the one we want
            with OpenKey(key, subkey) as _subkey:
                if QueryValueEx(_subkey, 'DisplayName')[0] == name:
                    return subkey
     return None

On my machine, querying for ActiveState's Komodo Edit (I actually used a regular expression rather than straight-value comparison), 1000 iterations of this took 8.18 seconds (timed using timeit), which seems like a negligible amount of time to me. Better yet, you can pull the UninstallString key from the registry and pass that straight to your subprocess (though you may want to add the /passive switch to the end.


Edit

Microsoft does, of course, provide a WMI class (Win32_Product) that provides a rather convenient interface to do all of this. Using Tim Golden's excellent WMI wrapper, one could initiate an install like this:

import wmi
c = wmi.WMI()
c.Win32_Product(Name = 'ProductName')[0].Uninstall()

However, as noted in this blog post, the Win32_Product class is extremely, painfully slow to use.

0

精彩评论

暂无评论...
验证码 换一张
取 消