检测在c#中使用openas_rundll打开的所选程序

我在c#的openas_rundll的帮助下使用openfile对话框打开一个文件。

Process.Start("rundll32.exe", string.Format("shell32.dll,OpenAs_RunDLL \"{0}\"", tempFilePath)); 

现在我想检测用于打开文件的程序。 我想跟踪这个过程。

我的目标是在用户关闭程序时删除该文件。

您可以尝试通过查找py父进程ID来捕获实际应用程序关闭的时刻。 如果找到它,只要可以接受就可以等待它关闭。 感谢jeremy-murray为GetAllProcessParentPids方法 :

 public void StartProcessAndWathTillTerminated(string tempFilePath) { // Show app selection dialog to user Process rundll32 = Process.Start("rundll32.exe", string.Format("shell32.dll,OpenAs_RunDLL {0}", tempFilePath)); int rundll32id = rundll32.Id; // Wait till dialog is closed while (!rundll32.HasExited) { System.Threading.Thread.Sleep(50); } // Get all running processes with parent id Dictionary allprocparents = GetAllProcessParentPids(); int openedAppId = 0; // Loop throu all processes foreach (var allprocparent in allprocparents) { // Found child process, started by our rundll32.exe instance if (allprocparent.Value == rundll32id) { openedAppId = allprocparent.Key; break; } } // Check if we actually found any process. It can not be found in two situations: // 1) Process was closed too soon, while we was looking for it // 2) User clicked Cancel and no application was opened // Also it is possible that chesen application is already running. In this // case new instance will be opened by rundll32.exe for a very short period of //time needed to pass file path to running instance. Anyway, this case falls into case 1). //If we ca not find process explicitly, we can try to find it by file lock, if one exists: //I'm using here a code snippet from https://stackoverflow.com/a/1263609/880156, //which assumes that there are possible more than one lock on this file. //I just take first. if (openedAppId==0) { Process handleExe = new Process(); handleExe.StartInfo.FileName = "handle.exe"; handleExe.StartInfo.Arguments = tempFilePath; handleExe.StartInfo.UseShellExecute = false; handleExe.StartInfo.RedirectStandardOutput = true; handleExe.Start(); handleExe.WaitForExit(); string outputhandleExe = handleExe.StandardOutput.ReadToEnd(); string matchPattern = @"(?<=\s+pid:\s+)\b(\d+)\b(?=\s+)"; foreach(Match match in Regex.Matches(outputhandleExe, matchPattern)) { openedAppId = int.Parse(match.Value); break; } } if (openedAppId != 0) { Process openedApp = Process.GetProcessById(openedAppId); while (!openedApp.HasExited) { System.Threading.Thread.Sleep(50); } } // When we reach this position, App is already closed or was never started. } public static Dictionary GetAllProcessParentPids() { var childPidToParentPid = new Dictionary(); var processCounters = new SortedDictionary(); var category = new PerformanceCounterCategory("Process"); // As the base system always has more than one process running, // don't special case a single instance return. var instanceNames = category.GetInstanceNames(); foreach(string t in instanceNames) { try { processCounters[t] = category.GetCounters(t); } catch (InvalidOperationException) { // Transient processes may no longer exist between // GetInstanceNames and when the counters are queried. } } foreach (var kvp in processCounters) { int childPid = -1; int parentPid = -1; foreach (var counter in kvp.Value) { if ("ID Process".CompareTo(counter.CounterName) == 0) { childPid = (int)(counter.NextValue()); } else if ("Creating Process ID".CompareTo(counter.CounterName) == 0) { parentPid = (int)(counter.NextValue()); } } if (childPid != -1 && parentPid != -1) { childPidToParentPid[childPid] = parentPid; } } return childPidToParentPid; } 

更新

由于许多原因,似乎没有100%成功保证的解决方案。 我认为找到一个由rundll32.exe启动的进程是其中所有其他进程中最可靠的。 如果失败,您仍然可以使用其他一些方法来完成它以确定进程ID。

据我所知,还有其他几种方法可以找到仍在使用的文件。 例如,Winword.exe在同一目录中创建一些临时文件,并在关闭时将其删除。 因此,如果您能够捕获临时文件删除的时刻,那么您可能会认为程序已关闭。

其他程序可以通过设置锁定来保持文件打开。 如果是这样,您可以通过查找锁所有者来找到该程序。 我从这个答案https://stackoverflow.com/a/1263609/880156使用了外部程序handle.exe的解决方案,所以看一看。

我必须提一下,根本没有永久文件锁。 它取决于程序架构。 例如,如果您使用Firefox打开html文件,它会尽可能快地读取文件并关闭它,并且不会再将文件锁定。 在这种情况下,即使您以某种方式找到进程名称(例如“firefox.exe”),您也无法找到用户使用您的文件关闭选项卡的时刻。

如果我是你,我会实施这个仍然不理想的解决方案,如果有必要,我会在以后升级它。

只是一个简单的帮助器类,它为您提供了一个使用Windows的OpenWithDialog打开文件的方法,并使用WMI监视已启动的进程以识别选择的应用程序。

对于WMI,添加System.Management.dll作为参考

注意:它不识别Windows照片查看器 – 这是一个dllhost.exe

针对您的情况的示例调用:

 using (OpenFileDialog ofd = new OpenFileDialog()) { ofd.Filter = "All files(*.*)|*.*"; if (ofd.ShowDialog() == System.Windows.Forms.DialogResult.OK) { using (Win.OpenWithDialogHelper helper = new Win.OpenWithDialogHelper()) { helper.OpenFileAndWaitForExit(ofd.FileName); File.Delete(helper.Filepath); } } } 

class级:

 namespace Win { using System.Management; using System.Threading; using System.Diagnostics; using System.IO; public class OpenWithDialogHelper : IDisposable { #region members private Process openWithProcess; private ManagementEventWatcher monitor; public string Filepath { get; set; } public Process AppProcess { get; private set; } #endregion #region .ctor public OpenWithDialogHelper() { } public OpenWithDialogHelper(string filepath) { this.Filepath = filepath; } #endregion #region methods public void OpenFileAndWaitForExit(int milliseconds = 0) { OpenFileAndWaitForExit(this.Filepath, milliseconds); } public void OpenFileAndWaitForExit(string filepath, int milliseconds = 0) { this.Filepath = filepath; this.openWithProcess = new Process(); this.openWithProcess.StartInfo.FileName = "rundll32.exe"; this.openWithProcess.StartInfo.Arguments = String.Format("shell32.dll,OpenAs_RunDLL \"{0}\"", filepath); this.openWithProcess.Start(); //using WMI, remarks to add System.Management.dll as reference! this.monitor = new ManagementEventWatcher(new WqlEventQuery("SELECT * FROM Win32_ProcessStartTrace")); this.monitor.EventArrived += new EventArrivedEventHandler(start_EventArrived); this.monitor.Start(); this.openWithProcess.WaitForExit(); //catching the app process... //it can't catched when the process was closed too soon //or the user clicked Cancel and no application was opened Thread.Sleep(1000); int i = 0; //wait max 5 secs... while (this.AppProcess == null && i < 3000) { Thread.Sleep(100); i++; } if (this.AppProcess != null) { if (milliseconds > 0) this.AppProcess.WaitForExit(milliseconds); else this.AppProcess.WaitForExit(); } } public void Dispose() { if (this.monitor != null) { this.monitor.EventArrived -= new EventArrivedEventHandler(start_EventArrived); this.monitor.Dispose(); } if(this.openWithProcess != null) this.openWithProcess.Dispose(); if (this.AppProcess != null) this.AppProcess.Dispose(); } #endregion #region events private void start_EventArrived(object sender, EventArrivedEventArgs e) { int parentProcessID = Convert.ToInt32(e.NewEvent.Properties["ParentProcessID"].Value); //The ParentProcessID of the started process must be the OpenAs_RunDLL process //NOTICE: It doesn't recognice windows photo viewer // - which is a dllhost.exe that doesn't have the ParentProcessID if (parentProcessID == this.openWithProcess.Id) { this.AppProcess = Process.GetProcessById(Convert.ToInt32( e.NewEvent.Properties["ProcessID"].Value)); if (!this.AppProcess.HasExited) { this.AppProcess.EnableRaisingEvents = true; } } } #endregion } }