程序退出后无法直接删除自身exe,需通过外部载体(如批处理、powershell或启动器)延时执行删除或替换;自更新须下载新版本到临时位置,由启动器完成移动并以相同权限启动,全程需校验签名与哈希、记录日志防失败。

程序退出后删除自身 EXE 文件
直接调用 File.Delete 会抛出 UnauthorizedAccessException 或 IOException,因为 Windows 锁住了正在运行的映像文件。必须让当前进程先退出,再由外部载体(如启动器、批处理、或另一个进程)完成删除。
- 最稳妥做法:启动一个独立的批处理脚本,延迟几毫秒再执行
del,然后立即退出主程序 - 批处理不能放在 EXE 同目录下直接双击运行——要由 C# 启动它,且需用
Process.Start("cmd.exe", "/c ...")并设置UseShellExecute = false避免窗口闪现 - 注意路径空格:命令中所有路径必须用英文双引号包裹,例如
"%~f0"在批处理里安全,但 C# 拼接时得写成"""+exePath+""" - 不要用
Thread.Sleep(100)等待文件释放——没用,锁是进程级的,不是 I/O 延迟问题
C# 自更新时替换正在运行的 EXE
不能边运行边覆盖自身文件,Windows 会拒绝写入。常见做法是把新版本下载到临时位置,用“启动器”接管后续流程:原程序退出 → 启动器把新 EXE 移到原位置 → 启动新 EXE。
- 启动器可以是一个轻量级 .NET Core/.NET 6+ 控制台程序(避免依赖目标框架),也可以是无依赖的原生 exe(如用 C++ 写)
- 关键点:原程序必须用
Environment.Exit(0)退出,不能只是关闭窗体(WinForms/WPF 的Application.Exit()不等于进程终止) - 启动器执行移动操作前,建议先尝试
File.Move,失败则 fallback 到File.Copy + File.Delete;Move在同卷上是原子操作,更安全 - 如果更新包是 zip,解压后务必校验
SHA256,否则被篡改的 EXE 替换进去就彻底不可逆了
用 PowerShell 脚本辅助删除或替换(绕过 cmd 限制)
PowerShell 比 cmd 更适合处理路径、权限和错误重试,尤其在企业环境组策略禁用 cmd 的情况下。
- 生成临时 ps1 文件时,内容开头加
Start-Sleep -Milliseconds 500,确保原进程已完全退出 - 用
Remove-Item -Path "...\app.exe" -Force -ErrorAction SilentlyContinue,-Force可绕过只读属性干扰 - 调用时必须加
-ExecutionPolicy Bypass参数,否则默认策略会阻止脚本运行:powershell.exe -ExecutionPolicy Bypass -File "cleanup.ps1" - PowerShell 脚本本身不能被主程序直接 File.Delete——它可能还在执行中,应让系统定时任务或另一层 wrapper 清理它
Update.exe 启动器如何知道该启动哪个新版本
启动器不负责下载,只负责“搬运+启动”,所以需要一种可靠方式传递目标路径。硬编码或配置文件都容易出错,推荐用启动参数传入。
- 主程序退出前启动
Update.exe "C:\temp\new\app.exe",启动器解析args[0]得到新路径 - 启动器执行
File.Move(args[0], currentExePath)后,再调用Process.Start(currentExePath) - currentExePath 必须用
Process.GetCurrentProcess().MainModule.FileName获取,不能用Application.ExecutablePath(WinForms 下可能含调试路径) - 如果新 EXE 和旧 EXE 签名不同,UAC 提权状态会丢失——启动器需以相同权限启动新进程,可检查
Process.GetCurrentProcess().Privileges并用Verb="runas"显式提权
实际最难的不是删文件,是保证“删完不丢数据、启新不卡死、出错能回滚”。临时目录权限、杀毒软件拦截、NTFS 符号链接、甚至 OneDrive 同步状态,都可能让看似简单的 Move/Delete 失败。留一手日志,比加十层 try-catch 更有用。









