import os import ctypes import ssl import urllib2 import subprocess import time import re import sys import shutil import fnmatch import csv import StringIO import _winreg class disable_file_system_redirection: _disable = ctypes.windll.kernel32.Wow64DisableWow64FsRedirection _revert = ctypes.windll.kernel32.Wow64RevertWow64FsRedirection def __enter__(self): self.old = ctypes.c_long() self.ok = self._disable(ctypes.byref(self.old)) return self def __exit__(self, *args): if self.ok: self._revert(self.old) product_name = "TreeSize Free" dirpath = r"C:\Program Files" dirpath_x86 = os.environ.get("ProgramFiles(x86)", r"C:\Program Files (x86)") dirpath_localappdata = os.path.join( os.environ.get("LOCALAPPDATA", r"C:\Users\Default\AppData\Local"), "Programs" ) systemroot = os.environ.get("SystemRoot", r"C:\Windows") dirpath_systemprofile_syswow64 = os.path.join( systemroot, r"SysWOW64\config\systemprofile\AppData\Local\Programs" ) dirpath_systemprofile_native = os.path.join( systemroot, r"System32\config\systemprofile\AppData\Local\Programs" ) ALL_BASE_DIRS = [ dirpath, dirpath_x86, dirpath_localappdata, dirpath_systemprofile_syswow64, dirpath_systemprofile_native, ] url = "https://cdn-patchportal-one.comodo.com/portal/packages/spm/TreeSize%20Free/x64/TreeSizeFreeSetup_4.8.1.exe" DownTo = os.path.join(os.environ["TEMP"], "TreeSizeFreeSetup_4.8.1.exe") PROC_PATTERNS = ["TreeSizeFreeSetup*.exe", "unins*.exe", "is-*.tmp", "TreeSizeFree.exe"] INSTALL_EXE_CANDIDATES = [ os.path.join(b, r"JAM Software\TreeSize Free\TreeSizeFree.exe") for b in ALL_BASE_DIRS ] UNINSTALLER_CANDIDATES = [ os.path.join(b, r"JAM Software\TreeSize Free\unins000.exe") for b in ALL_BASE_DIRS ] FOLDER_CANDIDATES = [ os.path.join(b, r"JAM Software\TreeSize Free") for b in ALL_BASE_DIRS ] SHORTCUT_PATHS = [ os.path.join( os.environ.get("ProgramData", r"C:\ProgramData"), r"Microsoft\Windows\Start Menu\Programs\TreeSize Free" ), os.path.join(os.environ.get("PUBLIC", r"C:\Users\Public"), r"Desktop\TreeSize Free.lnk"), ] def _tasklist_rows(): try: output = os.popen('tasklist /FO CSV /NH').read() except: return [] rows = [] try: reader = csv.reader(StringIO.StringIO(output)) for row in reader: if len(row) >= 2: rows.append((row[0], row[1])) except: pass return rows def _matching_pids(patterns): pids = [] for image_name, pid in _tasklist_rows(): for pat in patterns: if fnmatch.fnmatch(image_name.lower(), pat.lower()): pids.append((image_name, pid)) break return pids def kill_stale(): for image_name, pid in _matching_pids(PROC_PATTERNS): try: os.popen('taskkill /F /PID %s /T' % pid).read() except: pass # belt-and-suspenders literal-name attempts (harmless no-op if absent) for p in ["TreeSizeFreeSetup_4.8.1.exe", "TreeSizeFreeSetup.exe", "TreeSizeFree.exe", "unins000.exe"]: subprocess.call( "taskkill /F /IM %s /T" % p, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE ) time.sleep(2) def ecmd(command): with disable_file_system_redirection(): p = subprocess.Popen( command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE ) out, err = p.communicate() if p.returncode != 0: raise Exception( "Command failed (%s): %s" % (p.returncode, err.strip() or out.strip()) ) return out.strip() if out else p.returncode def downloadFile(): try: req = urllib2.Request( url, headers={"User-Agent": "Mozilla/5.0"} ) ctx = ssl._create_unverified_context() r = urllib2.urlopen(req, context=ctx, timeout=120) with open(DownTo, "wb") as f: while True: data = r.read(1024 * 1024) if not data: break f.write(data) r.close() if not os.path.isfile(DownTo) or os.path.getsize(DownTo) == 0: raise Exception("Downloaded installer is missing or empty.") except Exception as e: raise Exception("Download failed: %s" % e) def wait_install(): max_wait_seconds = 300 interval = 3 elapsed = 0 while elapsed < max_wait_seconds: time.sleep(interval) elapsed += interval if not _matching_pids(PROC_PATTERNS): return raise Exception( "TreeSize installer/uninstaller still running after %s seconds." % max_wait_seconds ) def _registry_entries(): found = [] uninstallkey32 = r"SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall" uninstallkey64 = r"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall" hu_uninstallkey32 = r".DEFAULT\Software\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall" hu_uninstallkey64 = r".DEFAULT\Software\Microsoft\Windows\CurrentVersion\Uninstall" reg_list = [ (_winreg.HKEY_LOCAL_MACHINE, uninstallkey32, _winreg.KEY_WOW64_32KEY | _winreg.KEY_ALL_ACCESS), (_winreg.HKEY_LOCAL_MACHINE, uninstallkey64, _winreg.KEY_WOW64_64KEY | _winreg.KEY_ALL_ACCESS), (_winreg.HKEY_USERS, hu_uninstallkey32, _winreg.KEY_ALL_ACCESS), (_winreg.HKEY_USERS, hu_uninstallkey64, _winreg.KEY_ALL_ACCESS), ] for reg_key, sub_key, access in reg_list: try: reg = _winreg.OpenKey(reg_key, sub_key, 0, access) except: continue i = 0 while True: try: key_value = _winreg.EnumKey(reg, i) except: break path = sub_key + "\\" + key_value try: Hkey = _winreg.OpenKey(reg_key, path, 0, access) dis_name, _ = _winreg.QueryValueEx(Hkey, "DisplayName") if product_name.lower() in dis_name.strip().lower(): try: uninstr, _ = _winreg.QueryValueEx(Hkey, "UninstallString") except: uninstr = None try: instloc, _ = _winreg.QueryValueEx(Hkey, "InstallLocation") except: instloc = None found.append([dis_name.strip(), path, reg_key, access, uninstr, instloc]) except: pass i += 1 return found def _promote_to_hklm(entry): dis_name, path, reg_key, access, uninstr, instloc = entry if reg_key != _winreg.HKEY_USERS: return try: src = _winreg.OpenKey(reg_key, path, 0, _winreg.KEY_READ) except: return values = [] i = 0 while True: try: name, data, vtype = _winreg.EnumValue(src, i) except: break values.append((name, data, vtype)) i += 1 try: _winreg.CloseKey(src) except: pass key_name = path.split("\\")[-1] dest_path = r"SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\%s" % key_name try: dest = _winreg.CreateKeyEx( _winreg.HKEY_LOCAL_MACHINE, dest_path, 0, _winreg.KEY_WOW64_32KEY | _winreg.KEY_ALL_ACCESS ) for name, data, vtype in values: _winreg.SetValueEx(dest, name, 0, vtype, data) _winreg.CloseKey(dest) except: return try: _winreg.DeleteKey(reg_key, path) except: pass def _delete_registry_entry(entry): dis_name, path, reg_key, access, uninstr, instloc = entry try: _winreg.DeleteKey(reg_key, path) return except: pass hive_name = "HKLM" if reg_key == _winreg.HKEY_LOCAL_MACHINE else "HKU" reg_view = "/reg:32" if (access & _winreg.KEY_WOW64_32KEY) else "/reg:64" try: os.popen('reg delete "%s\\%s" /f %s' % (hive_name, path, reg_view)).read() except: pass def _refresh_shell(): try: SHCNE_ASSOCCHANGED = 0x08000000 SHCNF_IDLIST = 0x0000 ctypes.windll.shell32.SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, None, None) except: pass def _clear_readonly(func, path, exc_info): try: os.chmod(path, 0o777) func(path) except: pass def _robust_rmtree(path, max_attempts=4): for attempt in range(max_attempts): if not os.path.isdir(path): return True kill_stale() time.sleep(1) try: shutil.rmtree(path, onerror=_clear_readonly) except: pass if not os.path.isdir(path): return True try: os.system('rmdir /s /q "%s"' % path) except: pass if not os.path.isdir(path): return True time.sleep(1) return not os.path.isdir(path) def _force_remove_all(): kill_stale() for entry in _registry_entries(): _delete_registry_entry(entry) for folder in FOLDER_CANDIDATES: if os.path.isdir(folder): _robust_rmtree(folder) for sp in SHORTCUT_PATHS: try: if os.path.isdir(sp): _robust_rmtree(sp) elif os.path.isfile(sp): os.chmod(sp, 0o777) os.remove(sp) except: pass _refresh_shell() still_present = bool(_registry_entries()) for p in INSTALL_EXE_CANDIDATES: if os.path.isfile(p): still_present = True return not still_present def verify(): if _registry_entries(): return True for p in INSTALL_EXE_CANDIDATES: if os.path.isfile(p): return True raise Exception("TreeSizeFree.exe not found after installation.") def _install_core(): kill_stale() downloadFile() ecmd( '"%s" /VERYSILENT /SUPPRESSMSGBOXES /NORESTART /SP- /ALLUSERS' % DownTo ) wait_install() verify() kill_stale() for entry in _registry_entries(): _promote_to_hklm(entry) _refresh_shell() if os.path.isfile(DownTo): try: os.remove(DownTo) except: pass return True def _uninstall_core(): kill_stale() ran_any = False for c in UNINSTALLER_CANDIDATES: if os.path.isfile(c): try: ecmd( '"%s" /VERYSILENT /SUPPRESSMSGBOXES /NORESTART /SP-' % c ) ran_any = True except: pass if not ran_any: for dis_name, path, reg_key, access, uninstr, instloc in _registry_entries(): if uninstr: m = re.match(r'^\s*"([^"]+)"(.*)$', uninstr) exe_path = m.group(1) if m else uninstr.strip().split(' ', 1)[0] if os.path.isfile(exe_path): try: ecmd( '"%s" /VERYSILENT /SUPPRESSMSGBOXES /NORESTART /SP-' % exe_path ) ran_any = True except: pass wait_install() kill_stale() still_present = bool(_registry_entries()) for p in INSTALL_EXE_CANDIDATES: if os.path.isfile(p): still_present = True if still_present: if not _force_remove_all(): raise Exception("TreeSize Free is still installed after forced cleanup.") _refresh_shell() return True def spm_install(dir, args): with disable_file_system_redirection(): try: _install_core() print("TreeSize Free installation successful.") print('retcode' + str(0) + 'retcode') sys.exit(0) except SystemExit: raise except Exception as e: if os.path.isfile(DownTo): try: os.remove(DownTo) except: pass print("TreeSize Free installation failed: %s" % e) print('retcode' + str(1) + 'retcode') sys.exit(1) def spm_uninstall(dir, args): with disable_file_system_redirection(): try: _uninstall_core() print("TreeSize Free uninstall successful.") print('retcode' + str(0) + 'retcode') sys.exit(0) except SystemExit: raise except Exception as e: print("TreeSize Free uninstall failed: %s" % e) print('retcode' + str(1) + 'retcode') sys.exit(1) def spm_update(dir, args): with disable_file_system_redirection(): try: installed = bool(_registry_entries()) for c in UNINSTALLER_CANDIDATES: if os.path.isfile(c): installed = True if installed: _uninstall_core() time.sleep(20) _install_core() print("TreeSize Free update successful.") print('retcode' + str(0) + 'retcode') sys.exit(0) except SystemExit: raise except Exception as e: print("TreeSize Free update failed: %s" % e) print('retcode' + str(1) + 'retcode') sys.exit(1)