最近遇到一个很离谱但又很现实的 macOS/Parallels Desktop 问题:下载了一个 oci-help.ini,双击时默认竟然不是用 macOS 文本编辑器打开,而是启动 PD 里的 Windows 虚拟机,用 Windows 记事本打开。
这事一开始看起来像是单个 .ini 文件的关联错了,但实际查下去发现不是。
现象
macOS 的 LaunchServices 里,很多文件类型的默认处理器被 Parallels 生成的 Windows app wrapper 抢走了,例如:
.ini→ Windows 记事本.svg→ Windows/Edge 相关 wrapper.rar/.zst/.cab→ Windows 安装/资源管理器相关 wrapper- 一些 RAW 图片、媒体格式、脚本格式也被 Windows app 注册成默认打开方式
当时对目标文件查出来的结果是:
mdls -name kMDItemContentType -name kMDItemContentTypeTree /Users/m/Downloads/oci-help-darwin-amd64-v2.3.1/oci-help.ini
显示它是:
kMDItemContentType = "com.microsoft.ini"
kMDItemContentTypeTree = (
"com.microsoft.ini",
"public.text",
"public.data",
"public.item",
"public.content"
)
也就是说,它本质上还是文本类文件,但默认打开程序被 PD 的 Windows 记事本 wrapper 接管了。
进一步用 CoreServices 查默认应用:
LSCopyDefaultApplicationURLForURL(...)
得到的是类似这样的路径:
/Users/m/Applications (Parallels)/{...} Applications.localized/记事本.app
根因
Parallels Desktop 的 Shared Applications / SmartSelect 会把 Windows 里的应用暴露到 macOS:
prlctl list "Windows 11" -i | sed -n '/Shared Applications:/,/SmartMount:/p'
当时配置是:
Shared Applications: (+)
Host-to-guest apps sharing: on
Guest-to-host apps sharing: on
其中 Guest-to-host apps sharing: on 的意思就是 Windows 应用可以出现在 macOS 这边。这个功能本身有用:例如可以从 Finder 的“打开方式”里手动选 Windows 应用。
问题是,PD 不只是“让你能选 Windows 应用”,它还会通过文件类型关联把某些 Windows 应用设成 macOS 默认打开程序。于是就出现了 .ini 双击启动 Windows 虚拟机这种奇观。
粗暴方案为什么不好
可以一键关掉 Windows 应用共享:
prlctl set "Windows 11" --sh-app-guest-to-host off
或者在 GUI 里关 Share Windows applications with Mac。
但这个太粗暴:关了之后,macOS 这边也就基本看不到 Windows 应用了,右键“打开方式”手动调用 Windows app 的便利也没了。对经常需要偶尔用 Windows 程序打开文件的人来说,不实用。
更优雅的方案
最后采用的是:
- 保持 Parallels 的 Shared Applications 开着
- 不删除 Windows app wrapper
- 只修 macOS 的默认打开方式
- 白名单保留真正应该进 Windows 的类型,比如
.exe/.msi/.bat/.cmd/.com - 其他被
com.parallels.winapp...抢成默认的类型拉回 macOS 本机应用
实际修复后,全量检查剩下的 Parallels 默认项只有 Windows 执行/安装入口:
.exe -> com.parallels.winapp...
.msi -> com.parallels.winapp...
而 .ini 已恢复成:
/Users/m/Downloads/oci-help-darwin-amd64-v2.3.1/oci-help.ini -> /System/Applications/TextEdit.app
.ini -> com.apple.TextEdit
.svg -> com.apple.Preview
.rar/.zst/.cab -> com.apple.archiveutility
.mhtml -> com.google.Chrome
做成一个守护脚本
我把这个做成了一个本机脚本,思路是扫描常见会被 PD 抢走的扩展名,检查当前默认 handler 是否是 com.parallels.winapp...。如果是,就按类型改回本机应用;如果是 .exe/.msi/.bat/.cmd/.com,保留给 Windows。
用法形态如下:
pd-file-association-guard.sh --audit
pd-file-association-guard.sh --apply
--audit 只看会改什么,--apply 才真正修复。
核心逻辑用 macOS CoreServices:
LSCopyDefaultRoleHandlerForContentType查当前默认 handlerLSSetDefaultRoleHandlerForContentType写回默认 handler- 同时兼容老 LaunchServices 动态 UTI 和新
UniformTypeIdentifiers.UTType生成的 UTI,因为同一个扩展名在 macOS 上可能有两套动态 UTI 变体
这一点很关键:第一次只用新 UTType(filenameExtension:) 改,发现 .ini 好了,但 .svg/.rar/.ps1 这类还有旧 UTI 变体会继续回到 Windows。补上老的 UTTypeCreatePreferredIdentifierForTag 后才彻底收敛。
结论
Parallels 的“一键关共享”不是最好的答案。更好用的模型是:
Windows 应用可以被 macOS 手动调用,但不能随便成为 macOS 的默认打开程序。
也就是“共享功能开着,默认关联白名单化”。这样既保留 PD 的便利,又不会出现双击 .ini 把 Windows 虚拟机叫醒这种奇葩体验。