WMI and the System Management assembly

One of the primary Windows interfaces for system management is Management Instrumentation, known affectionately by the acronym WMI. WMI is a management infrastructure, through which system components provide information about their state and notification of events. You can use WMI to change configuration, interrogate the local system or remote computers, and respond to events. Practical uses for WMI include tasks like inventorying all installed software, uninstalling programs, creating scheduled tasks, and obtaining information about running services. Additionally, applications can provide instrumentation so that they can be queried by WMI.

Despite having Windows in the name, WMI is an implementation of the platform-independent Web-Based Enterprise Management (WBEM) and Common Information Model (CIM) standards. But, although parts of the necessary components have been implemented in Mono, large parts of it are considered too Windows-specific and will probably never be implemented. Sadly, this means that most of the examples in this section don't work with Mono.

Although WMI provides you with access to some very low-level system information, it has a good high-level managed interface, in the form of the System.Management namespace. This makes it easier to work with WMI through .NET and IronPython than some of the alternatives.

10.2.1 System.Management

System.Management provides a managed interface to the WMI infrastructure. The core classes are ManagementObjectSearcher, ManagementQuery, and Management-Eventwatcher. WMI queries are created using Windows Query Language (WQL), which is a derivative of SQL. Much of working with WMI involves knowing how to construct your WQL queries.6 SIMPLE WQL QUERIES

Listing 10.6 shows a basic example of WMI that queries and prints the processor usage percentage every five seconds.

Listing 10.6 A simple WQL query to display CPU usage import clr clr.AddReference("System.Management") from System.Management import ManagementObject from System.Threading import Thread query = "Win32_PerfFormattedData_PerfOS_Processor.Name='_total'"

6 The Microsoft reference is at http://msdn2.microsoft.com/en-us/library/aa394606.aspx.

WQL query for processor usage while True: <—

mo = ManagementObject(query) print mo["PercentProcessorTime"] Thread.CurrentThread.Join(5 0 0 0)

ManagementObjectSearcher is a more commonly used way of executing queries, and it will return a collection of management objects. For example, listing 10.7 queries the system for information about all the attached logical disks.

Listing 10.7 Querying the system with ManagementObjectSearcher import clr clr.AddReference("System.Management")

from System.Management import ManagementObjectSearcher query = "Select * from Win32_LogicalDisk" searcher = ManagementObjectSearcher(query)

for drive in searcher.Get(): for p in drive.Properties:

print p.Name, p.Value print

If you know the property that you're interested in, you can index instead of going through drive.Properties. For example, to get the drive name you can use drive["Name"] . MONITORING EVENTS

Things get interesting when you start to monitor events. For this, you use the Manage-mentEventWatcher class. Listing 10.8 creates a watcher that calls an event handler when new processes start.

