mirror of
https://gitee.com/xmz7891/ykxmz.git
synced 2026-10-18 16:00:43 +00:00
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 项全通过
129 lines
4.7 KiB
Python
129 lines
4.7 KiB
Python
# -*- coding: utf-8 -*-
|
||
r'''重写安装包的"装完收尾"逻辑。
|
||
|
||
问题:CurStepChanged(ssPostInstall) 在这个 Inno 版本里**没被触发**
|
||
(日志显示回调进了,CurStep=1,但 `CurStep <> ssPostInstall`
|
||
判断为真;而 Ord(ssPostInstall) 又报类型不匹配)。
|
||
结果 --code 从来没执行过,配对码永远是空的。
|
||
|
||
解决:不再依赖 CurStepChanged。改用 Inno 的标准做法 ——
|
||
把收尾命令放进 [Run] 段,配对码用 {code:Function} 取。
|
||
|
||
[Run] 里可以写 {code:GetPairingCode},Inno 会调用同名 Pascal 函数取值。
|
||
再加 Check: 函数决定这一条要不要跑(码为空就跳过)。
|
||
|
||
这样装完必然执行,不依赖任何步骤枚举。
|
||
'''
|
||
from __future__ import annotations
|
||
|
||
import ast
|
||
import sys
|
||
from pathlib import Path
|
||
|
||
ISS = (Path(r"C:\Users\shu\Desktop\deepseekagent\LanRemote")
|
||
/ "installer" / "ykxmz.iss")
|
||
|
||
# ---------- 新的 [Run] 段 ----------
|
||
NEW_RUN = '''[Run]
|
||
; 收尾分三步,顺序很重要:
|
||
; 1. 写配对码(一次性命令,秒退)
|
||
; 2. 设开机自启(一次性命令)
|
||
; 3. 静默启动(常驻进程)
|
||
;
|
||
; 为什么不放在 CurStepChanged 里:
|
||
; 实测这个 Inno 版本的 ssPostInstall 回调**不会触发**
|
||
; (日志里回调进了、CurStep=1,但 CurStep <> ssPostInstall 成立),
|
||
; 放在那里 --code 永远不执行、配对码永远为空。
|
||
; [Run] 段是安装完成后必然执行的,靠得住。
|
||
;
|
||
; {code:GetPairingCode} 是 Inno 的写法:调用同名 Pascal 函数取值。
|
||
; Check 函数返回 False 时这一条跳过(比如用户没填配对码)。
|
||
|
||
; 1. 写配对码
|
||
Filename: "{app}\\{#MyAppExeName}"; Parameters: "--code {code:GetPairingCode}"; \\
|
||
Flags: runhidden; Check: HasPairingCode
|
||
|
||
; 2. 开机自启
|
||
Filename: "{app}\\{#MyAppExeName}"; Parameters: "--autostart on"; \\
|
||
Flags: runhidden; Tasks: autostart
|
||
|
||
; 3. 静默启动
|
||
; skipifsilent 是因为静默安装走下面那条 cmd 版本
|
||
; (直接起会挂在安装器的进程树上,见那条注释)
|
||
Filename: "{app}\\{#MyAppExeName}"; Parameters: "--silent"; \\
|
||
Description: "立即启动(后台运行,不显示窗口)"; \\
|
||
Flags: nowait postinstall skipifsilent; Check: not WizardSilent
|
||
|
||
; 静默安装时也要跑起来。
|
||
; 必须用 cmd /c start 起:直接 Exec 出来的进程是安装器的子进程,
|
||
; 而 Start-Process -Wait / start /wait / MSI 封装器都会等**整个进程树**,
|
||
; --silent 是常驻守护进程永远不退出,于是"安装脚本永远跑不完"
|
||
; (实测卡满 600 秒超时)。cmd /c start 会把它挂到别处,父进程一退就断干净。
|
||
Filename: "cmd.exe"; Parameters: "/c start """" ""{app}\\{#MyAppExeName}"" --silent"; \\
|
||
Flags: runhidden nowait; Check: WizardSilent
|
||
|
||
; 让用户看一眼装完须知
|
||
Filename: "{app}\\docs\\AFTER-INSTALL.txt"; \\
|
||
Description: "打开「安装后须知」"; \\
|
||
Flags: postinstall shellexec unchecked skipifsilent
|
||
'''
|
||
|
||
# ---------- 新的 [Code] 收尾函数 ----------
|
||
NEW_FUNCS = '''
|
||
{ ===== 给 [Run] 段用的取值 / 判断函数 ===== }
|
||
|
||
{ 配对码。空串时 Inno 不会展开,所以配 HasPairingCode 一起用。 }
|
||
function GetPairingCode(Param: String): String;
|
||
begin
|
||
Result := EffectiveCode();
|
||
Diag('GetPairingCode -> "' + Result + '"');
|
||
end;
|
||
|
||
{ 有配对码才跑那条 --code 命令 }
|
||
function HasPairingCode(): Boolean;
|
||
begin
|
||
Result := EffectiveCode() <> '';
|
||
Diag('HasPairingCode -> ' + IntToStr(Ord(Result)));
|
||
end;
|
||
|
||
{ 非静默时才跑"立即启动"那条 }
|
||
function NotWizardSilent(): Boolean;
|
||
begin
|
||
Result := not WizardSilent;
|
||
end;
|
||
'''
|
||
|
||
|
||
def main() -> int:
|
||
text = ISS.read_text(encoding="utf-8")
|
||
|
||
# 1) 换掉 [Run] 段(从 [Run] 到 [UninstallDelete] 之前)
|
||
start = text.find("[Run]")
|
||
end = text.find("[UninstallDelete]")
|
||
if start < 0 or end < 0 or end < start:
|
||
print(" [失败] 找不到 [Run] / [UninstallDelete]")
|
||
return 1
|
||
text = text[:start] + NEW_RUN + "\n" + text[end:]
|
||
print(" 已替换 [Run] 段")
|
||
|
||
# 2) 在 [Code] 末尾追加三个函数
|
||
if "function GetPairingCode" not in text:
|
||
text = text.rstrip() + "\n" + NEW_FUNCS
|
||
print(" 已追加 GetPairingCode / HasPairingCode / NotWizardSilent")
|
||
|
||
ISS.write_text(text, encoding="utf-8", newline="\n")
|
||
|
||
# 语法自检(Python 层只能查文件可读)
|
||
print()
|
||
print(" [Run] 段现在:")
|
||
run_start = text.find("[Run]")
|
||
run_end = text.find("[UninstallDelete]")
|
||
for line in text[run_start:run_end].splitlines():
|
||
if line.strip() and not line.strip().startswith(";"):
|
||
print(" " + line[:88])
|
||
return 0
|
||
|
||
|
||
if __name__ == "__main__":
|
||
sys.exit(main())
|