【问题标题】:Fast way to get process owners in .NET在 .NET 中获取进程所有者的快速方法
【发布时间】:2021-09-18 02:42:43
【问题描述】:

我有以下 .NET 代码可以访问OpenProcessToken Win32 API 以检索系统上所有进程的所有者名称:

using System.Security.Principal;
using System.Runtime.InteropServices;

public class Test
{
    [DllImport("advapi32.dll", SetLastError=true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    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);
    
    private const UInt32 TOKEN_QUERY = 0x0008;

    public static List<List<String>> GetProcessWithUsers() 
    {
        var processes = Process.GetProcesses();
        var result = new List<List<string>>();
        foreach (var proc in processes)
        {
            result.Add(new List<string> { proc.ProcessName, GetProcessUser(proc) });
        }
        return result;
    }

    public static string GetProcessUser(Process process)
    {
        IntPtr tokenHandle = IntPtr.Zero;
        try
        {
            OpenProcessToken(process.Handle, TOKEN_QUERY, out tokenHandle);
            WindowsIdentity wi = new WindowsIdentity(tokenHandle);
            return wi.Name;
        }
        catch
        {
            return null;
        }
        finally
        {
            if (tokenHandle != IntPtr.Zero)
            {
                CloseHandle(tokenHandle);
            }
        }
    }
}

调用Test.GetProcessWithUsers()(例如在 LinqPad 中)对于我系统上的 280 个进程大约需要 2 秒。

我认为这个任务的时间是可以接受的。

Process.GetProcesses() 很活泼,new WindowsIdentity() 的贡献可以忽略不计,那么OpenProcessToken() 的阻碍是什么?是否有其他更快的 Win32 API 函数?

【问题讨论】:

  • Process.Handle 是问题(导致访问被拒绝)。如果您以管理员身份运行,应该会快得多。如果您追求性能,您可以使用 P/Invoke 编写整个内容来避免这种情况
  • @SimonMourier 我不能以管理员身份运行它,就是这样。
  • 我明白,但它更快吗?如果是,则使用完整的 P/Invoke 重写
  • OpenProcessToken 没问题,如果您有PROCESS_QUERY_LIMITED_INFORMATION 访问权限的进程句柄,则不会造成任何问题或减速。但是 - 收集所有者 sids 的意义/最终目标是什么?
  • 这是一个应该更快的版本(完整 p/invoke)pastebin.com/raw/ae5Eg9iP

标签: .net winapi pinvoke


【解决方案1】:

使用的大部分时间似乎是由 .NET 的 Process 类引起的(合法抛出的 Access Denied 异常等),所以这里有一个完整的 P/Invoke 版本,它不使用它,而是使用原生的 @ 987654321@:

[DllImport("advapi32", SetLastError = true)]
private static extern bool OpenProcessToken(IntPtr ProcessHandle, int DesiredAccess, out IntPtr TokenHandle);

[DllImport("kernel32", SetLastError = true)]
private static extern IntPtr OpenProcess(int dwDesiredAccess, bool bInheritHandle, int dwProcessId);

[DllImport("kernel32", SetLastError = true)]
private static extern bool CloseHandle(IntPtr hObject);

[DllImport("kernel32", SetLastError = true)]
private static extern IntPtr CreateToolhelp32Snapshot(int dwFlags, int th32ProcessID);

[DllImport("kernel32", SetLastError = true, CharSet = CharSet.Unicode)]
private static extern bool Process32First(IntPtr hSnapshot, ref PROCESSENTRY32 lppe);

[DllImport("kernel32", SetLastError = true, CharSet = CharSet.Unicode)]
private static extern bool Process32Next(IntPtr hSnapshot, ref PROCESSENTRY32 lppe);

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
private struct PROCESSENTRY32
{
    public int dwSize;
    public int cntUsage;
    public int th32ProcessID;
    public IntPtr th32DefaultHeapID;
    public int th32ModuleID;
    public int cntThreads;
    public int th32ParentProcessID;
    public int pcPriClassBase;
    public int dwFlags;

    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
    public string szExeFile;
}

public static List<List<string>> GetProcessWithUsers()
{
    var result = new List<List<string>>();
    const int TH32CS_SNAPPROCESS = 2;
    var snap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    var entry = new PROCESSENTRY32();
    entry.dwSize = Marshal.SizeOf<PROCESSENTRY32>();
    if (Process32First(snap, ref entry))
    {
        do
        {
            const int PROCESS_QUERY_LIMITED_INFORMATION = 0x00001000;
            var handle = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, false, entry.th32ProcessID);
            result.Add(new List<string> { entry.szExeFile, GetProcessUser(handle) });
            if (handle != IntPtr.Zero)
            {
                CloseHandle(handle);
            }
        }
        while (Process32Next(snap, ref entry));
    }
    CloseHandle(snap);
    return result;
}

public static string GetProcessUser(IntPtr handle)
{
    if (handle == IntPtr.Zero)
        return null;

    const int TOKEN_QUERY = 0x0008;
    if (!OpenProcessToken(handle, TOKEN_QUERY, out var tokenHandle))
        return null;

    var wi = new WindowsIdentity(tokenHandle);
    CloseHandle(tokenHandle);
    return wi.Name;
}

在我的 PC 上,我已从 1500 毫秒降至 30 毫秒 (x50)。

【讨论】:

  • CreateToolhelp32Snapshot(尤其是Process32Next)非​​常糟糕的设计和较慢的比较使用NtQuerySystemInformation(SystemProcessInformation..)。如果想要最大速度当然最好使用NtQuerySystemInformation
  • 这里有很多缺失的错误检查和finally
  • @Charlieface - 不,它没有丢失,我对错误详细信息不感兴趣,我只是倾尽所能。至于最后这里没有很多可能的抛出(可能在 WindowsIdentity 构造函数中),它是互操作代码。无论如何,错误检查不是问题。
  • 我想我可以添加错误检查和 finally 子句。 “尽我所能”对我的用例来说已经足够了。
  • 我只是注意到NtQuerySystemInformation 会更快(我不认为您的解决方案不好,反之亦然。只是这会更快)。为什么Process32Next 不够好?因为它在内部将数据(从NtQuerySystemInformation)复制到部分,并且在每次调用Process32First/Process32Next 时,它都会映射和取消映射此部分并将数据复制到用户缓冲区。此地图/取消地图减速的主要点。还 Process32Next 因某种未知原因丢弃了一些由原始 SystemProcessInformation 返回的信息(终端会话 id)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-11-03
相关资源
最近更新 更多