locked
Get a executable path from a window handle? RRS feed

  • Question

  • The question is simple enough: How do I get a window's executable path given only the window handle (in C# preferably) on Windows XP or higher? 

    What I've tried:
    - GetModuleFileNameEx() WinAPI function: Works fine on 99% of applications.  However, it requires a call to OpenProcess using the PROCESS_VM_READ flag which causes the function to fail on some apps.  Apparently those apps intentionally prevent this somehow for security reasons.  I don't care about reading the process's memory, I just want the exe path!

    - Process.GetProcesses() method gives the same "Access Denied" error...presumably it tries code similar to the above internally.

    - CreateToolhelp32Snapshot() WinAPI function iterates through all the processes like it should and even shows processes that choke on OpenProcess using the methods above...but it only gives me the exe filename...not the entire path.

    I believe I need to do something with tokens/privileges but I don't know what.  According to the Task Manager the process I'm using to test this is not under the normal logged on user's account...I presume it's under some system account since the UserName is empty in the Task Manager.  Maybe I'm going about this the wrong way?

    Any tips would be appreciated!  I've been working on this for most of the day and can't seem to figure it out.

    Thanks :)
    • Moved by nobugz Monday, October 26, 2009 10:25 PM not a clr q (From:Common Language Runtime)
    Monday, October 26, 2009 9:33 PM

