Av rating:
Total votes: 27
Total comments: 7


James Moore
Integrating with WMI
17 October 2007

James shows how to add a simple WMI provider to a service so that you can monitor it, and make changes to it, remotely across the network

If you are writing an application, such as a service, that you want to be able to monitor and configure remotely then you'll want to ensure that your application integrates smoothly with Windows Management Instrumentation (WMI). WMI is the Microsoft implementation of an industry standard interface called Web Based Enterprise Management. It allows a user to access, and change, management information from a wide variety of devices across the enterprise. The classes that support WMI in the .NET Framework reside in the system.management namespace, within the framework’s class library.

In this article, we will first create a Windows Service that accepts a TCP connection and echoes back any text typed into a telnet connection. We will then add some WMI code to our service in order to enable it to publish information about the number of characters that have been echoed back. In other words, we will turn our Windows Service into a WMI Provider.

Although this is a fairly simple project, it does highlight the key steps that are required to WMI-enable your application.

Creating and installing the service

Create a new Windows Service project in Visual Studio and call it TestService. In Solution Explorer, open up the file called Service1.cs in the code editor.

Add a new member variable called m_engineThread of type Thread:

        Thread m_engineThread; 

We then want to kick it off this new listening thread during service startup:

        protected override void OnStart(string[] args)
        {
            m_engineThread = new Thread(new ThreadStart(ThreadMain));
            m_engineThread.Start();
        }

And make sure we terminate it when the service is stopped:

        protected override void OnStop()
        {
            try
            {
                m_engineThread.Abort();
            }
            catch (Exception) { ;}
        }

The code for ThreadMain is fairly simple; it just sets up a TCPListner and accepts connections. It then prints out any line entered via the TCP connection, until a single "." is received on a line by itself:

      public void ThreadMain()
      {
          // Setup the TCP Listener to bind to 127.0.0.1:50009
          IPAddress localAddr = IPAddress.Parse("127.0.0.1");
          TcpListener tlistener = new TcpListener(localAddr, 50009);
          try
          {
              // Start listening
              tlistener.Start();
              String data = null;
              // Enter processing loop
              while (true)
              {
                  // Block until we get a connection
                  TcpClient client = tlistener.AcceptTcpClient();

                  data = null;

                  // Get a stream object and
                  // then create a StreamReader for convience
                  NetworkStream stream = client.GetStream();
                  StreamReader sr = new StreamReader(stream);
                 
                  // Read a line from the client at a time.
                  while ((data = sr.ReadLine()) != null)
                  {
                      if (data == ".")
                      {
                          break;
                      }

                      byte[] msg = System.Text.Encoding.ASCII.GetBytes(data);
                      stream.Write(msg, 0, msg.Length);
                      stream.WriteByte((byte)'\r');
                      stream.WriteByte((byte)'\n');
                  }

                  // Shutdown and end connection
                  client.Close();
              }
          }
          catch (SocketException e)
          {
              ;
          }
          finally
          {
              // Stop listening for new clients.
              tlistener.Stop();
          }

      }

