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

时间:2012-07-30 09:39:40

标签: c# winapi shellexecute

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

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

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

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

2 个答案:

答案 0 :(得分:0)

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

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<int, int> 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<int, int> GetAllProcessParentPids()
{
    var childPidToParentPid = new Dictionary<int, int>();

    var processCounters = new SortedDictionary<string, PerformanceCounter[]>();
    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”),您也无法找到用户使用您的文件关闭选项卡的时刻。

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

答案 1 :(得分:0)

只是一个简单的帮助器类,它为您提供了一个使用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);
        }
    }
}

班级:

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
    }
}