Process Information And Notifications using WMI

I have often needed information about a process running or to see if it is running. Fortunately . NET framework comes up with System.Diagnostics name space, which is providing very useful classes like Process to access all kind of information about running process. But what if the process is not running and you need to wait idle until it completes the startup? In this case you will need an event raised to you as soon as the process has been started. That is where WMI comes very handy. You can do everything what System.Diagnostics provides to you and a little bit more by using WMI.

Background (Windows.Management Namespace)

WMI is the Microsoft implementation of Web-Based Enterprise Management (WBEM), an industry initiative to develop a standard technology for accessing management information in an enterprise environment. This initiative helps companies lower their total cost of ownership by enabling powerful management of systems, applications and devices.

This namespace provides several classes. Some of the are used to query information about a system resource like hard disk, Network Adaptor, Windows Service, Process, etc. I will use some of these classes to get the list of running processes. Query the system can take some unnecessary time of your running thread, that’s when the other set of classes come handy. You can subscribe to some system resources to get notification when your requested action takes place. I will use these classes to subscribe for process instantiation and termination.

Using the code

This sample code is just starting point to put you to the right direction and opens a hole powerful technology. You can use the same technics to query all about the Windows system. If anything is runing on your machine, like memory card, you can query it.

WQL = WMI Query Language

The WMI Query Language (WQL) is a subset of standard American National Standards Institute Structured Query Language (ANSI SQL) with minor semantic changes to support WMI.

An example of wql which will result to our ptocess list would look like this:

string queryString = "SELECT Name, ProcessId, Caption, ExecutablePath" +           " FROM Win32_Process";  

A SelectQuery can be instantiated using that string or also like the following

SelectQuery query = new SelectQuery(className, condition, selectedProperties); 

Scope Object

The scope is like the database you are sending the query to. It mentions the machine name and then the schema and then the path to get to the resource. A local machine in Microsoft platform is usually referred to by a dot. Our example to get the list of processes on the local machine will look like the following:

ManagementScope scope = new System.Management.ManagementScope(@"file://root/CIMV2"); 

Searcher Object

Now that we have our two base classes, we can create the query using the searcher class which and execute it by calling Get() method which returns us a collection of management objects.

    ManagementObjectSearcher searcher =          new ManagementObjectSearcher(scope, query);          ManagementObjectCollection processes = searcher.Get(); 

Retrieving the detail
From here we can just iterate through the processes’ properties and get our information.

foreach(ManagementObject mo in processes)  {      DataRow row = result.NewRow();      row["Name"] = mo["Name"].ToString();      row["ProcessId"] = Convert.ToInt32(mo["ProcessId"]);      if (mo["Caption"]!= null)          row["Caption"] = mo["Caption"].ToString();      if (mo["ExecutablePath"]!= null)          row["Path"] = mo["ExecutablePath"].ToString();      result.Rows.Add( row );  }  

Subscribing to an Event

So far we have just got a query to the system repository. Now the second part is even more intresting. Assume you are depending on a service running on a machine. Or you want to do an action when a service goes down. Or in this example find out when an application has been created (added to the process list).

All you need is a ManagementEventWatcher which has a delegate where you can subscribe. It has methods like Start() and Stop() which launch a different thread. And similar to the searcher object it works with a scope and a query.

string pol = "2";  string queryString =      "SELECT *" +      " FROM __InstanceOperationEvent " +      "WITHIN " + pol +      " WHERE TargetInstance ISA 'Win32_Process' " +      "   AND TargetInstance.Name = '" + appName + "'";      // You could replace the dot by a machine name to watch to that machine  string scope = @"//./root/CIMV2";  // create the watcher and start to listen  watcher = new ManagementEventWatcher(scope, queryString);  watcher.EventArrived +=  new EventArrivedEventHandler(this.OnEventArrived);  watcher.Start();  

Al of this makes it possible for us to use this class easily to figure out what happens for a process like notepad.exe

notePad = new ProcessInfo("notepad.exe");  notePad.Started +=      new Win32Process.ProcessInfo.StartedEventHandler(this.NotepadStarted);  notePad.Terminated +=      new Win32Process.ProcessInfo.TerminatedEventHandler(          this.NotepadTerminated);  

Note

It is possible to call Set the property values on the query and submit it. That is slightly more work but still very powerful.

Points of Interest

After I had installed WMI Server Explorer on my machine, I’ve got my Server Explorer on the Visual Studio extended to provide very nice tool which tels me what is correct name for different resources. And I was actually surprised how many resources I can access now.

See Also MSDN

Resource File Generator (Resgen.exe)

