NSIS中AdvUninstLog插件引发安装耗时过长问题及方案咨询
Q1: 为什么!insertmacro UNINSTALL.LOG_OPEN_INSTALL调用耗时极长?
这个问题的核心是AdvUninstLog插件的遍历逻辑和旧Uninstall.dat的无效内容冲突了。当机器上存在旧安装时,这个宏会执行两个关键操作:
- 读取旧的
Uninstall.dat,加载已有的卸载日志条目; - 通过
${Locate} "${TargetDir}" "/L=FD" "${UnLog_Install_Func_CallBack}"遍历整个安装目录,对比旧日志条目,补充新安装的文件,同时验证旧条目是否仍有效。
如果旧Uninstall.dat里包含用户已删除的无效文件/目录路径,插件的回调函数${UnLog_Install_Func_CallBack}在处理这些无效条目时,可能会陷入重复检查或无限递归的逻辑:比如反复尝试访问不存在的路径、或者因为无效路径的存在打乱了遍历的终止条件,导致${Locate}一直在安装目录下循环扫描,最终拖慢整个安装过程到5-10分钟。
结合你提到的"仅当旧安装存在且插件启用时才出现",旧日志的无效条目是最直接的诱因。
Q2: 该问题的合理解决方式是什么?
结合你尝试过的方案,这里有几个可行的思路,既能保持Uninstall.dat的完整性,又能避免超长耗时:
方案1:预处理旧Uninstall.dat,清理无效条目
在调用UNINSTALL.LOG_OPEN_INSTALL之前,先手动读取旧的Uninstall.dat,过滤掉其中不存在的文件和目录,生成一个临时的"干净"日志文件,再让插件基于这个临时文件来合并新条目:
; 1. 读取旧Uninstall.dat的文件列表 ReadINIStr $0 "$INSTDIR\Uninstall.dat" "UninstallLog" "Files" ; 2. 拆分条目,逐个检查文件是否存在 StrCpy $1 "" StrParse $0 "|" $2 $0 loop: IfFileExists "$2" +3 Goto next_entry StrLen $3 $1 ${If} $3 > 0 StrCat $1 "|" ${EndIf} StrCat $1 "$2" next_entry: StrParse $0 "|" $2 $0 ${If} $2 != "" Goto loop ${EndIf} ; 3. 将清理后的条目写入临时文件 WriteINIStr "$INSTDIR\Uninstall_temp.dat" "UninstallLog" "Files" "$1" ; 4. 让插件使用临时文件作为旧日志(需调整宏参数或调用底层函数) !insertmacro UNINSTALL.LOG_OPEN_INSTALL "$INSTDIR\Uninstall_temp.dat" ; 5. 安装完成后替换临时文件为正式文件 Delete "$INSTDIR\Uninstall.dat" Rename "$INSTDIR\Uninstall_temp.dat" "$INSTDIR\Uninstall.dat"
这个方案的核心是先排除无效条目,让插件只处理当前存在的文件,避免无意义的扫描。
方案2:替换全量遍历为增量记录
放弃插件的自动遍历,改为在安装过程中手动记录新增的文件,再合并旧日志的有效条目:
- 安装前读取旧
Uninstall.dat并清理无效条目(同方案1); - 在每个
File指令后,调用自定义宏记录文件路径:
!macro RecordInstalledFile filePath StrLen $0 $InstalledFilesList ${If} $0 > 0 StrCat $InstalledFilesList "|" ${EndIf} StrCat $InstalledFilesList "$INSTDIR\${filePath}" !macroend ; 使用示例 File "bin\app.exe" !insertmacro RecordInstalledFile "bin\app.exe"
- 安装完成后,将旧的有效条目和本次新增的条目合并,写入新的
Uninstall.dat。
这种方式完全避免了全量遍历,性能最优,缺点是需要修改所有File指令的调用,适合对安装包代码有完全控制权的场景。
Q3: 使用RMDir /r $path与RMDir效果一致,仅能删除空文件夹,原因是什么?
RMDir /r的作用是递归删除目录,但它只能删除空的子目录,如果目录下还有文件,它会直接跳过。出现和普通RMDir一样的效果,通常有两个核心原因:
- 卸载时未先删除目录内的文件:如果
Uninstall.dat为空或失效,卸载程序没有先删除目录下的文件,RMDir /r就无法删除非空目录,只能删掉空的子文件夹; - 目录被进程占用/权限不足:如果卸载时,安装目录下的文件正在被系统或其他进程锁定(比如应用未完全退出),或者卸载程序没有足够权限删除文件,都会导致文件残留,目录无法被递归删除。
解决这个问题的前提是确保卸载程序先根据Uninstall.dat删除所有文件,再调用RMDir /r清理目录。
最终可行方案推荐
优先选择方案1(预处理旧日志),它不需要大量修改现有安装包代码,只需在调用插件宏之前增加一段清理逻辑,就能有效解决耗时问题,同时保持Uninstall.dat的完整性。如果安装包的文件数量不多,也可以考虑方案2(增量记录),性能更优且更可控。
内容的提问来源于stack exchange,提问作者Koenigsberg

