Wix安装程序自定义动作执行Exe时触发FileNotFoundException问题求助
看起来你遇到的这个问题确实挺典型的,结合你给出的错误日志、Wix配置和代码,我帮你梳理几个核心的问题点和解决思路:
1. 核心根源:自定义动作运行的是临时Exe,而非正式安装的Exe
你当前的Wix配置里,是把Middleware.exe作为Binary嵌入到MSI中,当自定义动作执行时,MSI会把这个Exe解压到系统临时目录(比如日志里的MSIB354.tmp这类临时文件),而非你最终安装的目标文件夹。
这里有两个致命问题:
- 临时Exe的依赖DLL不会同步解压:手动运行时,Exe和依赖DLL在同一目录能正常加载;但MSI只会解压你指定的Exe,它的依赖DLL不在临时目录,运行时自然抛出
FileNotFoundException。 - 临时文件会被MSI自动清理:即使临时Exe侥幸运行,安装完成后MSI会清理临时目录,临时Exe被删除后,你的后台程序会直接崩溃。
而且你可疑的代码行Process.GetCurrentProcess().MainModule.FileName,在这个场景下拿到的是临时文件的路径,用这个路径注册开机启动项的话,下次开机根本找不到文件,完全没用。
2. 最有效的解决思路:运行安装到目标目录的正式Exe
你需要先把Middleware.exe和它的依赖DLL通过Wix的Component/File元素安装到目标目录,再通过自定义动作运行这个正式的Exe,具体步骤如下:
第一步:添加Component安装Exe到目标目录
在你的Wix Product或Fragment里添加安装组件(记得把所有依赖DLL也加进去):
<DirectoryRef Id="INSTALLFOLDER"> <Component Id="MiddlewareComponent" Guid="*"> <!-- 安装正式的Middleware.exe到目标目录 --> <File Id="MiddlewareExe" Source="$(var.MyApp.ProjectDir)..\..\bin.NET\Middleware.exe" KeyPath="yes" /> <!-- 把所有依赖的DLL都添加为File元素,示例: --> <!-- <File Id="MiddlewareDependency1" Source="$(var.MyApp.ProjectDir)..\..\bin.NET\Dependency1.dll" /> --> </Component> </DirectoryRef>
记得把这个Component包含到你的Feature中,确保安装时会被复制到目标目录。
第二步:修改自定义动作调用正式Exe
删掉原来的Binary配置,修改CustomAction为调用目标目录的Exe:
<!-- 直接调用安装后的正式Exe,不需要Binary元素 --> <CustomAction Id='MiddlewareApp' Directory='INSTALLFOLDER' ExeCommand='[#MiddlewareExe]' Return='asyncNoWait' /> <Custom Action="MiddlewareApp" After="InstallFinalize">NOT Installed</Custom>
这里[#MiddlewareExe]是Wix的格式化引用,会自动替换成Middleware.exe安装后的完整路径。
3. 优化可疑代码:更可靠的路径获取方式
改成运行正式Exe后,Process.GetCurrentProcess().MainModule.FileName能拿到正确路径,但还有更稳妥的方式:直接把安装路径通过命令行参数传给Exe,彻底规避进程路径的不确定性。
修改CustomAction的命令行:
<CustomAction Id='MiddlewareApp' Directory='INSTALLFOLDER' ExeCommand='[#MiddlewareExe] "[#MiddlewareExe]"' Return='asyncNoWait' />
然后在Program.Main里接收参数:
static void Main(string[] args) { // 优先用传入的正确路径, fallback 到当前进程路径 string appPath = args.Length > 0 ? args[0] : Process.GetCurrentProcess().MainModule.FileName; // 后续代码使用这个appPath即可... }
4. 额外的小建议
- Return属性的合理性:你的
Return='asyncNoWait'是合适的,因为你的Exe是长期运行的后台程序(代码里有Thread.Sleep(Timeout.Infinite)),MSI不需要等待它执行完成。 - 日志修复:之前EventLog写不进去,大概率是临时Exe还没来得及写日志就崩溃了。改成正式Exe后,建议先注册EventLog源再写日志:
if (!EventLog.SourceExists(appName)) { EventLog.CreateEventSource(appName, "Application"); } EventLog eventLog = new EventLog("Application"); eventLog.Source = appName; eventLog.WriteEntry(appPath, EventLogEntryType.Information, 101, 1); - 控制台隐藏逻辑:
!Environment.UserInteractive的判断在MSI环境下是有效的,这段代码可以保留。
先试试改成运行正式安装的Exe,这应该能直接解决FileNotFoundException和路径错误的核心问题。
内容来源于stack exchange