Listing 10.8 Responding to events with ManagementEventWatcher import clr clr.AddReference('System.Management') from System.Management import (

WqlEventQuery, ManagementEventWatcher

from System import TimeSpan from System.Threading import Thread timeout = TimeSpan(0, 0, 1)

query = WqlEventQuery("_InstanceCreationEvent", timeout,

'TargetInstance isa "Win32_Process"')

watcher = ManagementEventWatcher() watcher.Query = query def arrived(sender, event): print 'Event arrived' real_event = event .NewEvent instance = real_event['TargetInstance']

for entry in instance.Properties:

1 Loop, checking I every five seconds

Fetches real event!

Fetches process instance print entry.Name, entry.Value watcher.EventArrived += arrived watcher.Start()

while True: <—Waits for events

Thread.CurrentThread.Join(10 0 0)

This code is really very simple. All the magic happens in constructing the WQL query and adding an event handler to the event watcher instance. Under the hood, WqlEventQuery constructs the following WQL query:

select * from_InstanceCreationEvent within 1 where TargetInstance isa "Win32_Process"

You specify a timeout when you construct the query (using System.Timespan); the timeout corresponds to the within clause of the WQL query. Some events have a built-in mechanism for notifying WMI (WMI event providers); these are called extrinsic events. WMI discovers other events, intrinsic events, by polling, and the timeout tells WMI how often to poll for you.

This code snippet listens for events by hooking up a handler to the EventArrived event. Instead of using this event, you can make a call to watcher.WaitForNextEvent, which blocks until the event is raised. In this situation, you can also set a timeout directly on the watcher. Instead of blocking forever, the timeout causes the watcher to throw an exception if an event isn't raised in time. The following snippet shows this in practice:

>>> watcher = ManagementEventWatcher() >>> watcher. Query = query

>>> watcher.Options.Timeout = TimeSpan(0, 0, 5) >>> e = watcher.WaitForNextEvent() Traceback (most recent call last): SystemError: Timed out

As we mentioned, the secret knowledge needed for harnessing WMI is how to construct your WQL queries. For example, to be notified of new USB storage devices becoming available (plug-and-play events), you could use this query:

wql = ("Targetinstance isa ' Win32_PNPEntity' and "

"TargetInstance.DeviceId like '%USBStor%'") query = WqlEventQuery("_InstanceCreationEvent", timeout, wql)

Let's look a bit more at WQL and the elements available to you to construct queries. WQL, WMI CLASSES, AND EVENTS

The basic pattern for WQL notification queries is as follows:

SELECT * FROM _EventClass WITHIN PollingInterval WHERE TargetInstance ISA

WMIClassName AND TargetInstance.WMIClassPropertyName = Value

The key to constructing useful queries is knowing which events, classes, and properties provide you with the information you need.

Intrinsic events are represented by classes derived from one of the following:

■ _InstanceOperationEvent

■ _NamespaceOperationEvent

■ _ClassOperationEvent

The instance events, which are the most common, are as follow:

■ _InstanceCreationEvent

■ _InstanceModificationEvent

■ _InstanceDeletionEvent

Extrinsic events derive from the_ExtrinsicEvent class.

When an event is raised, the corresponding WMI class is instantiated; this is the TargetInstance we've already used in some of our examples. You can navigate the documentation for all the standard WMI classes at http://msdn.microsoft.com/ library/default.asp?url=/library/en-us/wmisdk/wmi/wmi_classes.asp.

Once you have an event, and have pulled the target instance out, you can explore the interesting properties through the Properties collection. Various tools are available to investigate WMI namespaces and all the classes they provide.7

Sometimes it's useful to work directly with these classes—which you do by creating an instance of ManagementClass corresponding to the WMI class you're interested in. Listing 10.9 illustrates this by creating events with a timer.

Listing 10.9 Creating timer events with ManagementClass from System.Management import ManagementClass, WqlEventQuery

TimerClass = ManagementClass("_IntervalTimerInstruction")

timer = TimerClass.CreateInstance()

timer["TimerId" ] = "Timerl"

timer["IntervalBetweenEvents" ] = 1000

Query for timer events query = WqlEventQuery("_TimerEvent", "TimerId='Timerl'")

This code8 would be useful for making your WMI demos a bit more predictable; but beyond that, it doesn't have much practical application. Fortunately, you can do more useful things with ManagementClass, such as listing all the processes that run on startup.

>>> StartupClass = ManagementClass('Win32_StartupCommand') >>> processes = StartupClass.GetInstances() >>> for p in processes:

. . . print p['Location'] , p['Caption'] , p['Command']

7 For example, Marc, The PowerShell Guy, has one tool aimed at PowerShell but useful for anyone interested in WMI. See http://thepowershellguy.com/blogs/posh/archive/2007/03/22/powershell-wmi-explorer-part-1.aspx.

8 The timer.Put() line of this example requires administrator access under Vista.

As well as interesting properties, many WMI instances also have useful methods (although not Win32_StartupCommand, as it happens). The Win32_Process class has some, though; and because WMI method invocation is slightly odd, here's an example:

>>> from System import Array

>>> StartupClass = ManagementClass('Win32_Processes')

>>> processes = StartupClass.GetInstances()

'csrss.exe'

>>> arg_array = Array.CreateInstance(object, 2) >>> proc.InvokeMethod('GetOwner', arg_array) 0

System.String[]('SYSTEM', 'NT AUTHORITY')

You can see from the GetOwner method documentation9 that it takes two strings as arguments. (The documentation also specifies the meaning of the return value—in this case, 0 for success.) These are out parameters to be populated with the user who owns the process and the domain under which it's running. But, because the arguments have to be supplied as an array, you can create a fresh array with two members and pass it into InvokeMethod along with the method name.

Another method on Win32_Process is SetPriority.10 This takes a single integer (the priority) as an argument (64 for idle priority), which you put in an object array.

>>> proc.InvokeMethod('SetPriority' , arg_array)

You'll see shortly that PowerShell can make it easier to discover the methods on WMI objects.

A lot of the real power of WMI for system administrators is in the ability to connect to computers on the network. Because this isn't something we've covered yet, let's see how it's done.

10.2.2 Connecting to remote computers

Here's where it starts to get fun. Connecting remotely isn't something you want to allow any old soul to do, and so the security permissions have to be set correctly on the target computer. There are a couple of places where you might have to adjust permissions. To allow remote access, the first place to try is Console Root > Component Services > My Computer > (right-click) Properties > COM Security from the DCOMCNFG application.11 You can launch DCOMCNFG from the command line, and it should look like figure 10.2.

9 See http://msdn2.microsoft.com/en-us/library/aa390460(VS.85).aspx.

10 See http://msdn2.microsoft.com/en-us/library/aa393587(VS.85).aspx.

11 See this page for the details: http://msdn2.microsoft.com/en-us/library/aa393266(VS.85).aspx.

Powershell Ntlogevent
Figure 10.2 Configuring remote access from Component Services

If you still get access permission errors in any of the following examples, you can also set the access permissions for individual WMI namespaces via the Computer Management console from the Control Panel. The full route to this dialog is Control Panel > Administrative Tools > Computer Management > Services & Applications > WMI Control > (right-click) Properties, and it should look like figure 10.3!

We haven't talked about WMI namespaces at all yet. All the examples we've looked at so far have worked without specifying an explicit scope. This means that they've connected to the default namespace on the local machine. To connect to machines on a network, you'll need to connect to an explicit scope.

The default scope is \\localhost\root\cimv2, which means the root\cimv2 namespace on the local machine. CIMV2 (where CIM stands for Common Information Model) is the default namespace and contains all the most commonly used classes, including all the ones we've used so far. There are other namespaces such as root\DEFAULT, which contains classes for working with the registry. Other providers can register namespaces to provide instrumentation via WMI. The BizTalk namespace is root\MicrosoftBizTalkServer, SQLServer is root\Microsoft\SqlServer\, and so on.12

12 There's a recipe in the IronPython Cookbook that will list all the available WMI namespaces and the classes they contain. See http://www.ironpython.info/index.php/WMI_with_IronPython.

Computer Management File Action View Help

JJp Computer Management (Local

* fi System Tools

> Shared Folders

> Local Users and Groups

> Reliability and Perform* ^ Device Manager

* Storage

Jg}1 Disk Management d ~ Services and Applications Services WMI Control

WMI Control Properties General [ Backup/Restore Secu^y ] Advanced]

