user564548
user564548

Reputation:

Can't always retrieve the path of a process

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

Answers (3)

Drunken Code Monkey
Drunken Code Monkey

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

Drunken Code Monkey
Drunken Code Monkey

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

WozzeC
WozzeC

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

Related Questions