Files
ykxmz/_build/test_uninstall_hang.py
xmz7891 df8056dd16 修四个安装/卸载卡死的 bug(都实测复现过)
1. 卸载里 input() 在无 stdin 时永久阻塞
   从控制面板卸载**永远卡住不结束**。安装包的 [UninstallRun]
   用 runhidden 调用,没有 stdin,input() 就一直等。
   改成只在 sys.stdin.isatty() 时才等回车。

2. Popen 没重定向子进程 stdio,导致 capture_output 等管道 EOF
   _schedule_self_delete 起的 cmd 继承了父进程的 stdout/stderr 句柄,
   任何用 capture_output 调 --uninstall 的地方都会一直等,
   表现为"程序卡死"(实测 90 秒不返回,改后 0.2 秒)。
   Popen 加 stdin/stdout/stderr=DEVNULL。
   顺带把延迟从 timeout 换成 ping —— timeout 在 DETACHED_PROCESS
   下没有控制台会立刻失败,等于没延迟直接删。

3. _kill_running 用 taskkill /IM ykxmz.exe 把**自己**杀了
   --uninstall 本身就跑在 ykxmz.exe 里,这条命令一发,
   后面的"删开机自启""删数据目录"全都没执行。
   现象:卸载看着完成了,HKCU 的自启注册表项还留着。
   改成 /FI "PID ne <self>" 排除自己。

4. 安装器关不掉以管理员身份运行的旧实例 -> 整个安装回滚
   Inno 自带机制只会"请求"对方退出,遇上常驻的服务进程关不掉,
   弹"无法自动关闭所有应用程序",静默模式下被压成"终止",
   安装直接回滚 —— 用户看到的是"装了没反应/还是旧版本"。
   改成 PrepareToInstall 里自己 taskkill /F,关掉 Inno 那套。

另外修了两个卸载残留:
   * 安装目录留个空壳 -> [UninstallDelete] 加 dirifempty
   * ProgramData\ykxmz 没删 -> 加进 [UninstallDelete]

还有一处 Inno 写法陷阱(踩了三次):
   注释里写 [区段名] 或 {常量} 会让编译直接失败,
   因为 [Code] 解析器连注释内容也处理。
   加了 _build/check_iss.py 专门扫这个,并进全量套件。

实测结果:
   静默安装  5 秒内退出,退出码 0,配对码正确写入,程序自动跑起来
   静默卸载  0.7 秒退出(原来无限卡住)
   全量回归  29 项全通过
   卸载逻辑  11 项全通过
2026-10-07 01:19:11 +08:00

97 lines
3.6 KiB
Python
Raw Permalink 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.
# -*- coding: utf-8 -*-
r'''验证卸载卡死的假设:--uninstall 里的自删逻辑和 Inno 卸载器抢目录。
步骤:
1. 彻底清干净
2. 静默安装
3. 单独跑 ykxmz.exe --uninstall,**计时**(不经过 Inno 卸载器)
如果它自己很快返回,说明卡的是 Inno 那一步(抢目录)
4. 看它有没有把安装目录删掉
'''
from __future__ import annotations
import subprocess
import sys
import time
from pathlib import Path
ROOT = Path(r"C:\Users\shu\Desktop\deepseekagent\LanRemote")
SETUP = ROOT / "dist" / "ykxmz-setup-1.7.1.exe"
INSTALL_DIR = Path(r"C:\Program Files\ykxmz")
CODE = "739251"
def ps(command: str, timeout: int = 300) -> tuple:
r = subprocess.run(
["powershell", "-NoProfile", "-ExecutionPolicy", "Bypass",
"-Command", command],
capture_output=True, text=True, timeout=timeout,
encoding="utf-8", errors="replace")
return r.returncode, (r.stdout or "").strip()
def main() -> int:
print(" 一、彻底清理")
ps("Get-Process ykxmz,unins000,_unins.tmp -ErrorAction SilentlyContinue | "
"Stop-Process -Force -ErrorAction SilentlyContinue; "
"Start-Sleep -Seconds 2; "
"Remove-Item 'C:\\Program Files\\ykxmz' -Recurse -Force "
"-ErrorAction SilentlyContinue; "
"Remove-Item 'C:\\ProgramData\\ykxmz' -Recurse -Force "
"-ErrorAction SilentlyContinue; "
"Remove-Item 'HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall"
"\\{8F3C1D42-7A5E-4B91-9C6D-2E8F4A1B7C30}_is1' -Recurse -Force "
"-ErrorAction SilentlyContinue; Write-Output ok")
ps("Get-Process ykxmz -ErrorAction SilentlyContinue | "
"Stop-Process -Force -ErrorAction SilentlyContinue")
time.sleep(3)
print(" 二、静默安装")
start = time.perf_counter()
ps("Start-Process -FilePath '%s' -ArgumentList "
"'/VERYSILENT /SUPPRESSMSGBOXES /NORESTART "
"/DIR=\"C:\\Program Files\\ykxmz\" /CODE=%s' -Wait" % (SETUP, CODE),
timeout=600)
print(" 安装耗时 %.1f 秒" % (time.perf_counter() - start))
time.sleep(5)
exe = INSTALL_DIR / "ykxmz.exe"
print(" ykxmz.exe 在: %s" % exe.is_file())
print()
print(" 三、单独跑 --uninstall 并计时(不经过 Inno 卸载器)")
print(" 这一步是关键:如果它很快返回,卡的就是 Inno 那边")
start = time.perf_counter()
try:
r = subprocess.run(
[str(exe), "--uninstall"],
capture_output=True, text=True, timeout=90,
encoding="utf-8", errors="replace")
elapsed = time.perf_counter() - start
print(" ✓ %.1f 秒返回,exit=%s" % (elapsed, r.returncode))
for line in (r.stdout or "").strip().splitlines():
print(" " + line.strip())
except subprocess.TimeoutExpired:
print(" ✗ 90 秒没返回 —— **它自己就会卡**")
print(" 假设不成立,问题在 --uninstall 本身")
print()
print(" 四、看安装目录现在什么样")
if INSTALL_DIR.is_dir():
files = list(INSTALL_DIR.iterdir())
print(" 目录还在,%d 个条目:" % len(files))
for f in files[:10]:
print(" %s" % f.name)
else:
print(" 目录已被自删逻辑删掉")
print()
print(" 五、进程状态")
rc, out = ps("(Get-Process ykxmz,unins000,_unins.tmp "
"-ErrorAction SilentlyContinue | Measure-Object).Count")
print(" 残留进程: %s" % (out.strip().splitlines()[-1] if out else "?"))
return 0
if __name__ == "__main__":
sys.exit(main())