
Introduction
This is a continuation of where I left off with Windows Management Instrumentation (WMI) in my last article 'My Explorer'. I will show how to access operating system information, services, and processes running on your machine as well as on a remote machine on your network, provided you have administration rights to them. Also I will show you how to start and stop services, terminate processes, and create new processes from WMI.
Getting Started
In this WMI application, I have created a WMIControlLibrary which contains four user controls. The four user controls are as follows: Explorer, SystemInfo, Services, and Processes. Each one of these controls does its own specific task. Here is a brief description of what each one of these control does.
Explorer Control - I converted 'My Explorer' to a user control, it still display your system drives, directories, and files information.
SystemInfo Control* - This control display operating system information and hardware data and specifications.
Services Control* - This control display services currently running on the system.
Process Control* - This control display processes currently running on the system.
(* Note: This control can be used to monitor local or remote system on the network.)
Every one of these controls uses the System.Management namespace to access their own specific system information.
Control's Status Event
Some of these controls take time to retrieve information back from the system, so I implemented the UpdateStatus(string e) event in each control. This will allow each control to update the status bar in the main application, to allow the user to see what the control is doing.
public delegate void Status(string e);
public event Status UpdateStatus;
UpdateStatus("Hello world.");
private void refreshStatusBar(string stringStatus)
{
statusBarStatus.Text = stringStatus;
}
Explorer Control

In the Explorer control, I used the WMI Win32_LogicalDisk class to get all the local and mapped drives on the local machine. To get access to drives information, I need to use the ManagementObjectSearcher class to obtain a ManagementOjbectCollection class containing the drive information I requested. We now have all the available drives' information at our disposal (such as drive name, type, volume, description, etc...). You can also just look for drives that have less then 1 Meg by changing the ManagementObjectSearcher parameter to:
Select * From Win32_LogicalDisk Where FreeSpace < 1000000
//get drive collection
ManagementObjectSearcher query = new ManagementObjectSearcher
("SELECT * From Win32_LogicalDisk ");
ManagementObjectCollection queryCollection = query.Get();
//loop throught each object to get drive information
foreach ( ManagementObject mo in queryCollection)
{
switch (int.Parse( mo["DriveType"].ToString()))
{
case Removable: //removable drives
imageIndex = 5;
selectIndex = 5;
break;
case LocalDisk: //Local drives
imageIndex = 6;
selectIndex = 6;
break;
case CD: //CD rom drives
imageIndex = 7;
selectIndex = 7;
break;
case Network: //Network drives
imageIndex = 8;
selectIndex = 8;
break;
default: //defalut to folder
imageIndex = 2;
selectIndex = 3;
break;
}
//get drive name
Console.WriteLine("Drive: " + mo["Name"].ToString());
}
SystemInfo Control

The SystemInfo control display many different types of information about your local machine or a remote machine on the network. It first establishes a ConnectionOptions object with the UserName and Password properties set. Then it creates a ManagementScope object with the local or remote host name and the ConnectionOptions object as the parameters.
ConnectionOptions co = new ConnectionOptions();
co.Username = textUserID.Text;
co.Password = textPassword.Text;
System.Management.ManagementScope ms = new System.Management.
ManagementScope("\\\\" + stringHostName + "\\root\\cimv2", co);
Now we are ready to access the system information by creating an ObjectQuery member object and passing it along with the ManagementScope object in to the ManagementObjectSearcher member object and invoke the Get() method to execute the command. Then I get back a ManagementObject collection containing the query information.
oq = new System.Management.ObjectQuery(
"SELECT * FROM Win32_OperatingSystem");
query = new ManagementObjectSearcher(ms,oq);
queryCollection = query.Get();
foreach ( ManagementObject mo in queryCollection)
{
createChildNode(nodeCollection, "Operating System: " +
mo["Caption"]);
createChildNode(nodeCollection, "Version: " + mo["Version"]);
createChildNode(nodeCollection, "Manufacturer : " +
mo["Manufacturer"]);
createChildNode(nodeCollection, "Computer Name : " +
mo["csname"]);
createChildNode(nodeCollection, "Windows Directory : " +
mo["WindowsDirectory"]);
}
If you are only concerned about the local host information, you can avoid the creation of ConnectionOption, ManagementScope, and ObjectQuery objects. All you need to do is just call the ManagementObjectSearcher member object with the query string and execute the ManagementObjectSearcher.Get() method to get the ManagementObjectCollection result back, for the local machine.
ManagementObjectSearcher query = new ManagementObjectSearcher
("SELECT * From Win32_OperatingSystem");
ManagementObjectCollection queryCollection = query.Get();
The SystemInfo control also displays the following information about the computer system being accessed: System Manufacturer, Processor, Bios, Time Zone, Memory, Network Connection, and Video Controller. The codes for these different queries are repetitive, it's just the query string and the result properties are different. So I will not display the code here to save space. You can download the code and look at them.
Service Control

