Logging and Systems Management in Microsoft .NET Framework Application Development Foundation

  • 11/12/2008

Lesson 3: Managing Computers

Applications often need to examine aspects of a computer, such as currently running processes and locally attached storage devices. In addition, it’s often useful to respond to changes in the system status, such as a new process starting or a newly attached storage device. You can use the Process class and Windows Management Instrumentation (WMI) to accomplish these tasks with the .NET Framework.

Examining Processes

You can use the Process.GetProcesses static method to retrieve a list of current processes. The following code sample lists the process ID (PID) and process name of all processes visible to the assembly. Processes run by other users might not be visible:

' VB
For Each p As Process In Process.GetProcesses()
   Console.WriteLine("{0}: {1}", p.Id.ToString(), p.ProcessName)
Next
// C#
foreach (Process p in Process.GetProcesses())
   Console.WriteLine("{0}: {1}", p.Id, p.ProcessName);

To retrieve a specific process by ID, call Process.GetProcessById. To retrieve a list of processes with a specific name, call Process.GetProcessesByName. To retrieve the current process, call Process.GetCurrentProcess.

Once you create a Process instance, you can access a list of the modules loaded by that process using Process.Modules (if you have sufficient privileges). If you lack sufficient privileges (which vary depending on the process), the CLR throws a Win32Exception. The following code sample demonstrates how to list all processes and modules when sufficient privileges are available:

' VB
For Each p As Process In Process.GetProcesses()
   Console.WriteLine("{0}: {1}", p.Id.ToString(), p.ProcessName)
   Try
       For Each pm As ProcessModule In p.Modules
           Console.WriteLine("    {0}: {1}", pm.ModuleName, _
               pm.ModuleMemorySize.ToString())
       Next
   Catch ex As System.ComponentModel.Win32Exception
       Console.WriteLine("    Unable to list modules")
   End Try
Next
// C#
foreach (Process p in Process.GetProcesses())
{
   Console.WriteLine("{0}: {1}", p.Id.ToString(), p.ProcessName);
   try
   {
       foreach (ProcessModule pm in p.Modules)
           Console.WriteLine("    {0}: {1}", pm.ModuleName,
               pm.ModuleMemorySize.ToString());
   }
   catch (System.ComponentModel.Win32Exception ex)
   {
       Console.WriteLine("    Unable to list modules");
   }
}

The first time you reference any Process property, the Process class retrieves and caches values for all Process properties. Therefore, property values might be outdated. To retrieve updated information, call the Process.Refresh method.

The following are some of the most useful Process properties:

  • BasePriority The priority of the process.

  • ExitCode After a process terminates, the instance of the Process class populates the ExitCode and ExitTime properties. The meaning of the ExitCode property is defined by the application, but typically zero indicates a nonerror ending, and any nonzero value indicates the application ended with an error.

  • ExitTime The time the process ended.

  • HasExited A boolean value that is true if the process has ended.

  • Id The PID.

  • MachineName The name of the computer on which the process is running.

  • Modules A list of modules loaded by the process.

  • NonpagedMemorySize64 The amount of nonpaged memory allocated to the process. Nonpaged memory must be stored in RAM.

  • PagedMemorySize64 The amount of paged memory allocated to the process. Paged memory can be moved to the paging file.

  • ProcessName The name of the process, which is typically the same as the executable file.

  • TotalProcessorTime The total amount of processing time the process has consumed.

To start a new process, call the Process.Start static method and specify the name of the executable file. If you want to pass the process parameters (such as command-line parameters), pass those as a second string. The following code sample shows how to start Notepad and have it open the C:\Windows\Win.ini file:

' VB
Process.Start("Notepad.exe", "C:\windows\win.ini")
// C#
Process.Start("Notepad.exe", @"C:\windows\win.ini");

Accessing Management Information

Windows exposes a great deal of information about the computer and operating system through WMI. WMI information is useful when you need to examine the computer to determine how to set up your application, or when creating tools for systems management or inventory.

First, define the management scope by creating a new ManagementScope object and calling ManagementScope.Connect. Typically, the management scope is \\<computer_name>\root\cimv2. The following code sample, which requires the System.Management namespace, demonstrates how to create the management scope:

