Pode parecer tarefa fácil, mas não é. O fato é que, obter o caminho do executável de um processo pode acabar tendo um destino trágico se você não estiver com sorte.
A maneira tradicional de fazer isso é utilizando a propriedade Process.MainModule.FileName.
Mas há um grande porém! Se o seu processo está executando em x64 (Processo 64 bits) e o outro processo, que você deseja obter o caminho, está executando em x86 (Processo 32 bits) você receberá a belíssima exceção abaixo:
A situação inversa também resulta neste exceção.
Esse é um problema cada vez mais fácil de ocorrer devido à maior adoção de sistemas operacionais x64.
Mas, contudo, todavia, começando com a versão Vista, o Windows possui uma função no Kernel que nos permite contornar este problema. Ela se chama QueryFullProcessImageName. Essa rotina não é afetada pela situação descrita acima.
Dessa forma, com algumas linhas de código, podemos implementar um método de extensão para a classe Process que irá facilitar as coisas:
using System; using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; namespace System.Diagnostics { public static class ProcessExtensions { /// <summary> /// Returns the process executable full path /// </summary> /// <param name="Process">The process</param> /// <returns>A string containing the process executable path</returns> public static string GetProcessPath(this Process Process) { int capacity = 1024; StringBuilder sb = new StringBuilder(capacity); IntPtr handle = OpenProcess(ProcessAccessFlags.QueryLimitedInformation, false, Process.Id); QueryFullProcessImageName(handle, 0, sb, ref capacity); string fullPath = sb.ToString(0, capacity); CloseHandle(handle); return fullPath; } [DllImport("kernel32.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] private static extern bool CloseHandle(IntPtr hObject); [DllImport("kernel32.dll", SetLastError = true)] private static extern bool QueryFullProcessImageName([In]IntPtr hProcess, [In]int dwFlags, [Out]StringBuilder lpExeName, ref int lpdwSize); [DllImport("kernel32.dll")] private static extern IntPtr OpenProcess( ProcessAccessFlags processAccess, bool bInheritHandle, int processId ); [Flags] private enum ProcessAccessFlags : uint { All = 0x001F0FFF, Terminate = 0x00000001, CreateThread = 0x00000002, VirtualMemoryOperation = 0x00000008, VirtualMemoryRead = 0x00000010, VirtualMemoryWrite = 0x00000020, DuplicateHandle = 0x00000040, CreateProcess = 0x000000080, SetQuota = 0x00000100, SetInformation = 0x00000200, QueryInformation = 0x00000400, QueryLimitedInformation = 0x00001000, Synchronize = 0x00100000 } } }
Após implementar o código acima, você poderá fazer coisas assim no seu código:
foreach (Process pr in System.IO.Kernel.GetProcessesLockingFile(dialog.FileName)) { DataRow r = dt.NewRow(); r[0] = pr.ProcessName; r[1] = pr.Id; r[2] = pr.GetProcessPath(); // Uso da função dt.Rows.Add(r); }
Como diria um amigo meu: Nice and flexible.
Muito bom, mas, na linha: “string fullPath = sb.ToString(0, capacity);” ele retorna o erro de:
“System.ArgumentOutOfRangeException: ‘O índice e o comprimento devem se referir a um local dentro da cadeia de caracteres.'”, Windows 10 x64!