Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

Working With Windows Management Instrumentation (WMI) - Part 4

4.00/5 (4 votes)
23 Feb 2010CPOL7 min read 3  
This article further dives deeper into useful utility functions that can help you build powerful WMI based applications.

Introduction

In this article, I mainly focus on a useful utility that will help the developers to accomplish several housekeeping tasks which will in turn make their WMI based applications much more robust and scalable. In the previous part of this series, I have discussed how we can write several VM management routines and integrate them into our custom application. However any application is incomplete without having to do certain housekeeping work like copying files, directories, deleting files, creating and executing processes, etc. In this part, I focus on one such utility and discuss why it would be useful to use WMI to do some of this rather than plain Win32 calls. Once you get the idea, then you can generically extend this method and use it in your own applications.

Background

WMI is quite a powerful way to manage multiple systems in a datacenter. WMI provides a layer of abstraction over the specifics of a subsystem while providing common framework for developers to write powerful manageability solutions. We have so far discussed how WMI classes and methods can be instantiated and used from within your C# applications. I specifically also discussed how the Hyper-V management methods and classes worked. In order to weave in some powerful functionality, I thought it might be appropriate to discuss some utilities that I have used extensively in my applications. I take you through one such utility that will help you create processes on remote systems and instantiate operations like Rocbocopy, or plain copy, etc. and also show you how to use the “System.Management” class to wait on completion of remote processes.

Creating a Remote Process

I think most people are familiar with how processes are created in Windows. However I just want to run through the concept of processes and then talk about the WMI way of doing it. As most people are aware, Windows exposes several important APIs through the Win32 programming API. This forms the backbone of several applications. People usually write their own wrappers around Win32 APIs and build complex functionality on top of it. Let's say you would like to create a process and execute a program called "foo.exe". So in the traditional Win32 approach, you would use the CreateProcess() API and pass the name of your process "foo.exe" to the API. While this approach works fine for most cases, it may be necessary to initiate the CreateProcess() from a remote computer on a remote computer using the binary in the remote computer. This is where using WMI makes a lot of sense.

Instantiating a Remote Process - The Win32_Process Class

The class we would be using in this article is the Win32_Process class. Win32_Process class consists of all the attributes and methods that are used for process manipulation and creation. The snippet below shows the attributes and methods supported by this class.

Attributes

C#
//Attributes of the Win32_class

class Win32_Process : CIM_Process
{
  string   Caption;
  string   CommandLine;
  string   CreationClassName;
  datetime CreationDate;
  string   CSCreationClassName;
  string   CSName;
  string   Description;
  string   ExecutablePath;
  uint16   ExecutionState;
  string   Handle;
  uint32   HandleCount;
  datetime InstallDate;
  uint64   KernelModeTime;
  uint32   MaximumWorkingSetSize;
  uint32   MinimumWorkingSetSize;
  string   Name;
  string   OSCreationClassName;
  string   OSName;
  uint64   OtherOperationCount;
  uint64   OtherTransferCount;
  uint32   PageFaults;
  uint32   PageFileUsage;
  uint32   ParentProcessId;
  uint32   PeakPageFileUsage;
  uint64   PeakVirtualSize;
  uint32   PeakWorkingSetSize;
  uint32   Priority;
  uint64   PrivatePageCount;
  uint32   ProcessId;
  uint32   QuotaNonPagedPoolUsage;
  uint32   QuotaPagedPoolUsage;
  uint32   QuotaPeakNonPagedPoolUsage;
  uint32   QuotaPeakPagedPoolUsage;
  uint64   ReadOperationCount;
  uint64   ReadTransferCount;
  uint32   SessionId;
  string   Status;
  datetime TerminationDate;
  uint32   ThreadCount;
  uint64   UserModeTime;
  uint64   VirtualSize;
  string   WindowsVersion;
  uint64   WorkingSetSize;
  uint64   WriteOperationCount;
  uint64   WriteTransferCount;
};

