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, 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 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
(.dll
s) 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 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.[10]
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).[11] Over time, when Windows 2000 is widely deployed, this has the potential to become an important part of the NT/2000 administration landscape.
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) 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).
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.[12] Using this schema extension and set of data access methods, we can explore how to perform process control using WMI in Perl.
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 backwbemErrFailed 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 6.5 in Chapter 6, 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
orData::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 namespaceUsing 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::OLE
in
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 6.5 in Chapter 6 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.
[10] Another module for GUI scripting you may
find useful is Ernesto Guisado’s
Win32Guitest
. It offers similar functionality to
Win32::Setupsup
.
[11] 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.
[12] 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.”
Get Perl for System Administration now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.