热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

c#使用handle.exe解决程序更新文件被占用的问题

这篇文章主要介绍了c#使用handle.exe解决程序更新文件被占用的问题,帮助大家更好的理解和学习使用c#,感

c# 使用handle.exe解决程序更新文件被占用的问题

我公司最近升级程序经常报出更新失败问题,究其原因,原来是更新时,他们可能又打开了正在被更新的文件,导致更新文件时,文件被其它进程占用,无法正常更新而报错,为了解决这个问题,我花了一周时间查询多方资料及研究,终于找到了一个查询进程的利器: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:

获取指定文件或目录中存在的(关联的)运行进程信息,以便后面可以解除占用

/// 
/// 获取指定文件或目录中存在的(关联的)运行进程信息,以便后面可以解除占用
/// 
/// 
/// 
private Dictionary GetRunProcessInfos(string filePath)
{
 
    Dictionary runProcInfos = new Dictionary();
    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:))pid: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(@"{0}.*$", fileDirName.Replace(@"", @"\").Replace("?",@"?")), System.Text.RegularExpressions.RegexOptions.IgnoreCase);
                var regex2 = new System.Text.RegularExpressions.Regex(@"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})占用,
您是否需要强制终止该进程?", 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方法则会报错,故这里自定义一个方法,只是简单的拼接

/// 
/// 拼接路径(不过滤殊字符)
/// 
/// 
/// 
private string PathJoin(params string[] paths)
{
    if (paths == null || paths.Length <= 0)
    {
        return string.Empty;
    }
 
    string newPath = paths[0];
 
    for (int i = 1; i 

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("
", 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解决程序更新文件被占用的问题的详细内容,更多关于c# 使用handle.exe解决程序更新问题的资料请关注编程笔记其它相关文章!


推荐阅读
  • 本文讨论了Kotlin中扩展函数的一些惯用用法以及其合理性。作者认为在某些情况下,定义扩展函数没有意义,但官方的编码约定支持这种方式。文章还介绍了在类之外定义扩展函数的具体用法,并讨论了避免使用扩展函数的边缘情况。作者提出了对于扩展函数的合理性的质疑,并给出了自己的反驳。最后,文章强调了在编写Kotlin代码时可以自由地使用扩展函数的重要性。 ... [详细]
  • 向QTextEdit拖放文件的方法及实现步骤
    本文介绍了在使用QTextEdit时如何实现拖放文件的功能,包括相关的方法和实现步骤。通过重写dragEnterEvent和dropEvent函数,并结合QMimeData和QUrl等类,可以轻松实现向QTextEdit拖放文件的功能。详细的代码实现和说明可以参考本文提供的示例代码。 ... [详细]
  • 生成式对抗网络模型综述摘要生成式对抗网络模型(GAN)是基于深度学习的一种强大的生成模型,可以应用于计算机视觉、自然语言处理、半监督学习等重要领域。生成式对抗网络 ... [详细]
  • Spring源码解密之默认标签的解析方式分析
    本文分析了Spring源码解密中默认标签的解析方式。通过对命名空间的判断,区分默认命名空间和自定义命名空间,并采用不同的解析方式。其中,bean标签的解析最为复杂和重要。 ... [详细]
  • 本文分享了一个关于在C#中使用异步代码的问题,作者在控制台中运行时代码正常工作,但在Windows窗体中却无法正常工作。作者尝试搜索局域网上的主机,但在窗体中计数器没有减少。文章提供了相关的代码和解决思路。 ... [详细]
  • 阿,里,云,物,联网,net,core,客户端,czgl,aliiotclient, ... [详细]
  • 本文介绍了如何将CIM_DateTime解析为.Net DateTime,并分享了解析过程中可能遇到的问题和解决方法。通过使用DateTime.ParseExact方法和适当的格式字符串,可以成功解析CIM_DateTime字符串。同时还提供了关于WMI和字符串格式的相关信息。 ... [详细]
  • 本文介绍了一个在线急等问题解决方法,即如何统计数据库中某个字段下的所有数据,并将结果显示在文本框里。作者提到了自己是一个菜鸟,希望能够得到帮助。作者使用的是ACCESS数据库,并且给出了一个例子,希望得到的结果是560。作者还提到自己已经尝试了使用"select sum(字段2) from 表名"的语句,得到的结果是650,但不知道如何得到560。希望能够得到解决方案。 ... [详细]
  • 本文详细介绍了Spring的JdbcTemplate的使用方法,包括执行存储过程、存储函数的call()方法,执行任何SQL语句的execute()方法,单个更新和批量更新的update()和batchUpdate()方法,以及单查和列表查询的query()和queryForXXX()方法。提供了经过测试的API供使用。 ... [详细]
  • 摘要: 在测试数据中,生成中文姓名是一个常见的需求。本文介绍了使用C#编写的随机生成中文姓名的方法,并分享了相关代码。作者欢迎读者提出意见和建议。 ... [详细]
  • 导出功能protectedvoidbtnExport(objectsender,EventArgse){用来打开下载窗口stringfileName中 ... [详细]
  • 先看官方文档TheJavaTutorialshavebeenwrittenforJDK8.Examplesandpracticesdescribedinthispagedontta ... [详细]
  • Android JSON基础,音视频开发进阶指南目录
    Array里面的对象数据是有序的,json字符串最外层是方括号的,方括号:[]解析jsonArray代码try{json字符串最外层是 ... [详细]
  • [大整数乘法] java代码实现
    本文介绍了使用java代码实现大整数乘法的过程,同时也涉及到大整数加法和大整数减法的计算方法。通过分治算法来提高计算效率,并对算法的时间复杂度进行了研究。详细代码实现请参考文章链接。 ... [详细]
  • Java学习笔记之面向对象编程(OOP)
    本文介绍了Java学习笔记中的面向对象编程(OOP)内容,包括OOP的三大特性(封装、继承、多态)和五大原则(单一职责原则、开放封闭原则、里式替换原则、依赖倒置原则)。通过学习OOP,可以提高代码复用性、拓展性和安全性。 ... [详细]
author-avatar
曾家宏惠茹冠宇
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有