' VB
Dim scope As New ManagementScope("\\localhost\root\cimv2")
scope.Connect()
// C#
ManagementScope scope =
   new ManagementScope(@"\\localhost\root\cimv2");
scope.Connect();

You also need to create a WMI Query Language (WQL) query using an instance of ObjectQuery, which will be executed within the scope you specified. WQL is a subset of Structured Query Language (SQL) with extensions to support WMI event notification and other WMI-specific features. The following code sample demonstrates how to query all objects in the Win32_OperatingSystem object. However, there are many different WMI objects. For a complete list, refer to WMI Classes at http://msdn.microsoft.com/en-us/library/aa394554.aspx.

' VB
Dim query As New ObjectQuery( _
    "SELECT * FROM Win32_OperatingSystem")
// C#
ObjectQuery query = new ObjectQuery(
   "SELECT * FROM Win32_OperatingSystem");

With the scope and query defined, you can execute your query by creating a ManagementObjectSearcher object and then calling the ManagementObjectSearcher.Get method to create a ManagementObjectCollection object.

' VB
Dim searcher As New ManagementObjectSearcher(scope, query)
Dim queryCollection As ManagementObjectCollection = searcher.Get()
// C#
ManagementObjectSearcher searcher = new ManagementObjectSearcher(scope, query);
ManagementObjectCollection queryCollection = searcher.Get();

Alternatively, you can use the overloaded ManagementObjectSearcher constructor to specify the query without creating separate scope or query objects, as the following example demonstrates:

' VB
Dim searcher As New ManagementObjectSearcher( _
    "SELECT * FROM Win32_LogicalDisk")
Dim queryCollection As ManagementObjectCollection = searcher.Get()
// C#
ManagementObjectSearcher searcher =
   new ManagementObjectSearcher(
       "SELECT * FROM Win32_LogicalDisk");
ManagementObjectCollection queryCollection = searcher.Get();

Finally, you can iterate through the ManagementObject objects in the ManagementObjectCollection and directly access the properties. The following loop lists several properties from the ManagementObject defined in the Win32_OperatingSystem example shown earlier:

' VB
For Each m As ManagementObject In queryCollection
    Console.WriteLine("Computer Name : {0}", m("csname"))
    Console.WriteLine("Windows Directory : {0}", m("WindowsDirectory"))
    Console.WriteLine("Operating System: {0}", m("Caption"))
    Console.WriteLine("Version: {0}", m("Version"))
    Console.WriteLine("Manufacturer : {0}", m("Manufacturer"))
Next
// C#
foreach (ManagementObject m in queryCollection)
{
    Console.WriteLine("Computer Name : {0}", m["csname"]);
    Console.WriteLine("Windows Directory : {0}", m["WindowsDirectory"]);
    Console.WriteLine("Operating System: {0}", m["Caption"]);
    Console.WriteLine("Version: {0}", m["Version"]);
    Console.WriteLine("Manufacturer : {0}", m["Manufacturer"]);
}

The following code sample demonstrates how to query the local computer for operating system details:

'VB
' Perform the query
Dim searcher As New ManagementObjectSearcher( _
    "SELECT * FROM Win32_OperatingSystem")
Dim queryCollection As ManagementObjectCollection = searcher.Get()

' Display the data from the query
For Each m As ManagementObject In queryCollection
    ' Display the remote computer information
    Console.WriteLine("Computer Name : {0}", m("csname"))
    Console.WriteLine("Windows Directory : {0}", m("WindowsDirectory"))
    Console.WriteLine("Operating System: {0}", m("Caption"))
    Console.WriteLine("Version: {0}", m("Version"))
    Console.WriteLine("Manufacturer : {0}", m("Manufacturer"))
Next
//C#
// Perform the query
ManagementObjectSearcher searcher =
   new ManagementObjectSearcher(
       "SELECT * FROM Win32_OperatingSystem");
ManagementObjectCollection queryCollection = searcher.Get();

