Get username from PID in VB.NET

As part of a larger program that I’m writing to work with Windows processes, threads and window objects, I wrote a function that allows you to get the username of the user who owns a given process. As simple as it sounds, there is no straightforward way of obtaining the username given the process id in Windows. You could use a WMI query to get the username from a PID but that is painfully slow especially if you plan on doing enumeration for all the processes running on your system. The faster way is to use the Win32 API. ┬áThe primary APIs that are needed┬áto achieve this are:

The flow would be as follows:

  1. Using OpenProcess with the PROCESS_QUERY_INFORMATION access right, you can open a handle to a process. This will fail if you do not have the necessary access levels. For example, a limited user using querying a process owned by NT Authority\System.
  2. Once you have the handle, you can call OpenProcessToken requesting the TOKEN_QUERY access right. This function returns a pointer to a handle that identifies the newly opened access token.
  3. Once you have obtained the handle to the access token, calling GetTokenInformation with the access token handle returns the length of the buffer for the TokenInformation parameter when a null TokenInformation value is passed.
  4. The GetTokenInformation function has to be called again with the length of the buffer obtained from the previous call along with a pointer to the buffer that will be populated via the TokenInformation parameter. The TokenInformation structure can then be cast to a structure that is defined as TOKEN_USER that contains a structure called SID_AND_ATTRIBUTES that defines the SID that represents the user associated with the access token that was passed to GetTokenInformation.
  5. Once the TokenUser variable is populated, we can use the ConvertSidToStringSid function which takes in a SID structure and provides a readable SID value.
  6. This SID value can then be passed to Principal.SecurityIdentifier to be translated to a username using the Principal.NTAccount type.

Here’s the code in VB.NET.

Imports System.Runtime.InteropServices
Imports System.Security
Imports System.ComponentModel

Module ProcessFunctions
 <DllImport("kernel32.dll")> _
 Public Function OpenProcess(processAccess As ProcessAccessFlags, bInheritHandle As Boolean, processId As Integer) As Integer
 End Function

 <DllImport("advapi32.dll", SetLastError:=True)> _
 Public Function OpenProcessToken(ByVal ProcessHandle As IntPtr, ByVal DesiredAccess As Integer, ByRef TokenHandle As IntPtr) As Boolean
 End Function

 <DllImport("advapi32.dll", SetLastError:=True)> _
 Public Function GetTokenInformation(ByVal TokenHandle As IntPtr, ByVal TokenInformationClass As TOKEN_INFORMATION_CLASS, _
 ByVal TokenInformation As IntPtr, ByVal TokenInformationLength As System.UInt32, ByRef ReturnLength As System.UInt32) As Boolean
 End Function

 <DllImport("advapi32.dll", SetLastError:=True)> _
 Public Function ConvertSidToStringSid(ByVal psid As IntPtr, ByRef pStringSid As String) As Boolean
 End Function

 <DllImport("kernel32.dll", SetLastError:=True)> _
 Public Function GetExitCodeProcess(ByVal hProcess As IntPtr, ByRef lpExitCode As System.UInt32) As Boolean
 End Function

 Public Structure SID_AND_ATTRIBUTES
   Public Sid As IntPtr
   Public Attributes As Int32
 End Structure

 Public Structure TOKEN_USER
 End Structure

 Enum ProcessAccessFlags
   All = &H1F0FFF
   Terminate = &H1
   CreateThread = &H2
   VirtualMemoryOperation = &H8
   VirtualMemoryRead = &H10
   VirtualMemoryWrite = &H20
   DuplicateHandle = &H40
   CreateProcess = &H80
   SetQuota = &H100
   SetInformation = &H200
   QueryInformation = &H400
   QueryLimitedInformation = &H1000
   Synchronize = &H100000
 End Enum

   TokenUser = 1
 End Enum

Public Function GetUsernameFromProcessID(pid As Long) As String
  Dim phnd As Integer = OpenProcess(ProcessAccessFlags.QueryInformation, False, pid)
  If phnd <> 0 Then
    Dim Thnd As Integer
    OpenProcessToken(phnd, Principal.TokenAccessLevels.Query, Thnd)
    Dim TokenUser As TOKEN_USER
    Dim TokenInfLength As Integer = 0
    Dim sUserSid As String = ""
    Dim TokenInformation As IntPtr

    GetTokenInformation(Thnd, TOKEN_INFORMATION_CLASS.TokenUser, IntPtr.Zero, 0, TokenInfLength)
    TokenInformation = Marshal.AllocHGlobal(TokenInfLength)
    GetTokenInformation(Thnd, TOKEN_INFORMATION_CLASS.TokenUser, TokenInformation, TokenInfLength, TokenInfLength)

    TokenUser = DirectCast(Marshal.PtrToStructure(TokenInformation, TokenUser.GetType), TOKEN_USER)

    ConvertSidToStringSid(TokenUser.User.Sid, sUserSid)

    Dim username As String = New Principal.SecurityIdentifier(sUserSid).Translate(GetType(Principal.NTAccount)).ToString()
    Return username
 Else : Return "<access denied>"
 End If
End Function
End Module
Add this code as part of your project in a module and access the function using GetUsernameFromProcessID(pid) which will return the username if you have access to the process and “<access denied>” if you don’t.
Happy Hacking!

Volume Mute and SendMessage() Fun

As the owner of the @wincmdfu twitter account, I try and find alternate ways of doing stuff in Windows. I normally have a bunch of batch files, VB script files and powershell cmdlets in a custom folder in %PATH% that I call when required. While listening to some music the other day, I realized there is no shortcut that I know (or have discovered yet) that allows me to mute the Audio on my system. I have a hardware key on the keyboard that mutes the volume, but no command to do this (remotely for example – which is cooler).

Enter the Windows SendMessage API. This is a fantastic API that Windows uses to send messages to windows given the handle of the target window. This window could be in a different process as well. The SendMessage function calls the window procedure for the specified window and does not return until the window procedure has processed the message.

I will be writing about additional fun stuff you can do with the SendMessage API, but for now coming back to my problem statement. SendMessage can be used to send the WM_APPCOMMAND with the APPCOMMAND_VOLUME_MUTE parameter to the current window handle to toggle mute. This can programmatically be put into a Form/console application and run from command line. Here’s my VB.NET code:

[code language=”vb” gutter=”false”]
Public Class Form1

<Runtime.InteropServices.DllImport("user32.dll")> _
Public Shared Function SendMessageW(ByVal hWnd As IntPtr, ByVal Msg As Integer, ByVal wParam As IntPtr, ByVal lParam As IntPtr) As IntPtr
End Function

Private Const APPCOMMAND_VOLUME_MUTE As Integer = &H80000
Private Const WM_APPCOMMAND As Integer = &H319

Private Sub Main_Load(sender As Object, e As EventArgs) Handles MyBase.Load
End Sub
End Class

Compile this into an executable called mute.exe, store it in %PATH% and you are done!
Happy Hacking!