Reputation:
I'm using the following code:
Using searcher As New ManagementObjectSearcher("SELECT * FROM Win32_Process WHERE ProcessId = " & 1234)
For Each mgmtObj As ManagementObject In searcher.Get()
Dim cl As String() = mgmtObj.Item("ExecutablePath").ToString().Split("""")
Console.WriteLine(cl(cl.Length - 1))
Next
End Using
NB: 1234 is an example ID which will be the result of GetProcessById.
This works when:
In all other cases I've tested I get the following error:
Object reference not set to an instance of an object.
As an alternative, I also have tried the following code:
Private Shared Function GetAssemblyPathAboveVista(ProcessId As Integer) As String
Dim buffer = New StringBuilder(1024)
Dim hprocess As IntPtr = OpenProcess(ProcessAccessFlags.QueryInformation, False, ProcessId)
If hprocess <> IntPtr.Zero Then
Try
Dim size As Integer = buffer.Capacity
If QueryFullProcessImageName(hprocess, 0, buffer, size) Then
Return buffer.ToString()
End If
Finally
CloseHandle(hprocess)
End Try
End If
Throw New Win32Exception(Marshal.GetLastWin32Error())
End Function
<DllImport("kernel32.dll")> _
Private Shared Function QueryFullProcessImageName(hprocess As IntPtr, dwFlags As Integer, lpExeName As StringBuilder, ByRef size As Integer) As Boolean
End Function
<DllImport("kernel32.dll")> _
Private Shared Function OpenProcess(dwDesiredAccess As ProcessAccessFlags, bInheritHandle As Boolean, dwProcessId As Integer) As IntPtr
End Function
<DllImport("kernel32.dll", SetLastError:=True)> _
Private Shared Function CloseHandle(hHandle As IntPtr) As Boolean
End Function
Enum ProcessAccessFlags As UInteger
All = &H1F0FFF
Terminate = &H1
CreateThread = &H2
VMOperation = &H8
VMRead = &H10
VMWrite = &H20
DupHandle = &H40
SetInformation = &H200
QueryInformation = &H400
Synchronize = &H100000
End Enum
This also only works when: - The process is running under the same user. - The process isn't a Windows Service (no matter what user it's running as).
In all other cases the OpenProcess function returns 0.
NB: I'm testing on Windows 8.1 with a user that has standard rights (non-administrator). My application must run as a standard user.
My question is: what can I do to always be able to retrieve a process path? I only need the path info.
Upvotes: 1
Views: 1447
Reputation: 1839
So indeed even the normal CLR methods cannot read an elevated process' filename, apparently because of a bug in the implementation. They added a new API flag in Windows Vista and up, to be able to read the information, but they have not implemented it in the CLR. The second example you posted seems to have been the same c# source I read, but it needed a little more attention than a C# to VB converter was able to provide.
In any case, here it is, all nicely fitted into an extension method for the Process class.
Module ProcessPathExtension
<DllImport("kernel32.dll")> _
Private Function QueryFullProcessImageName(hprocess As IntPtr, dwFlags As Integer, lpExeName As StringBuilder, ByRef size As Integer) As Boolean
End Function
<DllImport("kernel32.dll")> _
Private Function OpenProcess(dwDesiredAccess As ProcessAccessFlags, bInheritHandle As Boolean, dwProcessId As Integer) As IntPtr
End Function
<DllImport("kernel32.dll", SetLastError:=True)> _
Private Function CloseHandle(hHandle As IntPtr) As Boolean
End Function
Enum ProcessAccessFlags As UInteger
All = &H1F0FFF
Terminate = &H1
CreateThread = &H2
VMOperation = &H8
VMRead = &H10
VMWrite = &H20
DupHandle = &H40
SetInformation = &H200
QueryInformation = &H400
QueryLimitedInformation = &H1000
Synchronize = &H100000
End Enum
<Extension()> _
Public Function Path(ByVal _process As Process) As String
Dim processPath As String = ""
' The new QueryLimitedInformation flag is only available on Windows Vista and up.
If Environment.OSVersion.Version.Major >= 6 Then
Dim processHandle As IntPtr = OpenProcess(ProcessAccessFlags.QueryLimitedInformation, False, _process.Id)
Try
If Not processHandle = IntPtr.Zero Then
Dim buffer = New StringBuilder(1024)
If QueryFullProcessImageName(processHandle, 0, buffer, buffer.Capacity) Then
processPath = buffer.ToString()
End If
End If
Finally
CloseHandle(processHandle)
End Try
Else
processPath = _process.MainModule.FileName
End If
Return processPath
End Function
End Module
Include this module in your project, and you can access the process path through the new .Path method of the Process object:
For Each p In Process.GetProcesses().OrderBy(Function(x) x.Id, Nothing)
Console.WriteLine(p.Id & vbTab & p.ProcessName & vbCrLf & p.Path & vbCrLf & vbCrLf)
Next
Upvotes: 3
Reputation: 1839
Is there any particular reason why you are not using the normal way to do this, so that you can trap exceptions?
For Each p In Process.GetProcessesByName("someprocess.exe")
Try
Console.WriteLine(p.MainModule.FileName)
Catch ex As Exception
Console.WriteLine(ex.Message & vbCrLf & ex.StackTrace)
Exit Sub
End Try
Next
Or if you really need to get it by ID:
Try
Dim p = Process.GetProcessById(processId)
If p IsNot Nothing Then
Console.WriteLine(p.MainModule.FileName)
End If
Catch ex As Exception
Console.WriteLine(ex.Message & vbCrLf & ex.StackTrace)
End Try
Upvotes: 2
Reputation: 2660
Alright I tried this out myself. And from what I can see it comes down to rights. You are just not allowed to touch another users processes. Services are normally executed by SYSTEM, which as you probably know is another user acount. Running Visual Studio as administrator will give you what you want.
If you don't want to execute Visual Studio as administrator you would have to set your program to require administrative rights on startup. I expect that this cannot be solved in any other way.
Upvotes: 1