Finally, we need to get the service to install itself. To do this, add a project reference to System.Configuration.Install.dll and then add a new class to your project, called MyInstaller. This class should derive from Installer and be attributed with the RunInstallerAttribute:

    [System.ComponentModel.RunInstaller(true)]
    public class MyInstaller : Installer
    {

                …..

In the constructor of the MyInstaller class, we need the following code to install the service:

        public MyInstaller()
        {
            ServiceProcessInstaller procInstaller = new
ServiceProcessInstaller();
            ServiceInstaller sInstaller = new ServiceInstaller();
            procInstaller.Account = ServiceAccount.LocalSystem;
            sInstaller.StartType = ServiceStartMode.Automatic;
            sInstaller.ServiceName = "Simple-Talk Test Service";
            Installers.Add(sInstaller);
            Installers.Add(procInstaller);
        }

All this does is to ensure that the service installs correctly and appears in the services.msc control panel.

Starting and Stopping the Service

Let's give it a go. Hit F6 to build the project and then run InstallUtil.exe on the resulting binary. In my case, this is:

C:\Simple-Talk>InstallUtil.exe TestService.exe

You will see a large amount of text output. Once this has completed, hit start->Run and type services.msc. This will bring up the services control panel; scroll down until you find Simple-Talk Test Service and start it.

Having started the service, we can now try it out. Hit start->run again and type: telnet 127.0.0.1 50009. This will open up a telnet window; anything you type will be echo’d back to you when you hit enter.

To close the connection enter "a ." on a line, on its own.

We now need to stop the service, which you can do using the services control panel.

Adding WMI Support

We now want to add the WMI support to the service. As an example, we will publish the number of characters which have been echo’d back since the service started.

To WMI-enable our service, include a reference to System.Management.Dll and then add a new class to the project called EchoInfoClass. Attribute this class with the InstrumentationClass attribute, with its parameter as InstrumentationType.Instance. Then, add a public field called CharsEchoed of type int:

    [InstrumentationClass(InstrumentationType.Instance)]
    public class EchoInfoClass
    {
        public int CharsEchoed;
    }

The InstrumentationClass attribute specifies that the class provides WMI data; this WMI Data can either be an instance of a class, or a class used during a WMI event notification. In this case, we want to provide an instance of a class. Next, in order to WMI-enable our project, we need to modify the installer class we wrote earlier so that it registers our WMI object with the underlying WMI framework.

For safety, first run InstallUtil.exe /u against the binary we built before to uninstall the service.

Now, we can change the installer class so that it registers our WMI object correctly with the underlying WMI subsystem. Luckily, the .NET Framework architects made this easy for us. There is a class called DefaultManagementProjectInstaller in the framework that provides the default installation code to register classes attributed with InstrumentationClass. To take advantage of this we simply change the class MyInstaller to derive from DefaultManagementProjectInstaller rather than Installer.

    [System.ComponentModel.RunInstaller(true)]
    public class MyInstaller :
        DefaultManagementProjectInstaller
    {
           

We need to create and register an instance of this service class on service startup. To do this, first add a member variable to the service class:

        EchoInfoClass m_informationClass;

Then, add the following code to your OnStart override:

        protected override void OnStart(string[] args)
        {
            m_informationClass = new EchoInfoClass();
            m_informationClass.CharsEchoed = 0;
            Instrumentation.Publish(m_informationClass);

            m_engineThread = new Thread(new ThreadStart(ThreadMain));
            m_engineThread.Start();

        }

This creates the class instance and registers it with the WMI framework so that it is accessible via WMI. Once that is done we just use the class as normal.

We have now told the WMI Framework about our class (via the installer) and published an instance of it (in our OnStart method). Now, we just need to update the information we are publishing via WMI. To do this we increment the m_informationClass.CharsEchoed field whenever we echo a character back to the client. To do this add the following line to ThreadMain:

                    while ((data = sr.ReadLine()) != null)
                    {
                        if (data == ".")
                        {
                            break;
                        }

                        byte[] msg =
                            System.Text.Encoding.ASCII.GetBytes(data);
                        stream.Write(msg, 0, msg.Length);
                        stream.WriteByte((byte)'\r');
                        stream.WriteByte((byte)'\n');

                        m_informationClass.CharsEchoed += msg.Length;
                    }

Testing the WMI Provider

We are now ready to give it a go and see if it all works! Build your application by hitting F6 and then run InstallUtil again:

C:\Simple-Talk>InstallUtil.exe TestService.exe

Then just start the service and try it out:

C:\Simple-Talk>net start “Simple-talk test service”
C:\Simple-Talk>telnet 127.0.0.1 50009

The telnet command opens up a blank screen, waiting for you to type something in. I typed in "simple-talk" and hit enter and the service duly echo'd back "simple-talk" to the screen.

So, the service returned 11 characters and, hopefully, our WMI provider worked correctly and recorded that. Microsoft provides a WMI information browser called wbemtest – it's fairly ropey, but it will do for now so open that up:

C:\Simple-Talk>wbemtest

Click connect, leave all the settings at their default value, and click OK:

Next click the Query… button and enter the following:

WQL returns instances of the classes we requested, rather than rows, and presents us with the following screen:

NOTE: WQL is very similar to SQL – in fact it is a subset of SQL and allows you to query management information in a very similar way to an RDBMS. WQL generally returns “instances” rather than rows. However, these can be thought of as analogous.

Double click on the instance of the class:

The CharsEchoed property shows us that 11 characters have been sent back from the service.

Summary

WMI is a wide ranging infrastructure on Windows (and other platforms) for managing machines and programs across an enterprise. Although our example is fairly simple it should give you enough information to be able to include WMI integration next time you are writing a service or website that requires remote monitoring and management.

It is equally as easy to consume WMI information from .NET however that topic can wait for another article.



This article has been viewed 6292 times.
James Moore

Author profile: James Moore

James Moore is a developer at Red Gate Software and currently runs the DBA tools division.

Search for other articles by James Moore

Rate this article:   Avg rating: from a total of 27 votes.


Poor

OK

Good

Great

Must read
 
Have Your Say
Do you have an opinion on this article? Then add your comment below:
You must be logged in to post to this forum

Click here to log in.


Subject: fantastic!
Posted by: ronald (view profile)
Posted on: Wednesday, October 17, 2007 at 2:24 PM
Message: clear, concise, and beautiful!
a great tutorial!
thanks.

Subject: Performance Counters and Status Information
Posted by: Andy (view profile)
Posted on: Thursday, October 18, 2007 at 4:37 AM
Message: Your example here is using WMI like a performance counter. WMI can also be used for more general status and configuration information e.g. it could report the port number that this tool is listening on or the last message sent or a status message.

WMI can be read by most system monitoring tools including Microsoft MOM

Subject: Thread is not available in .NET2 System.Management
Posted by: Anonymous (not signed in)
Posted on: Thursday, October 18, 2007 at 10:48 AM
Message: What gives?? are you preaching to the converted?

Subject: Thread
Posted by: James (view profile)
Posted on: Tuesday, October 23, 2007 at 10:00 AM
Message: Hi there,

I'm not quite sure what you mean that Thread is not available in .NET2 System.Management - it exists in System.Threading - you might need to add use System.Threading to the top of your source file to get it all to compile!

James

Subject: Wonderful Monitor
Posted by: Shahid Munir (not signed in)
Posted on: Friday, November 02, 2007 at 12:22 AM
Message: It is not only a wonderful monitor, it helps making client server application...very Appreciating...

Subject: database data
Posted by: Jess (view profile)
Posted on: Thursday, January 17, 2008 at 8:41 PM
Message: Thanks James. This is very helpful to a newbie to C# and WMI. Got a few questions. I'd like to write a provider that will expose data from a database that's dynamically changing. Can I do this in a service in the same location as your tutorial (ThreadMain)?


Subject: Where can I download this project from?
Posted by: tzvikaz (view profile)
Posted on: Saturday, October 03, 2009 at 3:34 PM
Message: Great article.
Is there somewhere I can d/l this project from?

 






recommended site pinvoke

PInvoke.net is a user-driven wiki which provides .NET developers with native method signatures, so they don't have to spend time writing them from scratch.




Has .NET Reflector Saved Your Bacon?
 We think Reflector is a fantastic tool, and we know you do too. We'd love to hear about the times... Read more...

The Managed Heap
 Because Red-Gate's .NET team works closely with the users of their products in order to try to fit the... Read more...

Using Three Flavors of LINQ To Populate a TreeView
 LINQ is a valuable technology. LINQ to XML, LINQ to Objects and LINQ to XSD, in particular, can save... Read more...

How to build a Query Template Explorer
 Having introduced his cross-platform Query Template solution, Michael now gives us the technical... Read more...

How to Create Event Receivers for Windows SharePoint Services 3.0
 You'll be surprised how often that you'll use event receivers instead of Workflow in order to implement... Read more...

A Complete URL Rewriting Solution for ASP.NET 2.0
 Ever wondered whether it's possible to create neater URLS, free of bulky Query String parameters?... Read more...

Visual Studio Setup - projects and custom actions
 This article describes the kinds of custom actions that can be used in your Visual Studio setup project. Read more...

.NET Application Architecture: the Data Access Layer
 Find out how to design a robust data access layer for your .NET applications. Read more...

Web Parts in ASP.NET 2.0
 Most Web Parts implementations allow users to create a single portal page where they can personalize... Read more...

Configuring Forms Authentication in SharePoint 2007
 Damon Armstrong provides a step-by-step guide to the processes, quirks and pitfalls of setting up... Read more...

Over 150,000 Microsoft professionals subscribe to the Simple-Talk technical journal. Join today, it's fast, simple, free and secure.

Join Simple Talk