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

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

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

  • 相关阅读:
    移动端屏幕适配解决方案
    ES6学习笔记(1)——模块化
    弹性盒布局学习总结
    阮一峰之webpack-demos(译)
    阮一峰的Git分支管理策略之学习总结
    移动端测试之服务器搭建
    webApp 移动Touch框架
    Javascript 严格模式详解
    caller和callee的区别
    avalon 中require.config源码分析
  • 原文地址:https://www.cnblogs.com/zuowj/p/5840567.html
Copyright © 2011-2022 走看看