We're going to briefly look at four different ways to deal with process control on NT/2000 because each of these approaches opens up a door to interesting functionality outside the scope of our discussion. We're primarily going to concentrate on two tasks: finding all of the running processes and killing select processes.
As we've mentioned in Chapter 3, "User Accounts", the NT Resource Kit is a wonderful source of scripts and information. The two programs we are going to use from the resource kit are pulist.exe and kill.exe. The former lists processes, the second nukes them. There is another utility in the resource kit similar to pulist.exe called tlist.exe that can list processes in a pleasant tree format, but it but lacks some features of pulist.exe. For instance, pulist.exe can list processes on other machines besides the current one.
Here's an excerpt from some pulist output:
Process PID User TAPISRV.EXE 119 NT AUTHORITY\SYSTEM TpChrSrv.exe 125 NT AUTHORITY\SYSTEM RASMAN.EXE 131 NT AUTHORITY\SYSTEM mstask.exe 137 NT AUTHORITY\SYSTEM mxserver.exe 147 NT AUTHORITY\SYSTEM PSTORES.EXE 154 NT AUTHORITY\SYSTEM NDDEAGNT.EXE 46 OMPHALOSKEPSIS\Administrator explorer.exe 179 OMPHALOSKEPSIS\Administrator SYSTRAY.EXE 74 OMPHALOSKEPSIS\Administrator cardview.exe 184 OMPHALOSKEPSIS\Administrator ltmsg.exe 167 OMPHALOSKEPSIS\Administrator daemon.exe 185 OMPHALOSKEPSIS\Administrator
Using pulist.exe from Perl is trivial. Here's one way to do it:
$pulistexe = "\\bin\\PULIST.EXE"; # location of the executable open(PULIST,"$pulistexe|") or die "Can't execute $pulistexe:$!\n"; scalar <PULIST>; # drop the first title line while(defined($_=<PULIST>)){ ($pname,$pid,$puser) = /^(\S+)\s*(\d+)\s*(.+)/; print "$pname:$pid:$puser\n"; close(PULIST);
The other program we mentioned, kill.exe, is equally easy to use. It takes as an argument either a process ID or part of a task name. I recommend the process ID format, to err on the safe side, since it is very easy to kill the wrong process if you use task names.
kill.exe offers two different ways to shoot down processes. The first is the polite death: kill.exe <process id> will ask that process to shut itself down. But if we add /f to the command line, kill.exe /f <process id> works more like the native Perl function and kills the process with extreme prejudice.
Our second approach uses the Win32::IProc module by Amine Moulay Ramdane. Though you wouldn't know it from the name, Win32::IProc is actually more useful for our purposes than Win32::Process, the more obviously named choice. Win32::Process has one significant drawback that takes it out of the running: it is designed to control processes that are started by the module itself. We're more interested in the processes other users have started. If you have trouble installing Win32::IProc, see the section Section 4.5, "Module Information for This Chapter" at the end of the chapter for installation hints.
First, create a process object like so:
use Win32::IProc; # note case of object is important, must be "IProc" $pobj = new Win32::IProc or die "Unable to create proccess object: $!\n";
This object is mostly used as a springboard from which to launch the module's object methods. For instance, to find the list of all of the running processes on a machine, we would use:
$pobj-> EnumProccesses(\@processlist) or die "Unable to get process list:$!\n";
@processlist is now an array of references to anonymous hashes. Each anonymous hash has two keys, ProcessName and ProcessId, with their expected values. To display this info nicely, we could use the following code:
use Win32::IProc; $pobj=new Win32::IProc or die "Unable to create process object: $!\n"; $pobj->EnumProcesses(\@processlist) or die "Unable to get process list:$!\n"; foreach $process (@processlist){ $pid = $process->{ProcessId}; $name = $process->{ProcessName}; write; } format STDOUT_TOP = Process ID Process Name ========== =============================== . format STDOUT = @<<<<<<< @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< $pid, $name .
We get output like this:
Process ID Process Name ========== ================================ 0 System-Idle 2 System 25 smss.exe 39 winlogon.exe 41 services.exe 48 lsass.exe 78 spoolss.exe 82 DKSERVICE.EXE ...
One difference between this approach and our use of pulist.exe earlier is that Win32::IProc does not have the ability to tell you the user context for a given process. If this information is important to you, you will need to use pulist.exe.
pulist.exe can only produce one kind of output, but the fun with Win32::IProc is just beginning. Let's say you were curious about not only which processes were running, but also which executable and dynamically loaded libraries (.dlls) each process was using. Finding this information is simple:
# imports the FULLPATH constant to show the path for the dlls, could be NOPATH use Win32::IProc "FULLPATH"; $pobj = new Win32::IProc; $pobj->EnumProcesses(\@processlist) or die "Unable to get process list:$!\n"; foreach $process (@processlist){ print "\n",$process->{ProcessName}, "\n",('=' x length($process->{ProcessName})),"\n"; $pobj->GetProcessModules($process->{ProcessId},\@modules,FULLPATH); print join("\n",map {lc $_->{ModuleName}} @modules),"\n"; }
GetProcessModules( ) takes a process ID, an array reference, and a flag that indicates whether the full directory path of the module will be returned. The array we reference is populated with references to anonymous hashes that contain information about each module used for that process. In our code we gather the names of all of the modules. map( ) is used to iterate over the array of references, dereferencing each anonymous hash and looking up the ModuleName key as we go.
Here's an excerpt from some sample output:
smss.exe ======== \systemroot\system32\smss.exe c:\winnt\system32\ntdll.dll winlogon.exe ============ \??\c:\winnt\system32\winlogon.exe c:\winnt\system32\ntdll.dll c:\winnt\system32\msvcrt.dll c:\winnt\system32\kernel32.dll c:\winnt\system32\advapi32.dll c:\winnt\system32\user32.dll c:\winnt\system32\gdi32.dll c:\winnt\system32\rpcrt4.dll c:\winnt\system32\userenv.dll c:\winnt\system32\shell32.dll c:\winnt\system32\shlwapi.dll c:\winnt\system32\comctl32.dll c:\winnt\system32\netapi32.dll c:\winnt\system32\netrap.dll c:\winnt\system32\samlib.dll c:\winnt\system32\winmm.dll c:\winnt\system32\cwcmmsys.dll c:\winnt\system32\cwcfm3.dll c:\winnt\system32\msgina.dll c:\winnt\system32\rpclts1.dll c:\winnt\system32\rpcltc1.dll...
Let's take this train of thought one stop further. We can find out even more about a running process with just a little bit of effort. To get the information we need about a process, we first have to get a handle for that process.
A process handle can be thought of as an open connection to a particular process. To illuminate the difference between a process handle and a process ID, let's take the analogy of a trailer park. If each trailer in the park is a process, then you can think of the process ID as the address of a trailer. It is a way of finding that specific trailer. A process handle is like the power/water/phone lines that run from the park itself into a specific trailer. Once these lines are in place, not only can you find a particular trailer, but you can also begin to communicate and exchange information with it from the outside.
To get the process handle from a process if we have its ID, we use Win32::IProc's Open( ) method. Open( ) takes a process ID, an access flag, an inheritance flag, and a reference to the scalar that will store the handle. The access flags we'll be using in the following example request just enough access to query a process for information. For more information on these flags, see the Win32::IProc documentation and the "Processes and Threads" section of the Win32 SDK base services documentation found on http://msdn.microsoft.com. Process handles that are Open( )'d need to be closed using CloseHandle( ).
With process handle in hand, we can use the Kill( ) method to kill this process:
# kill process and make it exit with that code $pobj->Kill($handle,$exitcode);
But killing processes is not the only use for process handles. For instance, we can use methods like GetStatus( ) to learn more about the process. Here's sample code that dumps out timing information about a given process ID:
use Win32::IProc qw(PROCESS_QUERY_INFORMATION INHERITED DIGITAL); $pobj = new Win32::IProc; $pobj->Open($ARGV[0],PROCESS_QUERY_INFORMATION,INHERITED,\$handle) or warn "Can't get handle:".$pobj->LastError( )."\n"; # DIGITAL = pretty-printed times $pobj->GetStatus($handle,\$statusinfo,DIGITAL); $pobj->CloseHandle($handle); while (($procname,$value)=each %$statusinfo){ print "$procname: $value\n"; }
Its output looks something like this:
KernelTime: 00:00:22:442:270 ExitDate: ExitTime: CreationDate: 29/7/1999 CreationTime: 17:09:28:100 UserTime: 00:00:11:566:632
Now we know when this process was started and how much system time it has taken up. The ExitDate and ExitTime fields are blank because the process is still running. You may be wondering how these fields could ever get filled in, given that one has to use the process ID of a running process to get a process handle. There are two answers to this question. First, it is possible to get a process handle for a running process and have that process die before you've closed the handle. A GetStatus( ) at that point will yield exit information for the deceased process. The second possibility involves a method we haven't seen yet called Create( ).
Create( ) allows you to launch processes from Win32::IProc, similar to the Win32::Process functionality mentioned earlier. If you do launch processes from the module, then the process object ($pobj) we've mostly ignored so far will contain process and thread information for the created process. With this information, you can do fun things like manipulate thread priorities and the windows of that process. We're not going to look at this functionality, but its mention does offer us a good segue to the next process module approach.
If last section's mention of manipulating the windows of a process piqued your interest, you will like our next approach. For this approach, we'll be looking at a module by Jens Helberg called Win32::Setupsup. It's called "Setupsup" because it is primarily designed to be used to supplement software installation (which often uses a program called Setup.exe).
Some installers can be run in so-called "silent mode" for totally automated installation. In this mode they ask no questions and require no "OK" buttons to be pushed, freeing the administrator from having to babysit the install. Software installation mechanisms that do not offer this mode (and there are far too many of them) make a system administrator's life difficult. Win32::Setupsup helps deal with these deficiencies. It can find information on running processes and manipulate them (or manipulate them dead if you so choose).
To get and install Win32::Setupsup, you should refer to the section Section 4.5, "Module Information for This Chapter" later for hints on getting it installed.
With Win32::Setupsup, getting the list of running processes is easy. Here's a slightly different version of the first full code sample we saw in the last section:
use Win32::Setupsup; $machine = ""; # query the list on the current machine Win32::Setupsup::GetProcessList($machine, \@processlist, \@threadlist) or die "process list error: ".Win32::Setupsup::GetLastError( )."\n"; pop(@processlist); # remove the bogus entry always appended to the list foreach $processlist (@processlist){ $pid = $processlist->{pid}; $name = $processlist->{name}; write; } format STDOUT_TOP = Process ID Process Name ========== =============================== . format STDOUT = @<<<<<<< @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< $pid, $name .
Killing processes is equally easy:
KillProcess($pid, $exitvalule, $systemprocessflag) or die "Unable to kill process: ".Win32::Setupsup::GetLastError( )."\n";
The last two arguments are optional. The first kills the process and sets its exit value accordingly (by default it is set to 0). The second argument allows you to kill system-run processes (providing you have the Debug Programs user right).
That's the boring stuff. We can take process manipulation to yet another level by interacting with the windows a running process may have open. To list all of the windows available on the desktop, we use:
Win32::Setupsup::EnumWindows(\@windowlist) or die "process list error: ".Win32::Setupsup::GetLastError( )."\n";
@windowlist now contains a list of window handles that just look like normal numbers when you print them. To learn more about each window, you can use a few different functions. For instance, to find the titles of each window, you can use GetWindowText( ) like so:
use Win32::Setupsup; Win32::Setupsup::EnumWindows(\@windowlist) or die "process list error: ".Win32::Setupsup::GetLastError( )."\n"; foreach $whandle (@windowlist){ if (Win32::Setupsup::GetWindowText($whandle,\$text)){ print "$whandle: $text","\n"; } else { warn "Can't get text for $whandle" . Win32::Setupsup::GetLastError( )."\n"; } }
Here's a little bit of sample output:
66130: chapter02 - Microsoft Word 66184: Style 194905150: 66634: setupsup - WordPad 65716: Fuel 328754: DDE Server Window 66652: 66646: 66632: OleMainThreadWndName
As you can see, some windows have titles, while others do not. Observant readers might notice something else interesting about this output. Window 66130 belongs to a Microsoft Word session that is currently running (it is actually the one this chapter was composed into). Window 66184 looks vaguely like the name of another window that might be connected to Microsoft Word. How can we tell if they are related?
Win32::Setupsup also has an EnumChildWindows( ) function that can show us the children of any given window. Let's use it to write something that will show us a basic tree of the current window hierarchy:
use Win32::Setupsup; # get the list of windows Win32::Setupsup::EnumWindows(\@windowlist) or die "process list error: ".Win32::Setupsup::GetLastError( )."\n"; # turn window handle list into a hash # NOTE: this conversion populates the hash with plain numbers and # not actual window handles as keys. Some functions, like # GetWindowProperties (which we'll see in a moment), can't use these # converted numbers. Caveat implementor. for (@windowlist){$windowlist{$_}++;} # check each window for children foreach $whandle (@windowlist){ if (Win32::Setupsup::EnumChildWindows($whandle,\@children)){ # keep a sorted list of children for each window $children{$whandle} = [sort {$a <=>$b} @children]; # remove all children from the hash, we won't directly # iterate over them foreach $child (@children){ delete $windowlist{$child}; } } } # iterate through the list of parent or childless windows and # recursively print each window handle and its children (if any) foreach my $window (sort {$a <=> $b} keys %windowlist){ &printfamily($window,0); } # print a given window handle number and its children (recursively) sub printfamily { # starting window, how deep in a tree are we? my($startwindow,$level) = @_; # print the window handle number at the appropriate indentation print((" " x $level)."$startwindow\n"); return unless (exists $children{$startwindow}); # no children, done. # otherwise, we have to recurse for each child $level++; foreach $childwindow (@{$children{$startwindow}}){ &printfamily($childwindow,$level); } }
There's one last window property function we should look at before moving on: GetWindowProperties( ). GetWindowProperties( ) is basically a catchall for the rest of the window properties we haven't seen yet. For instance, using GetWindowProperties( ) we can query the process ID for the process that created a specific window. This could be combined with some of the functionality we just saw for the Win32::IProc module.
The Win32::Setupsup documentation contains a list of the available properties that can be queried. Let's use one of them to write a very simple program that will print the dimensions of the rectangle of a window on the desktop. GetWindowProperties( ) takes three arguments: a window handle, a reference to an array that contains the names of the properties to query, and a reference to a hash where the query results will be stored. Here's the code we need for our task:
Win32::Setupsup::GetWindowProperties($ARGV[0],[rect,id],\%info); print "\t" . $info{rect}{top} . "\n"; print $info{rect}{left} . " -" . $ARGV[0] . "- " . $info{rect}{right} . "\n"; print "\t" . $info{rect}{bottom} . "\n";
The output is a bit cutesy. Here's a sample showing the top, left, right, and bottom dimensions of the window with handle 66180:
154 272 -66180- 903 595
GetWindowProperties( ) returns a special data structure for only one property, rect. All of the others will simply show up in the referenced hash as normal keys and values. If you are uncertain about the properties being returned by Perl for a specific window, the windowse utility found at http://greatis.virtualave.net/products.htm is often helpful.
Now that we've seen how to determine various window properties, wouldn't it be spiffy if we could make changes to some of these properties? For instance, it might be useful to change the title of a particular window. With this capability, we could create scripts that used the window title as a status indicator:
"Prestidigitation In Progress ... 32% complete"
Making this change to a window is a single function call:
Win32::Setupsup::SetWindowText($handle,$text);
We can also set the rect property we just saw. This code makes the specified window jump to the position we've specified:
use Win32::Setupsup; $info{rect}{left} = 0; $info{rect}{right} = 600; $info{rect}{top} = 10; $info{rect}{bottom}= 500; Win32::Setupsup::SetWindowProperties($ARGV[0],\%info);
I've saved the most impressive function for last. With SendKeys( ) it is possible to send arbitrary keystrokes to any window on the desktop. For example:
use Win32::Setupsup; $texttosend = "\\DN\\Low in the gums"; Win32::Setupsup::SendKeys($ARGV[0],$texttosend,'',0);
This will send a "down cursor key" followed by some text to the specified window. The arguments to SendKeys( ) are pretty simple: window handle, text to send, a flag to determine if a window should be activated for each keystroke, and an optional time between keystrokes. Special key codes like the "down cursor" are surrounded by backslashes. The list of available keycodes can be found in the module's documentation.
With the help of this module, we've taken process control to an entirely new level. Now it is possible to remotely control applications (and parts of the OS) without requiring the explicit cooperation of those applications. We don't need them to offer command line support or a special API. We have the ability to essentially script a GUI, useful in a myriad of system administration contexts.[1]
[1]Another module for GUI scripting you may find useful is Ernesto Guisado's Win32Guitest. It offers similar functionality to Win32::Setupsup.
Let's look at one final approach to NT/2000 process control before we switch to another operating system. This approach might be subtitled "Futureland" because it involves a technology which isn't widely available now, but is right on the horizon. Window Management Instrumentation (WMI) is available in Windows 2000 (and NT4.0SP4+ if explicitly installed).[2] Over time, when Windows 2000 is widely deployed, this has the potential to become an important part of the NT/2000 administration landscape.
[2]The "Download SDK"page linked off of the WMI section at http://msdn.microsoft.com/developer/sdk lets you download the core WMI libraries needed to run WMI on an NT4.0SP4 (or higher) machine.
Unfortunately WMI is one of those not-for-the-faint-of-heart technologies that gets very complex, very quickly. It is based on an object-oriented model that has the power to represent not only data, but relationships between objects as well. For instance, it is possible to create an association between a web server and the Redundant Arrays of Independent Disks (RAID) that holds the data for this server, so if the RAID device should fail, a problem for the web server will be reported as well. To deal with this complexity, we're just going to skim the very surface of WMI by providing a small and simple introduction, followed by a few code samples.
If you want to get a deeper look at this technology, I recommend downloading the WMI white papers, LearnWBM tutorial, and WMI SDK from the WMI section found at http://msdn.microsoft.com/developer/sdk. You should also have a look at the information found provided at the Distributed Management Task Force's web site, http://www.dtmf.org. In the meantime, here is a brief synopsis to get you started.
WMI is the Microsoft implementation and extension of an unfortunately named initiative called the Web-Based Enterprise Management initiative, or WBEM for short. Though the name conjures up visions of something that requires a browser, it has virtually nothing to do with the World Wide Web. The companies that were part of the Distributed Management Task Force (DMTF) wanted to create something that could make it easier to perform management tasks using browsers. Putting the name aside, it is clearer to say that WBEM defines a data model for management and instrumentation information. It provides specifications for organizing, accessing, and moving this data around. WBEM is also meant to offer a cohesive frontend for accessing data provided by the other management protocols like Simple Network Management Protocol (SNMP) (discussed in Chapter 10, "Security and Network Monitoring") and Common Management Information Protocol (CMIP).
Data in the WBEM world is organized using the Common Information Model (CIM). CIM is the source of the power and complexity in WBEM/WMI. It provides an extensible data model that contains objects and object classes for any physical or logical entity one might want to manage. For instance, there are object classes for entire networks, and objects for a single slot in a specific machine. There are objects for hardware settings and objects for software application settings. On top of this, CIM allows us to define object classes that describe relationships between other objects.
This data model is documented in two parts: the CIM Specification and the CIM Schema. The former describes the how of CIM (how the data will be specified, its connection to prior management standards, etc.); the latter provides the what of CIM (the actual objects). This division may remind you of the SNMP SMI and MIB relationship (see Chapter 10, "Security and Network Monitoring").
In practice, you'll be consulting the CIM Schema more than the CIM Specification once you get the hang of how the data is represented. The schema format (called MOF for Managed Object Format) is fairly easy to read.
The CIM Schema has two layers:
The core model for objects and classes useful in all types of WBEM interaction.
The common model for generic objects that are vendor- and operating-system independent. Within the common model there are currently five specific areas defined: Systems, Devices, Applications, Networks, and Physical.
Built on top of these two layers can be any number of Extension schema that define objects and classes for vendor- and OS-specific information.
A crucial part of WMI that distinguishes it from generic WBEM implementations is the Win32 Schema, an extension schema for Win32-specific information built on the core and common models. WMI also adds to the generic WBEM framework by providing Win32-specific access mechanisms to the CIM data.[3] Using this schema extension and set of data access methods, we can explore how to perform process control using WMI in Perl.
[3]As much as Microsoft would like to see these data access mechanisms become ubiquitous, the likelihood of finding them in a non-Win32 environment is slight. This is why I refer to them as "Win32-specific."
Two of these access methods, Open Database Connectivity (ODBC) and Compnent Object Model/Distributed Component Object Model (COM/DCOM), will receive a more complete treatment in other places in this book. We're going to use the latter for these examples because ODBC only allows us to query information from WMI (albeit in a simple, database-like manner). COM/DCOM allows us to both query management information and interact with it, crucial for the "control" part of process control.
The Perl code that follows does not appear to be particularly complex, so you may wonder about the "gets very complex, very quickly" description. The code below looks simple because:
We're only scratching the surface of WMI. We're not even going to touch subjects like "associations" (i.e., relationships between objects and object classes).
The management operations we are performing are simple. Process control in this context will consist of querying the running processes and being able to terminate them at will. These operations are easy in WMI using the Win32 Schema extension.
Our samples are hiding the complexity of translating WMI documentation and code samples in VBscript/JScript to Perl code.
Our samples are hiding the opaqueness of the debugging process. When WMI-related Perl code fails, it does so with very little information that would help debug the problem. You may receive error messages, but they never say ERROR: YOUR EXACT PROBLEM IS.... You're more likely to get back wbemErrFailed 0x8004100 or just an empty data structure. To be fair to Perl, most of this opaqueness comes from Perl's role in this process. It is acting as a frontend to a set of fairly complex multilayered operations that don't concern themselves with passing back useful feedback when something fails.
I know this sounds pretty grim, so let me offer some potentially helpful advice before we actually get into the code itself:
Look at all of the Win32::OLE sample code you can lay your hands on. The ActiveState Win32-Users mailing list archives found at http://www.activestate.com are a good source for this. If you compare this sample code to equivalent VBscript examples, you'll start to understand the translation idioms necessary. Section 4.5, "ADSI (Active Directory Service Interfaces)" in Chapter 6, "Directory Services", may also help.
Make friends with the Perl debugger, and use it to try out code snippets as part of this learning process. Another way to test out Perl snippets on Win32 platforms is to combine the TurboPerl program by William P. Smith (found at http://users.erols.com/turboperl/) with the dumpvar.pl or Data::Dumper modules. It has some bugs (I recommend you save your code often), but in general it can make prototyping Perl code easier. Other Integrated Development Environment tools may also offer this functionality.
Keep a copy of the WMI SDK handy. The documentation and the VBscript code examples are very helpful.
Use the WMI object browser in the WMI SDK frequently. It helps you get the lay of the land.
Let's get to the Perl part of this section. Our initial task will be to determine the information we can retrieve about Win32 processes and how we can interact with that information.
First we need to establish a connection to a WMI namespace. A namespace is defined in the WMI SDK as "a unit for grouping classes and instances to control their scope and visibility." In our case we're interested in connecting to the root of the standard cimv2 namespace, which contains all of the data that is interesting to us.
We will also have to set up a connection with the appropriate security privileges and impersonation level. Our program will need to be given the privilege to debug a process and to impersonate us; in other words, run as the user calling the script. After we get this connection, we will retrieve a Win32_Process object (as defined in the Win32 Schema).
There is a hard way and an easy way to create this connection and get the object. We'll look at both in the first example, so you get an idea of what the methods entail. Here's the hard way, with explanation to follow.
use Win32::OLE('in'); $server = ''; # connect to local machine # get a SWbemLocator object $lobj = Win32::OLE->new('WbemScripting.SWbemLocator') or die "can't create locator object: ".Win32::OLE->LastError( )."\n"; # set the impersonate level to "impersonate" $lobj->{Security_}->{impersonationlevel} = 3; # use it to get a an SWbemServices object $sobj = $lobj->ConnectServer($server, 'root\cimv2') or die "can't create server object: ".Win32::OLE->LastError( )."\n"; # get the schema object $procschm = $sobj->Get('Win32_Process');
The hard way involves:
Getting a locator object, used to find a connection to a server object
Setting the impersonation so our program will run with our privileges
Using this locator object to get a server connection to the cimv2 WMI namespace
Using this connection to retrieve a Win32_Process object
We can do this all in one step using a COM moniker's display name. According to the WMI SDK, "in Common Object Model (COM), a moniker is standard mechanism for encapsulating the location and binding of another COM object. The textual representation of a moniker is called a display name." Here's an easy way to do the same thing as the previous code snippet:
use Win32::OLE('in'); $procschm = Win32::OLE->GetObject( 'winmgmts:{impersonationLevel=impersonate}!Win32_Process') or die "can't create server object: ".Win32::OLE->LastError( )."\n";
Now that we have a Win32_Process object in hand, we can use it to show us the relevant parts of the schema that represents processes under Win32. This includes all of the available Win32_Process properties and methods we can use. The code to do this is fairly simple; the only magic in the following code is the use of the Win32::OLEin operator. To explain this, we need a quick digression.
Our $procschm object has two special properties, Properties_ and Methods_. Each holds a special child object, known as a collection object in COM parlance. A collection object is just a parent container for other objects; in this case, they are holding the schema's property method description objects. The in operator just returns an array with references to each child object of a container object. Once we have this array, we can iterate through it, returning the Name property of each child object as we go. See the section on Section 4.5, "ADSI (Active Directory Service Interfaces)" in Chapter 6, "Directory Services" for another prominent use of in. Here's what the code looks like:
use Win32::OLE('in'); # connect to namespace, set the impersonate level, and retrieve the # Win32_process object just by using a display name $procschm = Win32::OLE->GetObject( 'winmgmts:{impersonationLevel=impersonate}!Win32_Process') or die "can't create server object: ".Win32::OLE->LastError( )."\n"; print "--- Properties ---\n"; print join("\n",map {$_->{Name}}(in $procschm->{Properties_})); print "\n--- Methods ---\n"; print join("\n",map {$_->{Name}}(in $procschm->{Methods_}));
The output (on an NT4.0 machine) looks like this:
--- Properties --- Caption CreationClassName CreationDate CSCreationClassName CSName Description ExecutablePath ExecutionState Handle InstallDate KernelModeTime MaximumWorkingSetSize MinimumWorkingSetSize Name OSCreationClassName OSName PageFaults PageFileUsage PeakPageFileUsage PeakWorkingSetSize Priority ProcessId QuotaNonPagedPoolUsage QuotaPagedPoolUsage QuotaPeakNonPagedPoolUsage QuotaPeakPagedPoolUsage Status TerminationDate UserModeTime WindowsVersion WorkingSetSize --- Methods --- Create Terminate GetOwner GetOwnerSid
Let's get down to the business at hand. To retrieve a list of running processes, we need to ask for all instances of Win32_Process objects:
use Win32::OLE('in'); # perform all of the initial steps in one swell foop $sobj = Win32::OLE->GetObject( 'winmgmts:{impersonationLevel=impersonate}') or die "can't create server object: ".Win32::OLE->LastError( )."\n"; foreach $process (in $sobj->InstancesOf("Win32_Process")){ print $process->{Name}." is pid #".$process->{ProcessId},"\n"; }
Our initial display name did not include a path to a specific object (i.e., we left off !Win32_Process). As a result, we receive a server connection object. When we call the InstancesOf( ) method, it returns a collection object that holds all of the instances of that particular object. Our code visits each object in turn and prints its Name and ProcessId property. This yields a list of all the running processes.
If we want to be a little less beneficent when iterating over each process, we could instead use one of the methods we saw listed above:
foreach $process (in $sobj->InstancesOf("Win32_Process")){ $process->Terminate(1); }
This will terminate every process running. I do not recommend you run this code as is; customize it for your specific needs by making it more selective.
Now you have the knowledge necessary to begin using WMI for process control. WMI has Win32 extensions for many other parts of the operating system, including the registry and event log facility.
This is as far as we're going to delve into process control on WinNT/2000. Let's turn our attention to one last operating system.
Copyright © 2001 O'Reilly & Associates. All rights reserved.