How do I determine the owner of a process in C#?
Asked Answered
K

11

59

I am looking for a process by the name of "MyApp.exe" and I want to make sure I get the process that is owned by a particular user.

I use the following code to get a list of the processes:

Process[] processes = Process.GetProcessesByName("MyApp");

This gives me a list of processes, but there does not appear to be a way in the Process class to determine who owns that process? Any thoughts on how I can do this?

Kirstenkirsteni answered 22/4, 2009 at 14:40 Comment(0)
C
74

You can use WMI to get the user owning a certain process. To use WMI you need to add a reference to the System.Management.dll to your project.

By process id:

public string GetProcessOwner(int processId)
{
    string query = "Select * From Win32_Process Where ProcessID = " + processId;
    ManagementObjectSearcher searcher = new ManagementObjectSearcher(query);
    ManagementObjectCollection processList = searcher.Get();

    foreach (ManagementObject obj in processList)
    {
        string[] argList = new string[] { string.Empty, string.Empty };
        int returnVal = Convert.ToInt32(obj.InvokeMethod("GetOwner", argList));
        if (returnVal == 0)
        {
            // return DOMAIN\user
            return argList[1] + "\\" + argList[0];
        }
    }

    return "NO OWNER";
}

By process name (finds the first process only, adjust accordingly):

public string GetProcessOwner(string processName)
{
    string query = "Select * from Win32_Process Where Name = \"" + processName + "\"";
    ManagementObjectSearcher searcher = new ManagementObjectSearcher(query);
    ManagementObjectCollection processList = searcher.Get();

    foreach (ManagementObject obj in processList)
    {
        string[] argList = new string[] { string.Empty, string.Empty };
        int returnVal = Convert.ToInt32(obj.InvokeMethod("GetOwner", argList));
        if (returnVal == 0)
        {
            // return DOMAIN\user
            string owner = argList[1] + "\\" + argList[0];
            return owner;       
        }
    }

    return "NO OWNER";
}
Cirsoid answered 22/4, 2009 at 14:44 Comment(5)
In your second method, your nested if contains a string, owner. I believe you were intending to return that string, instead.Clie
A word of warning: WMI queries such as these are only available with appropriate privileges. Non-admin accounts usually don't have access to WMI providers including Win32_Process. msdn.microsoft.com/en-us/library/windows/desktop/…Disseise
For some reason, the above example worked on my development computer, but I got "Invalid query" on a server machine. This was solved by rather than using string query = "...", using SelectQuery query = new SelectQuery("...").Bellyful
WMI is slow. Especially having to lookup every process on the system and then put exception handling around those which are denied due to security reasons. If you wanted to filter your process by user, you could possibly try pinvoking WTSQuerySessionInformationW().Frizette
Dont select *, when you dont need to. All what you need to select for GetOwner method is Handle, so SelectQuery is: string query = "Select Handle From Win32_Process Where ProcessID = " + processId;Wharton
J
53

Since WMI is not always a fast way of retrieving information, here is the native P/Invoke way of doing it:

The return value is null when unsuccessful. In order to get the names of processes running under the SYSTEM user, you need to execute this code as administrator.

private static string GetProcessUser(Process process)
{
    IntPtr processHandle = IntPtr.Zero;
    try
    {
        OpenProcessToken(process.Handle, 8, out processHandle);
        WindowsIdentity wi = new WindowsIdentity(processHandle);
        string user = wi.Name;
        return user.Contains(@"\") ? user.Substring(user.IndexOf(@"\") + 1) : user;
    }
    catch
    {
        return null;
    }
    finally
    {
        if (processHandle != IntPtr.Zero)
        {
            CloseHandle(processHandle);
        }
    }
}

[DllImport("advapi32.dll", SetLastError = true)]
private static extern bool OpenProcessToken(IntPtr ProcessHandle, uint DesiredAccess, out IntPtr TokenHandle);
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool CloseHandle(IntPtr hObject);
Jan answered 30/7, 2016 at 16:41 Comment(4)
I agree, thanks for this alternative, it's unhealthy to use WMI outside of internal business inventory applications (it's slow, and in the world at large is frequently corrupted/broken).Nianiabi
great one :) WMI would be much slowerObsession
Agreed! That it would be.Frizette
Note: WindowsIdentity is disposable, so you'd want to use a using statement when you new up the WindowsIdentity. Like this: using var wi = new WindowsIdentity(processHandle);Rinna
C
8