Methods

  • AttachDebugger - Launches the currently registered debugger for a process.
  • Create - Creates a new process.
  • GetOwner - Retrieves the user name and domain name under which the process is running.
  • GetOwnerSid - Retrieves the security identifier (SID) for the owner of a process.
  • SetPriority - Changes the execution priority of a process.
  • Terminate - Terminates a process and all of its threads.

In the above list of methods, the method that is of interest to us is the "Create" method. The Create method allows us to create a process similar to the Win32 API "CreateProcess()" call. The "Create" method takes two input parameters. One is the commandline to the process and other is the current directory from where the execution must happen.

C#
public static UInt32 RemoteCopy(String Node, String UserName, 
	String Password, String VMname, String src, String dst, Boolean local)
{
            if (local.Equals(false))
            {
                ManagementClass process = new ManagementClass("Win32_Process");
                ConnectionOptions co1 = new ConnectionOptions();
                co1.Username = UserName;
                co1.Password = Password;
                ManagementPath mp1 = new ManagementPath(@"\\" + Node + @"\" + @"root\cimv2");

                process.Scope = new ManagementScope(mp1, co1);
                process.Scope.Connect();

                ManagementBaseObject inparams = process.GetMethodParameters("Create");

                inparams["CommandLine"] = @"cmd.exe /c copy " + @src + " " + @dst;
                ManagementBaseObject outparams = 
				process.InvokeMethod("Create", inparams, null);

                String ProcID = outparams["ProcessID"].ToString();
                String retval = outparams["ReturnValue"].ToString();
}

In the above snippet, I am passing the node name, username, password and a Boolean value called local, which if set to true means the function is going to be executed locally and if false it’s a remote node. I also pass the source and destination folders on which the copy must work. There are several more efficient ways to figure out if the node is local or remote, but for now we will stick to this approach of passing an explicit Boolean value.

As usual, we set up the connection object and then connect to our namespace which is the root\cimv2. We use this namespace since it consists of all the system management classes. Since we have connected to the Win32_Process, we can now get the parameters needed for the "Create" method. The "Create" method takes the commandline parameters and the current directory. The current directory is not so relevant here since we pass the full path through the commandline parameters itself. Once we invoke the method, we get back the output parameters as usual in the “outparams” array. We get back two parameters in the output - one is the ProcessID of the process that has been created and any return value from the Create itself - for example if the Create failed and an error is returned to us.

At this point, the operation is only partially complete. As you might have noticed, we have initiated a copy operation. As long as there is no subsequent dependency of this method then you are good, however most often the next lines of code are dependent on the copy to have been completed. So it’s important for us to wait until this process completes. In the sub section below, I will show you how to write some simple code that will allow our code to wait until the process completes so that we can move on to other dependent functions.

Waiting on Completion of a Remote Process

In this section, we will improve our previous function in order to make it more complete. Anyone who has used CreateProcess locally in a system would know that most times you need to wait till that process completes. However when you are doing this locally, there are several ways to manage this using synchronization primitives like WaitForSingleObject.

The following snippet shows the additional lines of code that we can add to the method above in order to make it wait for the process to complete before returning.

C#
string pol = "2";

string queryString =
                    "SELECT *" +
                    "  FROM __InstanceOperationEvent " +
                    "WITHIN  " + pol +
                    " WHERE TargetInstance ISA 'Win32_Process' " +
                    "   AND TargetInstance.Name = '" + "robocopy.exe" + "'";
WqlEventQuery wql = new WqlEventQuery(queryString);
// You could replace the dot by a machine name to watch to that machine
//string scope = @"\\" + Node + @"\" + @"root\cimv2";
mp1 = new ManagementPath(@"\\" + Node + @"\" + @"root\cimv2");
co1 = new ConnectionOptions();
co1.Username = UserName;
co1.Password = Password;
ManagementScope Scope = new ManagementScope(mp1, co1);
Scope.Connect();
// create the watcher and start to listen
ManagementEventWatcher watcher = new ManagementEventWatcher();
watcher.Query = wql;
watcher.Scope = Scope;
int i = 1;
while (i == 1)
{
    ManagementBaseObject MBOobj = watcher.WaitForNextEvent();
    if ((((ManagementBaseObject)MBOobj["TargetInstance"])
    	["ProcessID"].ToString() == ProcID))
    {
        i = 2; //just exit otherwise it will block
        Console.WriteLine("Robocop Exit event arrived");
    }
}
watcher.Stop();

So in the above snippet, we initialize a WQL query (WMI Query Lanaguage) - remember WQL is similar to the syntax of SQL and allows you to write nested queries that can narrow down the object you wish to get from the CIM namespace. We initialize a WQL query event that will be used to notify us. This takes the query that we have created and returns an object that is of WqlEventQuery type. We then initialize a ManagementEvenWatcher that actually watches for an event to occur as specified by the WqlEventQuery object. We pass the WqlEventQuery object "Wq1" and the "Scope" of our object to the "Watcher" object. Now we create an infinite loop for polling for the event that interests us. "watcher.WaitForNextEevent" will track all events that are returned. Once we compare the event returned to our ProcessID, then we immediately exit the loop. As you can see, there is a potential for getting into an infinite loop in case the process hangs for some reason. I am sure you can modify it in such a way as to avoid potential hang issues (like timeouts).

Now putting the two pieces of code together, we have the following function complete with the ability to create a process remotely and wait over it to finish.

C#
public static UInt32 RemoteCopy(String Node, String UserName, 
	String Password, String VMname, String src, String dst, Boolean local)
{
            if (local.Equals(false))
            {
                ManagementClass process = new ManagementClass("Win32_Process");
                ConnectionOptions co1 = new ConnectionOptions();
                co1.Username = UserName;
                co1.Password = Password;
                ManagementPath mp1 = new ManagementPath(@"\\" + Node + @"\" + @"root\cimv2");

                process.Scope = new ManagementScope(mp1, co1);
                process.Scope.Connect();

                ManagementBaseObject inparams = process.GetMethodParameters("Create");

                inparams["CommandLine"] = @"cmd.exe /c Robocopy " + @src + " " + @dst;
                ManagementBaseObject outparams = 
			process.InvokeMethod("Create", inparams, null);

                String ProcID = outparams["ProcessID"].ToString();
                String retval = outparams["ReturnValue"].ToString();

               string pol = "2";
               string queryString =
                    "SELECT *" +
                    "  FROM __InstanceOperationEvent " +
                    "WITHIN  " + pol +
                    " WHERE TargetInstance ISA 'Win32_Process' " +
                    "   AND TargetInstance.Name = '" + "robocopy.exe" + "'";

                WqlEventQuery wql = new WqlEventQuery(queryString);               
                mp1 = new ManagementPath(@"\\" + Node + @"\" + @"root\cimv2");
                co1 = new ConnectionOptions();
                co1.Username = UserName;
                co1.Password = Password;
                ManagementScope Scope = new ManagementScope(mp1, co1);
                Scope.Connect();

                // create the watcher and start to listen
                ManagementEventWatcher watcher = new ManagementEventWatcher();
                watcher.Query = wql;
                watcher.Scope = Scope;
                int i = 1;
                while (i == 1)
                {
                    ManagementBaseObject MBOobj = watcher.WaitForNextEvent();
                    if ((((ManagementBaseObject)MBOobj
			["TargetInstance"])["ProcessID"].ToString() == ProcID))
                    {
                        i = 2; //just exit otherwise it will block
                        Console.WriteLine("Robocop Exit event arrived");
                    }
                }
                watcher.Stop();
    return ((UInt32)0);//return success
}

With this, I complete one part of the utility functions that can assist you with developing more robust WMI based applications. In subsequent parts, I might try to cover different methods and utilities and also perhaps experiment with a different programming language like C++ (which continues to be a very popular language among developers).

Reference

  • MSDN Online API Reference WMI SDK Samples and documentation

History

  • 23rd February, 2010: Initial post

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)