Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Windows Management Instrumentation (WMI) Implementation

0.00/5 (No votes)
19 Sep 2002 1  
Implementing Windows Management Instrumentation (WMI) in your application.

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.

//Control Code
//delegate status event
public delegate void Status(string e);
public event Status UpdateStatus;
//Update status bar
UpdateStatus("Hello world.");

//Main Application Code
//Set all control UpdateStatus event to this fuction
private void refreshStatusBar(string stringStatus)
{
    //update status bar
    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.

//Connect to the remote computer
ConnectionOptions co = new ConnectionOptions();

co.Username = textUserID.Text;
co.Password = textPassword.Text;

//Point to machine
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.

//Query system for Operating System information
oq = new System.Management.ObjectQuery(
    "SELECT * FROM Win32_OperatingSystem");
query = new ManagementObjectSearcher(ms,oq);

queryCollection = query.Get();
foreach ( ManagementObject mo in queryCollection)
{
    //create child node for operating system
    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

Services Image

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.

/// <summary>
/// List view mouse down event to built 
/// context menu dynamically 
/// </summary>
/// 
/// 
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;

    //check if right button
    if (e.Button == System.Windows.Forms.MouseButtons.Right) 
    {
        //get service name
        ServiceName = listViewObject.GetItemAt(e.X, e.Y).Text;
        //set list view item
        ServiceItem = listViewObject.GetItemAt(e.X,e.Y);

        //create popup menu
        listViewObject.ContextMenu = mnuContextMenu;
        try
        {
            //get specific service object
            queryCollection = getServiceCollection("SELECT * FROM 
                Win32_Service Where Name = '" + ServiceName + "'");
            foreach ( ManagementObject mo in queryCollection)
            {
                //create menu depending on service state
                if (mo["Started"].Equals(true))
                {
                    menuItem.Text = "Stop";
                    //set action property
                    ServiceAction = "StopService";
                }
                else
                {
                    menuItem.Text = "Start";
                    ServiceAction = "StartService";
                }
                mnuContextMenu.MenuItems.Add(menuItem);

                // Add functionality to the menu items 
                //using the Click event. 
                menuItem.Click  += new System.EventHandler
                    (this.menuItem_Click);
            }
        }
        catch (Exception e1)
        {
            MessageBox.Show("Error: " + e1);
        }
    }
}

/// <summary>
/// List view context menu click event to invoke start/stop service
/// </summary>
/// 
/// 
private void menuItem_Click(object sender, System.EventArgs e)
{   
    ManagementObjectCollection queryCollection;
    ListViewItem lvItem;

    //Set up a handler for the asynchronous callback
    ManagementOperationObserver observer = new 
        ManagementOperationObserver(); 
    completionHandler.MyHandler completionHandlerObj = new 
        completionHandler.MyHandler(); 
    observer.ObjectReady += new ObjectReadyEventHandler
        (completionHandlerObj.Done);

    //get specific service object
    queryCollection = getServiceCollection("Select * 
        from Win32_Service Where Name ='" + ServiceName + "'");
    
    //Status 
    updateStatus("Starting/Stopping service..."); 
    
    foreach ( ManagementObject mo in queryCollection)  
    { 
        //start or stop  service 
        mo.InvokeMethod(observer, ServiceAction, null); 
    } 
    //wait until invoke method is complete  or 5 sec timeout 
    
    int intCount = 0;  
    while
    (!completionHandlerObj.IsComplete) 
    { 
        if 
        (intCount >  10)
        {
            MessageBox.Show("Terminate process timed out.", 
                "Terminate Process Status");
            break;
        }
        //wait 1/2 sec.
        System.Threading.Thread.Sleep(500); 
    
        //increment counter
        intCount++;
    } 

    //see if call was successful.
    if (completionHandlerObj.ReturnObject.
        Properties["returnValue"].Value.ToString() == "0")
    { 
        //succeeded
        lvItem = ServiceItem;

        if (ServiceAction == "StartService")
            lvItem.SubItems[2].Text = "Started";
        else
            lvItem.SubItems[2].Text = "Stop";
    }
    else
    {
        //error message
        string stringAction;

        if (ServiceAction == "StartService")
            stringAction = "start";
        else
            stringAction = "stop";

        MessageBox.Show("Failed to " + stringAction + 
            " service " + ServiceName + ".", 
            "Start/Stop Service Failure");
    }

    //clean-up objects
    ServiceName = "";
    ServiceAction = "";
    ServiceItem = null;

    //Status
    updateStatus("Ready");
    this.Update();
}


//----------------------------------
// Completion Handler 
//----------------------------------
using System;
using System.Management;

namespace completionHandler
{
    /// <summary>
    /// MyHandler class handle notification 
    /// when InvokeMethod call is complete
    /// </summary>
    public class MyHandler
    {
        private bool isComplete = false;
        private ManagementBaseObject returnObject;
             
        /// <summary>
        /// Trigger Done event when InvokeMethod is complete
        /// </summary>
        public void Done(object sender, ObjectReadyEventArgs e)
        { 
            isComplete = true;
            returnObject = e.NewObject;
        }


        /// <summary>
        /// Get property IsComplete
        /// </summary>
        public bool IsComplete 
        {
            get 
            {
                return isComplete;
            }
        }

        /// <summary>
        /// Property allows accessing the result 
        /// object in the main function
        /// </summary>
        public ManagementBaseObject ReturnObject 
        {
            get 
            {
                return returnObject;
            }
        }

    }
}

Process Control

Processes Image

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.

//------------------------------------------------- 
//Get process owner info without the observer object 
//-------------------------------------------------- 
//Createan array containing all arguments for the method

string[] methodArgs = {"", ""}; 

//Get process owner info 
mo.InvokeMethod("GetOwner", methodArgs); 

//methodArgs[0] - contain process user 
//methodArgs[1] = contain process domain 

//-----------------------------------------------
//Getprocess owner info with the observer object
//-----------------------------------------------
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.

//Set up a handler for the asynchronous callback
ManagementOperationObserver observer = new 
    ManagementOperationObserver(); 
completionHandler.MyHandler completionHandlerObj = new 
    completionHandler.MyHandler(); 
observer.ObjectReady  += new ObjectReadyEventHandler
    (completionHandlerObj.Done);

//Get ManagementObject for process
queryCollection = getProcessCollection("Select * from 
    Win32_Process Where ProcessID = '" + ProcessID + "'");

//Status
updateStatus("Invoking terminate process");

foreach ( ManagementObject mo in queryCollection)
{
    //start or stop service
    mo.InvokeMethod(observer, "Terminate", null);
}

//wait until invoke method is complete or 5 sec timeout
int intCount = 0;
while (!completionHandlerObj.IsComplete) 
{ 
    if (intCount == 10)
    {
        MessageBox.Show("Terminate 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
    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.
//Create an array containing all arguments for the method
object[] methodArgs = {stringCommandLine, null, null, 0};

//Execute the method
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.

/// <summary>
/// Invoke method 'Create' on local or remote machine
/// </summary>
/// 
private void CreateProcess(string stringCommandLine)
{   
    //Set up a handler for the asynchronous callback
    ManagementOperationObserver observer = new 
        ManagementOperationObserver(); 
    completionHandler.MyHandler completionHandlerObj = new 
        completionHandler.MyHandler(); 
    observer.ObjectReady  += new ObjectReadyEventHandler
        (completionHandlerObj.Done);

    string stringMachineName = "";

    //Connect to the remote computer
    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;
    }

    //get user and password
    if (textUserID.Text.Trim().Length   > 0)
    {
        co.Username = textUserID.Text;
        co.Password = textPassword.Text;
    }

    //Point to machine
    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

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here