Here is the VB version for the non C# speakers:

Function GetProcessOwner(ProcessName As String) As String
    Dim query = "Select * from Win32_Process Where Name = """ + ProcessName + """"
    Dim searcher = New ManagementObjectSearcher(query)
    Dim processList = searcher.Get()

    For Each obj As ManagementObject In processList
      Dim argList As String() = {String.Empty, String.Empty}
      Dim returnVal = Convert.ToInt32(obj.InvokeMethod("GetOwner", argList))
      If returnVal = 0 Then
        ' return DOMAIN\user
        Dim owner = argList(1) + "\\" + argList(0)
        Return owner
      End If
    Next

    Return "NO OWNER"
  End Function

  Function GetProcessOwner(processId As Integer) As String
    Dim query = "Select * From Win32_Process Where ProcessID = " & processId
    Dim searcher = New ManagementObjectSearcher(query)
    Dim processList = searcher.Get()

    For Each obj As ManagementObject In processList
      Dim argList As String() = {String.Empty, String.Empty}
      Dim returnVal = Convert.ToInt32(obj.InvokeMethod("GetOwner", argList))
      If returnVal = 0 Then
        ' return DOMAIN\user
        Return argList(1) + "\\" + argList(0)
      End If
    Next

    Return "NO OWNER"
  End Function
Capon answered 5/9, 2012 at 22:34 Comment(2)
Question does not ask for VB. If you feel eager, try making a new question ("how to do this in VB") and answer it yourself - "non C# speakers" will look for tags other than VB.Divine
Actually, I'm a C# dev who has to write this in VB.NET. I searched for C# so I could quickly understand it. Having the VB code in here as well saved me a lot of time. Thank you.Rycca
J
4

Unfortunately there's no native .Net way of getting the process owner.

Have a look at these for a potential solution:

Joslin answered 22/4, 2009 at 14:47 Comment(0)
Z
1
    var myApp = Process.GetProcessesByName("MyApp").FirstOrDefault();
    if (myApp != null)
    {
        string username = GetUsername(myApp.SessionId);
    }

Implementation of method GetUsername here: https://mcmap.net/q/304107/-how-to-get-current-windows-username-from-windows-service-in-multiuser-environment-using-net

Zoo answered 23/7, 2019 at 8:0 Comment(1)
This solution does not give the correct result in general. Session user is different from process owner/user in general -- have a look at Windows task manager (add column for session id). One can easily run a process as another user in the current session (RunAs).Heterologous
O
1

With the help from detecting-user-name-from-process-id I have written the better - quicker - version of this function:

public static string GetProcessOwnerByID(int processId)
{
  IntPtr processHandle = IntPtr.Zero;
  IntPtr tokenHandle = IntPtr.Zero;
  try
  {
    processHandle = OpenProcess(PROCESS_QUERY_INFORMATION, false, processId);
    if (processHandle == IntPtr.Zero)
      return "NO ACCESS";

    OpenProcessToken(processHandle, TOKEN_QUERY, out tokenHandle);
    using (WindowsIdentity wi = new WindowsIdentity(tokenHandle))
    {
      string user = wi.Name;
      return user.Contains(@"\") ? user.Substring(user.IndexOf(@"\") + 1) : user;
    }
  }
  finally
  {
    if (tokenHandle != IntPtr.Zero) CloseHandle(tokenHandle);
    if (processHandle != IntPtr.Zero) CloseHandle(processHandle);
  }
}

Whole file can be found on GitHub gist

Outline answered 8/1, 2022 at 20:42 Comment(1)
Seems like just a rehash of ByteCode77's answer above from 6 years earlier. Though, it does have better memory management. https://mcmap.net/q/303746/-how-do-i-determine-the-owner-of-a-process-in-cAlcoholometer
W
0

Add a reference to your project:

System.Management

Then add the following method to your project:

    public string GetProcessOwner(int processId)
    {
        string MethodResult = null;
        try
        {
            StringBuilder sb = new StringBuilder();

            sb.Append(" SELECT ");
            sb.Append("     * ");
            sb.Append(" FROM ");
            sb.Append("     WIN32_PROCESS");
            sb.Append(" WHERE ");
            sb.Append("     ProcessId = " + processId);

            string Query = sb.ToString();

            ManagementObjectCollection Processes = new ManagementObjectSearcher(Query).Get();

            foreach (ManagementObject Process in Processes)
            {
                string[] Args = new string[] { "", "" };

                int ReturnCode = Convert.ToInt32(Process.InvokeMethod("GetOwner", Args));

                switch(ReturnCode)
                {
                    case 0:
                        MethodResult = Args[1] + "\\" + Args[0];
                        break;

                    default:
                        MethodResult = "None";
                        break;

                }

            }

        }
        catch //(Exception ex)
        {
            //ex.HandleException();
        }
        return MethodResult;
    }

Then add this method:

    public DataTable GetProcessTable()
    {
        DataTable MethodResult = null;
        try
        {
            List<Process> Processes = Process.GetProcesses().ToList<Process>();

            DataTable dt = new DataTable();
            dt.Columns.Add("Name", typeof(string));
            dt.Columns["Name"].ReadOnly = true;

            dt.Columns.Add("Id", typeof(string));
            dt.Columns["Id"].ReadOnly = true;

            dt.Columns.Add("Owner", typeof(string));
            dt.Columns["Owner"].ReadOnly = true;

            foreach (Process p in Processes)
            {
                DataRow r = dt.NewRow();

                bool Match = false;

                r["Id"] = p.Id.ToString();
                r["Name"] = p.ProcessName;
                r["Owner"] = GetProcessOwner(p.Id);

                dt.Rows.Add(r);

            }

            MethodResult = dt;

        }
        catch //(Exception ex)
        {
            //ex.HandleException();
        }
        return MethodResult;
    }

Calling GetProcessTable() gives you a DataTable of all running processes along with their Id and Name, which is handy because it can be used as a DataGridView's Datasource parameter.

Let me know if you need any more fields adding to the table.

Wade answered 29/2, 2016 at 8:29 Comment(0)
W
0

WMI is really the worst possible way how to get this information from Process. But... sometimes you need to get that info from remote process, and in that case you sadly need WMI. So if you have to, or want to use WMI, I suggest to do it like this (it's more than 60% quicker then classic WMI methods above):

Method:

public struct WMIProcessProperties
{
    public string Owner;
    public int ID;
}


public static async Task<Dictionary<Process, WMIProcessProperties>> GetWMIProperties(this IEnumerable<Process> processes)
{
    Dictionary<Process, WMIProcessProperties> result = new Dictionary<Process, WMIProcessProperties>();

    if (processes == null || processes.Count() == 0) { return result; }

    string selectQuery = "SELECT Handle, ProcessID FROM Win32_Process";
    selectQuery += processes.Count() <= 10 ? string.Format(" WHERE ProcessID = {0}", string.Join(" OR ProcessID = ", processes.Select(p => p.Id))) : string.Empty;

    using (CimSession session = await Task.Run(() => CimSession.Create(processes.ElementAt(0).MachineName)))
    {
        List<CimInstance> instances = await Task.Run(() => session.QueryInstances(@"root\cimv2", "WQL", selectQuery).ToList());

        List<Task<WMIProcessProperties>> tasks = new List<Task<WMIProcessProperties>>();

        for (int i = 0; i < instances.Count; i++)
        {
            CimInstance currentInstance = instances[i];

            tasks.Add(Task.Run(() =>
            {
                int id = Convert.ToInt32(currentInstance.CimInstanceProperties["ProcessID"].Value);
                string owner;
                using (CimMethodResult getOwnerResult = session.InvokeMethod(currentInstance, "GetOwner", null))
                {
                     owner = getOwnerResult.OutParameters["User"]?.Value?.ToString();
                }

                currentInstance.Dispose();

                return new WMIProcessProperties { Owner = owner, ID = id };

            }));
        }

        WMIProcessProperties[] wmiProcessProperties = await Task.WhenAll(tasks).ConfigureAwait(false);

        for (int i = 0; i < wmiProcessProperties.Length; i++)
        {
            result.Add(processes.Single(p => p.Id == wmiProcessProperties[i].ID), wmiProcessProperties[i]);
        }
    }

    return result;
}

If you want to see little time comparison, see this answer.

Wharton answered 28/8, 2019 at 12:48 Comment(0)
F
0

Loop through collection to check for permissions. Most cases current user will not be administrator

List<Process> processes = Process.GetProcessesByName(Text).ToList();
for (int i = processes.Count - 1; i > -1; i--)
{
    try
    {
        if (processes[i].MainModule?.FileName is null)
            processes.RemoveAt(i);
    }
    catch (Exception)
    {
        processes.RemoveAt(i);
    }
}
Fescennine answered 11/5, 2021 at 22:33 Comment(0)
O
0

An improved version of the @bytecode77 and @Bill Tarbell answers:

using Microsoft.Win32.SafeHandles;
using System.Runtime.InteropServices;
using System.Security.Principal;

public static class WinApi
{
    [Flags]
    public enum ProcessAccessRights : long
    {
        PROCESS_QUERY_INFORMATION = 0x0400,
    }

    [Flags]
    public enum TokenAccessRights : ulong
    {
        TOKEN_QUERY = 0x0008
    }

    [DllImport("kernel32.dll", SetLastError = true)]
    public static extern SafeProcessHandle OpenProcess(
        ProcessAccessRights dwDesiredAccess,
        [MarshalAs(UnmanagedType.Bool)] bool bInheritHandle,
        UIntPtr dwProcessId);

    [DllImport("advapi32.dll", SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    public static extern bool OpenProcessToken(
        SafeProcessHandle ProcessHandle,
        TokenAccessRights DesiredAccess,
        out SafeFileHandle TokenHandle);
}

public static class ProcessUtils
{
    public static (string domain, string user) GetProcessOwnerDomainAndUserNames(int processId)
    {
        using var processHandle = WinApi.OpenProcess(WinApi.ProcessAccessRights.PROCESS_QUERY_INFORMATION, false, (UIntPtr)processId);
        if (processHandle.IsInvalid)
        {
            return ("", "");
        }

        if (!WinApi.OpenProcessToken(processHandle, WinApi.TokenAccessRights.TOKEN_QUERY, out var tokenHandle))
        {
            return ("", "");
        }

        using (tokenHandle)
        {
            using var wi = new WindowsIdentity(tokenHandle.DangerousGetHandle());
            var domainAndUser = wi.Name.Split(new char[] { '\\' });
            return (domainAndUser[0], domainAndUser[1]);
        }
    }
}

public class Program
{
    public static void Main()
    {
        var (domainName, userName) = ProcessUtils.GetProcessOwnerDomainAndUserNames(2680);
        Console.WriteLine($"Domain: {domainName}, User: {userName}");
    }
}
Oly answered 31/3 at 15:40 Comment(0)
G
-1
System.Security.Principal.WindowsIdentity.GetCurrent().Name
Giaimo answered 16/8, 2016 at 20:11 Comment(4)
An explanation about this would be helpful.Geometrician
Sorry, It's not the answer to this thread's question. It just shows the process user id (windows account) in a .net application at run-time.Giaimo
This is helpful for related tasks like "get current user's processes" which is how I landed hereHemstitch
You would only want to use this in conjunction with process start -> 'runas' to launch your process in administrator mode if it is not already. This will give you more information on the PIC during query. However, this is not what was being asked in the question.Frizette

© 2022 - 2024 — McMap. All rights reserved.