// Display the data from the query
foreach (ManagementObject m in queryCollection)
{
   // Display the remote computer information
   Console.WriteLine("Computer Name : {0}", m["csname"]);
   Console.WriteLine("Windows Directory : {0}", m["WindowsDirectory"]);
   Console.WriteLine("Operating System: {0}", m["Caption"]);
   Console.WriteLine("Version: {0}", m["Version"]);
   Console.WriteLine("Manufacturer : {0}", m["Manufacturer"]);
}

Similarly, the following code lists all disks connected to the local computer:

'VB
' Create a scope to identify the computer to query
Dim scope As New ManagementScope("\\localhost\root\cimv2")
scope.Connect()

' Create a query for operating system details
Dim query As New ObjectQuery("SELECT * FROM Win32_LogicalDisk")

' Perform the query
Dim searcher As New ManagementObjectSearcher(scope, query)
Dim queryCollection As ManagementObjectCollection = searcher.Get()

' Display the data from the query
For Each m As ManagementObject In queryCollection
    ' Display the remote computer information
    Console.WriteLine("{0} {1}", m("Name").ToString(), _
        m("Description").ToString())
Next
//C#
// Create a scope to identify the computer to query
ManagementScope scope = new ManagementScope(@"\\localhost\root\cimv2");
scope.Connect();

// Create a query for operating system details
ObjectQuery query =
   new ObjectQuery("SELECT * FROM Win32_LogicalDisk");

// Perform the query
ManagementObjectSearcher searcher =
   new ManagementObjectSearcher(scope, query);
ManagementObjectCollection queryCollection = searcher.Get();

// Display the data from the query
foreach (ManagementObject m in queryCollection)
{
   // Display the remote computer information
   Console.WriteLine("{0} {1}", m["Name"].ToString(),
       m["Description"].ToString());
}

Waiting for WMI Events

You can also respond to WMI events, which are triggered by changes to the operating system status by creating an instance of WqlEventQuery. To create an instance of WqlEventQuery, pass the constructor an event class name, a query interval, and a query condition. Then, use the WqlEventQuery to create an instance of ManagementEventWatcher.

You can then use ManagementEventWatcher to either create an event handler that will be called (using ManagementEventWatcher.EventArrived) or wait for the next event (by calling ManagementEventWatcher.WaitForNextEvent). If you call ManagementEventWatcher.WaitForNextEvent, it returns an instance of ManagementBaseObject, which you can use to retrieve the query-specific results.

The following code creates a WQL event query to detect a new process, waits for a new process to start, and then displays the information about the process:

'VB
' Create event query to be notified within 1 second of a change
' in a service
Dim query As New WqlEventQuery("__InstanceCreationEvent", _
    New TimeSpan(0, 0, 1), "TargetInstance isa ""Win32_Process""")

' Initialize an event watcher and subscribe to events that match this query
Dim watcher As New ManagementEventWatcher(query)

' Block until the next event occurs
Dim e As ManagementBaseObject = watcher.WaitForNextEvent()

' Display information from the event
Console.WriteLine("Process {0} has been created, path is: {1}", _
    DirectCast(e("TargetInstance"), ManagementBaseObject)("Name"), _
    DirectCast(e("TargetInstance"), ManagementBaseObject)("ExecutablePath"))

' Cancel the subscription
watcher.Stop()
//C#
// Create event query to be notified within 1 second of a change
// in a service
WqlEventQuery query = new WqlEventQuery("__InstanceCreationEvent",
       new TimeSpan(0, 0, 1),
       "TargetInstance isa \"Win32_Process\"");

// Initialize an event watcher and subscribe to events that match this query
ManagementEventWatcher watcher = new ManagementEventWatcher(query);

// Block until the next event occurs
ManagementBaseObject e = watcher.WaitForNextEvent();
// Display information from the event
Console.WriteLine("Process {0} has been created, path is: {1}",
   ((ManagementBaseObject)e["TargetInstance"])["Name"],
   ((ManagementBaseObject)e["TargetInstance"])["ExecutablePath"]);

// Cancel the subscription
watcher.Stop();

Responding to WMI Events with an Event Handler