The Service control uses the query:
SELECT * FROM Win32_Service
to retrieve all the services information in the system. To start or stop a service, I dynamically create a popup menu to the ListView. When you right click on an item, a start or stop menu pops up, depending on the service state. When the MenuItem is clicked, I need to get the ManagementObject for that service with this query:
SELECT * FROM Win32_Service WHERE Name = 'ServiceName'.
Then I call the ManagementObject.InvokeMethod() to start or stop the service. The first parameter in the InvokeMethod method is the Observer parameter. I pass in a ManagementOperationObserver class to manage asynchronous operations and handle management information and events received asynchronously. By checking the returnValue property in completionHandlerObj.ReturnObject, I can determine if the operation was successful or not.
{
MessageBox.Show("Terminate process timed out.",
"Terminate Process Status");
break;
}
System.Threading.Thread.Sleep(500);
intCount++;
}
if (completionHandlerObj.ReturnObject.
Properties["returnValue"].Value.ToString() == "0")
{
lvItem = ServiceItem;
if (ServiceAction == "StartService")
lvItem.SubItems[2].Text = "Started";
else
lvItem.SubItems[2].Text = "Stop";
}
else
{
string stringAction;
if (ServiceAction == "StartService")
stringAction = "start";
else
stringAction = "stop";
MessageBox.Show("Failed to " + stringAction +
" service " + ServiceName + ".",
"Start/Stop Service Failure");
}
ServiceName = "";
ServiceAction = "";
ServiceItem = null;
updateStatus("Ready");
this.Update();
}
using System;
using System.Management;
namespace completionHandler
{
public class MyHandler
{
private bool isComplete = false;
private ManagementBaseObject returnObject;
public void Done(object sender, ObjectReadyEventArgs e)
{
isComplete = true;
returnObject = e.NewObject;
}
public bool IsComplete
{
get
{
return isComplete;
}
}
public ManagementBaseObject ReturnObject
{
get
{
return returnObject;
}
}
}
}
Process Control

