niumasoftware/update_helper.py
2026-04-07 23:21:58 +08:00

123 lines
3.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
以“计划任务(最高权限)”方式执行静默覆盖更新。
工作流:
- 主程序(普通权限)下载新 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"
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 _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:
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:
return 3
# 等待主程序退出,最多 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)
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)
except Exception:
pass
return 0
return 4
if __name__ == "__main__":
raise SystemExit(main())