Getting fancy with PowerShell and WMI

Last time we got our feet wet with a simple PowerShell script to query Ethernet MAC addresses.  It looked easy, but of course, it requires you to know the magic WMI class name "MSNdis_EthernetCurrentAddress".  How do you go about discovering other interesting WMI classes?  Once again, PowerShell to the rescue:

PS > Get-WmiObject -Namespace root\wmi -List  | Where-Object {$_.name -Match "MSNdis" } | Sort-Object

This command will list all of the NDIS WMI classes available on your system.  (Try it out!)  There are quite a few, but you can bring order to chaos by classifying them into several main categories:

Class name prefix Description
MSNdis_80211 WiFi wireless miniports
MSNdis_Ethernet Ethernet (or Ethernet-emulating) miniports
MSNdis_Atm
MSNdis_Fddi
MSNdis_TokenRing
Very old media types (ATM, FDDI, and Token Ring)
MSNdis_Co CoNDIS drivers
MSNdis_Notify
MSNdis_Status
WMI Events (another great feature, which we'll talk about later)
MSNdis_Wmi Data structures used as parameters to methods (which we'll talk about right now)

 

Let's pick one of the classes at random, and see what we can do with it.  How about MSNdis_ReceiveScaleCapabilities?  From its name, we infer that it reads the adapter's RSS capabilities.  Recall that many of NDIS's WMI classes are translated directly from the underlying OID — knowing this will help us quite a bit as we start to decode the WMI class.  A quick search on MSDN turns up OID_GEN_RECEIVE_SCALE_CAPABILITIES, which uses an NDIS_RECEIVE_SCALE_CAPABILITIES structure.  Keep that documentation in mind as we explore the WMI class.

Now let's get our hands dirty:

PS > $Adapters = Get-WmiObject -Namespace root\wmi -Class MSNdis_ReceiveScaleCapabilities
PS > $Adapters[6]
Active           : True
InstanceName     : BitBlaster 2000 Ethernet Adapter

Well that's… disappointing.  We got back an array of instances (one for each network adapter), but the only real properties are the name a boolean Active flag.  I know that this network adapter supports RSS, so where is all the RSS info hiding?

It turns out that many NDIS classes don’t expose their information directly.  Instead, they require you to invoke a WMI method on the class to retrieve the information.  In order to find the method, let's use the PowerShell Get-Member cmdlet:

PS > Get-WmiObject -Namespace root\wmi -Class MSNdis_ReceiveScaleCapabilities | Get-Member

   TypeName: System.Management.ManagementObject#root\wmi\MSNdis_ReceiveScaleCapabilities

Name                             MemberType   Definition
----                             ----------   ----------
WmiQueryReceiveScaleCapabilities Method       System.Management.ManagementBaseObject WmiQueryReceiveScaleCapabilitie...
Active                           Property     System.Boolean Active {get;set;}
InstanceName                     Property     System.String InstanceName {get;set;}
__CLASS                          Property     System.String __CLASS {get;set;}
__DERIVATION                     Property     System.String[] __DERIVATION {get;set;}
__DYNASTY                        Property     System.String __DYNASTY {get;set;}
__GENUS                          Property     System.Int32 __GENUS {get;set;}
__NAMESPACE                      Property     System.String __NAMESPACE {get;set;}
__PATH                           Property     System.String __PATH {get;set;}
__PROPERTY_COUNT                 Property     System.Int32 __PROPERTY_COUNT {get;set;}
__RELPATH                        Property     System.String __RELPATH {get;set;}
__SERVER                         Property     System.String __SERVER {get;set;}
__SUPERCLASS                     Property     System.String __SUPERCLASS {get;set;}
ConvertFromDateTime              ScriptMethod System.Object ConvertFromDateTime();
ConvertToDateTime                ScriptMethod System.Object ConvertToDateTime();

There's a lot of stuff in there, but you can see that one member is a method named WmiQueryReceiveScaleCapabilities.  Let's dig into it:

PS > Get-WmiObject -Namespace root\wmi -Class MSNdis_ReceiveScaleCapabilities | Get-Member -Name WmiQueryReceiveScaleCapabilities | Format-List

TypeName   : System.Management.ManagementObject#root\wmi\MSNdis_ReceiveScaleCapabilities
Name       : WmiQueryReceiveScaleCapabilities
MemberType : Method
Definition : System.Management.ManagementBaseObject
             WmiQueryReceiveScaleCapabilities(System.Management.ManagementObject#MSNdis_WmiMethodHeadr Header)

So it's a function that takes an MSNdis_WmiMethodHeader as input, and returns another WMI class as output.  You'll find that this is the general pattern for NDIS's WMI methods — fortunately, the MSNdis_WmiMethodHeader is basically boilerplate; you don't have to fret over how to create each one.  Let's create a little helper function to do that boilerplate for us:

$NDIS_WMI_METHOD_HEADER_REVISION_1             = 1
$NDIS_WMI_OBJECT_TYPE_METHOD                   = 0x02
$NDIS_SIZEOF_WMI_METHOD_HEADER_REVISION_1      = 0xffff

function Get-NdisObjectHeader
{
    param(
        $revision = $NDIS_WMI_METHOD_HEADER_REVISION_1,
        $type     = $NDIS_WMI_OBJECT_TYPE_METHOD,
        $size     = $NDIS_SIZEOF_WMI_METHOD_HEADER_REVISION_1
    )

    $hdr = ([wmiclass]'root\wmi:MSNdis_ObjectHeader').CreateInstance()
    $hdr.Revision      = $revision
    $hdr.Type          = $type
    $hdr.Size          = $size

    return $hdr
}

function Get-NdisWmiHeader
{
    param($timeout = 5)

    $whdr = ([wmiclass]'root\wmi:MSNdis_WmiMethodHeader').CreateInstance()
    $whdr.Header       = Get-NdisObjectHeader
    $whdr.PortNumber   = 0
    $whdr.NetLuid      = 0
    $whdr.Padding      = 0
    $whdr.RequestId    = 0
    $whdr.Timeout      = $timeout
    return $whdr
}

Now that we can conjure up the input parameter, we can return to the task of executing our WMI method.  This part should be easy:

PS > $whdr = Get-NdisWmiHeader
PS > $RSS = $Adapters[6].WmiQueryReceiveScaleCapabilities($whdr)
PS > $RSS

RssCaps          : System.Management.ManagementBaseObject

PS > $RSS.RssCaps

CapabilitiesFlags         : 184550145
Header                    : System.Management.ManagementBaseObject
NumberOfInterruptMessages : 1
NumberOfReceiveQueues     : 2

Ah-hah!  Now that looks like a promising result!  (You didn't really have doubts that we'd figure it out eventually, did you?)

The last mystery remaining is the number 184550145.  But if you remember what we said earlier — WMI classes are based on OID requests — you'll check the documentation for NDIS_RECEIVE_SCALE_CAPABILITIES.  And indeed, each field listed above corresponds to a field in that driver structure.  So you can conclude that you should use the flags documented there to decode the CapabilitiesFlags.  In this case, decimal 184550145 is hex 0x0B000301, which corresponds to NDIS_RSS_CAPS_MESSAGE_SIGNALED_INTERRUPTS | NDIS_RSS_CAPS_CLASSIFICATION_AT_ISR | NDIS_RSS_CAPS_USING_MSI_X | NDIS_RSS_CAPS_HASH_TYPE_TCP_IPV4 | NDIS_RSS_CAPS_HASH_TYPE_TCP_IPV6 | NdisHashFunctionToeplitz.

Now that you're armed with the techniques above, you have the chops to explore the remaining NDIS WMI classes.  Next time we'll talk about one more cool trick you can do with WMI.