c# 使用handle.exe解決程序更新文件被占用的問題
我公司最近升級(jí)程序經(jīng)常報(bào)出更新失敗問題,究其原因,原來是更新時(shí),他們可能又打開了正在被更新的文件,導(dǎo)致更新文件時(shí),文件被其它進(jìn)程占用,無法正常更新而報(bào)錯(cuò),為了解決這個(gè)問題,我花了一周時(shí)間查詢多方資料及研究,終于找到了一個(gè)查詢進(jìn)程的利器:handle.exe,下載地址:https://technet.microsoft.com/en-us/sysinternals/bb896655.aspx,我是通過它來找到被占用的進(jìn)程,然后KILL掉占用進(jìn)程,最后再來更新,這樣就完美的解決了更新時(shí)文件被占用報(bào)錯(cuò)的問題了,實(shí)現(xiàn)方法很簡單,我下面都有列出主要的方法,一些注意事項(xiàng)我也都有說明,大家一看就明白了,當(dāng)然如果大家有更好的方案,歡迎交流,謝謝!
IsFileUsing:
判斷文件是否被占用
[DllImport("kernel32.dll")]
public static extern IntPtr _lopen(string lpPathName, int iReadWrite);
[DllImport("kernel32.dll")]
public static extern bool CloseHandle(IntPtr hObject);
public const int OF_READWRITE = 2;
public const int OF_SHARE_DENY_NONE = 0x40;
public readonly IntPtr HFILE_ERROR = new IntPtr(-1);
private bool <strong>IsFileUsing</strong>(string filePath)
{
if (!File.Exists(filePath))
{
return false;
}
IntPtr vHandle = _lopen(filePath, OF_READWRITE | OF_SHARE_DENY_NONE);
if (vHandle == HFILE_ERROR)
{
return true;
}
CloseHandle(vHandle);
return false;
}
GetRunProcessInfos:
獲取指定文件或目錄中存在的(關(guān)聯(lián)的)運(yùn)行進(jìn)程信息,以便后面可以解除占用
/// <summary>
/// 獲取指定文件或目錄中存在的(關(guān)聯(lián)的)運(yùn)行進(jìn)程信息,以便后面可以解除占用
/// </summary>
/// <param name="filePath"></param>
/// <returns></returns>
private Dictionary<int, string> GetRunProcessInfos(string filePath)
{
Dictionary<int, string> runProcInfos = new Dictionary<int, string>();
string fileName = Path.GetFileName(filePath);
var fileRunProcs = Process.GetProcessesByName(fileName);
if (fileRunProcs != null && fileRunProcs.Count() > 0)
{
runProcInfos = fileRunProcs.ToDictionary(p => p.Id, p => p.ProcessName);
return runProcInfos;
}
string fileDirName = Path.GetDirectoryName(filePath); //查詢指定路徑下的運(yùn)行的進(jìn)程
Process startProcess = new Process();
startProcess.StartInfo.FileName = RelaseAndGetHandleExePath();
startProcess.StartInfo.Arguments = string.Format("\"{0}\"", fileDirName);
startProcess.StartInfo.UseShellExecute = false;
startProcess.StartInfo.RedirectStandardInput = false;
startProcess.StartInfo.RedirectStandardOutput = true;
startProcess.StartInfo.CreateNoWindow = true;
startProcess.StartInfo.StandardOutputEncoding = ASCIIEncoding.UTF8;
startProcess.OutputDataReceived += (sender, e) =>
{
if (!string.IsNullOrEmpty(e.Data) && e.Data.IndexOf("pid:", StringComparison.OrdinalIgnoreCase) > 0)
{
//var regex = new System.Text.RegularExpressions.Regex(@"(^[\w\.\?\u4E00-\u9FA5]+)\s+pid:\s*(\d+)", System.Text.RegularExpressions.RegexOptions.IgnoreCase);
var regex = new System.Text.RegularExpressions.Regex(@"(^.+(?=pid:))\bpid:\s+(\d+)\s+", System.Text.RegularExpressions.RegexOptions.IgnoreCase);
if (regex.IsMatch(e.Data))
{
var mathedResult = regex.Match(e.Data);
int procId = int.Parse(mathedResult.Groups[2].Value);
string procFileName = mathedResult.Groups[1].Value.Trim();
if ("explorer.exe".Equals(procFileName, StringComparison.OrdinalIgnoreCase))
{
return;
}
//var regex2 = new System.Text.RegularExpressions.Regex(string.Format(@"\b{0}.*$", fileDirName.Replace(@"\", @"\\").Replace("?",@"\?")), System.Text.RegularExpressions.RegexOptions.IgnoreCase);
var regex2 = new System.Text.RegularExpressions.Regex(@"\b\w{1}:.+$", System.Text.RegularExpressions.RegexOptions.IgnoreCase);
string procFilePath = (regex2.Match(e.Data).Value ?? "").Trim();
if (filePath.Equals(procFilePath, StringComparison.OrdinalIgnoreCase) || filePath.Equals(PathJoin(procFilePath, procFileName), StringComparison.OrdinalIgnoreCase))
{
runProcInfos[procId] = procFileName;
}
else //如果亂碼,則進(jìn)行特殊的比對(duì)
{
if (procFilePath.Contains("?") || procFileName.Contains("?")) //?亂碼比對(duì)邏輯
{
var regex3 = new System.Text.RegularExpressions.Regex(procFilePath.Replace(@"\", @"\\").Replace(".", @"\.").Replace("?", ".{1}"), System.Text.RegularExpressions.RegexOptions.IgnoreCase);
if (regex3.IsMatch(filePath))
{
runProcInfos[procId] = procFileName;
}
else
{
string tempProcFilePath = PathJoin(procFilePath, procFileName);
regex3 = new System.Text.RegularExpressions.Regex(tempProcFilePath.Replace(@"\", @"\\").Replace(".", @"\.").Replace("?", ".{1}"), System.Text.RegularExpressions.RegexOptions.IgnoreCase);
if (regex3.IsMatch(filePath))
{
runProcInfos[procId] = procFileName;
}
}
}
else if (procFilePath.Length == filePath.Length || PathJoin(procFilePath, procFileName).Length == filePath.Length) //其它亂碼比對(duì)邏輯,僅比對(duì)長度,如果相同交由用戶判斷
{
if (MessageBox.Show(string.Format("發(fā)現(xiàn)文件:{0}可能被一個(gè)進(jìn)程({1})占用,\n您是否需要強(qiáng)制終止該進(jìn)程?", filePath, procFileName), "發(fā)現(xiàn)疑似被占用進(jìn)程", MessageBoxButtons.YesNo, MessageBoxIcon.Warning) == DialogResult.Yes)
{
runProcInfos[procId] = procFileName;
}
}
}
}
}
};
startProcess.Start();
startProcess.BeginOutputReadLine();
startProcess.WaitForExit();
return runProcInfos;
}
上述代碼邏輯簡要說明:創(chuàng)建一個(gè)建程來啟動(dòng)handle.exe(以資源形式內(nèi)嵌到項(xiàng)目中),然后異步接收返回?cái)?shù)據(jù),并通過正則表達(dá)式來匹配獲取進(jìn)程數(shù)據(jù),由于handle.exe對(duì)于中文路徑或文件名兼容不好,返回的數(shù)據(jù)存在?或其它亂碼字符,故我作了一些特殊的模糊匹配邏輯;
RelaseAndGetHandleExePath:
從項(xiàng)目中釋放handle.exe并保存到系統(tǒng)的APPData目錄下,以便后續(xù)直接可以使用(注意:由于handle.exe需要授權(quán)同意后才能正常的使用該工具,故我在第一次生成handle.exe時(shí),會(huì)直接運(yùn)行進(jìn)程,讓用戶選擇Agree后再去進(jìn)行后面的邏輯處理,這樣雖能解決問題,但有點(diǎn)不太友好,目前一個(gè)是中文亂碼、一個(gè)是必需同意才能使用handle.exe我認(rèn)為如果微軟解決了可能會(huì)更好)
private string RelaseAndGetHandleExePath()
{
var handleInfo = new FileInfo(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + "\\SysUpdate\\handle.exe");
if (!File.Exists(handleInfo.FullName))
{
if (!Directory.Exists(handleInfo.DirectoryName))
{
Directory.CreateDirectory(handleInfo.DirectoryName);
}
byte[] handleExeData = Properties.Resources.handle;
File.WriteAllBytes(handleInfo.FullName, handleExeData);
var handleProc = Process.Start(handleInfo.FullName);//若第一次,則彈出提示框,需要點(diǎn)擊agree同意才行
handleProc.WaitForExit();
}
return handleInfo.FullName;
}
PathJoin:
拼接路徑(不過濾特殊字符),由于handle.exe對(duì)于中文路徑或文件名兼容不好,返回的數(shù)據(jù)存在?或其它亂碼字符,如查采用:Path.Combine方法則會(huì)報(bào)錯(cuò),故這里自定義一個(gè)方法,只是簡單的拼接
/// <summary>
/// 拼接路徑(不過濾殊字符)
/// </summary>
/// <param name="paths"></param>
/// <returns></returns>
private string PathJoin(params string[] paths)
{
if (paths == null || paths.Length <= 0)
{
return string.Empty;
}
string newPath = paths[0];
for (int i = 1; i < paths.Length; i++)
{
if (!newPath.EndsWith("\\"))
{
newPath += "\\";
}
if (paths[i].StartsWith("\\"))
{
paths[i] = paths[i].Substring(1);
}
newPath += paths[i];
}
return newPath;
}
CloseProcessWithFile:
核心方法,關(guān)閉指定文件被占用的進(jìn)程,上述所有的方法均是為了實(shí)現(xiàn)該方法的功能
private void CloseProcessWithFile(string filePath)
{
if (!IsFileUsing(filePath)) return;
ShowDownInfo(string.Format("正在嘗試解除占用文件 {0}", _FilePaths[_FileIndex]));
var runProcInfos = GetRunProcessInfos(filePath); //獲取被占用的進(jìn)程
System.IO.File.WriteAllText(Path.Combine(Application.StartupPath, "runProcInfos.txt"), string.Join("\r\n", runProcInfos.Select(p => string.Format("ProdId:{0},ProcName:{1}", p.Key, p.Value)).ToArray()));//DEBUG用,正式發(fā)布時(shí)可以去掉
var localProcesses = Process.GetProcesses();
bool hasKilled = false;
foreach (var item in runProcInfos)
{
if (item.Key != currentProcessId) //排除當(dāng)前進(jìn)程
{
var runProcess = localProcesses.SingleOrDefault(p => p.Id == item.Key);
//var runProcess = Process.GetProcessById(item.Key);
if (runProcess != null)
{
try
{
runProcess.Kill(); //強(qiáng)制關(guān)閉被占用的進(jìn)程
hasKilled = true;
}
catch
{ }
}
}
}
if (hasKilled)
{
Thread.Sleep(500);
}
}
上述代碼邏輯簡要說明:先判斷是否被占用,若被占用,則獲取該文件被占用的進(jìn)程列表,然后獲取一下當(dāng)前操作系統(tǒng)的所有進(jìn)程列表,最后通過進(jìn)程ID查詢得到排除當(dāng)前程序自己的進(jìn)程ID(currentProcessId = Process.GetCurrentProcess().Id)列表,若能獲取得到,表明進(jìn)程仍在運(yùn)行,則強(qiáng)制終止該進(jìn)程,實(shí)現(xiàn)解除文件占用
注意:KILL掉占用進(jìn)程后,可能由于緩存原因,若直接進(jìn)行文件的覆蓋與替換或轉(zhuǎn)移操作,可能仍會(huì)報(bào)錯(cuò),故這里作了一個(gè)判斷,若有成功KILL掉進(jìn)程,則需等待500MS再去做更新文件之類的操作;
以上就是c# 使用handle.exe解決程序更新文件被占用的問題的詳細(xì)內(nèi)容,更多關(guān)于c# 使用handle.exe解決程序更新問題的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
WinForm中BackgroundWorker控件用法簡單實(shí)例
這篇文章主要介紹了WinForm中BackgroundWorker控件用法,以一個(gè)簡單實(shí)例形式分析了BackgroundWorker控件的定義、設(shè)置及使用技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-08-08
C#之HttpClient設(shè)置cookies的兩種方式
這篇文章主要介紹了C#之HttpClient設(shè)置cookies的兩種方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-11-11
C# 實(shí)現(xiàn)特殊字符快速轉(zhuǎn)碼
這篇文章主要介紹了C# 實(shí)現(xiàn)特殊字符快速轉(zhuǎn)碼,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2021-01-01
C#根據(jù)年月日計(jì)算星期幾的函數(shù)小例子
這篇文章介紹了C#根據(jù)年月日計(jì)算星期幾的函數(shù)小例子,有需要的朋友可以參考一下2013-07-07