You can respond to the ManagementEventWatcher.EventArrived event to call a method each time a WMI event occurs. Your event handler must accept two parameters: an object parameter and an EventArrivedEventArgs parameter. EventArrivedEventArgs.NewEvent is a ManagementBaseObject that describes the event.

The following Console application demonstrates how to handle WMI events asynchronously. It performs the exact same task as the previous code sample:

'VB
Sub Main()
    Dim watcher As ManagementEventWatcher = Nothing

    Dim receiver As New EventReceiver()

    ' Create the watcher and register the callback.
    watcher = GetWatcher(New EventArrivedEventHandler( _
        AddressOf receiver.OnEventArrived))

    ' Watcher starts to listen to the Management Events.
    watcher.Start()

    ' Run until the user presses a key
    Console.ReadKey()
    watcher.Stop()
End Sub

' Create a ManagementEventWatcher object.
Public Function GetWatcher(ByRef handler As EventArrivedEventHandler) _
    As ManagementEventWatcher
    ' Create event query to be notified within 1 second of a change
    ' in a service
    Dim query As New WqlEventQuery("__InstanceCreationEvent", _
        New TimeSpan(0, 0, 1), "TargetInstance isa ""Win32_Process""")

    ' Initialize an event watcher and subscribe to events that match
    ' this query
    Dim watcher As New ManagementEventWatcher(query)

    ' Attach the EventArrived property to EventArrivedEventHandler method with the required
    handler to allow watcher object communicate to the application.
    AddHandler watcher.EventArrived, handler
    Return watcher
End Function

Class EventReceiver
    ' Handle the event and display the ManagementBaseObject properties.
    Public Sub OnEventArrived(ByVal sender As Object, _
        ByVal e As EventArrivedEventArgs)
        ' EventArrivedEventArgs is a management event.
        Dim evt As ManagementBaseObject = e.NewEvent

        ' Display information from the event
        Console.WriteLine("Process {0} has been created, path is: {1}", _
            DirectCast(evt("TargetInstance"), _
                ManagementBaseObject)("Name"),  _
            DirectCast(evt("TargetInstance"), _
                ManagementBaseObject)("ExecutablePath"))
    End Sub
End Class
//C#
static void Main(string[] args)
{
   ManagementEventWatcher watcher = null;

   EventReceiver receiver = new EventReceiver();

   // Create the watcher and register the callback
   watcher = GetWatcher(
       new EventArrivedEventHandler(receiver.OnEventArrived));

   // Watcher starts to listen to the Management Events.
   watcher.Start();

   // Run until the user presses a key
   Console.ReadKey();
    watcher.Stop();
}

// Create a ManagementEventWatcher object.
public static ManagementEventWatcher GetWatcher(
   EventArrivedEventHandler handler)
{
   // Create event query to be notified within 1 second of a
   // change in a service
   WqlEventQuery query = new WqlEventQuery("__InstanceCreationEvent",
       new TimeSpan(0, 0, 1),
       "TargetInstance isa \"Win32_Process\"");

   // Initialize an event watcher and subscribe to events that
   // match this query
   ManagementEventWatcher watcher = new ManagementEventWatcher(query);
   // Attach the EventArrived property to
   // EventArrivedEventHandler method with the
   // required handler to allow watcher object communicate to
   // the application.
   watcher.EventArrived += new EventArrivedEventHandler(handler);
   return watcher;
}

// Handle the event and display the ManagementBaseObject
// properties.
class EventReceiver
{
   public void OnEventArrived(object sender,
       EventArrivedEventArgs e)
   {
       // EventArrivedEventArgs is a management event.
       ManagementBaseObject evt = e.NewEvent;

       // Display information from the event
       Console.WriteLine("Process {0} has been created, path is: {1}",
           ((ManagementBaseObject)
               evt["TargetInstance"])["Name"],
           ((ManagementBaseObject)
               evt["TargetInstance"])["ExecutablePath"]);
   }
}

Lab: Create an Alarm Clock

In this lab, you create a WPF application that uses WMI events to trigger an alarm every minute.

Exercise 1: Respond to a WMI Event

