【问题标题】:How to get owner of process running on remote machine without WMI如何获得在没有 WMI 的远程计算机上运行的进程的所有者
【发布时间】:2020-01-01 10:55:35
【问题描述】:

我正在创建远程任务管理器应用程序,并且我正在尝试弄清楚如何获得在没有 WMI 的远程计算机上运行的进程的进程所有者。使用 WMI 真的很容易,但是太慢了。我尝试使用 WTSQuerySessionInformation,但它只适用于本地机器。

为了更详细地说明,我的远程任务管理器应用程序将在工作站上运行,并将连接到另一个工作站,也连接到同一网络中的服务器。将运行应用程序的用户将是两台计算机上的管理员。

请问,您知道如何获取远程进程所有者的另一种方法,或者对下面的代码进行一些改进/修复吗?

我的 WMI 版本(太慢了……)

public static Dictionary<Process, string> GetOwners(this IEnumerable<Process> processes)
{
        Dictionary<Process, string> result = new Dictionary<Process, string>();

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

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

        ManagementScope scope = new ManagementScope(string.Format("\\\\{0}\\root\\cimv2", processes.ElementAt(0).MachineName));
        SelectQuery selectQuery = new SelectQuery(select);

        scope.Connect();

        using (ManagementObjectSearcher searcher = new ManagementObjectSearcher(scope, selectQuery))
        {
            using (ManagementObjectCollection objectCollection = searcher.Get())
            {
                foreach (ManagementObject managementObject in objectCollection)
                {
                    try
                    {
                        int id = Convert.ToInt32(managementObject["ProcessID"]);
                        string owner = managementObject.InvokeMethod("GetOwner", null, null)["User"]?.ToString();

                        result.Add(processes.Single(p => p.Id == id), owner);
                    }
                    catch
                    {
                    }
                }
            }
        }

        return result;
}

我的 WTSQuerySessionInformation 版本(仅适用于本地机器)

public static Dictionary<Process, string> GetPInvokeProperties(this IEnumerable<Process> processes)
{
        Dictionary<Process, string> result = new Dictionary<Process, string>();

        if (processes == null || processes.Count() == 0) { return result; }
        string machineName = processes.ElementAt(0).MachineName;
        IntPtr serverHandle = (machineName == Environment.MachineName || machineName == ".") ? IntPtr.Zero : NativeMethods.OpenServer(machineName);

        foreach (Process process in processes)
        {
            try
            {
                IntPtr buffer;
                int strLen;
                string username = "SYSTEM";
                if (NativeMethods.QuerySessionInformation(serverHandle, process.SessionId, WTS_INFO_CLASS.WTSUserName, out buffer, out strLen) && strLen > 1)
                {
                    username = Marshal.PtrToStringUni(buffer);
                    NativeMethods.FreeMemory(buffer);
                }

                result.Add(process, username);
            }
            catch
            {}
        }

        NativeMethods.CloseServer(serverHandle);

        return result;
}

单独类中的 NativeMethods:

public static class NativeMethods
{
    #region Native Methods

    [DllImport("wtsapi32.dll")]
    private static extern IntPtr WTSOpenServer([MarshalAs(UnmanagedType.LPStr)] string pServerName);

    [DllImport("wtsapi32.dll")]
    private static extern void WTSCloseServer(IntPtr hServer);

    [DllImport("Wtsapi32.dll")]
    private static extern void WTSFreeMemory(IntPtr pointer);

    [DllImport("Wtsapi32.dll")]
    private static extern bool WTSQuerySessionInformationW(IntPtr hServer, int sessionId, WTS_INFO_CLASS wtsInfoClass, out IntPtr ppBuffer, out int pBytesReturned);

    #endregion

    #region Public Methods

    public static IntPtr OpenServer(string Name)
    {
        IntPtr server = WTSOpenServer(Name);
        return server;
    }

    public static void CloseServer(IntPtr ServerHandle)
    {
        WTSCloseServer(ServerHandle);
    }

    public static void FreeMemory(IntPtr pointer)
    {
        WTSFreeMemory(pointer);
    }

    public static bool QuerySessionInformation(IntPtr hServer, int sessionId, WTS_INFO_CLASS wtsInfoClass, out IntPtr ppBuffer, out int pBytesReturned)
    {
        return WTSQuerySessionInformationW(hServer, sessionId, wtsInfoClass, out ppBuffer, out pBytesReturned);
    }

    #endregion
}

【问题讨论】:

    标签: c# windows process pinvoke remote-access


    【解决方案1】:

    我建议迁移到较新的命名空间,因为 System.Management 较旧、较慢且无法扩展。你所追求的新框架是Microsoft.Management.Infrastructure。这是 Microsoft documentation 解释这一点以及两者的示例。

    所以你会使用这样的东西:

    Using Microsoft.Management.Infrastructure;
    
    CimSession Session = CimSession.Create("computer_name");
    CimInstance Instance = Session.QueryInstances(@"root\cimv2", "WQL", "SELECT Name FROM Win32_ComputerSystem");
    
    foreach (CimInstance i in Instance){
        Console.WriteLine(i.CimInstanceProperties["Name"].Value);
    }
    

    Using Microsoft.Management.Infrastructure;
    
    CimSession Session = CimSession.Create("computer_name");
    CimInstance Instance = Session.QueryInstances(@"root\cimv2", "WQL", "SELECT Name FROM Win32_ComputerSystem").First();
    
    Console.WriteLine(Instance.CimInstanceProperties["Name"].Value);
    

    我希望这能给你一些新的兔子洞来跑下来:-D 如果你需要任何其他东西,请告诉我们:)

    【讨论】:

    • 感谢您的回答。经过快速学习,它确实比旧的System.Management 好得多。实际上没有很大的时差,但是......它把我带到了下面的结局解决方案,所以我真的很感激你。 :)
    【解决方案2】:

    经过一些测试并感谢@I.T Delinquent 的精彩提示,我创建了获取流程所有者的最终方法。它仍然不是超级快,但已经足够快了。在我的问题中,针对上述 WMI 方法是否有超过 60% 的加速,我相信仍有改进的空间。

    示例: 从另一个 VLAN 中的工作站获取数据(进程所有者、ID、Handle、ExecutablePath、描述、命令行),但相同的网络域和大约。 200 个进程:

    • 使用上述旧 WMI 方法:大约。 7000 毫秒
    • 使用下面的这种新方法:大约。 2400 毫秒

    方法:

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

    【讨论】:

    • 才看到这个但是使用Task.Run()是个坏习惯。您应该改用 cim 查询的异步版本 .QueryInstancesAsync
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-04-03
    • 1970-01-01
    相关资源
    最近更新 更多