C# - WMI query of multiple servers too slow - c#

I'm trying to return all enabled features from a remote win2008 server. And that wasn't any problem at all really - as long as I knew what exactly to query.
The problems I'm having however is when my query doesn't find a result, it takes forever to validate if the feature is installed or not - sometimes up to 2 minutes. (Not good enough when querying over 600 nodes).
The following code is the fastest way I've found of doing it, however as I said: it takes forever to return false:
public bool serverFeatureEnabled(string machineName, Win32_ServerFeature_ID id)
{
ManagementClass serviceClass = new ManagementClass("Win32_ServerFeature");
string strScope = string.Format(#"\\{0}\root\cimv2", machineName);
ConnectionOptions conOpt = new ConnectionOptions();
serviceClass.Scope = new ManagementScope(strScope, conOpt);
foreach (ManagementObject obj in serviceClass.GetInstances())
{
if ((UInt32)obj["ID"] == (uint)id)
{
return true;
}
}
return false;
}
Does anyone have a better idea of doing this, I don't mind if it not using WMI queries at all.
All I want to do is to speed it up a bit really.
I hope I made some sense!
Any help is appreciated.
Edit:
I have tried to "directly select required feature from server features with ManagementObjectSearcher class" as proposed by Sergrey V.
It did speed up the return of the first false, however it takes around 14 sec to finish, that all adds upp to 140 minutes of all the servers queried in the cluster.
Edit 2:
I tried to run tests against Win32_ServerFeature using WBEMTEST (Windows Management Instrumentation Tester), the connection to a remote computer seems to be the problem - running the test against one of the remote computers takes about 12 seconds to connect and around 2 seconds to return the data.
So the solution proposed by Sergrey V seems to be the fastest solution for WMI queries so far.

Don't run the action in series. I don't know if WMI has a bottleneck internally but the most obvious optimisation is to run the entire thing in Parallel.

Try to directly select required feature from server features with ManagementObjectSearcher class:
string serverName = "serverName";
string className = "Win32_ServerFeature";
string propertyName = "ID";
int propertyValue = 144;
string query = string.Format("SELECT * FROM {0} WHERE {1} = {2}", className, propertyName, propertyValue);
ManagementObjectSearcher searcher = new ManagementObjectSearcher(query);
string scopePath = string.Format(#"\\{0}\root\cimv2", serverName);
searcher.Scope = new ManagementScope(string.Format(scopePath));
ManagementObjectCollection mCollection = searcher.Get();
bool featureEnabled = mCollection.Count > 0;

So here is what I ended up doing:
First of all as proposed by M Afifi I started a new task for each node in a list.
The WMI queries are then being executed parallel using 'Task.Factory.StartNew()'.
(using System.Threading.Tasks;)
The full execution of all nodes in the list takes now about 12 sec!
referenceList.connectionNodes().ForEach(x =>
{
var genericTask = Task.Factory.StartNew(() =>
{
regkeyFactory.testParallelExecution(x);
});
});
Thank you guys for the help!

Faster way is to scan all in one, i've made an HWID class using ManagementClass and it takes only 0,07s to get the unique hardware id of the machine

Related

C# SCCM - Client Action

I've seen many ways in powershell to force a computer to do a client action from the configuration manager.
Where I do work, it is not possible because we can't invoke commands on distant computer because it is blocked and the senior IT do not want to unlock it.
I did find a library in c# that allow me to do some action in sccm :
AdminUI.SmsTraceListener.dll
AdminUI.WqlQueryEngine.dll
I can add/remove computer to a collections, make queries and get the data, but I didn't find the way to force a computer to make an action from the configuration manager.
Is there someone here that knows if it is possible and how?
Thanks.
Edit 1: While searching in the MSDN documentation, I did find the TriggerSchedule Method in Class SMS_Client but I don't find the way to use it correctly. I think it might be the way to go, but i'm still stuck on this.
It is possible to trigger an Machine Policy Update via TriggerSchedule like this
ManagementScope scope = new ManagementScope(#"\\.\root\ccm");
ManagementClass cls = new ManagementClass(scope.Path.Path, "SMS_Client", null);
ManagementBaseObject inParams = cls.GetMethodParameters("TriggerSchedule");
inParams["sScheduleID"] = "{00000000-0000-0000-0000-000000000021}";
ManagementBaseObject outMPParams = cls.InvokeMethod("TriggerSchedule", inParams, null);
You already found the other Parameters for the sScheduleID in the link you posted. This uses standard WMI. With WqlQueryEngine you would get access to some WMI wrappers that can basically do the same thing. I do not see many advantages however.
Using the scope like this
\\.\root\ccm
makes the whole thing only work locally which is what you want if I understood you correctly. Otherwise replacing the . With a hostname or IP would make it work remotely. Only thing I found a bit strange is that it needs administrative rights, which should in theory not be necessary for a policy update request.
if someone is having the issue that nothing is happening, it is because WMI required higher rights. To leave triggering the actions also by the user, I switched to use the CPApplet:
TriggerSccmActions("Request & Evaluate", true);
private static List<string> TriggerSccmActions(string stringActions, bool boolContains)
{
List<string> actionName = new List<string>();
try {
const string ProgID = "CPApplet.CPAppletMgr";
Type foo = Type.GetTypeFromProgID(ProgID);
dynamic COMobject = Activator.CreateInstance(foo);
var oClientActions = COMobject.GetClientActions;
foreach (var oClientAction in oClientActions)
{
if (oClientAction.Name.ToString().Contains(stringActions) && boolContains)
{
var result = oClientAction.PerformAction();
actionName.Add(oClientAction.Name.ToString());
}
else if (!(oClientAction.Name.ToString().Contains(stringActions)) && !(boolContains))
{
var result = oClientAction.PerformAction();
actionName.Add(oClientAction.Name.ToString());
}
}
} catch(Exception e)
{
actionName.Add("Error: " + e.Message.ToString());
}
return actionName;
}
For me, EvaluateMachinePolicy Method in Class SMS_Client class worked. Here is the code:
public static void RefreshMachinePolicy(string machineName)
{
ManagementScope scope = new ManagementScope(string.Format(#"\\{0}\root\ccm", machineName));
ManagementClass cls = new ManagementClass(scope.Path.Path, "SMS_Client", null);
ManagementBaseObject inParams = cls.GetMethodParameters("EvaluateMachinePolicy");
ManagementBaseObject outMPParams = cls.InvokeMethod("EvaluateMachinePolicy", inParams, null);
Console.WriteLine("Policy refreshed successfully by EvaluateMachinePolicy method");
}
Here is the MSDN link for method details. Please include below namespace at the top of your source code file:
using System.Management;

Unable to format a drive (Unknown Error)

I'm using the WMI Volume ManagementObject to format a drive (Docs). If I try to do this from a non-elevated application, I get a result code of 3 (Access denied) which makes sense. Running the application as an administrator means I get a return code of 18 (Unknown Error).
ManagementObjectSearcher searcher = new ManagementObjectSearcher(
String.Format("select * from Win32_Volume WHERE DriveLetter = \"{0}\"", Drive));
foreach (ManagementObject vi in searcher.Get()) {
FormatResult result = (FormatResult)(int)(uint)vi.InvokeMethod("Format", new object[] { FileSystem, QuickFormat, ClusterSize, Label, EnableCompression });
if (result != FormatResult.Success) {
throw new FormatFailedException(String.Format("{0} (Error code {1})", result.ToString(), (int)result));
}
}
Parameters:
FileSystem: "NTFS"
Quick: false
ClusterSize: 4096
Label: "Test"
EnableCompression: false
I can format the drive with the above parameters through Explorer without any problems. How can I diagnose the issue and find out what's going on?
Since there seems to be some confusion, FormatResult is just an enum to simplify handling return codes...
enum FormatResult {
Success = 0,
UnsupportedFileSystem = 1,
IncompatibleMediaInDrive = 2,
AccessDenied = 3,
CallCanceled = 4,
...
UnknownError = 18
}
To give an idea of what I'm doing:
Since the docs mention that this method is usually called asynchronously, I've tried switching to an Async call with the same result (code available upon request). I've also tried various combinations of FileSystem (NTFS/FAT32), ClusterSize (including 0 to let the system pick a default), and Quick/Full formats. All give the same result of 18.
What am I missing?

How to obtain IP addresses of everything on network in c#

I have a sonicwall on a network of around 100 machines.
I have tried and tried to find a way of creating 2 combobox's that will contain both the IP addresses linked to the sonicwall, as well as the currently logged in user of the machine as well.
What i am trying to create is a employee monitoring software (like Interguard/ActivTrak/etc), but am unable at present to even get near to finding this information?
I've been researching A LOT of stuff (~25 pages of google, with not a single link not clicked), and have met no conclusion as to get this information.
I'm not exactly a great programmer, but I would LOVE to be able to get this sort of information into two combobox's/arrays/other suitable object/control.
If anyone knows a way of even creating an array of IP addresses, along with corresponding Logins, that would be of great help to this project!
(PLEASE NOTE: I know there is a Networking exchange site, but I have already looked! Also, since i'm designing a piece of software for this, I thought i'd ask it here!)
Thanks for any advice/suggestions much appreciated!
What you looking for is similar to a packet sniffer application like wireshark or ethereal. But if you want to design it yourself, I think your best solution is a collection/list to store the data that is binded to a datagrid.
If you're in an Active Directory domain, you could use the code from my S/O question. Instead of using the CheckConnectionAsync() method to check for connectivity, you could just re-write it to get the IP address using System.Net.Dns.
To retrieve the user, you can use WMI. For this, you need to make a reference to the System.Management namespace.
public Task<string> GetUserName(string ComputerName)
{
return Task.Run(() =>
{
ConnectionOptions conn = ConnectionOptions()
{
EnablePrivileges = true,
Username = // string in format of #"DomainName\UserName",
Password = password,
Authentication = AuthenticationLevel.PacketPrivacy,
Impersonation = ImpersonationLevel.Impersonate
};
ManagementScope scope = new ManagementScope(#"\\" + ComputerName + #"\root\cimv2", conn);
try
{
scope.Connect();
ObjectQuery user = new ObjectQuery("Select UserName From Win32_ComputerSystem");
ManagementObjectSearcher searcher = new ManagementObjectSearcher(scope, user);
ManagementObjectCollection collection = searcher.Get();
foreach (ManagementObject m in collection)
{
string username = m["UserName"].ToString().Trim();
if(!String.IsNullOrEmpty(username))
{
return username;
}
}
return null; // no current logged in user
}
catch (Exception) // error handling...
});
}
Then, just iterate through your collection of computers and retrieve the username like so:
private async Task RetrieveUsers()
{
Parallel.ForEach(Computers, c =>
{
string user = await GetUserName(c.Name); // or c.IP. both work.
});
}

How to list all local computer user profiles in a combo box?

I need to enumerate all user profiles on a local computer and list them in a combo box. Any special accounts need to be filtered out. I'm only concerned about actual user profiles on the computer where the app is running. I have done some searching but I haven't found a clear answer posted anywhere. I did find some code that might work but SelectQuery and ManagementObjectSearcher are displaying errors in VS and I'm not sure what I need to do to make this work.
using System.Management;
SelectQuery query = new SelectQuery("Win32_UserAccount");
ManagementObjectSearcher searcher = new ManagementObjectSearcher(query);
foreach (ManagementObject envVar in searcher.Get())
{
Console.WriteLine("Username : {0}", envVar["Name"]);
}
By saying "SelectQuery and ManagementObjectSearcher are displaying errors" I guess you didn't reference the System.Management dll.
You should right click References in your solution and add System.Management.
Then, with your using statement, the errors should disappear.
Anyway, including the error next time will assist everyone to help you :)
The mentioned code is great but when I tried on a machine connected to a Active Directory Domain all the usernames where returned for the domain. I was able to tweak the code a bit to only return the users that actually have a local directory on the current machine. If a better C# developer can refactor the code to make it cleaner - please help!
var localDrives = Environment.GetLogicalDrives();
var localUsers = new List<string>();
var query = new SelectQuery("Win32_UserAccount") { Condition = "SIDType = 1 AND AccountType = 512" };
var searcher = new ManagementObjectSearcher(query);
foreach (ManagementObject envVar in searcher.Get())
{
foreach (string drive in localDrives)
{
var dir = Path.Combine(String.Format("{0}Users", drive), envVar["name"].ToString());
if (Directory.Exists(dir))
{
localUsers.Add(envVar["name"].ToString());
}
}
}
Once you have the localUsers variable you can set this as the data source to your ComboBox control of our choice.

How can I get the Workgroup name of a computer using C#?

I've read about getting it with the Environment class, but can't find it.
Thanks guys.
A way based on the Jono response, but shorter:
public static string GetWorkGroup()
{
ManagementObject computer_system = new ManagementObject(
string.Format(
"Win32_ComputerSystem.Name='{0}'",
Environment.MachineName));
object result = computer_system["Workgroup"];
return result.ToString();
}
You can do this with WMI; add a reference to System.Management.dll and a using statement for System.Management namespace, then call the following code:
ManagementObjectSearcher mos =
new ManagementObjectSearcher(#"root\CIMV2", #"SELECT * FROM Win32_ComputerSystem");
foreach (ManagementObject mo in mos.Get()) {
Console.WriteLine(mo["Workgroup"]);
}
I tried this using the WMI options suggested here, but it turned out to be excruciatingly slow (sometimes over 5 seconds) on my machine (and several others in my office). What ended up working for me was using the API call "NetGetJoinInformation" (PInvoke.net). The API call returns very quickly for me and does exactly what I need.
Look here for an example. You will have to use P/Invoke.

Categories