自動更新的常規流程:客戶端檢測版本 → 下載新文件 → 覆蓋舊文件 → 重啟。但 Windows 禁止覆蓋正在運行的 EXE/DLL,導致主程式自身無法更新。若在更新程式中強行 File.Copy 會拋出 IOException: The process cannot access the file because it is being used by another process。
利用 cmd /c 批處理腳本的特性:
XCopyFiles.bat(負責覆蓋)、KillSelf.bat(負責刪除舊 EXE、調用覆蓋、啟動新 EXE、自刪)。KillSelf.bat 並 立即退出(Environment.Exit(0)),釋放文件鎖定。KillSelf.bat 循環嘗試刪除舊 EXE(屬性去除 → del → 存在則跳轉繼續刪),成功後調用 XCopyFiles.bat 覆蓋新文件,刪除臨時文件,start 新 EXE,最後 del %0 自刪。
private void btnFinish_Click(object sender, EventArgs e) {
this.Dispose();
try {
CopyFile(tempUpdatePath, Directory.GetCurrentDirectory());
Directory.Delete(tempUpdatePath, true);
}
catch (Exception ex) when (ex is IOException) {
// 主程式被佔用,啟動批處理自殺更新
KillSelfThenRun();
}
catch (Exception ex) {
MessageBox.Show(ex.Message);
}
if (isRun) Process.Start(mainAppExe);
}
private void KillSelfThenRun() {
string xcopyBat = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "XCopyFiles.bat");
using (var sw = File.CreateText(xcopyBat)) {
string src = tempUpdatePath.TrimEnd('\\');
string dest = Directory.GetCurrentDirectory();
// /y 覆蓋不提示 /s 遞歸目錄 /e 包含空目錄 /v 驗證寫入
sw.WriteLine($@"@echo off
xcopy /y /s /e /v ""{src}"" ""{dest}\""
");
}
string killBat = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "killmyself.bat");
using (var sw = File.CreateText(killBat)) {
string exeName = AppDomain.CurrentDomain.FriendlyName;
sw.WriteLine($@"@echo off
:selfkill
attrib -a -r -s -h ""{exeName}""
del ""{exeName}""
if exist ""{exeName}"" goto selfkill
call XCopyFiles.bat
del XCopyFiles.bat
del /f /q ""{tempUpdatePath}""
rd ""{tempUpdatePath}""
start """" ""{mainAppExe}""
del %0
");
}
// 隱藏視窗啟動批處理
var psi = new ProcessStartInfo(killBat) {
WindowStyle = ProcessWindowStyle.Hidden,
UseShellExecute = false
};
Process.Start(psi);
// 主程式立即退出,釋放文件鎖
Environment.Exit(0);
}
| 命令 | 作用 |
|---|---|
attrib -a -r -s -h | 移除封存、唯讀、系統、隱藏屬性,確保 del 能刪除 |
if exist ... goto selfkill | 循環嘗試刪除,直到文件鎖完全釋放(主進程退出需幾十毫秒) |
xcopy /y /s /e /v | 遞歸覆蓋整個更新目錄,保留目錄結構,驗證寫入 |
start "" ""{exe}"" | 啟動新程式,空標題避免路徑含空格被誤解 |
del %0 | 批處理執行結束後自刪,不留垃圾文件 |
tempUpdatePath 應包含完整目錄結構(如 App.exe、Libs/、Config/),xcopy 會鏡像覆蓋。Mutex 確保單實例,避免多開導致更新衝突。| 方案 | 優點 | 缺點 |
|---|---|---|
| 批處理自殺式 (本文) | 無需額外更新程式、零依賴、代碼極簡、支援整目錄覆蓋 | 閃現黑框(已隱藏)、殺毒軟體可能誤報、需寫入權限 |
| 獨立 Updater.exe | 邏輯清晰、可做差分更新、UI 友好 | 需維護兩個專案、Updater 自身更新仍需自殺技巧 |
| ClickOnce | 微軟原生、自動檢測更新、沙箱隔離 | 安裝路徑固定、難以自定義啟動參數、企業環境常禁用 |
| Squirrel / NSIS | 專業安裝更新框架、差分包、回滾 | 學習曲線陡、體積大、配置複雜 |
「自殺式批處理更新」是一個經典的「土辦法」解決「硬約束」的典範。雖不夠優雅,但在「無額外進程、無安裝器、需整目錄覆蓋、主程式必須自更新」的約束下,它以幾十行代碼完美閉環。理解 xcopy、attrib、start、del %0 等命令行細節,對排查更新失敗、適配不同 Windows 版本極有幫助。若專案後期規模增長,建議遷移至 Squirrel.Windows 或自建 Updater 服務,但這招依然是應急與輕量級專案的利器。