In this exercise, you create a WPF application that displays a dialog box every minute by responding to WMI events when the value of the computer’s clock equals zero seconds.

  1. Use Visual Studio to create a new WPF Application project named Alarm, in either Visual Basic.NET or C#.

  2. In the XAML, add handlers for the Loaded and Closing events, as shown in bold here:

    <Window x:Class="Alarm.Window1"
       xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
       xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Loaded="Window_Loaded"
        Closing="Window_Closing"
       Title="Window1" Height="300" Width="300">
  3. Add a reference to System.Management.dll to your project. Then add the System.Management namespace to the code file.

  4. In the window’s class, declare an instance of ManagementEventWatcher so that it can be accessible from all methods in the class. You need to use this instance to start and stop the EventArrived handler:

    ' VB
    Class Window1
        Dim watcher As ManagementEventWatcher = Nothing
    End Class
    // C#
    public partial class Window1 : Window
    {
       ManagementEventWatcher watcher = null;
    }
  5. Add a class and a method to handle the WMI query event by displaying the current time in a dialog box. The following code sample demonstrates this:

    ' VB
    Class EventReceiver
        Public Sub OnEventArrived(ByVal sender As Object, _
            ByVal e As EventArrivedEventArgs)
            Dim evt As ManagementBaseObject = e.NewEvent
    
            ' Display information from the event
            Dim time As String = [String].Format("{0}:{1:00}", _
                DirectCast(evt("TargetInstance"), _
                    ManagementBaseObject)("Hour"), _
                DirectCast(evt("TargetInstance"), _
                    ManagementBaseObject)("Minute"))
    
            MessageBox.Show(time, "Current time")
        End Sub
    End Class
    // C#
    class EventReceiver
    {
        public void OnEventArrived(object sender, EventArrivedEventArgs e)
        {
            ManagementBaseObject evt = e.NewEvent;
    
            // Display information from the event
            string time = String.Format("{0}:{1:00}",
                ((ManagementBaseObject)evt["TargetInstance"])["Hour"],
                ((ManagementBaseObject)evt["TargetInstance"])["Minute"]);
    
            MessageBox.Show(time, "Current time");
        }
    }
  6. Add a method to the Window class to create a WMI event query that is triggered when the number of seconds on the computer’s clock is zero. This causes the event to be triggered every minute. Then register OnEventArrived as the event handler. The following code demonstrates this:

    ' VB
    Public Shared Function GetWatcher(ByVal handler As _
        EventArrivedEventHandler) As ManagementEventWatcher
        ' Create event query to be notified within 1 second of a
        ' change in a service
        Dim query As New WqlEventQuery("__InstanceModificationEvent", _
            New TimeSpan(0, 0, 1), _
            "TargetInstance isa 'Win32_LocalTime' AND " + _
            "TargetInstance.Second = 0")
    
        ' Initialize an event watcher and subscribe to events that
        ' match this query
        Dim watcher As New ManagementEventWatcher(query)
    
        ' Attach the EventArrived property to EventArrivedEventHandler method
        ' with the required handler to allow watcher object communicate to the
        ' application.
        AddHandler watcher.EventArrived, handler
        Return watcher
    End Function
    // C#
    public static ManagementEventWatcher GetWatcher(
        EventArrivedEventHandler handler)
    {
        // Create event query to be notified within 1 second of a change in a
        // service
        WqlEventQuery query = new WqlEventQuery("__InstanceModificationEvent",
            new TimeSpan(0, 0, 1),
            "TargetInstance isa 'Win32_LocalTime' AND " +
            "TargetInstance.Second = 0");
    
        // Initialize an event watcher and subscribe to events that
        // match this query
        ManagementEventWatcher watcher = new ManagementEventWatcher(query);
    
        // Attach the EventArrived property to EventArrivedEventHandler method
        // with the required handler to allow watcher object communicate to the
        // application.
        watcher.EventArrived += new EventArrivedEventHandler(handler);
        return watcher;
    }
  7. Finally, handle the window’s Loaded and Closing events to start and stop the event handler, as follows:

    ' VB
    Private Sub Window_Loaded(ByVal sender As Object, _
        ByVal e As RoutedEventArgs)
        ' Event Receiver is a user-defined class.
        Dim receiver As New EventReceiver()
    
        ' Here, we create the watcher and register the callback with it
        ' in one shot.
        watcher = GetWatcher(New EventArrivedEventHandler( _
            AddressOf receiver.OnEventArrived))
    
        ' Watcher starts to listen to the Management Events.
        watcher.Start()
    End Sub
    
    Private Sub Window_Closing(ByVal sender As Object, _
        ByVal e As System.ComponentModel.CancelEventArgs)
        watcher.Stop()
    End Sub
    // C#
    private void Window_Loaded(object sender, RoutedEventArgs e)
    {
        // Event Receiver is a user-defined class.
        EventReceiver receiver = new EventReceiver();
    
        // Here, we create the watcher and register the callback with it
        // in one shot.
        watcher = GetWatcher(
            new EventArrivedEventHandler(receiver.OnEventArrived));
    
        // Watcher starts to listen to the Management Events.
        watcher.Start();
    }
    
    private void Window_Closing(object sender,
        System.ComponentModel.CancelEventArgs e)
    {
        watcher.Stop();
    }
  8. Build and run the application. When the number of seconds on your computer’s clock equals zero, the OnEventArrived method displays a dialog box showing the current time.

