我公司最近升级程序经常报出更新失败问题,究其原因,原来是更新时,他们可能又打开了正在被更新的文件,导致更新文件时,文件被其它进程占用,无法正常更新而报错,为了解决这个问题,我花了一周时间查询多方资料及研究,终于找到了一个查询进程的利器:handle.exe,下载地址:https://technet.microsoft.com/en-us/sysinternals/bb896655.aspx,我是通过它来找到被占用的进程,然后KILL掉占用进程,最后再来更新,这样就完美的解决了更新时文件被占用报错的问题了,实现方法很简单,我下面都有列出主要的方法,一些注意事项我也都有说明,大家一看就明白了,当然如果大家有更好的方案,欢迎交流,谢谢!

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 IsFileUsing(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:获取指定文件或目录中存在的(关联的)运行进程信息,以便后面可以解除占用

        /// <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我认为如果微软解决了可能会更好)

        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方法则会报错,故这里自定义一个方法,只是简单的拼接

        /// <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:核心方法,关闭指定文件被占用的进程,上述所有的方法均是为了实现该方法的功能

        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再去做更新文件之类的操作;

c#+handle.exe实现升级程序在运行时自动解除文件被占用的问题的更多相关文章

  1. 更为复杂C程序的运行时结构

    运行环境 win 10 企业版 1809 17763.194,MinGW V3.14 32位,Bundled V3.13.2,Bundled GDB V8.2. 在C语言中,栈的方向是从高地址向低地址 ...

  2. 图解简单C程序的运行时结构

    程序在内存中的存储分为三个区域,分别是动态数据区.静态数据区和代码区.函数存储在代码区,全局变量以及静态变量存储在静态数据区,而在程序执行的时候才会在动态数据区产生数据.程序执行的本质就是代码区的指令 ...

  3. [快手(AAuto)学习笔记]如何让程序在运行时请求管理员权限(UAC)

    作者:ffsystem 作为(糟糕的)程序猿,习惯写代码解决一些简单事务.正常用批处理就能解决大部分工作,复杂一点用AutoIt 3. 有时候要分发给别人,就需要一个界面.外行你程序写得如何他看不懂, ...

  4. (转载)让XCode运行时自动更新资源

    转自http://goldlion.blog.51cto.com/4127613/1351616 用过XCode的人都知道,XCode有一个臭名昭著的bug——除非你修改了源代码造成了重新编译,否则游 ...

  5. mybatis JdbcTypeInterceptor - 运行时自动添加 jdbcType 属性

    上代码: package tk.mybatis.plugin; import org.apache.ibatis.executor.ErrorContext; import org.apache.ib ...

  6. java实现创建临时文件然后在程序退出时自动删除文件(转)

    这篇文章主要介绍了java实现创建临时文件然后在程序退出时自动删除文件,从个人项目中提取出来的,小伙伴们可以直接拿走使用. 通过java的File类创建临时文件,然后在程序退出时自动删除临时文件.下面 ...

  7. 将ZIP文件添加到程序集资源文件然后在运行时解压文件

    今天做安装打包程序研究,之前同事将很多零散的文件发布成一个安装文件夹给用户,这样体验不好,我希望将所有文件打包成一个.net程序,运行此程序的时候自解压然后执行后续的安装步骤. 解决过程: 1,将所有 ...

  8. 如何让Qt程序在运行时获取UAC权限

    在pro文件中加入以下语句: QMAKE_LFLAGS += /MANIFESTUAC:\"level=\'requireAdministrator\' uiAccess=\'false\' ...

  9. Laravel 使用 Provider 为程序提供运行时配置服务

    需求: 配置参数存在数据库中,Model 是 aah,需要在每次运行时,程序可以在任何地方采用 config("aah.name") 的方式访问配置信息. 思路: 采用 Provi ...

随机推荐

  1. Android业务组件化之URL Scheme使用

    前言: 最近公司业务发展迅速,单一的项目工程不再适合公司发展需要,所以开始推进公司APP业务组件化,很荣幸自己能够牵头做这件事,经过研究实现组件化的通信方案通过URL Scheme,所以想着现在还是在 ...

  2. 【原创分享·微信支付】 C# MVC 微信支付教程系列之扫码支付

    微信支付教程系列之扫码支付                  今天,我们来一起探讨一下这个微信扫码支付.何为扫码支付呢?这里面,扫的码就是二维码了,就是我们经常扫一扫的那种二维码图片,例如,我们自己添 ...

  3. 【NLP】前戏:一起走进条件随机场(一)

    前戏:一起走进条件随机场 作者:白宁超 2016年8月2日13:59:46 [摘要]:条件随机场用于序列标注,数据分割等自然语言处理中,表现出很好的效果.在中文分词.中文人名识别和歧义消解等任务中都有 ...

  4. 文件随机读写专用类——RandomAccessFile

     RandomAccessFile类可以随机读取文件,但是在测试中并不好用;File类可以测试文件存不存在,不存在可以创建文件;FileWriter类可以对文件进行重写或者追加内容;FileReade ...

  5. Flyweight(享元模式)

    import java.util.Hashtable; /** * 享元模式 * @author TMAC-J * 享元模式一般和工厂模式一起使用,但此处为了更好说明,只用享元模式 * 定义:享元模式 ...

  6. Android开发学习—— Broadcast广播接收者

    现实中:电台要发布消息,通过广播把消息广播出去,使用收音机,就可以收听广播,得知这条消息.Android中:系统在运行过程中,会产生许多事件,那么某些事件产生时,比如:电量改变.收发短信.拨打电话.屏 ...

  7. VS2015墙内创建ionic2 【利用nrm更换源,完美!】

    STEP 1 设置cnpm npm install -g cnpm --registry=https://registry.npm.taobao.org   一句话建立cnpm STEP 2 安装nr ...

  8. The Coroutine

    关于Coroutine 说到coroutine就不的不说subroutine,也就是我们常用到的一般函数.调用一个函数开始执行,然后函数执行完成后就退出,再次调用的时候,再从头开始,调用之间是没有保存 ...

  9. C++虚函数和函数指针一起使用

    C++虚函数和函数指针一起使用,写起来有点麻烦. 下面贴出一份示例代码,可作参考.(需要支持C++11编译) #include <stdio.h> #include <list> ...

  10. 3D游戏中的画质与效率适配

      哪里来的需求? 众所周知,由于不同的设备配置不同.导致其CPU和GPU处理能力有高有低.同样的游戏想要在所有设备上运行流畅且画面精美,是不可能的.这就需要我们针对不同的设备能力进行画质调节,以保证 ...