桌面快捷方式为什么找不到脚本:一次 MSIX 文件系统虚拟化排查
给 Windows 上的 WSL 装好 KDE 桌面后,我希望像打开普通软件一样,双击桌面的「Ubuntu KDE Plasma」就能进入 Linux 桌面。启动器负责唤起后台服务,再用 Edge 的独立窗口打开 noVNC。
桌面本身已经能用了,快捷方式却反复出问题:最早提示「无法找到脚本文件」,换成 CMD 启动器后,又表现为双击没有反应,或者命令窗口一闪而过。奇怪的是,让 Codex 执行同一个快捷方式,窗口总能正常打开。
最后查到,启动器被写进了 MSIX 应用的私有目录。Codex 的工具进程能够通过原路径读到它,Windows 资源管理器却看不到这份文件。把启动器放到普通文档目录,并更新快捷方式之后,手动关闭窗口、再次双击也能正常重开。
两个入口使用的是同一条绝对路径。为什么一个找得到文件,另一个却找不到?
启动日志把问题定位到了脚本入口
这次环境是 Windows 11,WSL 中运行 Ubuntu 和 KDE,浏览器通过本机 noVNC 页面显示桌面。故障发生时,后台服务仍能访问,已经打开的浏览器窗口也能显示 KDE。
继续重启 Linux 桌面并不能解释一个差异:工具执行快捷方式能打开,手动双击却打不开。
启动脚本最前面有一条日志,位置在启动 WSL、检查服务、打开 Edge 之前。下面是关键节选:
@echo off
setlocal
set "LOG=%LOCALAPPDATA%\UbuntuPlasma\launcher.log"
>>"%LOG%" echo [%DATE% %TIME%] Launch requested
工具执行快捷方式时,日志依次出现 Launch requested、Desktop service ready 和 Desktop window requested。手动双击后,监测的日志却没有新增记录。这时还不能单凭日志断言脚本没有执行:日志位置也可能有问题。但至少,排查范围已经到了 Windows 的启动入口和文件访问。
重新生成快捷方式、简化 cmd.exe 的参数、取消最小化,都没解决问题。把诊断入口临时改成 cmd.exe /d /k 后,命令窗口终于保留下来,里面显示:
系统找不到指定的路径。
按 CMD 的参数定义,/k 会在执行命令后保留命令解释器,方便查看失败信息;正常入口使用的 /c 执行后就会退出。命令窗口一闪而过,只能说明窗口很快结束,不能区分正常完成和提前失败。
更有用的证据是:诊断入口还要求把输出重定向到启动器旁边的文件,但连这个输出文件都没有创建。结合后面的独立路径检查,可以确认资源管理器启动的命令找不到那个目录。
同一条路径,两个文件视图
最初选择的启动器位置很常见:
%LOCALAPPDATA%\UbuntuPlasma\StartUbuntuPlasma.cmd
在普通桌面程序里,%LOCALAPPDATA% 通常展开为当前用户的 AppData\Local。把个人工具放在这里,看起来既不用管理员权限,也不会散落到桌面上。
但这次写文件的进程来自打包安装的 Codex 桌面应用,它的文件访问受到 Windows 应用包的虚拟化规则影响。
MSIX 是 Windows 的应用打包和部署格式。这里涉及的「文件系统虚拟化」,是 Windows 根据应用的运行上下文,把部分文件操作重定向到应用专属位置,并在读取时提供合并视图。它不需要另外启动一台虚拟机。
下图只表示这次故障中涉及的两个读取视图;具体路径放在后面的取证结果中。
对于启用了相应虚拟化行为的应用,Windows 会把特定 AppData 位置下新建的文件写入每用户、每应用的私有位置。应用读取原路径时,又能通过合并视图找到这些文件。微软的 MSIX 运行机制文档明确区分了这种行为及其适用范围。
于是,文件确实写成功了,Test-Path 也确实返回了 True。这些检查证明的是当前进程能够通过这个路径读取文件,并没有证明普通桌面进程也能读到它。
这次工具内部调用启动器仍能成功,说明这条验证路线也能访问私有副本。手动双击由 Windows 资源管理器发起,访问普通位置时就遇到了缺失的目录。
另一个桌面入口的只读采样显示,原目标路径与 MSIX 私有副本可以同时存在;这组本机结果不能推广成所有版本与应用的固定规则。完整内容见桌面进程的文件视图检查弹窗。
为什么清单里写着 disabled,仍然发生重定向
继续读取本机安装包的 AppxManifest.xml,还发现了一个容易漏看的细节。采样版本是 OpenAI.Codex 26.1013.3656.0,Windows 构建号为 26200。下面只保留与文件虚拟化有关的属性,省略命名空间声明和其他属性;配套 XML 节选补有命名空间,方便独立查看。
<desktop6:FileSystemWriteVirtualization>disabled</desktop6:FileSystemWriteVirtualization>
<virtualization:FileSystemWriteVirtualization>
<virtualization:ExcludedDirectories>
<virtualization:ExcludedDirectory>$(KnownFolder:LocalAppData)\OpenAI</virtualization:ExcludedDirectory>
</virtualization:ExcludedDirectories>
</virtualization:FileSystemWriteVirtualization>
只看第一行,很容易以为整个文件系统写入虚拟化都关闭了。但这个包还声明了 Windows 11 支持的目录排除列表。
微软的灵活虚拟化文档说明,同时存在旧属性和 Windows 11 新声明时,Windows 11 及更高版本使用新声明。ExcludedDirectories 指定的是取消虚拟化的目录,字段含义也可以在 FileSystemWriteVirtualization 文档中核对。
本机列表只排除了 AppData\Local\OpenAI,我们创建的 AppData\Local\UbuntuPlasma 不在这个目录下。现场出现的私有文件落点与这份配置一致。
这里也不能把「MSIX 打包」「AppContainer」「管理员权限」当成同一件事。本机应用入口声明的是 Windows.FullTrustApplication;微软文档也分别描述了包身份和应用容器。判断文件会出现在哪里,仍要查看实际虚拟化配置和运行结果。
让启动器成为桌面能访问的文件
这个启动器要交给 Windows 资源管理器使用,文件必须放在双方都能访问的位置。这次采用的修复很小:把启动器复制到当前用户的普通文档目录,更新桌面和开始菜单快捷方式,并把日志放在脚本旁边。
本机最终位置如下,用户目录继续脱敏:
C:\Users\<USER>\Documents\UbuntuPlasma\StartUbuntuPlasma.cmd
日志位置则由原来的 %LOCALAPPDATA% 改为:
set "LOG=%~dp0launcher.log"
在批处理文件中,%~dp0 展开为当前脚本所在的驱动器和目录,d、p 是批处理参数的路径修饰符。只要启动器位于约定的位置,日志就跟随这份脚本,排查时不用再猜它写到了哪一个 AppData 视图里。迁移目录时也要确认目标目录可写。
选择 Documents 是这台机器上经过验证的解决方案。其他机器的文档目录可能被重定向到 OneDrive,安装工具应使用系统提供的已知文件夹位置,并验证实际落点。AppData 仍然可以作为安装位置,前提是安装过程确实把文件写到了外部进程可见的位置;对于应用自己的私有状态,应用包目录也有它的用途。
直接把快捷方式指向 Packages\…\LocalCache 虽然能定位到现有副本,却会让启动器依赖另一个应用包的私有数据生命周期。这次把启动器作为独立的本机工具保存,更符合它要被桌面长期调用的用途。
开始菜单也值得单独检查。它的每用户快捷方式通常位于 AppData\Roaming\Microsoft\Windows\Start Menu\Programs,这次同样出现了工具能看到、外部检查却不存在的情况。只改桌面 .lnk 的目标还不够,还需要确认开始菜单文件已经落在真实目录中。
验证要回到手动双击
修复后的检查分成了两步。
先从 WSL 启动 Windows 的 cmd.exe,执行新目录中的启动器自检。下面这条命令已在本机执行;发行版名和目录需要按自己的环境调整:
wsl.exe -d Ubuntu-26.04 --cd '/mnt/c/Users/<USER>/Documents/UbuntuPlasma' --exec /mnt/c/Windows/System32/cmd.exe /d /c StartUbuntuPlasma.cmd --check
本机 wsl.exe --help 说明,--exec 直接执行指定程序,不使用默认 Linux shell;帮助节选保留了相关参数。这里用它避免 Linux shell 再解释一次参数。自检输出为:
Ubuntu KDE desktop ready at http://127.0.0.1:6080/
这次退出码为 0,新的日志文件也出现在普通文档目录。它证明新位置的启动器可以执行、后台服务可以访问,还没有验证到独立窗口的完整使用过程。
最后关闭已有的 noVNC 独立窗口,手动双击 Windows 桌面的「Ubuntu KDE Plasma」。KDE 桌面能够重新打开,新的启动日志也随之出现。这个结果才覆盖了最初失败的入口。
总结
这次失败发生在 Windows 启动器的文件访问上:MSIX 应用的工具进程能通过合并视图读到私有副本,资源管理器却找不到普通位置的脚本。同一条绝对路径和一次 Test-Path 成功,都不足以证明外部进程能使用这份文件。
把启动器放到双方可见的普通目录、同步快捷方式和日志位置后,独立自检与手动双击才共同覆盖了原来的故障。文档目录是本机验证过的选择;换机器或安装包配置时,仍应核验实际文件落点、目标目录可写性和真正的调用入口。
桌面进程的文件视图检查
从另一个入口检查实际文件
为了避免再次用同一个文件视图验证自己,我通过 WSL 的 /mnt/c 检查 Windows 卷上的对应位置。这里的 WSL 只是额外的取证入口,检查本身没有修改文件。
结果很直接。以下是 2026 年 10 月 20 日只读复核的节选,字段名沿用配套采样文件,省略了环境信息和修复后的检查项:
{
"logicalWindowsView": {
"originalLauncherExists": true,
"privateLauncherExists": true
},
"physicalNtfsViewViaWsl": {
"originalLauncherExists": false,
"privateLauncherExists": true
}
}
两个检查中的 originalLauncherExists 指向同一个预期位置。下面的用户目录以 <USER> 脱敏,其余关键路径结构保留:
C:\Users\<USER>\AppData\Local\UbuntuPlasma\StartUbuntuPlasma.cmd
独立检查没有找到这份文件,却在应用包的私有位置找到了它:
C:\Users\<USER>\AppData\Local\Packages\OpenAI.Codex_2p2nqsd0c76g0\LocalCache\Local\UbuntuPlasma\StartUbuntuPlasma.cmd
LocalCache\Local 下面出现了原本打算创建在 AppData\Local 下的 UbuntuPlasma。这个实际落点、工具里的存在检查,以及手动启动的路径错误,能够互相对应。
修复后,原来的私有副本仍然保留,所以这组差异还能只读复核;它记录的是本机结果,不代表所有版本的 Codex、所有 MSIX 应用都会采用相同配置。