Windows .lnk 启动失败诊断与修复
适用场景
用户报告某个 .lnk 快捷方式无法启动、双击无反应、报"不是内部或外部命令"、"系统找不到指定的路径"等。常见于 cmd 包装的命令(如 cmd.exe /k codex)、被卸载软件残留的快捷方式、或安装目录未加入 PATH 的工具。
本机安全约束(关键,先读)
当前环境禁用了以下常规手段,必须走替代路径:
- 禁用的:WScript.Shell / Shell.Application COM 对象、reg.exe、rundll32.exe、PowerShell Add-Type、Bash 直接调用 powershell.exe。
- 可用的替代:
- 解析/改写 .lnk 字节 → 用 Python 直接读文件(struct + 正则提取 UTF-16LE/ASCII 字符串)。
- 读/写系统 PATH → 用 PowerShell 的 .NET API:[Environment]::GetEnvironmentVariable("Path","User") / SetEnvironmentVariable("Path", $v, "User")(不走 reg.exe)。
- 广播 WM_SETTINGCHANGE 让 explorer 刷新 → 不可用(rundll32/Add-Type 均被禁)。因此"加 PATH"对当前已运行的 explorer 会话不立即生效,必须改用"改 .lnk 绝对路径"才能双击立即生效。
诊断流程
- 定位 .lnk 文件:确认路径存在(可能在
C:\Users\<user>\Desktop、C:\Users\Public\Desktop、D 盘重定向桌面等)。 - 解析 .lnk(Python,见
scripts/lnk_diag.py的parse_lnk):
- 读二进制,提取 UTF-16LE 与 ASCII 可读字符串,快速定位 target / 启动参数 / 工作目录 / 图标。
- 精确解析:LinkFlags在偏移0x14(4 字节);IDList之后是StringData。用 walk ItemID 找到TerminalID(size=0)确定 IDList 结束,其后即 StringData。
- 坑:本机常见.lnk的IsUnicode标志(LinkFlags 的0x200位)未设置,但 StringData 实际是 UTF-16LE 编码。按 ANSI 读会错位。解析时按 Unicode 读字段即可(保持原文件非标准状态,Windows 仍可正常解析)。
- 各 StringData 字段顺序由 flag 决定:Name(0x04) → RelativePath(0x08) → WorkingDir(0x10) → Arguments(0x20) → IconLocation(0x40)。每个字段 = 2 字节字符数前缀 + 内容。 - 判断根因:
- target 是cmd.exe且参数形如/k <cmd>:检查<cmd>是否在 PATH。当前 bash 会话的 PATH 是 WorkBuddy 隔离环境,不能代表系统真实 PATH,必须读[Environment]::GetEnvironmentVariable("Path","User")+"Machine")并在常见安装目录(AppData\Local\、AppData\Roaming\npm、Program Files)深度搜索该可执行文件。
- target 是普通 exe:检查该 exe 路径是否存在。
- 工作目录是否存在。
修复手法
二选一,优先"改绝对路径"(立即生效、不依赖 explorer 刷新)。
A. 改 .lnk 参数为绝对路径(推荐,立即生效)
- 用 Python 在
.lnk文件中find各已知字符串的 UTF-16LE 偏移,校验其前的 2 字节长度前缀 = 字符数(匹配才动手,不匹配则中止,绝不盲改)。 - 把启动参数替换成目标可执行文件的绝对路径(例如
cmd /k "C:\...\codex.cmd",codex.cmd 是转发器)。 - 重建 StringData 段(Name + WorkingDir + 新Arguments + Icon),保持 UTF-16LE,ExtraData(图标追踪块等,特征签名
0xA0000003)整体后移,结构不变。 - 先备份原文件为
<name>.lnk.bak。 - 校验:新文件 size 合理、CLSID(
data[4:20]==01 14 02 00 ... 46)、四字段齐全、ExtraData 签名仍在。
B. 加用户 PATH(持久,但当前会话需刷新才生效)
- 用 PowerShell:读当前 User PATH,若不含目标
bin目录则追加,SetEnvironmentVariable("Path", $new, "User")。 - 同时在
bin目录放一个<tool>.cmd转发器(@"绝对路径\tool.exe" %*),使版本更新只改转发器一行。 - 告知用户:双击 .lnk 前需重启文件资源管理器 / 注销重登(当前环境无法自动广播刷新)。
验证
- 直接运行转发器/绝对路径 exe(
--version或无害子命令)确认可执行。 - 解析校验改写后的 .lnk 结构完整(见脚本
verify_lnk逻辑)。
常见陷阱
- 不要盲信"where <cmd>"在隔离 bash 里的空输出——它只代表当前会话 PATH,不代表系统 PATH。
- 改 .lnk 务必先校验长度前缀,并保留 ExtraData,否则图标/追踪数据损坏。
- CodexPlusPlus 这类 GUI 增强启动器不提供命令行,无法修复
/k <cmd>类 .lnk,需向用户说明。