c# 使用handle.exe解决程序更新文件被占用的问题
我公司最近升级程序经常报出更新失败问题,究其原因,原来是更新时,他们可能又打开了正在被更新的文件,导致更新文件时,文件被其它进程占用,无法正常更新而报错,为了解决这个问题,我花了一周时间查询多方资料及研究,终于找到了一个查询进程的利器:handle.exe,下载地址:https://technet.microsoft.com/en-us/sysinternals/bb896655.aspx,我是通过它来找到被占用的进程,然后KILL掉占用进程,最后再来更新,这样就完美的解决了更新时文件被占用报错的问题了,实现方法很简单,我下面都有列出主要的方法,一些注意事项我也都有说明,大家一看就明白了,当然如果大家有更好的方案,欢迎交流,谢谢!
IsFileUsing:
判断文件是否被占用
[DllImport("kernel32.dll")] publicstaticexternIntPtr_lopen(stringlpPathName,intiReadWrite); [DllImport("kernel32.dll")] publicstaticexternboolCloseHandle(IntPtrhObject); publicconstintOF_READWRITE=2; publicconstintOF_SHARE_DENY_NONE=0x40; publicreadonlyIntPtrHFILE_ERROR=newIntPtr(-1); privateboolIsFileUsing(stringfilePath) { if(!File.Exists(filePath)) { returnfalse; } IntPtrvHandle=_lopen(filePath,OF_READWRITE|OF_SHARE_DENY_NONE); if(vHandle==HFILE_ERROR) { returntrue; } CloseHandle(vHandle); returnfalse; }
GetRunProcessInfos:
获取指定文件或目录中存在的(关联的)运行进程信息,以便后面可以解除占用
//////获取指定文件或目录中存在的(关联的)运行进程信息,以便后面可以解除占用 /// ////// privateDictionary GetRunProcessInfos(stringfilePath) { Dictionary runProcInfos=newDictionary (); stringfileName=Path.GetFileName(filePath); varfileRunProcs=Process.GetProcessesByName(fileName); if(fileRunProcs!=null&&fileRunProcs.Count()>0) { runProcInfos=fileRunProcs.ToDictionary(p=>p.Id,p=>p.ProcessName); returnrunProcInfos; } stringfileDirName=Path.GetDirectoryName(filePath);//查询指定路径下的运行的进程 ProcessstartProcess=newProcess(); 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) { //varregex=newSystem.Text.RegularExpressions.Regex(@"(^[\w\.\?\u4E00-\u9FA5]+)\s+pid:\s*(\d+)",System.Text.RegularExpressions.RegexOptions.IgnoreCase); varregex=newSystem.Text.RegularExpressions.Regex(@"(^.+(?=pid:))\bpid:\s+(\d+)\s+",System.Text.RegularExpressions.RegexOptions.IgnoreCase); if(regex.IsMatch(e.Data)) { varmathedResult=regex.Match(e.Data); intprocId=int.Parse(mathedResult.Groups[2].Value); stringprocFileName=mathedResult.Groups[1].Value.Trim(); if("explorer.exe".Equals(procFileName,StringComparison.OrdinalIgnoreCase)) { return; } //varregex2=newSystem.Text.RegularExpressions.Regex(string.Format(@"\b{0}.*$",fileDirName.Replace(@"\",@"\\").Replace("?",@"\?")),System.Text.RegularExpressions.RegexOptions.IgnoreCase); varregex2=newSystem.Text.RegularExpressions.Regex(@"\b\w{1}:.+$",System.Text.RegularExpressions.RegexOptions.IgnoreCase); stringprocFilePath=(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("?"))//?乱码比对逻辑 { varregex3=newSystem.Text.RegularExpressions.Regex(procFilePath.Replace(@"\",@"\\").Replace(".",@"\.").Replace("?",".{1}"),System.Text.RegularExpressions.RegexOptions.IgnoreCase); if(regex3.IsMatch(filePath)) { runProcInfos[procId]=procFileName; } else { stringtempProcFilePath=PathJoin(procFilePath,procFileName); regex3=newSystem.Text.RegularExpressions.Regex(tempProcFilePath.Replace(@"\",@"\\").Replace(".",@"\.").Replace("?",".{1}"),System.Text.RegularExpressions.RegexOptions.IgnoreCase); if(regex3.IsMatch(filePath)) { runProcInfos[procId]=procFileName; } } } elseif(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(); returnrunProcInfos; }
上述代码逻辑简要说明:创建一个建程来启动handle.exe(以资源形式内嵌到项目中),然后异步接收返回数据,并通过正则表达式来匹配获取进程数据,由于handle.exe对于中文路径或文件名兼容不好,返回的数据存在?或其它乱码字符,故我作了一些特殊的模糊匹配逻辑;
RelaseAndGetHandleExePath:
从项目中释放handle.exe并保存到系统的APPData目录下,以便后续直接可以使用(注意:由于handle.exe需要授权同意后才能正常的使用该工具,故我在第一次生成handle.exe时,会直接运行进程,让用户选择Agree后再去进行后面的逻辑处理,这样虽能解决问题,但有点不太友好,目前一个是中文乱码、一个是必需同意才能使用handle.exe我认为如果微软解决了可能会更好)
privatestringRelaseAndGetHandleExePath() { varhandleInfo=newFileInfo(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); varhandleProc=Process.Start(handleInfo.FullName);//若第一次,则弹出提示框,需要点击agree同意才行 handleProc.WaitForExit(); } returnhandleInfo.FullName; }
PathJoin:
拼接路径(不过滤特殊字符),由于handle.exe对于中文路径或文件名兼容不好,返回的数据存在?或其它乱码字符,如查采用:Path.Combine方法则会报错,故这里自定义一个方法,只是简单的拼接
//////拼接路径(不过滤殊字符) /// ////// privatestringPathJoin(paramsstring[]paths) { if(paths==null||paths.Length<=0) { returnstring.Empty; } stringnewPath=paths[0]; for(inti=1;i CloseProcessWithFile:
核心方法,关闭指定文件被占用的进程,上述所有的方法均是为了实现该方法的功能
privatevoidCloseProcessWithFile(stringfilePath) { if(!IsFileUsing(filePath))return; ShowDownInfo(string.Format("正在尝试解除占用文件{0}",_FilePaths[_FileIndex])); varrunProcInfos=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用,正式发布时可以去掉 varlocalProcesses=Process.GetProcesses(); boolhasKilled=false; foreach(variteminrunProcInfos) { if(item.Key!=currentProcessId)//排除当前进程 { varrunProcess=localProcesses.SingleOrDefault(p=>p.Id==item.Key); //varrunProcess=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再去做更新文件之类的操作;
以上就是c#使用handle.exe解决程序更新文件被占用的问题的详细内容,更多关于c#使用handle.exe解决程序更新问题的资料请关注毛票票其它相关文章!
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。