This is about a small tool I have been using for a while now to let me talk to the WMI (Windows Management Instrumentation) object model exposed by the Windows operating system. WMI is used for various management related tasks and is a very powerful API.
I wanted to write the script at one sitting (and not a long one) and so a scripting language was an obvious choice. This could have been written in C++ or C# or any COM interface aware language also. I started of with a Ruby version, but decided to quit on that because the Ruby libraries for interfacing with OLE were not very stable when it came to WMI programming. I believe that is being fixed now. Perl was good choice because it was sufficiently dynamic and more importantly it had stable libraries. The source here can be readily translated to VBScript or JScript versions also. If you do so, drop me a mail.
I had mailed about this script to the user groups in the past, but I got a feeling that I had done no justice to what this script and WMI could actually achieve. Hence this article.
Download and Docs
You can download the script here, or simply copy paste it and save it from this article. To make this work, you need to have Perl on your machine. Perl can be downloaded for free from here.
If you are new to WMI and what to have a good understanding of what is available I suggest you take a look at:
· WMI Scripting Primer Part 1 Part 2 Part 3
· The MSDN site on scripting is a good resource: http://msdn.microsoft.com/scripting
· Scriptomatic: playing around with this HTA application for auto-generating WMI scripts is also lots of fun
· My previous blog entry Bangalore User Group – WSH, WMI and System.Management has some beginner level scripts that I had demoed at one the UG meetings.
What can you do with wmi.pl ?
The following is a brief description of what you can do with the wmi.pl.
Listing Instances
> wmi.pl
Lists all the running processes on your computer. The out looks a little like this:
D:\scr>wmi.pl
1 System Idle Process
2 System
3 SMSS.EXE
4 CSRSS.EXE
5 WINLOGON.EXE
6 SERVICES.EXE
Specifying properties to display
> wmi.pl -executablepath
Lists all the processes as well as their physical paths on disk.
D:\scr>wmi.pl -executablepath
1 System Idle Process
2 System
3 SMSS.EXE C:\WINNT\System32\smss.exe
4 CSRSS.EXE
5 WINLOGON.EXE C:\WINNT\system32\winlogon.exe
6 SERVICES.EXE C:\WINNT\system32\services.exe
7 LSASS.EXE C:\WINNT\system32\lsass.exe
8 svchost.exe C:\WINNT\system32\svchost.exe
> wmi.pl -< property name >
Lists all the processes with the specified property of the process displayed. This requires some explanation. The processes I refer to here are instance of the WMI class called Win32_Process. You can look at the documentation for the class to see what properties this class supports. Or you can you some of the reflection capabilities of WMI which I have discussed in the next usage sample. The ‘executablepath’ that was shown earlier was one such property. There are several others that the Win32_Process class supports.
This shows the process id of each process as well as how many threads each process currently has. Powerful?
D:\scr>wmi.pl -processid -threadcount
1 System Idle Process 0 1
2 System 8 46
3 SMSS.EXE 152 6
4 CSRSS.EXE 176 12
5 WINLOGON.EXE 172 19
6 SERVICES.EXE 224 37
7 LSASS.EXE 236 18
8 svchost.exe 420 12
9 spoolsv.exe 452 11
Examining Classes for Properties and Methods
> wmi.pl ?
Lists all the properties and methods available for the process class. This uses the reflection capabilities of WMI. It examines the Win32_Process class and tells you what properties and methods the class provides. So you can use the information retrieved to know what property name parameters you can use. The output here is edit, the Win32_Process class support many more properties than shown here.
D:\scr>wmi.pl ?
Win32_Process Properties : -----------------------------
1 Caption
2 CreationClassName
3 CreationDate
4 CSCreationClassName
5 CSName
6 Description
7 ExecutablePath
8 ExecutionState
44 WriteTransferCount
Win32_Process Methods : -----------------------------
1 Create
2 Terminate
3 GetOwner
4 GetOwnerSid
So it is easy to see that ‘wmi.pl – creationdate’ is a valid query. Exploring this API can give you lots of useful information. I shall shortly show you what we can do with the methods.
Displaying all the Properties of a Class
> wmi.pl -*
This will list all available information about every process on the system. Information such as memory usage, kernel usage, thread count etc are available. In case you want to see a lot of information about every process, the tabular view (generated by specifying property names in the command line) maybe inadequate. This is again highly truncated output that is shown.
51
Caption = perl.exe
CreationClassName = Win32_Process
CreationDate = 20040430233435.908750+330
CSCreationClassName = Win32_ComputerSystem
CSName = RMZ10F01
Description = perl.exe
ExecutablePath = D:\RoshanJ\Progs\perl\bin\perl.exe
ExecutionState =
Handle = 3368
HandleCount = 106
PeakWorkingSetSize = 5586944
Priority = 8
PrivatePageCount = 2265088
ProcessId = 3368
QuotaNonPagedPoolUsage = 4780
QuotaPagedPoolUsage = 20416
QuotaPeakNonPagedPoolUsage = 5176
QuotaPeakPagedPoolUsage = 20420
ReadOperationCount = 54
Specifying classes to use
> wmi.pl < class name>
So far we have seen that wmi.pl retrieves information about the Win32_Process class. What about all the other information that WMI? The Perl script basically has Win32_Process hard coded as the default class to use. To provide all the behavior that you saw above, taking place for some other class (other than Win32_Process), simply specify the name of that class at the command line.
Doing so will cause the rest of the parameters to act for the given class name. For example Win32_Share is the class name for shares of your computer. So ‘wmi.pl win32_share’ will list all the shares and ‘wmi.pl win32_share –path’ will list the share names and their physical paths on your computer.
D:\scr>wmi.pl win32_share
2 System.DDL
4 ut
7 dotnet-talk
8 install
10 ftproot
and (notice we pass –path here, so ‘path’ must be a property of win32_share)
D:\scr>wmi.pl win32_share -path
2 System.DDL D:\RoshanJ\Homepage\work\System.DDL
4 ut D:\ut
7 dotnet-talk D:\dotnet-talk
8 install D:\RoshanJ\install
10 ftproot C:\Inetpub\ftproot
To take a look at what Win23_Share provides
D:\scr>wmi.pl win32_share ?
win32_share Properties : -----------------------------
1 AccessMask
2 AllowMaximum
3 Caption
4 Description
5 InstallDate
6 MaximumAllowed
7 Name
8 Path
9 Status
10 Type
win32_share Methods : -----------------------------
1 Create
2 SetShareInfo
3 Delete
Win32_Service represents services on your computer. Win32_LogicalDisk represent drives on your system and so on. All available properties about a win32_LogicalDisk maybe queried by typing ‘wmi.pl win32_logicaldisk ?’, similar to the above sample with Win32_Share. Simple?
Now you may ask, how you can know what classes are available. Read on.
Examining Namespaces for Classes and Namespaces
> wmi.pl dir
In WMI classes are organized into namespaces. The script wmi.pl is configured to use the namespace ‘root/cimv2’ by default. The above line will list all the classes and nexted namespaces in a namespace. This is a rather huge list so you may want to save this into a text file. Once you find a class that you think is useful, you can query information about the classes listed here by specifying the class name and using the ‘?’ parameter.
D:\scr>wmi.pl dir
root/cimv2 Classes : -----------------------------
441 \\RMZ10F01\ROOT\CIMV2:Win32_DeviceBus
442 \\RMZ10F01\ROOT\CIMV2:Win32_CIMLogicalDeviceCIMDataFile
443 \\RMZ10F01\ROOT\CIMV2:Win32_ShareToDirectory
444 \\RMZ10F01\ROOT\CIMV2:Win32_NetworkAdapterConfiguration
445 \\RMZ10F01\ROOT\CIMV2:Win32_NetworkAdapterSetting
446 \\RMZ10F01\ROOT\CIMV2:Win32_PortableBattery
447 \\RMZ10F01\ROOT\CIMV2:Win32_SystemSlot
448 \\RMZ10F01\ROOT\CIMV2:Win32_PortConnector
449 \\RMZ10F01\ROOT\CIMV2:Win32_PhysicalMemory
450 \\RMZ10F01\ROOT\CIMV2:Win32_SystemEnclosure
451 \\RMZ10F01\ROOT\CIMV2:Win32_BaseBoard
Win32_Process Namespaces : -----------------------------
1 Applications
2 ms_409
To redirect content to text file
D:\scr>wmi.pl dir > filename.txt
Specifying other Namespaces
> wmi.pl /ns:< namespace> < other parameters>
You can change the namespace where the script should find the WMI class you mention by using the /ns: specification. This might be a little abstract to understand but consider this. The previous option ‘dir’ showed you only contents of the ‘root/cimv2’ namespace, if you wanted to see the contents of the ‘root’ namespace the you could use this feature.
D:\scr>wmi.pl /ns:root dir
root Classes : -----------------------------
1 \\RMZ10F01\ROOT:__SystemClass
2 \\RMZ10F01\ROOT:__NAMESPACE
3 \\RMZ10F01\ROOT:__Provider
4 \\RMZ10F01\ROOT:__Win32Provider
5 \\RMZ10F01\ROOT:__ProviderRegistration
6 \\RMZ10F01\ROOT:__ObjectProviderRegistration
7 \\RMZ10F01\ROOT:__ClassProviderRegistration
8 \\RMZ10F01\ROOT:__InstanceProviderRegistration
9 \\RMZ10F01\ROOT:__PropertyProviderRegistration
Win32_Process Namespaces : -----------------------------
1 DEFAULT
2 SECURITY
3 CIMV2
4 WMI
5 directory
6 MSAPPS10
7 NetFrameworkv1
8 MSAPPS
9 MSAPPS11
As you can see, the root namespace contains a namespace called the ‘cimv2’.
Here is an example that lists instances of the Win32_OleDbProvider which is part of the ‘root/msapps’ namespace.
D:\scr>wmi.pl /ns:root/msapps win32_oledbprovider
1 VSEE Versioning Enlistment Manager Proxy Data Source
2 MediaCatalogDB OLE DB Provider
3 Microsoft OLE DB Provider for SQL Server
4 Microsoft OLE DB Provider for DTS Packages
5 SQL Server Replication OLE DB Provider for DTS
6 MediaCatalogMergedDB OLE DB Provider
7 Microsoft ISAM 1.1 OLE DB Provider
8 Microsoft OLE DB Provider For Data Mining Services
9 MSDataShape
10 VSEE Versioning Enlistment Manager Proxy Data Source
Collecting data from remote computers
> wmi.pl @< computer name > [some other parameters]
All the commands that you saw so far were getting information about your computer. If you are on a network, WMI can let you gather such information about a remote computer as well.
Specifying a computer name or IP address preceded by an @ will cause the whole command to run on a remote system. To execute this successfully your current logged on user id should have admin rights on the remote system. Such a security check is essential because WMI can do potentially powerful and dangerous things such as stopping and starting of services, processes, access to disk partitions, bios etc.
The following command lists shares on a remote computer and shows you the physical paths of the share on that computer.
D:\scr>wmi.pl @machine01 win32_share -path
22 v0.2_dist D:\v0.2_dist
23 ecma D:\rotor_text\ecma
25 rotor_text D:\rotor_text
26 ruby1.8 D:\RoshanJ\Progs\ruby1.8
27 1.0.0001.0020 D:\blog\blogx\1.0.0001.0020
Calling Methods on WMI classes
wmi.pl –terminate()
Will terminate every process on your system (that you have rights to terminate). I am not running this as a demo right now, just take my word for it.
If you remember the output of the ‘?’ parameter you might recollect that it displays properties and methods that are available for a class. Thus terminate() was a method of the Win32_Process class. Calling terminate on a process, as expected, would try to terminate it.
wmi.pl -< property name >=< value > < method name>()
Often we do not want to want to call a method on every instance of a class. Like, would would not want to terminate() all the process, but say only a set of them. This is when the above syntax comes in useful. This will call the method specified on every instance of the specified class where the given property has the given value.
So if you want to terminate all the instances of Internet explorer that are running on your system you would say:
D:\scr>wmi.pl -name=iexplore.exe terminate()
Query = select * from Win32_Process where name='iexplore.exe'
0 IEXPLORE.EXE->terminate
1 IEXPLORE.EXE->terminate
2 IEXPLORE.EXE->terminate
Similarly if you want to terminate all instances of IE on a remote system ‘machine01’, you could say:
D:\scr>wmi.pl @machine01 -name=iexplore.exe terminate()
Query = select * from Win32_Process where name='iexplore.exe'
0 IEXPLORE.EXE->terminate
1 IEXPLORE.EXE->terminate
2 IEXPLORE.EXE->terminate
Here is another example.
wmi.pl @machine01 win32_operatingsystem reboot()
This will reboot the remote machine. Of course, your current logged on user needs admin permissions on the remote system for this to happen.
That’s it for now.
I haven’t finished shaking my head at the power of this API that has been ignored to obscurity, and my neck’s beginning to hurt. Try your own stuff and in the meanwhile I will be adding to the script.
There are some things I want to add to script, but haven’t done out of sheer laziness and the thought of getting back to Perl’s rather arcane syntax.
· Support for impersonation
· Support for regular expressions
· Support for method calls with parameters
· Support for display the CIM class definitions
Here is the Perl source (Those of you who have used WMI in some way will notice the consistency when you use the API across languages). How large did you expect the source to be? Since I am a Perl newbie, this code maybe substantially clunkier than what could be written by experienced hands.
use Win32;
use Win32::OLE qw (in);
$system = ".";
$classname = "Win32_Process";
@props = ("name");
%prop_value=();
$namespace = "root/cimv2";
$call = "instances";
$serial_no = 1;
sub list_instances {
$serv = Win32::OLE->GetObject("winmgmts://$system/$namespace");
$objs = $serv->InstancesOf("$classname");
$i = 1;
foreach $obj (in($objs)) {
if($serial_no == 1){
$str = "$i ";
}
else {
$str = "";
}
if ($all_props) {
foreach $prop (in($obj->{Properties_})) {
$str = "$str\n $prop->{name} = $obj->{$prop->{name}}";
}
$str = "$str\n"
}
else{
foreach $prop (in(@props)) {
$str = "$str\t$obj->{$prop}";
}
}
$str = "$str\n";
print $str;
$i = $i + 1;
}
}
sub list_classinfo {
$obj = Win32::OLE->GetObject("winmgmts://$system/$namespace:$classname");
print "$classname Properties : -----------------------------\n";
$i = 1;
foreach $prop (in($obj->{Properties_})) {
print "$i\t$prop->{name}\n";
$i = $i + 1;
}
print "$classname Methods : -----------------------------\n";
$i = 1;
foreach $m (in($obj->{Methods_})) {
print "$i\t$m->{name}\n";
$i = $i + 1;
}
}
sub list_namespaceinfo {
$serv = Win32::OLE->GetObject("winmgmts://$system/$namespace");
print "$namespace Classes : -----------------------------\n";
$i = 1;
foreach $class (in($serv->SubClassesOf())) {
$path = $class->{Path_}->{Path};
print "$i\t$path\n";
$i = $i + 1;
}
print "$classname Namespaces : -----------------------------\n";
$i = 1;
foreach $ns (in($serv->InstancesOf("__NAMESPACE"))) {
print "$i\t$ns->{name}\n";
$i = $i + 1;
}
}
sub call_method {
$serv = Win32::OLE->GetObject("winmgmts://$system/$namespace");
if ((scalar keys (%prop_value)) == 0){
$query="select * from $classname";
} else{
$query="select * from $classname where ";
$and = "";
for $key (keys %prop_value) {
$value = %prop_value->{$key};
$query = "$query $and $key=\'$value\'";
$and = "and";
}
}
print "Query = $query\n";
$objs = $serv->ExecQuery($query);
$i = 0;
foreach $obj (in ($objs)) {
print "$i $obj->{name}->$methodname\n";
$obj->{$methodname};
$i = $i + 1
}
}
foreach $arg (in(@ARGV)) {
if ($arg =~ m#/sys:(.*)#) {
$system = $1;
} elsif ($arg =~ m#@(.*)#) {
$system = $1;
} elsif ($arg =~ m#\/class\:(.*)#) {
$classname = $1;
} elsif ($arg =~ m#\-\*#) {
$all_props = 1;
} elsif ($arg =~ m#\-(.*)=(.*)#) {
%prop_value->{$1}=$2;
} elsif ($arg =~ m#\-(.*)#) {
@props[$#props+1]=$1;
} elsif ($arg =~ m#^\?$#) {
$call = "classinfo";
} elsif ($arg =~ m#dir#) {
$call = "dir";
} elsif ($arg =~ m#(.*)\(.*\)#) {
$methodname = $1;
$call = "method";
} elsif ($arg =~ m#\/ns\:(.*)#) {
$namespace = $1;
} elsif ($arg eq "/no_serial") {
$serial_no = 0;
} elsif (($arg eq "help") || ($arg eq '/?')) {
print "WMI command line Tool (c)Roshan James, 2004\n\n";
print "Help is available at:\n";
print "http://www.thinkingms.com/pensieve/CommentView,guid,64df1ee9-a582-474c-960a-0063cd848609.aspx\n";
print "Or mail spark\@mvps.org\n\n";
exit 0
} else {
$classname = $arg;
}
}
if ($call eq "instances") {
list_instances();
} elsif ($call eq "dir") {
list_namespaceinfo();
} elsif ($call eq "classinfo") {
list_classinfo();
} elsif ($call eq "method") {
call_method();
}