2

I'm working on an application that should be able to show and retrieve information from all processes in the system.

I try to get the process full path using the function QueryFullProcessImageName but it fails with error 31 (A device attached to the system is not functioning), this happens only with new processes created after the program's first enumeration of the running processes.

This is the method that I use to get the full path:

    public static string GetProcessFullPath(SafeProcessHandle Handle)
    {
        uint MaxPathLength = 1024;
        StringBuilder ExePath = new StringBuilder(1024);
        if (NativeMethods.Win32ProcessFunctions.QueryFullProcessImageName(Handle.DangerousGetHandle(), 0, ExePath, ref MaxPathLength))
        {
            return ExePath.ToString();
        }
        else
        {
            Win32Exception ex = new Win32Exception(Marshal.GetLastWin32Error());
            Logger.WriteEntry(new LogEntry("Non è stato possibile recuperare il percorso completo dell'eseguibile di un processo, codice di errore: " + ex.NativeErrorCode + " (" + ex.Message + ")", EventSource.WindowsAPI, EventSeverity.Error));
            return "Non disponibile";
        }
    }

This is the declaration of the function:

        [DllImport("Kernel32.dll", EntryPoint = "QueryFullProcessImageNameW", SetLastError = true, CharSet = CharSet.Unicode)]
        [return: MarshalAs(UnmanagedType.Bool)]
        public static extern bool QueryFullProcessImageName(IntPtr ProcessHandle, uint PathType, StringBuilder ExePath, ref uint Characters);

I don't know what could be causing this error, I get notifications of new processes from WMI (Win32_ProcessStartTrace).

This is how I get the process handle that is used in the GetProcessFullPath method (the PID is from the WMI notification):

    public static SafeProcessHandle GetProcessHandle(uint ProcessID)
    {
        IntPtr UnsafeProcessHandle = NativeMethods.Win32ProcessFunctions.OpenProcess(NativeMethods.Win32Enumerations.ProcessAccessRights.PROCESS_ALL_ACCESS, false, ProcessID);
        if (UnsafeProcessHandle == IntPtr.Zero)
        {
            UnsafeProcessHandle = NativeMethods.Win32ProcessFunctions.OpenProcess(NativeMethods.Win32Enumerations.ProcessAccessRights.PROCESS_QUERY_LIMITED_INFORMATION, false, ProcessID);
            if (UnsafeProcessHandle == IntPtr.Zero)
            {
                Win32Exception ex = new Win32Exception(Marshal.GetLastWin32Error());
                Logger.WriteEntry(new LogEntry("Non è stato possibile aprire un processo, codice di errore: " + ex.NativeErrorCode + " (" + ex.Message + ")", EventSource.WindowsAPI, EventSeverity.Error));
                return new SafeProcessHandle(UnsafeProcessHandle, true);
            }
            else
            {
                return new SafeProcessHandle(UnsafeProcessHandle, true);
            }
        }
        else
        {
            return new SafeProcessHandle(UnsafeProcessHandle, true);
        }
    }

Edit: Further testing indicated that the problem occurs even if the process whose full path I'm trying to get was already started when the function is called.

  • What `Handle` are you passing in to `GetProcessFullPath()`? Please provide a [mcve]. – Remy Lebeau Feb 26 '21 at 17:57
  • The GetProcessFullPath method that I posted in the question is how I retrieve the full path. The handle is a process handle returned by the OpenProcess function called with the PID given by WMI and it is valid. – dt_overflow Feb 26 '21 at 18:25
  • Did you verify that `OpenProcess()` is not returning `IntPtr.Zero`? – Remy Lebeau Feb 26 '21 at 19:02
  • Yes, I tried multiple times, every time the handle was valid but the function still failed. – dt_overflow Feb 26 '21 at 19:25
  • 1. Have you tested it on other machines? 2. Will each process you test fail? – Strive Sun Mar 01 '21 at 07:27
  • Unfortunately I have only one machine (I'm a student and this is a personal project), I tried to test it in a completely new project to see if it was something in the program causing it but it returned the same error. – dt_overflow Mar 01 '21 at 11:17
  • While looking for a solution I also tried other functions, the error is returned also by GetModuleFileName (with NULL module handle), GetProcessImageFileName works but it returns the path in device form and I need a Win32 path. – dt_overflow Mar 01 '21 at 11:18

2 Answers2

0

GetProcessImageFileName works but it returns the path in device form and I need a Win32 path.

You can use GetLogicalDriveStrings and QueryDosDevice convert dos file path to windows file path.

Some code:

// dos file path => windows file path
BOOL DosPathToNtPath(LPTSTR pszDosPath, LPTSTR pszNtPath)
{
    TCHAR           szDriveStr[500];
    TCHAR           szDrive[3];
    TCHAR           szDevName[100];
    INT             cchDevName;
    INT             i;

    //Check the parameters
    if (!pszDosPath || !pszNtPath)
        return FALSE;

    //Get local disk string
    if (GetLogicalDriveStrings(sizeof(szDriveStr), szDriveStr))
    {
        for (i = 0; szDriveStr[i]; i += 4)
        {
            if (!lstrcmpi(&(szDriveStr[i]), _T("A:\\")) || !lstrcmpi(&(szDriveStr[i]), _T("B:\\"))) { continue; }

            szDrive[0] = szDriveStr[i];
            szDrive[1] = szDriveStr[i + 1];
            szDrive[2] = '\0';
            // Query Dos device name
            if (!QueryDosDevice(szDrive, szDevName, 100)) { return FALSE; }

            // Hit
            cchDevName = lstrlen(szDevName);
            if (_tcsnicmp(pszDosPath, szDevName, cchDevName) == 0) {
                // Copy drive
                lstrcpy(pszNtPath, szDrive);

                // Copy path
                lstrcat(pszNtPath, pszDosPath + cchDevName);

                return TRUE;
            }
        }
    }

    lstrcpy(pszNtPath, pszDosPath);
    return FALSE;
}

Then simply call,

GetProcessImageFileName(hProcess, szImagePath, MAX_PATH);
DosPathToNtPath(szImagePath,pszFullPath);

You can convert it to C# code.

Strive Sun
  • 5,988
  • 1
  • 9
  • 26
0

The QueryFullProcessImageName function fails with ERROR_GEN_FAILURE (31 or 0x1f, "A device attached to the system is not functioning") if the process is a "zombie" process, i.e. the process terminated but not all handles to it were closed. In this case, you can still use QueryFullProcessImageNameW with the PROCESS_NAME_NATIVE flag to get the native path, but you probably just want to skip it since it's no longer running.

Paul
  • 6,061
  • 6
  • 39
  • 70