Lesson Summary

  • You can examine processes by calling the Process.GetProcesses method. To start a process, call Process.Start.

  • To read WMI data, first define the management scope by creating a new ManagementScope object and calling ManagementScope.Connect. Then create a query using an instance of ObjectQuery. With the scope and query defined, you can execute your query by creating a ManagementObjectSearcher object and then calling the ManagementObjectSearcher.Get method. You can also respond to WMI events by creating an instance of WqlEventQuery. Then, use the WqlEventQuery to create an instance of ManagementEventWatcher. You can then use ManagementEventWatcher to either create an event handler or wait for the next event.

Lesson Review

You can use the following questions to test your knowledge of the information in Lesson 3, “Managing Computers.” The questions are also available on the companion CD if you prefer to review them in electronic form.

  1. You need to retrieve a list of all running processes. Which method should you call?

    1. Process.GetProcessesByName

    2. Process.GetCurrentProcess

    3. Process.GetProcesses

    4. Process.GetProcessById

  2. You need to query WMI for a list of logical disks attached to the current computer. Which code sample correctly runs the WMI query?

    1. ' VB
      Dim searcher As New ObjectQuery("SELECT * FROM Win32_LogicalDisk")
      Dim query As ManagementObject = searcher.Get()
      // C#
      ObjectQuery searcher = new ObjectQuery("SELECT * FROM Win32_LogicalDisk");
      ManagementObject query = searcher.Get();
    2. ' VB
      Dim searcher As New ManagementObjectSearcher( _
          "SELECT * FROM Win32_LogicalDisk")
      Dim queryCollection As ManagementObjectCollection = searcher.Get()
      // C#
      ManagementObjectSearcher searcher =
          new ManagementObjectSearcher("SELECT * FROM Win32_LogicalDisk");
      ManagementObject query = searcher.Get();
    3. ' VB
      Dim searcher As New ObjectQuery("SELECT * FROM Win32_LogicalDisk")
      Dim queryCollection As ManagementObjectCollection = searcher.Get()
      // C#
      ObjectQuery searcher = new ObjectQuery("SELECT * FROM Win32_LogicalDisk");
      ManagementObjectCollection queryCollection = searcher.Get();
    4. ' VB
      Dim searcher As New ManagementObjectSearcher( _
          "SELECT * FROM Win32_LogicalDisk")
      Dim queryCollection As ManagementObjectCollection = searcher.Get()
      // C#
      ManagementObjectSearcher searcher =
          new ManagementObjectSearcher("SELECT * FROM Win32_LogicalDisk");
      ManagementObjectCollection queryCollection = searcher.Get();
  3. You are creating an application that responds to WMI events to process new event log entries. Which of the following do you need to do? (Choose all that apply.)

    1. Call the ManagementEventWatcher.Query method.

    2. Create a ManagementEventWatcher object.

    3. Create an event handler that accepts object and ManagementBaseObject parameters.

    4. Register the ManagementEventWatcher.EventArrived handler.