""" 以“计划任务(最高权限)”方式执行静默覆盖更新。 工作流: - 主程序(普通权限)下载新 exe 到可写目录(建议 ProgramData)并写入 update_request.json - 计划任务以 SYSTEM/管理员身份运行本程序 - 本程序读取请求文件 -> 等待主程序退出 -> 覆盖安装目录 exe -> 重启 -> 清理请求文件 """ from __future__ import annotations import json import os import subprocess import sys import time APP_NAME = "CleanDesktopOrganizer" REQUEST_FILE = "update_request.json" LOG_FILE = "update.log" def _programdata_dir() -> str: base = os.environ.get("PROGRAMDATA") or r"C:\ProgramData" return os.path.join(base, APP_NAME) def _request_path() -> str: return os.path.join(_programdata_dir(), REQUEST_FILE) def _log(msg: str): try: os.makedirs(_programdata_dir(), exist_ok=True) p = os.path.join(_programdata_dir(), LOG_FILE) with open(p, "a", encoding="utf-8") as f: f.write(msg.rstrip() + "\n") except Exception: pass def _read_request() -> dict: p = _request_path() with open(p, "r", encoding="utf-8") as f: return json.load(f) def _pid_exists(pid: int) -> bool: if pid <= 0: return False try: # tasklist 不需要管理员权限,SYSTEM 下也可用 out = subprocess.run( ["tasklist", "/FI", f"PID eq {pid}"], capture_output=True, text=True, encoding="utf-8", errors="replace", creationflags=subprocess.CREATE_NO_WINDOW, ).stdout return str(pid) in out except Exception: return False def _copy_with_retries(src: str, dst: str, retries: int = 10) -> bool: for _ in range(max(1, retries)): try: os.makedirs(os.path.dirname(dst), exist_ok=True) if os.path.exists(dst): try: os.chmod(dst, 0o666) except Exception: pass # 用二进制流复制,避免权限/锁问题时直接抛错 with open(src, "rb") as rf: data = rf.read() with open(dst, "wb") as wf: wf.write(data) return True except Exception: time.sleep(0.8) return False def main() -> int: try: req = _read_request() except FileNotFoundError: return 0 except Exception: _log("ERROR: cannot read request file") return 2 src = str(req.get("src") or "") dst = str(req.get("dst") or "") pid = int(req.get("pid") or 0) restart = bool(req.get("restart", True)) if not src or not dst: _log(f"ERROR: bad request src={src!r} dst={dst!r} pid={pid}") return 3 _log(f"START: pid={pid} src={src} dst={dst}") # 等待主程序退出,最多 60 秒 waited = 0.0 while _pid_exists(pid) and waited < 60.0: time.sleep(0.5) waited += 0.5 ok = _copy_with_retries(src, dst, retries=15) _log(f"COPY: ok={ok}") try: os.remove(src) except Exception: pass # 清理请求文件(成功/失败都尽量清掉,避免重复执行) try: os.remove(_request_path()) except Exception: pass if ok and restart: try: subprocess.Popen([dst], creationflags=subprocess.DETACHED_PROCESS | subprocess.CREATE_NO_WINDOW) _log("RESTART: started") except Exception: _log("RESTART: failed") pass return 0 _log("DONE: failed") return 4 if __name__ == "__main__": raise SystemExit(main())