import os import subprocess # ============================================================ # Shared helpers # ============================================================ def _refresh_shell(): # Best-effort UI refresh. Note: script runs as SYSTEM in Session 0, # so this may not reach Explorer's session live. Reopening # Control Panel will always show the correct state regardless, # since the registry itself is written correctly. import ctypes try: HWND_BROADCAST = 0xFFFF WM_SETTINGCHANGE = 0x001A SMTO_ABORTIFHUNG = 0x0002 result = ctypes.c_ulong() ctypes.windll.user32.SendMessageTimeoutW( HWND_BROADCAST, WM_SETTINGCHANGE, 0, u'Environment', SMTO_ABORTIFHUNG, 5000, ctypes.byref(result) ) except Exception as e: print 'Broadcast refresh failed: ' + str(e) try: ctypes.windll.shell32.SHChangeNotify(0x08000000, 0x0000, None, None) except Exception as e: print 'SHChangeNotify failed: ' + str(e) def _promote_default_to_hklm(display_name_substring): # Only needed if the installer wrote to HKEY_USERS\.DEFAULT instead # of HKLM (SYSTEM has no real profile). Safe no-op if already in HKLM. import _winreg uninstallkey = 'SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall' try: root = _winreg.OpenKey(_winreg.HKEY_LOCAL_MACHINE, uninstallkey, 0, _winreg.KEY_READ) idx = 0 while True: try: sub = _winreg.EnumKey(root, idx) except WindowsError: break idx += 1 try: k = _winreg.OpenKey(root, sub, 0, _winreg.KEY_READ) name = _winreg.QueryValueEx(k, 'DisplayName')[0] _winreg.CloseKey(k) if display_name_substring.lower() in name.lower(): return True # already visible in HKLM except WindowsError: continue _winreg.CloseKey(root) except WindowsError: pass default_path = '.DEFAULT\\' + uninstallkey try: root = _winreg.OpenKey(_winreg.HKEY_USERS, default_path, 0, _winreg.KEY_READ) except WindowsError: return False idx = 0 promoted = False while True: try: sub = _winreg.EnumKey(root, idx) except WindowsError: break idx += 1 try: src = _winreg.OpenKey(root, sub, 0, _winreg.KEY_READ) except WindowsError: continue try: name = _winreg.QueryValueEx(src, 'DisplayName')[0] except WindowsError: _winreg.CloseKey(src) continue if display_name_substring.lower() not in name.lower(): _winreg.CloseKey(src) continue print 'Promoting HKU\\.DEFAULT entry to HKLM: ' + name dest = _winreg.CreateKey(_winreg.HKEY_LOCAL_MACHINE, uninstallkey + '\\' + sub) i = 0 while True: try: val_name, val_data, val_type = _winreg.EnumValue(src, i) except WindowsError: break i += 1 try: _winreg.SetValueEx(dest, val_name, 0, val_type, val_data) except Exception as e: print 'Could not copy value ' + val_name + ': ' + str(e) _winreg.CloseKey(dest) _winreg.CloseKey(src) promoted = True _winreg.CloseKey(root) return promoted def _post_install_fix(display_name_substring): import time time.sleep(3) _promote_default_to_hklm(display_name_substring) _refresh_shell() # ============================================================ # Main SPM functions # ============================================================ # dir: string -> directory where installer exist # args: string -> which includes all parameters with space delimiter def spm_install(dir, args): if dir: os.chdir(dir) argslist = args.split() try: subprocess.check_call(argslist) _post_install_fix('Audacity') print 'retcode' + str(0) + 'retcode' except subprocess.CalledProcessError as e: print 'retcode' + str(e.returncode) + 'retcode' # dir: string -> directory where installer exist # args: string -> which includes all parameters with space delimiter def spm_uninstall(dir, args): if dir: os.chdir(dir) argslist = args.split() try: subprocess.check_call(argslist) _refresh_shell() print 'retcode' + str(0) + 'retcode' except subprocess.CalledProcessError as e: print 'retcode' + str(e.returncode) + 'retcode' # dir: string -> directory where installer exist # args: string -> which includes all parameters with space delimiter def spm_update(dir, args): import re import time import _winreg # Matches "Audacity 3.x" only -- never matches "Audacity 4.x" OLD_NAME_REGEX = r'^Audacity\s+3\.' uninstallkey = 'SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall' if 'PROGRAMFILES(X86)' in os.environ: views = [_winreg.KEY_WOW64_32KEY, _winreg.KEY_WOW64_64KEY] else: views = [0] hives = [_winreg.HKEY_LOCAL_MACHINE, _winreg.HKEY_CURRENT_USER] def read_val(k, name): try: return _winreg.QueryValueEx(k, name)[0] except WindowsError: return None def find_old(): found = [] seen = set() for hive in hives: for view in views: try: root = _winreg.OpenKey(hive, uninstallkey, 0, _winreg.KEY_READ | view) except WindowsError: continue idx = 0 while True: try: sub = _winreg.EnumKey(root, idx) except WindowsError: break idx += 1 try: k = _winreg.OpenKey(root, sub, 0, _winreg.KEY_READ | view) except WindowsError: continue name = read_val(k, 'DisplayName') if name and re.search(OLD_NAME_REGEX, name.strip()): ident = (hive, sub) if ident not in seen: seen.add(ident) found.append({ 'hive': hive, 'view': view, 'sub': sub, 'name': name.strip(), 'uninst': read_val(k, 'UninstallString') or '', 'msi': read_val(k, 'WindowsInstaller') == 1 }) _winreg.CloseKey(k) _winreg.CloseKey(root) return found def still_present(entry): try: k = _winreg.OpenKey(entry['hive'], uninstallkey + '\\' + entry['sub'], 0, _winreg.KEY_READ | entry['view']) _winreg.CloseKey(k) return True except WindowsError: return False def build_cmd(entry): us = entry['uninst'] if entry['msi'] or 'msiexec' in us.lower(): guid = entry['sub'] if entry['sub'].startswith('{') else None if guid is None: m = re.search(r'\{[0-9A-Fa-f\-]+\}', us) guid = m.group(0) if m else None if guid: return ['msiexec.exe', '/x', guid, '/qn', '/norestart'] return None m = re.match(r'^\s*"([^"]+)"', us) exe = m.group(1) if m else us.split(' /')[0].strip() if not exe: return None return [exe, '/VERYSILENT', '/SUPPRESSMSGBOXES', '/NORESTART'] # ---- 1. Uninstall old versions ---- old = find_old() if not old: print 'No older version found' for entry in old: cmd = build_cmd(entry) if not cmd: print entry['name'] + ' : could not build uninstall command' continue print 'Removing ' + entry['name'] + ' -> ' + ' '.join(cmd) try: rc = subprocess.call(cmd) if rc in (0, 3010): print entry['name'] + ' older version removed (rc=' + str(rc) + ')' else: print entry['name'] + ' uninstaller returncode: ' + str(rc) except Exception as e: print entry['name'] + ' uninstall failed: ' + str(e) for _ in range(45): if not still_present(entry): break time.sleep(2) else: print entry['name'] + ' registry key still present after wait' time.sleep(5) # ---- 2. Install new version ---- if not dir: print 'No install directory supplied by portal for update operation' print 'retcode' + str(1) + 'retcode' return if not args: print 'No install arguments supplied by portal for update operation' print 'retcode' + str(1) + 'retcode' return os.chdir(dir) argslist = args.split() try: subprocess.check_call(argslist) _post_install_fix('Audacity') print 'retcode' + str(0) + 'retcode' except subprocess.CalledProcessError as e: print 'retcode' + str(e.returncode) + 'retcode'