Python中的msiexec脚本编写
大部分内容是背景信息,接下来的三段可以跳过,直接看问题:
我开发了一个工具,它可以调用一些安装程序,修改注册表项,并移动文件,帮助我测试一个更新速度很快的产品。目前一切正常,我有一个图形界面(GUI),它和业务逻辑在不同的进程中运行,这样可以避免因为全局解释器锁(GIL)导致的卡顿,所有功能都正常。不过,我对代码中调用msiexec的部分有些担心。
具体来说,我担心的是卸载的部分。目前GUID(全局唯一标识符)没有变化,所以我可以用os.system('msiexec /x "{GUID}" /passive')
这样的方式来卸载产品。其实这有点复杂,因为我使用了subprocess.Popen,并在事件循环中轮询它,直到它完成,这样可以和其他步骤并行进行。
我的担心是,如果GUID发生变化,这种方法就不管用了。我不想直接把msiexec指向安装源,因为如果我“丢失”了msi文件(我把它存放在临时目录中),这样就会出问题。
我想要的是一种通过程序名称查询来获取GUID的方法,或者一个可以为msiexec封装的工具,帮我完成所有这些操作,包括卸载。我考虑过扫描注册表,但_winreg模块似乎很慢,所以如果能避免的话我更愿意不使用它。如果有更好的方法来扫描注册表,我非常乐意听取,因为这也会加快工具的其他部分。
更新0
这个性能非常关键,因为设计目标之一是让工具的处理过程比任何其他方法(无论是手动还是其他方式)都要快,以便能够广泛使用。
更新1
我尝试了下面的注册表版本的一个小变种,但它总是返回None
。我不太确定这是怎么回事——看起来是无法打开相应的键,因为我在with
语句后插入的断点从来没有被触发过……
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')
更新2
算了,我真是个傻瓜,没有仔细检查缩进——print get_guid_by_name('Microsoft Visual Studio')
其实是在get_guid_by_name
内部……
1 个回答
我觉得 _winreg 模块并没有那么慢。如果你想要遍历整个注册表去找某个字符串的所有实例,那可能会花不少时间,但如果你有个明确的查询,速度还是挺快的。
这里有个例子:
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
在我的电脑上,查询 ActiveState 的 Komodo Edit(我其实用了正则表达式,而不是直接比较值),运行1000次这个查询花了8.18秒(用 timeit 测试的),我觉得这个时间几乎可以忽略不计。更棒的是,你可以直接从注册表中提取 UninstallString 这个键,然后把它传给你的子进程(不过你可能想在后面加上 /passive
这个参数)。
补充说明
当然,微软提供了一个 WMI 类(Win32_Product),可以很方便地完成这些操作。使用 Tim Golden 的优秀 WMI 封装,你可以这样启动安装:
import wmi
c = wmi.WMI()
c.Win32_Product(Name = 'ProductName')[0].Uninstall()
不过,正如这篇博客中提到的,Win32_Product 类的使用速度非常慢,真的是让人受不了。