【问题标题】:How do I determine the owner of a process in C#?如何在 C# 中确定进程的所有者?
【发布时间】:2010-10-21 02:58:23
【问题描述】:

我正在寻找一个名为“MyApp.exe”的进程,并且我想确保我获得了某个特定用户拥有的进程。

我使用以下代码获取进程列表:

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

这给了我一个进程列表,但 Process 类中似乎没有办法确定谁拥有该进程?关于如何做到这一点的任何想法?

【问题讨论】:

    标签: c# .net process


    【解决方案1】:

    您可以使用 WMI 让用户拥有某个进程。要使用 WMI,您需要在项目中添加对 System.Management.dll 的引用。

    按进程 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";
    }
    

    按进程名称(仅查找第一个进程,相应调整):

    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";
    }
    

    【讨论】:

    • 在您的第二种方法中,您的嵌套 if 包含一个字符串,所有者。我相信你打算返回那个字符串,而不是。
    • 一个警告:像这样的 WMI 查询只有在适当的权限下才可用。非管理员帐户通常无权访问 WMI 提供程序,包括 Win32_Process。 msdn.microsoft.com/en-us/library/windows/desktop/…
    • 由于某种原因,上面的示例在我的开发计算机上运行,​​但在服务器计算机上出现“无效查询”。这是通过使用SelectQuery query = new SelectQuery("...")而不是使用string query = "..."来解决的。
    • WMI 很慢。尤其是必须查找系统上的每个进程,然后对那些因安全原因被拒绝的进程进行异常处理。如果您想按用户过滤您的流程,您可以尝试调用 WTSQuerySessionInformationW()。
    • 不需要时不要选择*。您需要为 GetOwner 方法选择的所有内容都是 Handle,因此 SelectQuery 为:string query = "Select Handle From Win32_Process Where ProcessID = " + processId;
    【解决方案2】:

    由于 WMI 并不总是一种检索信息的快速方式,因此这里是本地 P/Invoke 的执行方式:

    不成功时返回值为null。为了获取SYSTEM用户下运行的进程的名称,您需要以管理员身份执行此代码。

    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);
    

    【讨论】:

    • 我同意,感谢这个替代方案,在内部业务库存应用程序之外使用 WMI 是不健康的(它很慢,而且在整个世界范围内经常损坏/损坏)。
    • 伟大的 :) WMI 会慢得多
    • 同意!就是这样。
    • 注意:WindowsIdentity 是一次性的,因此当您新建 WindowsIdentity 时,您可能需要使用 using 语句。像这样:using var wi = new WindowsIdentity(processHandle);
    【解决方案3】:

    这里是非 C# 使用者的 VB 版本:

    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
    

    【讨论】:

    • 问题不要求VB。如果您感到急切,请尝试提出一个新问题(“如何在 VB 中执行此操作”)并自己回答——“非 C# 使用者”将寻找 VB 以外的标签。
    • 实际上,我是一名 C# 开发人员,必须在 VB.NET 中编写此代码。我搜索了 C#,以便我可以快速理解它。在这里也有 VB 代码为我节省了很多时间。谢谢。
    【解决方案4】:

    不幸的是,没有本地的 .Net 方法来获取进程所有者。

    看看这些寻找潜在的解决方案:

    【讨论】:

      【解决方案5】:
          var myApp = Process.GetProcessesByName("MyApp").FirstOrDefault();
          if (myApp != null)
          {
              string username = GetUsername(myApp.SessionId);
          }
      

      这里GetUsername方法的实现:https://stackoverflow.com/a/35810391/10412686

      【讨论】:

      • 此解决方案通常不会给出正确的结果。会话用户通常不同于进程所有者/用户——查看 Windows 任务管理器(为会话 ID 添加列)。可以在当前会话 (RunAs) 中轻松地以其他用户身份运行进程。
      【解决方案6】:

      添加对您项目的引用:

      System.Management
      

      然后将以下方法添加到您的项目中:

          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;
          }
      

      然后添加这个方法:

          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;
          }
      

      调用 GetProcessTable() 会为您提供所有正在运行的进程及其 ID 和名称的 DataTable,这很方便,因为它可以用作 DataGridView 的 Datasource 参数。

      如果您需要在表格中添加更多字段,请告诉我。

      【讨论】:

        【解决方案7】:

        WMI 确实是从 Process 获取此信息的最糟糕的方法。但是...有时您需要从远程进程中获取该信息,在这种情况下,您很遗憾需要 WMI。因此,如果您必须或想要使用 WMI,我建议您这样做(比上面的经典 WMI 方法快 60% 以上):

        方法:

        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;
        }
        

        如果你想看一点时间比较,看这个answer

        【讨论】:

          【解决方案8】:

          遍历集合以检查权限。 大多数情况下,当前用户不是管理员

          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);
              }
          }
          

          【讨论】:

            【解决方案9】:

            detecting-user-name-from-process-id 的帮助下,我编写了这个函数的更好、更快的版本:

            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);
              }
            }
            

            整个文件可以在GitHub gist找到

            【讨论】:

              【解决方案10】:
              System.Security.Principal.WindowsIdentity.GetCurrent().Name
              

              【讨论】:

              • 对此进行解释会有所帮助。
              • 对不起,这不是这个线程问题的答案。它只是在运行时显示 .net 应用程序中的进程用户 ID(Windows 帐户)。
              • 这对相关任务很有帮助,例如“获取当前用户的进程”,这就是我在这里登陆的方式
              • 您只想将它​​与 process start -> 'runas' 结合使用,以在管理员模式下启动您的进程(如果尚未启动)。这将在查询期间为您提供有关 PIC 的更多信息。但是,这不是问题中要问的内容。
              猜你喜欢
              • 1970-01-01
              • 2017-05-27
              • 1970-01-01
              • 2017-11-10
              • 2016-02-01
              • 1970-01-01
              • 2010-09-30
              相关资源
              最近更新 更多