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.
Use Visual Studio to create a new WPF Application project named Alarm, in either Visual Basic.NET or C#.
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">
Add a reference to System.Management.dll to your project. Then add the System.Management namespace to the code file.
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; }
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"); } }
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; }
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(); }
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.
You need to retrieve a list of all running processes. Which method should you call?
Process.GetProcessesByName
Process.GetCurrentProcess
Process.GetProcesses
Process.GetProcessById
You need to query WMI for a list of logical disks attached to the current computer. Which code sample correctly runs the WMI query?
-
' 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();
-
' 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();
-
' 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();
-
' 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();
-
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.)
Call the ManagementEventWatcher.Query method.
Create a ManagementEventWatcher object.
Create an event handler that accepts object and ManagementBaseObject parameters.
Register the ManagementEventWatcher.EventArrived handler.