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<intstring> GetRunProcessInfos(string filePath)
{
    Dictionary<intstring> runProcInfos = new Dictionary<intstring>();
    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.mzph.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;面试官问的问题也更深入。 在我初…