2

I'm creating remote task manager app and I'm trying to figure out how to get process owner of process running on remote machine without WMI. With WMI it's really easy, but it's too slow. I'm tried to use WTSQuerySessionInformation, but it only worked for local machine.

For closer specification, my remote task manager app will run on workstations and will connect against another workstations and also against servers in the same network. User, which will run the app, wil be administrator on both machines.

Please, do you know some another way how to get owner of remote process, or some improvement/fix for my code below?

My WMI version (it's too slow...)

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

My WTSQuerySessionInformation version (works only for local machine)

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 in separate class:

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
}
Blaato
  • 129
  • 10

2 Answers2

2

I would recommend moving to the newer namespace as System.Management is older, slower and doesn't scale. The newer framework you're after is Microsoft.Management.Infrastructure. Here is the Microsoft documentation explaining this as well as examples for both.

So you would use something like this:

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

OR

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

I hope this gives you some new rabbit holes to run down :-D Let us know if you need anything else :)

I.T Delinquent
  • 2,305
  • 2
  • 16
  • 33
  • Thank you for your answer. After quick study it's really way better then old `System.Management`. In fact there is not big time difference, but... It's leaded me to my finale solution below, so I'm really gratefull to you. :) – Blaato Aug 28 '19 at 12:33
-1

After some tests and thanks to great tip from @I.T Delinquent I created the final method for getting the process owner. It's still not super fast, but it's fast enough. Against WMI method above in my question is there more then 60% speed-up and I believe that there is still space for improvement.

Example: Getting data (process owner, ID, Handle, ExecutablePath, Description, CommandLine) from workstation in another VLAN, but same network domain and with approx. 200 processes:

  • With old WMI method above: approx. 7000ms
  • With this new method below: approx. 2400ms

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;
}
Community
  • 1
  • 1
Blaato
  • 129
  • 10
  • 2
    Only just seen this but using `Task.Run()` is a bad habit. You should instead use the async version of the cim query `.QueryInstancesAsync` – I.T Delinquent Jan 16 '20 at 11:39