繁体   English   中英

如何在 C# 中确定进程的所有者?

[英]How do I determine the owner of a process in C#?

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

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

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

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

您可以使用 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";
}

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

这是非 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

不幸的是,没有获得流程所有者的本地 .Net 方式。

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

    var myApp = Process.GetProcessesByName("MyApp").FirstOrDefault();
    if (myApp != null)
    {
        string username = GetUsername(myApp.SessionId);
    }

在此处实现方法 GetUsername: https ://stackoverflow.com/a/35810391/10412686

添加对您的项目的引用:

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() 会为您提供所有正在运行的进程的 DataTable 以及它们的 Id 和 Name,这很方便,因为它可以用作 DataGridView 的 Datasource 参数。

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

@ bytecode77:

我现在可以仅通过查看代码来告诉您,您将生成潜在的FirstChanceExceptions…特别是由于这些的“访问权限被拒绝”(Win32Exception)和“ NOT RUNNING”(ArgumentException)导致的。 “ process.Handle”的表达式求值器。

进程句柄是应用程序专用的-换句话说,进程句柄不能共享。

另请参见LinkDemand = 6和SecurityCriticalAttribute

尽管您可能仍然需要为此属性“工具->调试->启用我的代码”,但仍会引发FirstChanceExceptions。

除此之外,我确实同意您在pinvoke Win32调用中的回答要比WMI快,尤其是在遍历所有进程时。

[DebuggerNonUserCode]
private static IEnumerable<Process> GetProcesses() =>
  Process.GetProcesses().Where(p => {
    var hasException = false;
    try {
      var x = p.Handle;
    } catch {
      hasException = true;
    }
    return !hasException;
  }).ToArray();

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

如果您想查看时间比较少,请参阅此答案

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

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

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

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);
  }
}
System.Security.Principal.WindowsIdentity.GetCurrent().Name

这是我发现的最简单的方法:

Process[] processes = Process.GetProcessesByName("MyApp");
foreach (Process process in processes)
{
     string username = process.StartInfo.Environment["USERNAME"];

     // do some stuff
} 

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM