detect selected program opened using openas_rundll in c#

牧云@^-^@ 提交于 2019-12-07 13:06:30

问题


I am opening a file using openfile dialog with the help of openas_rundll in c#.

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

Now I want to detect which program is used to open the file. I want to trace the process.

My goal is to delete the file when user close the program.


回答1:


You can try to catch the moment when actual app is closed by finding it py parent process id. If you found it, you can wait it to close as long as it is acceptable. Thanks jeremy-murray for GetAllProcessParentPids 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;
}

Update

It seems that there is no solution with 100% guarantee of success due to many reasons. I think that finding a process started by rundll32.exe is most solid among all other. If it fails, you still able to complete it with some other methods to determine process id.

As far as i know, there are several other ways to find that file is still used. Winword.exe, for example, creates some temp files in same directory and removes them when it closes. So if you able to catch a moment of temp files deleting then you may assume that program been closed.

Other programs may hold your file open by setting a lock on it. If so, you can find that program by finding lock owner. I used a solution with external program handle.exe from this answer https://stackoverflow.com/a/1263609/880156, so take a look.

I have to mention, that there may be no permanent file lock at all. It depend on program architecture. For example, if you open html file with Firefox, it reads file as fast as it can and closes it and does not leave file locked no more. In this case, even if you somehow find process name (e.g. "firefox.exe"), you will not able to find a moment when user closes a tab with your file.

If i were you, i would implement this solution, that still not ideal, and i would updgrade it later if it is necessary.




回答2:


Just a simple helper class which provides you with a method to open a file with the OpenWithDialog of windows and monitors the started processes with WMI to identify the choosen application.

for WMI, add System.Management.dll as reference

NOTICE: It doesn't recognice windows photo viewer - which is a dllhost.exe

Example call for your situation:

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

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


来源:https://stackoverflow.com/questions/11718855/detect-selected-program-opened-using-openas-rundll-in-c-sharp

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!