Answers

  • Hi psychogeek:

    I do not know how you did apply the code. However, I managed to fixe you a good working
    code-snippet which allows you to the the path of a running executable direct from it's
    Window handle ID.

    Code Snippet: NativeMethods class
    1. using System;
      using System.Collections.Generic;
      using System.Linq;
      using System.Text;
      using System.Runtime.InteropServices;
      using System.Diagnostics;
    2. namespace NativeMethods
      {
         
    3.    
          /// <summary>
          /// Process Specific Access Mode.
          /// http://msdn.microsoft.com/en-us/library/ms684880(VS.85).aspx
          /// </summary>
          [Flags]
          public enum ProcessSpecificAccess:uint
          {
              PROCESS_VM_READ = 0x0010,
              PROCESS_VM_WRITE = 0x0020
          }

    4.     class NativeMethods
          {
          
               /*
                   Includes some P/Invoked Methods();
               */
    5.       
    6.         /// <summary>
              /// Retrieves the fully-qualified path for the file containing the specified module.
              /// http://msdn.microsoft.com/en-us/library/ms683198(VS.85).aspx
              /// </summary>
              /// <param name="hProcess"></param>
              /// <param name="hModule"></param>
              /// <param name="lpBaseName"></param>
              /// <param name="nSize"></param>
              /// <returns></returns>
              [DllImport("psapi.dll")] //Supported under Windows Vista and Windows Server 2008.
              static extern uint GetModuleFileNameEx(IntPtr hProcess, IntPtr hModule, [Out] StringBuilder lpBaseName,
              [In] [MarshalAs(UnmanagedType.U4)] int nSize);
    7.        
            
    8.         /// <summary>
              /// Retrieves the identifier of the thread that created the specified window and, optionally, the identifier of the process that created the window.
              /// http://msdn.microsoft.com/en-us/library/ms633522(VS.85).aspx
              /// </summary>
              /// <param name="handle"></param>
              /// <param name="processId"></param>
              /// <returns></returns>
              [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
              private static extern int GetWindowThreadProcessId(IntPtr handle, out uint processId);

    9.       
    10.         /// <summary>
              /// Retrieves the Path of a running process.
              /// </summary>
              /// <param name="hwnd"></param>
              /// <returns></returns>
              public static string GetProcessPath(IntPtr hwnd)
              {
                  try
                  {
                      uint pid = 0;
                      GetWindowThreadProcessId(hwnd, out pid);
                      Process proc = Process.GetProcessById((int)pid); //Gets the process by ID.
                      return proc.MainModule.FileName.ToString();    //Returns the path.
                  }
                  catch (Exception ex)
                  {
                      return ex.Message.ToString();
                  }
              }
    11.     }
      }


    Below, you can see how to apply the GetProcessPath() method.

    Code Snippet: How to apply?
    1. int i = Convert.ToInt32(textBox1.Text.ToString());
      label1.Text = NativeMethods.NativeMethods.GetProcessPath((IntPtr)i);   



    http://plpqzq.blu.livefilestore.com/y1pCICpORUx0iFNO1VraqB2eGqkxP7v9071mNyhts4R--teQylcGrpSaREG1j0y2_JRDawer-C53RBElDxs0Z2mFtdDSGZWgdKj/WindowCalc.png 


    Figure A: Using the Microsoft Spy++ program, we can get the Windows Handle ID of any running program (example: Calc.exe in this image).

    We then take the HEX = 0009065E and convert to dec = 591454, and as we paste it inside the textBox control we then get the path. (See Figure B, below).


    http://plpqzq.blu.livefilestore.com/y1pMCE-BffrzBjYppJ1E6caENOYBUfhh0ALYjVuLTV0e8YsJvxAZzDj8bgAERgoubIACWkm8GkThSGiJB2BzmKexSRAfJdwoW9m/Result.PNG
    Figure B: When we enter the decimal converted value of
    the Window ID, we get the path of the running process,
    and the open window.

    Regarding the "Access Denied" error: The GetProcessByID() will first check
    the PID of the process, to ensure that the process is running, if the process 
    is terminated then it will throw a "Access Denied" message.   

    http://plpqzq.blu.livefilestore.com/y1pOk6TXPuXdircEHHgQ--xiZTzyJRyBX_iYJbS2QGVP1DR_5k6wwiGsRf4Mps20VPWs47diGkDvVVMvE-oE2vGQn0hex2CyFCJ/shield.png Security Note: If you are logged in as a regular user and try to get
    information on a running process that is [not] yours, Windows will
    give you an "Access Denied" error message.

    Have a nice day...

    Best regards,
    Fisnik 


    Coder24.com
    Wednesday, October 28, 2009 10:32 AM

All replies

  • Hi psychogeek:

    I do not know how you did apply the code. However, I managed to fixe you a good working
    code-snippet which allows you to the the path of a running executable direct from it's
    Window handle ID.

    Code Snippet: NativeMethods class
    1. using System;
      using System.Collections.Generic;
      using System.Linq;
      using System.Text;
      using System.Runtime.InteropServices;
      using System.Diagnostics;
    2. namespace NativeMethods
      {
         
    3.    
          /// <summary>
          /// Process Specific Access Mode.
          /// http://msdn.microsoft.com/en-us/library/ms684880(VS.85).aspx
          /// </summary>
          [Flags]
          public enum ProcessSpecificAccess:uint
          {
              PROCESS_VM_READ = 0x0010,
              PROCESS_VM_WRITE = 0x0020
          }

    4.     class NativeMethods
          {
          
               /*
                   Includes some P/Invoked Methods();
               */
    5.       
    6.         /// <summary>
              /// Retrieves the fully-qualified path for the file containing the specified module.
              /// http://msdn.microsoft.com/en-us/library/ms683198(VS.85).aspx
              /// </summary>
              /// <param name="hProcess"></param>
              /// <param name="hModule"></param>
              /// <param name="lpBaseName"></param>
              /// <param name="nSize"></param>
              /// <returns></returns>
              [DllImport("psapi.dll")] //Supported under Windows Vista and Windows Server 2008.
              static extern uint GetModuleFileNameEx(IntPtr hProcess, IntPtr hModule, [Out] StringBuilder lpBaseName,
              [In] [MarshalAs(UnmanagedType.U4)] int nSize);
    7.        
            
    8.         /// <summary>
              /// Retrieves the identifier of the thread that created the specified window and, optionally, the identifier of the process that created the window.
              /// http://msdn.microsoft.com/en-us/library/ms633522(VS.85).aspx
              /// </summary>
              /// <param name="handle"></param>
              /// <param name="processId"></param>
              /// <returns></returns>
              [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
              private static extern int GetWindowThreadProcessId(IntPtr handle, out uint processId);

    9.       
    10.         /// <summary>
              /// Retrieves the Path of a running process.
              /// </summary>
              /// <param name="hwnd"></param>
              /// <returns></returns>
              public static string GetProcessPath(IntPtr hwnd)
              {
                  try
                  {
                      uint pid = 0;
                      GetWindowThreadProcessId(hwnd, out pid);
                      Process proc = Process.GetProcessById((int)pid); //Gets the process by ID.
                      return proc.MainModule.FileName.ToString();    //Returns the path.
                  }
                  catch (Exception ex)
                  {
                      return ex.Message.ToString();
                  }
              }
    11.     }
      }


    Below, you can see how to apply the GetProcessPath() method.

    Code Snippet: How to apply?
    1. int i = Convert.ToInt32(textBox1.Text.ToString());
      label1.Text = NativeMethods.NativeMethods.GetProcessPath((IntPtr)i);   



    http://plpqzq.blu.livefilestore.com/y1pCICpORUx0iFNO1VraqB2eGqkxP7v9071mNyhts4R--teQylcGrpSaREG1j0y2_JRDawer-C53RBElDxs0Z2mFtdDSGZWgdKj/WindowCalc.png 


    Figure A: Using the Microsoft Spy++ program, we can get the Windows Handle ID of any running program (example: Calc.exe in this image).

    We then take the HEX = 0009065E and convert to dec = 591454, and as we paste it inside the textBox control we then get the path. (See Figure B, below).


    http://plpqzq.blu.livefilestore.com/y1pMCE-BffrzBjYppJ1E6caENOYBUfhh0ALYjVuLTV0e8YsJvxAZzDj8bgAERgoubIACWkm8GkThSGiJB2BzmKexSRAfJdwoW9m/Result.PNG
    Figure B: When we enter the decimal converted value of
    the Window ID, we get the path of the running process,
    and the open window.

    Regarding the "Access Denied" error: The GetProcessByID() will first check
    the PID of the process, to ensure that the process is running, if the process 
    is terminated then it will throw a "Access Denied" message.   

    http://plpqzq.blu.livefilestore.com/y1pOk6TXPuXdircEHHgQ--xiZTzyJRyBX_iYJbS2QGVP1DR_5k6wwiGsRf4Mps20VPWs47diGkDvVVMvE-oE2vGQn0hex2CyFCJ/shield.png Security Note: If you are logged in as a regular user and try to get
    information on a running process that is [not] yours, Windows will
    give you an "Access Denied" error message.

    Have a nice day...

    Best regards,
    Fisnik 


    Coder24.com
    Wednesday, October 28, 2009 10:32 AM
  • Thanks for the detailed answer!

    Unfortunately I tried the exact same method (and several others) before posting and I inevitably get an Access Denied error (through GetLastError() or an exception depending on how I do it).  GetWindowThreadProcessId() works fine of course...and so does OpenProcess() if I call it without the PROCESS_VM_READ flag that's required to make subsequent calls GetModuleFileNameEx() successful. 

    Although the specific application that causes my issue is initiated by the user, the Task Manager shows that it isn't using the logged on user's account ("User Name" field is empty).  I'm certain this is the source of my problems.  Running my app as an Administrator fixes everything of course but I don't want to subject my users to dealing with the UAC prompt every time they start my application.

    I guess I was hoping that someone would reply with some magic API function that I didn't know of that would allow me to get full path info from any running application without having to have Administrator priviledges.  It seems silly that just obtaining the path to a running exe would be a security concern.  Anyway, I went ahead and marked this as the answer since it appears that there's no way to accomplish what I want without Admin priviledges.

    Thanks!
    Wednesday, October 28, 2009 3:44 PM
  • Hi psychogeek:

    Every action you want to perform in Windows Vista might require UAC elevation.
    I will give you some more details.

    Going straight, you said: Although the specific application that causes my issue is initiated by the user, the Task Manager shows that it isn't using the logged on user's account ("User Name" field is empty).

    That user can be the "SYSTEM" user (See Figure A, below).

    http://plpqzq.blu.livefilestore.com/y1p12g6_Xwd1bZWF7lp5ZBU1lXbJ_5Fw9OR6SUWcV_SlX91CqManoWaQEBLc1QsjXE8uRQ23VJ8VKBF7peERCKDnw/taskMgr.png
    Figure A: Winlogon.exe is running in the background, and as we are
    viewing the process list using the Task Manager (taskmgr.exe),
    we can see that it shows the user name as "Blank" (nothing).

    We are running the Task Manager (taskmgr.exe), in none-UAC-elevate
    mode, this means that we executed the Task Manager as a regular user (A standard user). 
    Now, Task Manager is running under restricted rules, since the user is a standard user (which also is restricted)  
    if we now right-click we get the "Perform Administrative Tasks" option (See Figure B, below).


    http://plpqzq.blu.livefilestore.com/y1pxTEzRt1TZwWIW8IDnqFIwuWzJoDn00TD49weHDE_MwfDo_29VjptB_F2ZfpueQFmLJfnVV7LpYFkmnX4-VRGn3TJ_0zE3cNg/taskMgr2.png
    Figure B: Running Task Manager with a standard user, gives you only one 
    option when right-clicking a process.  

    To be able to kill a process which is showing a "Blank" user name, we need
    to run the Task Manager as administrator and UAC elevate it (See Figure C, below).

    http://plpqzq.blu.livefilestore.com/y1p94OwvgPGft8F6hhInztzC3VCWiAp5UQEjIT10pI_ycNCHfsLJTKL5beu9S3tbsk3zLTmclomWskmSoHr5wjHwUtPfRCDLHcp/taskMgr3.png
    Figure C: When we choose the "Peform Administrative Tasks" option, a UAC dialog appears,
    and asks for permission, to restart Task Manager and bypass a UAC elevation, and run the
    Task Manager with the highest-available account (The Built-in Administrator in Windows Vista).  

    As I told you before: Read the Security Note, below.

    http://plpqzq.blu.livefilestore.com/y1pOk6TXPuXdircEHHgQ--xiZTzyJRyBX_iYJbS2QGVP1DR_5k6wwiGsRf4Mps20VPWs47diGkDvVVMvE-oE2vGQn0hex2CyFCJ/shield.png Security Note: If you are logged in as a regular user and try to get
    information on a running process that is [not] yours, Windows will
    give you an "Access Denied" error message.

    I hope this information was helpful...

    Have a nice day...

    Best regards,
    Fisnik


    Coder24.com
    • Edited by Fisnik Hasani Thursday, October 29, 2009 9:38 AM spell fix sorry
    • Proposed as answer by Pam Pam Thursday, October 29, 2009 9:39 AM
    Wednesday, October 28, 2009 8:30 PM
  • Persistence pays off :)  The method below contains a few ways to obtain the full application path without requiring a privilege elevation for unowned processes.  I left out the dll exports but it should be clear without them.

            private string GetExecutablePath(IntPtr hwnd)
            {
                StringBuilder buff = new StringBuilder(1024);
    
                // Get the process id
                UIntPtr hprocessid = new UIntPtr();
                NativeMethods.GetWindowThreadProcessId(hwnd, out hprocessid);
    
                // Try different methods depending on the OS version
                if (Environment.OSVersion.Version.Major >= 6)   // If Vista or higher this is the simplest method.  Works regardless of who owns the process without priviledge elevation.
                {
                    IntPtr hprocess = NativeMethods.OpenProcess(NativeMethods.ProcessAccessFlags.PROCESS_QUERY_INFORMATION, false, hprocessid.ToUInt32());
                    if (hprocess != IntPtr.Zero)
                    {
                        try
                        {
                            int size = buff.Capacity;
                            if (NativeMethods.QueryFullProcessImageName(hprocess, 0, buff, ref size))
                            {
                                return buff.ToString();
                            }
                        }
                        finally
                        {
                            NativeMethods.CloseHandle(hprocess);
                        }
                    }
                }
    
                // Try the GetModuleFileName method (Windows 2000 or higher).  May return ACCESS_DENIED (due to VM_READ flag) if the process is not owned by the current user.
                IntPtr hprocess1 = NativeMethods.OpenProcess(NativeMethods.ProcessAccessFlags.PROCESS_QUERY_INFORMATION | NativeMethods.ProcessAccessFlags.PROCESS_VM_READ,
                    false, hprocessid.ToUInt32());
                if (hprocess1 != IntPtr.Zero)
                {
                    try
                    {
                        if (NativeMethods.GetModuleFileNameEx(hprocess1, IntPtr.Zero, buff, (uint)buff.Capacity) > 0)
                        {
                            return buff.ToString();
                        }
                    }
                    finally
                    {
                        NativeMethods.CloseHandle(hprocess1);
                    }
                }
    
                // Try the GetProcessImageFileName method (XP or higher).  Works regardless of who owns the process without priviledge elevation. 
                IntPtr hprocess2 = NativeMethods.OpenProcess(NativeMethods.ProcessAccessFlags.PROCESS_QUERY_INFORMATION, false, hprocessid.ToUInt32());
                if (hprocess2 != IntPtr.Zero)
                {
                    try
                    {
                        if (NativeMethods.GetProcessImageFileName(hprocess2, buff, (uint)buff.Capacity) > 0)
                        {
                            string path = buff.ToString();
                            string driveletter = Path.GetPathRoot(path);
    
                            foreach (string drive in Environment.GetLogicalDrives())
                            {
                                if (NativeMethods.QueryDosDevice(drive.TrimEnd('\\'), buff, buff.Capacity) > 0)
                                {
                                    if (path.StartsWith(buff.ToString()))
                                    {
                                        path = path.Remove(0, buff.Length);
                                        return drive + path;
                                    }
                                }
                            }
                        }
                    }
                    finally
                    {
                        NativeMethods.CloseHandle(hprocess2);
                    }
                }
                return null;    // hasn't happened yet :)
            }
    Tuesday, November 3, 2009 2:39 AM