博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
c#+handle.exe实现升级程序在运行时自动解除文件被占用的问题
阅读量:6250 次
发布时间:2019-06-22

本文共 7836 字,大约阅读时间需要 26 分钟。

我公司最近升级程序经常报出更新失败问题,究其原因,原来是更新时,他们可能又打开了正在被更新的文件,导致更新文件时,文件被其它进程占用,无法正常更新而报错,为了解决这个问题,我花了一周时间查询多方资料及研究,终于找到了一个查询进程的利器:handle.exe,下载地址:,我是通过它来找到被占用的进程,然后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  ,如需转载请自行联系原作者

你可能感兴趣的文章
使用JQ实现相同行或列合并
查看>>
Python中的__name__和类
查看>>
Lambda 表达式的示例-来源(MSDN)
查看>>
python socket之tcp服务器与客户端demo
查看>>
CesiumLab V1.4 新功能 BIM数据处理
查看>>
Red Hat发布开源PaaS OpenShift Origin
查看>>
python常用模块-time,datetime
查看>>
Linux中常用操作命令
查看>>
httpd基于用户的站点访问控制
查看>>
网页中的各种长宽、坐标
查看>>
lua程序设计之协同程序
查看>>
我的友情链接
查看>>
Nginx配置SSL证书
查看>>
AskoziaPBX 安装
查看>>
Tutorial for adding a library project as git submodule and then using it as a studio Module
查看>>
crontab + mysqldump 解决每天定时自动备份MySQL数据库
查看>>
metasploit扫描vsftp服务器root权限
查看>>
bzoj 3489: A simple rmq problem
查看>>
linux的grub的背景颜色
查看>>
计算器代码
查看>>