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.
private void listViewServices_MouseDown(object sender,
System.Windows.Forms.MouseEventArgs e)
{
System.Windows.Forms.ListView listViewObject =
(System.Windows.Forms.ListView) sender;
ContextMenu mnuContextMenu = new ContextMenu();
MenuItem menuItem = new MenuItem();
ManagementObjectCollection queryCollection;
if (e.Button == System.Windows.Forms.MouseButtons.Right)
{
ServiceName = listViewObject.GetItemAt(e.X, e.Y).Text;
ServiceItem = listViewObject.GetItemAt(e.X,e.Y);
listViewObject.ContextMenu = mnuContextMenu;
try
{
queryCollection = getServiceCollection("SELECT * FROM
Win32_Service Where Name = '" + ServiceName + "'");
foreach ( ManagementObject mo in queryCollection)
{
if (mo["Started"].Equals(true))
{
menuItem.Text = "Stop";
ServiceAction = "StopService";
}
else
{
menuItem.Text = "Start";
ServiceAction = "StartService";
}
mnuContextMenu.MenuItems.Add(menuItem);
menuItem.Click += new System.EventHandler
(this.menuItem_Click);
}
}
catch (Exception e1)
{
MessageBox.Show("Error: " + e1);
}
}
}
private void menuItem_Click(object sender, System.EventArgs e)
{
ManagementObjectCollection queryCollection;
ListViewItem lvItem;
ManagementOperationObserver observer = new
ManagementOperationObserver();
completionHandler.MyHandler completionHandlerObj = new
completionHandler.MyHandler();
observer.ObjectReady += new ObjectReadyEventHandler
(completionHandlerObj.Done);
queryCollection = getServiceCollection("Select *
from Win32_Service Where Name ='" + ServiceName + "'");
updateStatus("Starting/Stopping service...");
foreach ( ManagementObject mo in queryCollection)
{
mo.InvokeMethod(observer, ServiceAction, 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 (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 Object
s.
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.
private void CreateProcess(string stringCommandLine)
{
ManagementOperationObserver observer = new
ManagementOperationObserver();
completionHandler.MyHandler completionHandlerObj = new
completionHandler.MyHandler();
observer.ObjectReady += new ObjectReadyEventHandler
(completionHandlerObj.Done);
string stringMachineName = "";
ConnectionOptions co = new ConnectionOptions();
if (radioMachine.Checked == true)
{
stringMachineName = "localhost";
}
else
{
stringMachineName = textIP.Text;
}
if (stringMachineName.Trim().Length == 0)
{
MessageBox.Show("Must enter machine IP address or name.");
return;
}
if (textUserID.Text.Trim().Length > 0)
{
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