问题
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
}
回答1:
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 :)
回答2:
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;
}
来源:https://stackoverflow.com/questions/57688574/how-to-get-owner-of-process-running-on-remote-machine-without-wmi