сценарии msiexec на python

#python #windows #pyqt #pyqt4 #windows-installer

#python #Windows #pyqt #pyqt4 #windows-установщик

Вопрос:

Большая часть этого является фоновой, пропустите следующие 3 абзаца для вопроса:

Я разработал инструмент, который вызывает некоторые установщики, изменяет элементы реестра и перемещает файлы, чтобы помочь мне протестировать продукт, который имеет довольно быстрый цикл обновления. Пока все хорошо, у меня есть графический интерфейс, который выполняется в отдельном процессе для бизнес-логики, чтобы предотвратить его блокировку из-за GIL, все работает и т.д., Однако у меня есть проблемы с разделом моего кода, где я выполняю вызовы msiexec.

В частности, это часть удаления, которая вызывает у меня опасения. В настоящее время GUID не меняется, поэтому я могу удалить продукт, используя os.system('msiexec /x "{GUID}" /passive') что-то вроде. На самом деле это немного сложнее, поскольку я использую подпроцесс.Открывайте и опрашивайте его, пока он не завершится, из цикла событий, чтобы обеспечить параллелизм с другими шагами.

Меня беспокоит то, что в случае изменения GUID, очевидно, это не сработает. Я не хочу указывать msiexec непосредственно на источник установки, поскольку это означало бы, что он не будет работать, если я «потеряю» msi-файл, который я храню во временном каталоге.

Что я ищу, так это способ запроса по имени программы для получения GUID или даже оболочки для msiexec, которая сделала бы все это, включая удаление, для меня. Я думал о сканировании через реестр, но модуль _winreg кажется очень медленным, поэтому я бы предпочел избежать этого, если это вообще возможно. Если есть лучший способ сканирования реестра, я весь внимание, так как это также ускорило бы работу других частей инструмента.


Update0

Производительность при этом имеет решающее значение, поскольку одна из целей разработки — ускорить процесс, которому следует инструмент, по сравнению с любым другим методом, ручным или иным, чтобы добиться повсеместного внедрения.


Обновление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'SOFTWAREMicrosoftWindowsCurrentVersionUninstall') 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

Поразительно, что — я дурак, который недостаточно тщательно проверяет свой отступ — 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'SoftwareMicrosoftWindowsCurrentVersionUninstall') 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
  

На моем компьютере, запрашивающем редактирование Komodo в ActiveState (на самом деле я использовал регулярное выражение, а не сравнение прямых значений), 1000 итераций этого заняли 8,18 секунды (рассчитано с использованием timeit), что для меня кажется незначительным количеством времени. Еще лучше, вы можете извлечь ключ UninstallString из реестра и передать его прямо в свой подпроцесс (хотя вы можете добавить /passive переключатель в конец.


Редактировать

Microsoft, конечно, предоставляет класс WMI (Win32_Product), который предоставляет довольно удобный интерфейс для выполнения всего этого. Используя превосходную оболочку WMI от Тима Голдена, можно было бы инициировать установку следующим образом:

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

Однако, как отмечалось в этом сообщении в блоге, класс Win32_Product чрезвычайно, мучительно медленный в использовании.

Комментарии:

1. Я посмотрю на это и посмотрю, обеспечивают ли они приемлемую производительность — одна из целей этого инструмента — сделать его быстрее, чем вы могли бы выполнить переустановку вручную, в противном случае оба эти варианта определенно были бы хорошими. Как есть, я должен проверить.