1

I'm working on a C++ Windows application that's 32-bit that for reasons outside of my control needs to stay 32-bit. This application needs to be able to get the full path of the executable of other runnings processes given a PID.

Ordinarily for a 64-bit application you can use OpenProcess() to get a handle to the process with a particular PID and then pass the handle it returns to GetModuleFileNameEx() to get its executable path. However, OpenProcess() will fail if you try to get a handle to a 64-bit process from a 32-bit process, so this method isn't available to me. (This is the accepted solution to this question but since neither it nor any of its other answers address the problem with accessing 64-bit processes from a 32-bit process it doesn't answer my question.)

Is there any way to do this?

Bri Bri
  • 2,169
  • 3
  • 19
  • 44
  • You are probably going to need a 64-bit process that you communicate with via some IPC mechanism that gets the information for you see https://learn.microsoft.com/en-us/windows/win32/winprog64/interprocess-communication for additional info – Richard Critten Apr 21 '20 at 20:46
  • You could also just write a 64-bit console program that takes a PID in its command line, does this check and writes the executable path into stdout and then run this program from your 32-bit process with hidden window, redirect its stdout and read the result back. – CherryDT Apr 21 '20 at 20:49
  • 4
    "*However, OpenProcess() will fail if you try to get a handle to a 64-bit process from a 32-bit process*" - this is not true. A 32bit process *CAN* open a handle to a 64bit process just fine, provided that the user account of the 32bit process has access to the 64bit process. You can use `QueryFullProcessImageName()` in a 32bit process to query the filename of a 64bit process, which means you have to be able to open a handle to the 64bit process to begin with... – Remy Lebeau Apr 21 '20 at 20:56
  • 1
    ... This is even stated as much in the [`CreateToolhelp32Snapshot` documentation](https://learn.microsoft.com/en-us/windows/win32/api/tlhelp32/nf-tlhelp32-createtoolhelp32snapshot): "*Note that you can use the QueryFullProcessImageName function to retrieve the full name of an executable image for both 32- and 64-bit processes from a 32-bit process.*" – Remy Lebeau Apr 21 '20 at 20:59
  • use `GetModuleFileNameEx` when you need *exe* path wrong at all. you need use or `QueryFullProcessImageName` and here no 32-64 bit problems - your process can be and 32 bit. but you need open process with `PROCESS_QUERY_LIMITED_INFORMATION` access right. possible and use undocumented `SystemProcessIdInformation` - here you not need open process at all – RbMm Apr 21 '20 at 21:03
  • 1
    Actually nowadays `GetModuleFileNameEx` also works since it uses the exact same system call as `QueryFullProcessImageName` when `hModule` is `NULL`, but unfortunately this is undocumented behavior. – Eryk Sun Apr 21 '20 at 21:05
  • @RemyLebeau You are correct, `OpenProcess` doesn't fail for a 32-bit process accessing a 64-bit process. It's `GetModuleFileNameEx` that fails, while `QueryFullProcessImageName` will succeed. If you submit this as a full answer I'll mark it as correct. – Bri Bri Apr 22 '20 at 03:53

2 Answers2

0

There are already a few ways mentioned in the comments. Probably the best and simplest way would be to open the process with PROCESS_QUERY_LIMITED_INFORMATION and call QueryFullProcessImageName on it, as Remy Lebeau and RbMm wrote.

Still, let me show another way: You could leverage WMI. Note that this is by far not the best option (as it is very "expensive" behind the scenes) but in case you can't use other options for some reason, it may be helpful to know nonetheless.

The WMI class Win32_Process has a field ExecutablePath which contains the information you seek. You should be able to read it from a 32-bit process as well.

Here is a description how to read data from WMI using C++.

Example in PowerShell:

PS Z:\> Get-WmiObject -Query 'Select ExecutablePath From Win32_Process Where ProcessId = 6056'


__GENUS          : 2
__CLASS          : Win32_Process
__SUPERCLASS     :
__DYNASTY        :
__RELPATH        :
__PROPERTY_COUNT : 1
__DERIVATION     : {}
__SERVER         :
__NAMESPACE      :
__PATH           :
ExecutablePath   : C:\Program Files (x86)\Moo0\AlwaysOnTop 1.24\WindowMenuPlus64.exe
PSComputerName   :
CherryDT
  • 25,571
  • 5
  • 49
  • 74
  • 2
    use WMI - worst way from all possible, not need at all – RbMm Apr 21 '20 at 21:02
  • It's just another (working) option, I haven't claimed that it's the best possible one... – CherryDT Apr 21 '20 at 21:05
  • yes, but very not optimal. always ask yourself - how wmi do this internal ? why not use this way direct ? wmi primary for another languages, but not for c/c++ (this is my private opinion) – RbMm Apr 21 '20 at 21:08
  • I agree with you. I thought you meant that the answer was useless, I misunderstood that. – CherryDT Apr 21 '20 at 21:08
  • i simply note that this not optimal way. but it correct of course. – RbMm Apr 21 '20 at 21:09
  • Yes you are correct as well. I edited the answer to state that it's not a very good way even though it works. – CherryDT Apr 21 '20 at 21:09
  • if we need **once** get process path - this is possible but, but very slow and hard call from c/c++. if we need like taskmngr many time qury process path - not solution. need use `QueryFullProcessImageName` or `ZwQuerySystemInformation(SystemProcessIdInformation` – RbMm Apr 21 '20 at 21:12
0

I came onto this question looking for a way to do this from powershell. The Get-WmiObject approach failed in the same way for me as (Get-Process).Path, which is that while the list of processes (and their PIDs and process names) can be retrieved, the module information (including path) cannot be retrieved when the bit-ness of the process doesn't match the shell.

The comments on the question helped me, especially @RemyLebeau, and I came up with the following. I know the question was originally c++, but maybe this will be helpful to someone. It's the same WinAPI calls, loaded into an inline c# scope in powershell.

$pinvoke = Add-Type -PassThru -Name pinvoke -MemberDefinition @'
    [DllImport("kernel32.dll", SetLastError=true)]
    private static extern bool CloseHandle(
        IntPtr hObject);

    [DllImport("kernel32.dll", SetLastError = true)]
    private static extern IntPtr OpenProcess(
        uint processAccess,
        bool bInheritHandle,
        int processId);

    [DllImport("kernel32.dll", SetLastError=true)]
    private static extern bool QueryFullProcessImageName(
        IntPtr hProcess,
        int dwFlags,
        System.Text.StringBuilder lpExeName,
        ref int lpdwSize);
    private const int QueryLimitedInformation = 0x00001000;

    public static string GetProcessPath(int pid)
    {
        var size = 1024;
        var sb = new System.Text.StringBuilder(size);
        var handle = OpenProcess(QueryLimitedInformation, false, pid);
        if (handle == IntPtr.Zero) return null;
        var success = QueryFullProcessImageName(handle, 0, sb, ref size);
        CloseHandle(handle);
        if (!success) return null;
        return sb.ToString();
    }
'@

And then you can get the path of a 32 or 64 bit process by

$pinvoke::GetProcessPath($pid)
user1169420
  • 680
  • 7
  • 18