首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> C# >> 關於C#

C# 巧用批處理實現運行中程式自更新:自殺式覆蓋主 EXE 技巧

     編輯:關於C#

痛點:主程式運行時無法覆蓋自身

自動更新的常規流程:客戶端檢測版本 → 下載新文件 → 覆蓋舊文件 → 重啟。但 Windows 禁止覆蓋正在運行的 EXE/DLL,導致主程式自身無法更新。若在更新程式中強行 File.Copy 會拋出 IOException: The process cannot access the file because it is being used by another process。

核心思路:批處理「接力棒」實現自殺式更新

利用 cmd /c 批處理腳本的特性:

  1. 主程式檢測到需更新自身時,生成兩個 .bat 文件:XCopyFiles.bat(負責覆蓋)、KillSelf.bat(負責刪除舊 EXE、調用覆蓋、啟動新 EXE、自刪)。
  2. 主程式啟動 KillSelf.bat 並 立即退出(Environment.Exit(0)),釋放文件鎖定。
  3. KillSelf.bat 循環嘗試刪除舊 EXE(屬性去除 → del → 存在則跳轉繼續刪),成功後調用 XCopyFiles.bat 覆蓋新文件,刪除臨時文件,start 新 EXE,最後 del %0 自刪。

完整實現代碼

1. 更新入口:捕獲 IOException 觸發自殺流程


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);
}

2. 生成覆蓋腳本 XCopyFiles.bat


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);
}

3. 批處理關鍵命令解析

命令作用
attrib -a -r -s -h移除封存、唯讀、系統、隱藏屬性,確保 del 能刪除
if exist ... goto selfkill循環嘗試刪除,直到文件鎖完全釋放(主進程退出需幾十毫秒)
xcopy /y /s /e /v遞歸覆蓋整個更新目錄,保留目錄結構,驗證寫入
start "" ""{exe}""啟動新程式,空標題避免路徑含空格被誤解
del %0批處理執行結束後自刪,不留垃圾文件

測試驗證與注意事項

  • 測試環境:Windows XP/7/10/11 均通測,.NET Framework 2.0+ 適用。
  • 權限要求:程式安裝目錄需寫入權限(建議安裝到非 Program Files,或清單請求 admin)。
  • 防毒軟體:部分殺毒軟體會攔截「程式啟動另一程式後自刪」行為,需加入白名單或簽名。
  • 更新包結構:tempUpdatePath 應包含完整目錄結構(如 App.exe、Libs/、Config/),xcopy 會鏡像覆蓋。
  • 多實例防護:啟動更新前建議用 Mutex 確保單實例,避免多開導致更新衝突。

優缺點對比

方案優點缺點
批處理自殺式 (本文)無需額外更新程式、零依賴、代碼極簡、支援整目錄覆蓋閃現黑框(已隱藏)、殺毒軟體可能誤報、需寫入權限
獨立 Updater.exe邏輯清晰、可做差分更新、UI 友好需維護兩個專案、Updater 自身更新仍需自殺技巧
ClickOnce微軟原生、自動檢測更新、沙箱隔離安裝路徑固定、難以自定義啟動參數、企業環境常禁用
Squirrel / NSIS專業安裝更新框架、差分包、回滾學習曲線陡、體積大、配置複雜

結語

「自殺式批處理更新」是一個經典的「土辦法」解決「硬約束」的典範。雖不夠優雅,但在「無額外進程、無安裝器、需整目錄覆蓋、主程式必須自更新」的約束下,它以幾十行代碼完美閉環。理解 xcopy、attrib、start、del %0 等命令行細節,對排查更新失敗、適配不同 Windows 版本極有幫助。若專案後期規模增長,建議遷移至 Squirrel.Windows 或自建 Updater 服務,但這招依然是應急與輕量級專案的利器。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved