c#+handle.exe實現升級程序在運行時自動解除文件被占用的問題

我公司最近升級程序經常報出更新失敗問題,究其原因,原來是更新時,他們可能又打開了正在被更新的文件,導致更新文件時,文件被其它進程占用,無法正常更新而報錯,為了解決這個問題,我花了一周時間查詢多方資料及研究,終于找到了一個查詢進程的利器:handle.exe,下載地址:https://technet.microsoft.com/en-us/sysinternals/bb896655.aspx,我是通過它來找到被占用的進程,然后KILL掉占用進程,最后再來更新,這樣就完美的解決了更新時文件被占用報錯的問題了,實現方法很簡單,我下面都有列出主要的方法,一些注意事項我也都有說明,大家一看就明白了,當然如果大家有更好的方案,歡迎交流,謝謝!

IsFileUsing:判斷文件是否被占用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
[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:獲取指定文件或目錄中存在的(關聯的)運行進程信息,以便后面可以解除占用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
/// <summary>
/// 獲取指定文件或目錄中存在的(關聯的)運行進程信息,以便后面可以解除占用
/// </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);?//查詢指定路徑下的運行的進程
????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?//如果亂碼,則進行特殊的比對
????????????????{
????????????????????if?(procFilePath.Contains("?") || procFileName.Contains("?"))?//?亂碼比對邏輯
????????????????????{
????????????????????????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)?//其它亂碼比對邏輯,僅比對長度,如果相同交由用戶判斷
????????????????????{
????????????????????????if?(MessageBox.Show(string.Format("發現文件:{0}可能被一個進程({1})占用,\n您是否需要強制終止該進程?", filePath, procFileName),?"發現疑似被占用進程", MessageBoxButtons.YesNo, MessageBoxIcon.Warning) == DialogResult.Yes)
????????????????????????{
????????????????????????????runProcInfos[procId] = procFileName;
????????????????????????}
????????????????????}
????????????????}
????????????}
????????}
????};
????startProcess.Start();
????startProcess.BeginOutputReadLine();
????startProcess.WaitForExit();
????return?runProcInfos;
}

上述代碼邏輯簡要說明:創建一個建程來啟動handle.exe(以資源形式內嵌到項目中),然后異步接收返回數據,并通過正則表達式來匹配獲取進程數據,由于handle.exe對于中文路徑或文件名兼容不好,返回的數據存在?或其它亂碼字符,故我作了一些特殊的模糊匹配邏輯;

RelaseAndGetHandleExePath:從項目中釋放handle.exe并保存到系統的APPData目錄下,以便后續直接可以使用(注意:由于handle.exe需要授權同意后才能正常的使用該工具,故我在第一次生成handle.exe時,會直接運行進程,讓用戶選擇Agree后再去進行后面的邏輯處理,這樣雖能解決問題,但有點不太友好,目前一個是中文亂碼、一個是必需同意才能使用handle.exe我認為如果微軟解決了可能會更好)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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);//若第一次,則彈出提示框,需要點擊agree同意才行
????????handleProc.WaitForExit();
????}
????return?handleInfo.FullName;
}

PathJoin:拼接路徑(不過濾特殊字符),由于handle.exe對于中文路徑或文件名兼容不好,返回的數據存在?或其它亂碼字符,如查采用:Path.Combine方法則會報錯,故這里自定義一個方法,只是簡單的拼接

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/// <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:核心方法,關閉指定文件被占用的進程,上述所有的方法均是為了實現該方法的功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
private?void?CloseProcessWithFile(string?filePath)
{
????if?(!IsFileUsing(filePath))?return;
????ShowDownInfo(string.Format("正在嘗試解除占用文件 {0}", _FilePaths[_FileIndex]));
????var?runProcInfos = GetRunProcessInfos(filePath);?//獲取被占用的進程
????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用,正式發布時可以去掉
????var?localProcesses = Process.GetProcesses();
????bool?hasKilled =?false;
????foreach?(var?item?in?runProcInfos)
????{
????????if?(item.Key != currentProcessId)?//排除當前進程
????????{
????????????var?runProcess = localProcesses.SingleOrDefault(p => p.Id == item.Key);
????????????//var runProcess = Process.GetProcessById(item.Key);
????????????if?(runProcess !=?null)
????????????{
????????????????try
????????????????{
????????????????????runProcess.Kill();?//強制關閉被占用的進程
????????????????????hasKilled =?true;
????????????????}
????????????????catch
????????????????{ }
????????????}
????????}
????}
????if?(hasKilled)
????{
????????Thread.Sleep(500);
????}
}

上述代碼邏輯簡要說明:先判斷是否被占用,若被占用,則獲取該文件被占用的進程列表,然后獲取一下當前操作系統的所有進程列表,最后通過進程ID查詢得到排除當前程序自己的進程ID(currentProcessId = Process.GetCurrentProcess().Id)列表,若能獲取得到,表明進程仍在運行,則強制終止該進程,實現解除文件占用

注意:KILL掉占用進程后,可能由于緩存原因,若直接進行文件的覆蓋與替換或轉移操作,可能仍會報錯,故這里作了一個判斷,若有成功KILL掉進程,則需等待500MS再去做更新文件之類的操作;

本文轉自 夢在旅途 博客園博客,原文鏈接:http://www.cnblogs.com/zuowj/p/5840567.html??,如需轉載請自行聯系原作者

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/392828.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/392828.shtml
英文地址,請注明出處:http://en.pswp.cn/news/392828.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

播客#50:Sacha Greif

On todays episode of the freeCodeCamp Podcast, Quincy Larson interviews Sacha Greif, a designer, developer, and prolific open source project creator.在今天的免費CodeCamp播客中&#xff0c;昆西拉爾森(Quincy Larson)采訪了設計師&#xff0c;開發人員和多產的開源…

leetcode 977. 有序數組的平方(雙指針)

給定一個按非遞減順序排序的整數數組 A&#xff0c;返回每個數字的平方組成的新數組&#xff0c;要求也按非遞減順序排序。 示例 1&#xff1a; 輸入&#xff1a;[-4,-1,0,3,10] 輸出&#xff1a;[0,1,9,16,100] 示例 2&#xff1a; 輸入&#xff1a;[-7,-3,2,3,11] 輸出&am…

Spring.net的一個小例子

入門級的Spring.net的例子&#xff0c;比Spring.net帶的例子還要簡單。容易上手。下載地址&#xff1a;http://files.cnblogs.com/elevenWolf/SpringTest.rar轉載于:https://www.cnblogs.com/martinxj/archive/2005/07/18/195105.html

使用JavaScript的Platformer游戲教程

Learn how to create a platformer game using vanilla JavaScript.了解如何使用香草JavaScript創建平臺游戲。 This tutorial starts with teaching how to organize the code using the Model, View, Controller (MVC) strategy and the principles of Object Oriented Prog…

leetcode 52. N皇后 II(回溯)

n 皇后問題研究的是如何將 n 個皇后放置在 nn 的棋盤上&#xff0c;并且使皇后彼此之間不能相互攻擊。 給定一個整數 n&#xff0c;返回 n 皇后不同的解決方案的數量。 示例: 輸入: 4 輸出: 2 解釋: 4 皇后問題存在如下兩個不同的解法。 [ [".Q…", // 解法 1 “……

uic計算機課程表,美國UIC大學研究生畢業率能達到多少?申請條件、專業課程匯總...

UIC大學也就是伊利諾伊大學芝加哥分校&#xff0c;這所學校始建于1982年&#xff0c;該校擁有東、西兩個校區&#xff0c;皆位于美國第二大商業中心芝加哥市的心臟地帶&#xff0c;地理位置優勢顯著&#xff0c;UIC大學有著豐富的教學資源和出色的教學水準&#xff0c;那么接下…

#region(C# 參考)

< DOCTYPE html PUBLIC -WCDTD XHTML StrictEN httpwwwworgTRxhtmlDTDxhtml-strictdtd> #region&#xff08;C# 參考&#xff09; #region 使您可以在使用 Visual Studio 代碼編輯器的大綱顯示功能時指定可展開或折疊的代碼塊。例如&#xff1a; #region MyClass defin…

java中常用的包、類、以及包中常用的類、方法、屬性----sql和text\swing

java中常用的包、類、以及包中常用的類、方法、屬性 常用的包 java.io.*; java.util.*; java.lang.*; java.sql.*; java.text.*; java.awt.*; javax.swing.*; 包名 接口 類 方法 屬性 java.sql.*; public class DriverManager extends Object static Connection…

Reindex SQL Server DB table

DBCCDBReindex(TableName,,90) Or ALTERINDEXALLONTableNameREBUILDWITH(FILLFACTOR90,SORT_IN_TEMPDBON,STATISTICS_NORECOMPUTEOFF,ONLINEOFF); 90 Refers to page density 90%, 10% is reserved for update. Show Index result by DBCCSHOWCONTIG 轉載于:https://www.cnblo…

cloudwatch監控_Amazon CloudWatch:無服務器日志記錄和監控基礎

cloudwatch監控Amazon CloudWatch is a monitoring and management service built for developers, system operators, site reliability engineers (SRE), and IT managers.Amazon CloudWatch是為開發人員&#xff0c;系統操作員&#xff0c;站點可靠性工程師(SRE)和IT經理構建…

電大計算機考試題目excel,電大計算機考試復習題EXCEL部分

電大計算機考試復習題001_prac2.xls(1) 將Sheet1工作表命名為dubug1.(2) 在debug1工作表中&#xff0c;試采用數據的填充功能分別填充A3;A30、B3&#xff1a;B30、C3&#xff1a;C30區域&#xff0c;前一區域中的前兩個單元格的內容為“10”和“11”&#xff0c;中間區域中的前…

leetcode 19. 刪除鏈表的倒數第N個節點(雙指針)

給定一個鏈表&#xff0c;刪除鏈表的倒數第 n 個節點&#xff0c;并且返回鏈表的頭結點。 示例&#xff1a; 給定一個鏈表: 1->2->3->4->5, 和 n 2. 當刪除了倒數第二個節點后&#xff0c;鏈表變為 1->2->3->5. 代碼 /*** Definition for singly-li…

Tegra3 vSMP架構Android運行時CPU熱插拔及高低功耗CPU切換

Tegra3采用vSMP&#xff08;VariableSymmetric Multiprocessing&#xff09;架構&#xff0c;共5個cortex-a9處理器&#xff0c;其中4個為高性能設計&#xff0c;1個為低功耗設計&#xff1a; 在系統運行過程中&#xff0c;會根據CPU負載切換低功耗處理器和高功耗處理器&#x…

Linux 內核總線

一個總線是處理器和一個或多個設備之間的通道. 為設備模型的目的, 所有的設備都通過 一個總線連接, 甚至當它是一個內部的虛擬的,"平臺"總線. 總線可以插入另一個 - 一個 USB 控制器常常是一個 PCI 設備, 例如. 設備模型表示在總線和它們控制的設備之間的 實際連接. …

leetcode 844. 比較含退格的字符串

給定 S 和 T 兩個字符串&#xff0c;當它們分別被輸入到空白的文本編輯器后&#xff0c;判斷二者是否相等&#xff0c;并返回結果。 # 代表退格字符。 注意&#xff1a;如果對空文本輸入退格字符&#xff0c;文本繼續為空。 示例 1&#xff1a; 輸入&#xff1a;S “ab#c”…

P1093 獎學金

題目描述 某小學最近得到了一筆贊助&#xff0c;打算拿出其中一部分為學習成績優秀的前5名學生發獎學金。期末&#xff0c;每個學生都有3門課的成績:語文、數學、英語。先按總分從高到低排序&#xff0c;如果兩個同學總分相同&#xff0c;再按語文成績從高到低排序&#xff0c;…

phpMyAdmin安裝

phpMyAdmin下載、安裝和使用入門對于PHP的逐漸流行&#xff0c;我們有目共睹&#xff1a;無論是BLOG程序中的WordPress&#xff0c;還是CMS程序中的DEDECMS&#xff0c;還是BBS程序中的Discuz!&#xff0c;都可謂經典。隨著程序語言選擇的不同&#xff0c;WEB應用所使用的數據庫…

react中樣式沖突_如何通過React中的樣式使您的應用漂亮

react中樣式沖突by Vinh Le由Vinh Le 如何通過React中的樣式使您的應用漂亮 (How to make your apps pretty with styling in React) When it comes to styling in React, there are just so many ways and choices of technologies to beautify your web app. Nonetheless, b…

英語磁帶與計算機磁帶區別,小學教材仍配發英語磁帶遭吐槽:誰還用錄音機

據中國之聲《新聞晚高峰》報道&#xff0c;時間倒回十多年&#xff0c;大家聽歌、聽英語還是用磁帶&#xff0c;復讀機、錄音機也是學生人手必備的學習用品。但在“互聯網”的今天&#xff0c;全國不少地方的小學教材中&#xff0c;仍給學生發磁帶&#xff0c;引起家長吐槽。電…

近5年133個Java面試問題列表

2019獨角獸企業重金招聘Python工程師標準>>> Java 面試隨著時間的改變而改變。在過去的日子里&#xff0c;當你知道 String 和 StringBuilder 的區別就能讓你直接進入第二輪面試&#xff0c;但是現在問題變得越來越高級&#xff0c;面試官問的問題也更深入。 在我初…