The Process control display the system running processes, user that started the process, CPU utilization, and memory usage. To get the process user, I need to call the GetOwner(User, Domain) method. The User and Domain parameters are output parameters. How do we get to these output parameters from InvokeMethod? This depends on how we implement the InvokeMethod. If we do not need to manage asynchronous operations, then we need to pass in a string[] to the InvokeMethod method to retrieve the output parameters. But if we need to manage asynchronous operations, then we do not need to pass in any parameters to InvokeMethod method. You will get the output parameters from the completionHandlerObj.ReturnObject properties collection.
string[] methodArgs = {"", ""};
mo.InvokeMethod("GetOwner", methodArgs);
mo.InvokeMethod(observer,"GetOwner", null);
while (!completionHandlerObj.IsComplete)
{
System.Threading.Thread.Sleep(500);
}
if (completionHandlerObj.ReturnObject["returnValue"].
ToString() == "0")
structProcess.stringUserName = completionHandlerObj.
ReturnObject.Properties["User"].Value.ToString();
else
structProcess.stringUserName = "";
Terminating process
Terminating a specific process is the same as starting or stopping a service. First get the ManagementObject for the selected process then call the InvokeMethod(observer, "Terminate", null) to kill the process.
ManagementOperationObserver observer = new
ManagementOperationObserver();
completionHandler.MyHandler completionHandlerObj = new
completionHandler.MyHandler();
observer.ObjectReady += new ObjectReadyEventHandler
(completionHandlerObj.Done);
queryCollection = getProcessCollection("Select * from
Win32_Process Where ProcessID = '" + ProcessID + "'");
updateStatus("Invoking terminate process");
foreach ( ManagementObject mo in queryCollection)
{
mo.InvokeMethod(observer, "Terminate", null);
}
int intCount = 0;
while (!completionHandlerObj.IsComplete)
{
if (intCount == 10)
{
MessageBox.Show("Terminate process timed out.",
"Terminate Process Status");
break;
}
System.Threading.Thread.Sleep(500);
intCount++;
}
if (intCount != 10)
{
if (completionHandlerObj.ReturnObject.Properties
["returnValue"].Value.ToString() == "0")
{
lvItem = ProcessItem;
lvItem.Remove();
}
else
{
MessageBox.Show("Error terminating process.",
"Terminate Process");
}
}
Creating process
To create a new process, we need to call the InvokeMethod method from the ManagementClass class. We can get the ManagementClass object as:
ManagementClass processClass = New ManagementClass(ms,path,null);
Here ms is the ManagementScope class and path is the ManagementPath class. The ManagementScope represents a scope for management operations. The ManagementPath class provides a wrapper for parsing and building paths to Win32_Process. We still need one more thing before calling the ManagementClass.InvokeMethod(observer, methodName, inParameters). We need to pass four parameters in to inParameters as an array of Objects.
uint32 Create(string CommandLine,
string CurrentDirectory,
Win32_ProcessStartup ProcessStartupInformation,
uint32* ProcessId);
Parameters
CommandLine - [in] Command line to execute. The system adds a null character to the command line, trimming the string if necessary, to indicate which file was actually used.
CurrentDirectory - [in] Current drive and directory for the child process. The string requires that the current directory resolves to a known path. A user can specify an absolute path or a path relative to the current working directory. If this parameter is NULL, the new process will have the same path as the calling process. This option is provided primarily for shells that must start an application and specify the application's initial drive and working directory.
ProcessStartupInformation - [in] The startup configuration of a Windows process. For more information see Win32_ProcessStartup.
ProcessId - [out] Global process identifier that can be used to identify a process. The value is valid from the time the process is created until the time the process is terminated.
object[] methodArgs = {stringCommandLine, null, null, 0};
processClass.InvokeMethod (observer, "Create", methodArgs);
Here is the code for implementing the Create process. I created a CreateProcess function which accepts a stringCommandLine parameter. When you call the CreateProcess("Calc.exe"), you will create a new calculator process. It's that simple.
{
co.Username = textUserID.Text;
co.Password = textPassword.Text;
}
System.Management.ManagementScope ms = new System.
Management.ManagementScope("\\\\" +
stringMachineName + "\\root\\cimv2", co);
//get process path
ManagementPath path = new ManagementPath( "Win32_Process");
//Get the object on which the method will be invoked
ManagementClass processClass = new ManagementClass
(ms,path,null);
//Status
updateStatus("Create process " + stringCommandLine + ".");
//Create an array containing all arguments for the method
object[] methodArgs = {stringCommandLine, null, null, 0};
//Execute the method
processClass.InvokeMethod (observer, "Create", methodArgs);
//wait until invoke method is complete or 5 sec timeout
int intCount = 0;
while (!completionHandlerObj.IsComplete)
{
if (intCount > 10)
{
MessageBox.Show("Create process timed out.",
"Terminate Process Status");
break;
}
//wait 1/2 sec.
System.Threading.Thread.Sleep(500);
//increment counter
intCount++;
}
if (intCount != 10)
{
//InvokeMethod did not time out
//check for error
if (completionHandlerObj.ReturnObject.Properties
["returnValue"].Value.ToString() == "0")
{
//refresh process list
this.Refresh();
}
else
{
MessageBox.Show("Error creating new process.",
"Create New Process");
}
}
//Status
updateStatus("Ready");
this.Update();
}
Conclusion
This was a fun experience for me creating this WMI demo application. This is just a small sample of what WMI could do. I think I have commented the code pretty well so it is easier to understand.
Here is a list of things you could use WMI for:
- Controlling Hardware and Software
- Monitoring Events
- Running a Script Based on an Event
- Sending E-mail Based on an Event
Here is a link to learn more about WMI:
Windows Management Instrumentation