The Resource File Generator converts .txt files and .resx (XML-based resource format) files to common language runtime binary .resources files that can be embedded in a runtime binary executable or compiled into satellite assemblies. For information about deploying and retrieving .resources files, see Resources in Applications.

Resgen.exe performs the following conversions:

  • Converts .txt files to .resources or .resx files.
  • Converts .resources files to text or .resx files.
  • Converts .resx files to text or .resources files.
resgen [/compile] filename.extension [outputFilename.extension][…]

Option Description

/compile

Allows you to specify multiple .resx or .txt files to convert to .resources files in a single bulk operation. If you do not specify this option, you can specify only one input file argument.


/r: assembly

Specifies that types are to be loaded from assembly. If you specify this option, a .resx file with a previous version of a type will use the type in assembly.


/str: language[,namespace[,classname[,filename]]]

Creates a strongly-typed resource class file in the programming language (C# or Visual Basic) specified in the language option. You can use the namespace option to specify the project’s default namespace, the classname option to specify the name of the generated class, and the filename option to specify the name of the class file.

/usesourcepath

Specifies that the input file’s current directory is to be used for resolving relative file paths.

resgen myResources.resx myResources.resources /str:C#,Namespace1,MyClass,MyFile.vb
 

Binding Data To The User Interface

This is not the very first time I am digging this subject but this is the first time I feel like I don’t know nothing. I am going to start from scratch and explore different technics on this subject…and again, I will use Amit Kalani’s book. This book will help me explore the following concepts :

  • Simple Data Binding
  • Complex Data Binding
  • One-way and two-way Data Binding
  • The BindingContext object
  • The Data Form Wizard

Simple Data Binding

Simple data binding means connecting a single value from the data model to a single property of a control. For example, you might bind the Vendor object name from a list of vendors to the Text property of a TextBox control.

private void SimpleBindingForm_Load(object sender, System.EventArgs e)
{
// Create an array of vendor names
String [] astrVendorNames = {"Microsoft", "Rational", "Premia"};

// Bind the array to the text box
txtVendorName.DataBindings.Add("Text", astrVendorNames, "");
}

The Binding class can accept many other types of data sources, including the following:

  • An instance of any class that implements the IBindingList or ITypedList interface, including the DataSet, DataTable, DataView, and DataViewManager classes.
  • An instance of any class that implements the IList interface on an indexed collection of objects. In particular, this applies to classes that inherit from System.Array, including C# arrays.
  • An instance of any class that implements the IList interface on an indexed collection of strongly typed objects. For example, you can bind to an array of Vendor objects

Binding to a collection of strongly typed objects is a convenient way to handle data from an object-oriented data model.

private void SimpleBindingForm_Load(object sender, System.EventArgs e)
{
// Initialize the vendors array
aVendors[0] = new Vendor("Microsoft");
aVendors[1] = new Vendor("Rational");
aVendors[2] = new Vendor("Premia");
// Bind the array to the textbox
txtVendorName.DataBindings.Add("Text", aVendors, "VendorName");
}

Navigating through data, using BindingContext :

private void btnPrevious_Click(object sender, System.EventArgs e)
{
// Move to the previous item in the data source
this.BindingContext[aVendors].Position -= 1;
}
private void btnNext_Click(object sender, System.EventArgs e)
{
// Move to the next item in the data source
this.BindingContext[aVendors].Position += 1;
}

Complex Data Binding

In complex data binding, you bind a user interface control to an entire collection of data, rather than to a single data item. A good example of complex data binding involves the DataGrid control. Obviously, complex data binding is a powerful tool for transferring large amounts of data from a data model to a user interface.code

private void ComplexBindingForm_Load(object sender, System.EventArgs e)
{
// Create an array of exams
Exam[] aExams =
{
new Exam("315", "Web Applications With Visual C# .NET"),
new Exam("316", "Windows Applications With Visual C# .NET"),
new Exam("320", "XML With Visual C# .NET"),
new Exam("305", "Web Applications With VB.NET"),
new Exam("306", "Windows Applications With VB.NET"),
new Exam("310", "XML With VB.NET")
};

// Bind the array to the list box
lbExams.DataSource = aExams;
lbExams.DisplayMember = "ExamName";
lbExams.ValueMember = "ExamNumber";
// Create an array of candidates
Candidate[] aCandidates = {
new Candidate("Bill Gates", "305"),
new Candidate("Steve Ballmer", "320")};
// Bind the candidates to the text boxes
txtCandidateName.DataBindings.Add( "Text", aCandidates, "CandidateName");
txtExamNumber.DataBindings.Add( "Text", aCandidates, "ExamNumber");

// And bind the exam number to the list box value
lbExams.DataBindings.Add( "SelectedValue", aCandidates, "ExamNumber");

In this example Exam class could be any user defined type you can think of, as long as it supports String properties ExamNumber and ExamName like this one:

public class Exam
{
private String examNumber;
private String examName;

public String ExamNumber
{
get{ return examNumber;}
}
public String ExamName
{
get{ return examName;}
}
public Exam(String strExamNumber, String strExamName)
{
examNumber = strExamNumber;
examName = strExamName;
}

}
And Candidate is also something similar to the following:

public class Candidate
{
private string examNumber;
private string candidateName;
public string ExamNumber
{
get {return examNumber;}
set {examNumber = value;}
}
public string CandidateName
{
get {return candidateName;}
set {candidateName = value;}
}
public Candidate(String strCandidateName, String strExamNumber)
{
this.CandidateName = strCandidateName;
this.ExamNumber = strExamNumber;
}
}

Filtering With DataView Objects
A DataSet object contains two collections. The Tables collection is made up of DataTable objects, each of which represents a single table in the data source. The Relatios collection is made up of DataRelation objects, each of whic represents the relationship between two DataTable objects.
A DataView represents a bindable, customized view of a DataTable object.You can sort them or filter the records of a DataTable object to build a DataView Object.
private void FilterGridView_Load(object sender, System.EventArgs e)
{
// Move the data from the database to the DataSet
sqlDataAdapter1.Fill(dsCustomers1, "Customers");
// Create a dataview to filter the Customers table
System.Data.DataView dvCustomers = new System.Data.DataView(dsCustomers1.Tables["Customers"]);

// Apply a sort to the dataview
dvCustomers.Sort = "ContactName";

// Apply a filter to the dataview
dvCustomers.RowFilter = "Country = 'France'";
// and bind the results to the grid
dgCustomers.DataSource = dvCustomers;
}

This way of retrieving data is consuming lots of network resources and to bring all the records of a table to the client prior to filtering it doesn’t seem an efficient way of using (available?) resources. Therefor it is wise to create the view and on the database server and filter it before sending it to client.
A very nice example of binding is to bind a textbox to the collection within the combobox. This dead-easy sample is binding the text property of the TextBox to a combobox using its SelectedObject attribute. txtRAM.DataBindings.Add(“Text”, cmbComputers, “SelectedValue”);

Currency Manager

One new challenge I was face with recently is ability of dinamically load a grid based on the selection on one other grid. This is usefull when for example you have to tables loaded and you want to be able to navigate the first table and when move the cursor from one record to the other, the second grid which is related by a foreign key to the first one, gets filtered.This can be achieved in several ways. I just found one very easy way using the BindingContext and CurrencyManager.Let’s say you have a data view keeping the data about the parent relation and you will assign a grid to it like this:
DataView dvParent = base.DataSet.Tables[0].DefaultView ;
...
dgCodeGroup.DataSource = dvParent ;

What you need to do here is to get a reference to the ContextManager which is available through the BindingContext:

myCurrencyManager = (CurrencyManager)this.BindingContext[dvParent];
Then you subscribe to its PositionChanged event to get a notification when user moves from one record to the other on the grid:

myCurrencyManager.PositionChanged +=new EventHandler(OnCurrencyManager_PositionChanged);
Now all you need to do is to filter your second grid based on the selection. And the selection is easy to find:

DataRowView drv = (DataRowView) myCurrencyManager.Current ;

This is a collection of fields in that row. So you can use it to get into the fields and build your second data view.
Navigating the Current Position
This is where the CurrencyManager comes very handy. It has a property called Position which you can access to figure out the index and you can also assign a value to reposition it. Also the Count property is very handy to avoid moving out of boundries.

private void MoveNext(CurrencyManager myCurrencyManager)
{
if(myCurrencyManager.Count == 0)
{
Console.WriteLine("No records to move to."); return;
}
if (myCurrencyManager.Position == myCurrencyManager.Count - 1)
{ Console.WriteLine("You're at end of the records"); }
else
{ myCurrencyManager.Position += 1; }
}
private void MoveFirst(CurrencyManager myCurrencyManager)
{ if(myCurrencyManager.Count == 0)
{ Console.WriteLine("No records to move to.");
return;
}
myCurrencyManager.Position = 0;}
private void MovePrevious(CurrencyManager myCurrencyManager)
{ if(myCurrencyManager.Count == 0)
{ Console.WriteLine("No records to move to.");
return;
}
if(myCurrencyManager.Position == 0)
{ Console.WriteLine("You're at the beginning of the records."); }
else{ myCurrencyManager.Position -= 1; }}private void MoveLast(CurrencyManager myCurrencyManager){ if(myCurrencyManager.Count == 0) { Console.WriteLine("No records to move to.");
return;
}
myCurrencyManager.Position = myCurrencyManager.Count - 1;}