Namespace navigation alows you io set namespace specific security

C3 tocrosoftWs S-C] MSAPPS12

a Q nap

é CJ security ffi-O SeeurttyCerter * f"~l SeeurityCenler2 •2 Q Seivice Model -Pi fit fosrnntnw___

SCBCHi

Actions

WMI Control

Security for ROOT\CIMV2

Security ]

Group or user names:

Security ]

Group or user names:

Authenticated Users

LOCAL SERVICE

ÜJ NETWORK SERVICE

¿4 Administrators (MICHAELFOORA13C 'Administrators)

Add... ¡

Refriega

Permissions for Authenticated

Users

jHow

Dany

Enabie Account

-

Remóle EnaWe

ÍVI.

0

Read Security

r;

-i

Edit Security

B

Special permissions

*

Forspecal permissions or advanced settings, dick Advanced,

Learn about access control and permissions

Forspecal permissions or advanced settings, dick Advanced,

Learn about access control and permissions

Figure 10.3 Configuring WMI access through Computer Management

To specify the default namespace on a remote machine (in the same domain on the network), you specify a scope like \\FullComputerName\root\cimv2. You do this with the .NET ManagementScope class. CONNECTION AUTHENTICATION AND IMPERSONATION

We've already talked about how you enable permissions for remote connections, but you have two choices about how to connect. You can either connect using the credentials of the user running the script, called impersonation, or you can explicitly specify a username and password for the connection.

Listing 10.10 shows how to create a ManagementScope for a connection to a remote computer with a specific username and password.

Listing 10.10 Specifying username and password for a WMI connection from System.Management import (

ConnectionOptions, ManagementScope

options = ConnectionOptions() options.EnablePrivileges = True options.Username = "administrator" options.Password = "******"

network_scope = r"\\FullComputerName\root\cimv2" scope = ManagementScope(network_scope, options)

Listing 10.11 shows how to make the same connection using impersonation. Listing 10.11 A WMI connection with impersonation from System.Management import (

AuthenticationLevel, ImpersonationLevel, ManagementScope, ConnectionOptions

options = ConnectionOptions() options.EnablePrivileges = True options.Impersonation = ImpersonationLevel.Impersonate options.Authentication = AuthenticationLevel.Default network_scope = r"\\FullComputerName\root\cimv2" scope = ManagementScope(network_scope, options)

Whether you should use authentication or impersonation depends on the details of the network you're working with. If the computers you're connecting to are configured to allow remote connections from any user with the correct privileges, then impersonation is easier. If the computer limits connections to a specific user, or set of users, then you'll need to use authentication. QUERYING REMOTE COMPUTERS

Having created the scope, you use it to create a ManagementEventWatcher and start listening for events. Listing 10.12 is more of a real-world example than some of the examples we've used so far. It monitors a remote computer for low memory situations (specifically when the available physical memory drops below 10 MB).

Listing 10.12 Monitoring memory use on a remote computer import clr clr.AddReference('System.Management') from System.Management import (

ConnectionOptions, ManagementScope, WqlEventQuery, ManagementEventWatcher

from System import TimeSpan from System.Threading import Thread options = ConnectionOptions() options.Username = "administrator" options.Password = "******"

network_scope = r"\\FullComputerName\root\cimv2" scope = ManagementScope(network_scope, options)

wql = ('TargetInstance ISA "Win32_OperatingSystem" AND ' 'TargetInstance.FreePhysicalMemory < 10000')

query = WqlEventQuery("_InstanceModificationEvent", timeout, wql)

watcher = ManagementEventWatcher()

watcher.Query = query watcher .Scope = scope <— Specifies scope for query interesting_properties = ( 'FreePhysicalMemory', 'FreeSpaceInPagingFiles', 'FreeVirtualMemory', 'NumberOfProcesses', 'SizeStoredInPagingFiles', 'TotalVirtualMemorySize', 'TotalVisibleMemorySize', 'LocalDateTime'

def arrived(sender, event) : print 'Event arrived' real_event = event.NewEvent instance = real_event['TargetInstance']

for prop in interesting_properties: entry = instance.Properties[prop] print entry.Name, entry.Value watcher.EventArrived += arrived watcher.Start()

print 'started' while True:

Thread.CurrentThread.Join(10 0 0)

If you're monitoring a network of servers, you're going to be interested in (and concerned about) events like this. Because you're monitoring for a change in the system, this event is an__InstanceModificationEvent, and the WQL is as follows:

TargetInstance ISA "Win32_OperatingSystem" AND TargetInstance.FreePhysicalMemory < 10000

Another useful thing to watch for13 might be disk space dropping below a certain threshold on any fixed disk (that is, not including USB sticks/CDs and so on). Here's WQL with the threshold set at 1 MB:

TargetInstance ISA 'Win32_LogicalDisk' AND TargetInstance.DriveType = 3 AND TargetInstance.FreeSpace < 1000000

(You could achieve a similar goal by watching for the extrinsic event Win32_Volume-ChangeEvent.)

To be notified if CPU usage goes above 80 percent on any processor, the WQL is as follows:

TargetInstance ISA 'Win32_Processor' AND TargetInstance.LoadPercentage > 80

The next query monitors for unauthorized access (failed login attempts). This query relies on access auditing being in place so that the entries go into the event logs. To

13 Many thanks to Tim Golden, a Python and WMI guru, for his help with these examples. Tim has created an excellent module for using WMI from CPython. See http://timgolden.me.uk/python/wmi.html.

remotely access the security logs, you'll need to specify the security privilege. Setting options.EnablePrivileges = True should be enough; but, if you're using authentication, then you may need to set options.Authentication = AuthenticationLevel. Security. This event is an__InstanceCreationEvent, and the WQL is as follows:

TargetInstance ISA 'Win32_NTLogEvent' AND

TargetInstance.CategoryString = 'Logon/Logoff' AND TargetInstance.Type = 'audit failure'

Systems administration requires a great many tools for different situations. Despite its baroque interface, WMI is an extremely powerful tool. Because of the level of integration with .NET through the managed APIs, WMI works very well with IronPython. In exploring those APIs, we've uncovered quite a few different ways it can be useful, whether you're investigating a single machine or monitoring a whole network of computers. The advantage of Python here is that, as well as rapidly creating simple diagnostic scripts (or even working interactively), you can also build larger monitoring applications where WMI is only a small part of the whole solution.

Another useful tool for Windows system administration is PowerShell. It's more commonly used as a standalone environment, but we're going to look at how Iron-Python can be part of the answer from inside PowerShell and how PowerShell can become another component for use in IronPython.

Was this article helpful?

0 0

Post a comment