desk_spill_large

As part of another book I’m working on (yes, I haven’t learned to stop trying to write yet), I’m contrasting some task-oriented scenarios between PowerShell and VBScript.  In this case: querying WMI (or CIM, actually).  As a side note, Microsoft has recommended that developers switch to using Get-CimInstance over Get-WmiObject, and therefor I switched over early on.

So, what do I mean by “late-binding”?  I’m probably misusing the term here, but what I’m really referring to is hard-coded vs. dynamic coding patterns.  Another way to state this might be: rather than querying for an explicit WMI namespace, lass name and property name, I’m passing in those items as variables.  PowerShell developers who’ve rarely (or never) worked with VBScript are probably saying “duh!“, since this is rather a non-issue for them.  However, in VBScript context, late-binding in this situation is rarely discussed, even though it is possible.

First up to bat, PowerShell:

$CN = "Win32_ComputerSystem"
$Prop = "Model"
Get-CimInstance -ClassName $CN -NameSpace root/cimv2 | 
  Select-Object -ExpandProperty $Prop

The PowerShell cmdlet “Get-CimInstance” supports late-binding by very nature, because you specify the namespace, class name, and property at runtime.

Next up, VBScript (standard approach):

Note: I’m using “.” rather than an explicit computer hostname or assigning a variable and passing it in and concatenating it because I’m trying to keep this short and simple.  The connection is to the local machine only, so I’m just using a static reference for the “winmgmts” connection.

cs = "winmgmts:{impersonationLevel=impersonate}\\.\root\cimv2"
Set objWMI = GetObject(cs)
query = "select * from Win32_ComputerSystem"
Set colItems = objWMI.ExecQuery(query,,48)
For each objItem in colItems
  wscript.echo objItem.Model
Next

Notice that “select * from Win32_ComputerSystem” and “wscript.echo objItem.Model” both use a hard-coded approach.  Even if you change the query string to “select Model from Win32_ComputerSystem” you still need to reference the property name to fetch its assigned value (in the for/each loop).

This works fine, but when you want to make multiple queries for properties, particularly from different classes, you could end up with a lot of redundant code and a real mess.  Remember: Refactoring is king!

Wouldn’t it be nice in VBScript if you could declare an abstract interface to the WMI stack, at least for classes within a common namespace?  (See notes below)

And finally, VBScript, late-binding approach:

CN = "Win32_ComputerSystem"
Prop = "Model"
cs = "winmgmts:{impersonationLevel=impersonate}\\.\root\cimv2"
Set objWMI = GetObject(cs)
query = "select * from " & CN
Set colItems = objWMI.ExecQuery(query,,48)
For each objItem in colItems
  result = objItem.Properties_.Item(Prop)
Next
wscript.echo result

This approach allows you to encapsulate the code within a reusable function…

Function Get_CimInstance (ClassName, Property)
  Dim objWMI, query, colItems, cs, result : result = ""
  query = "select * from " & ClassName"
  cs = "winmgmts:{impersonationLevel=impersonate}\\.\root\cimv2"
  Set objWMI = GetObject(cs)
  Set colItems = objWMI.ExecQuery(query,,48)
  For each objItem in colItems
    result = objItem.Properties_.Item(Property)
  Next
  Get_CimInstance = result
End Function

Some example use-cases:

ModelName = Get_CimInstance ("Win32_ComputerSystem", "Model")
OsCaption = Get_CimInstance ("Win32_OperatingSystem", "Caption")

This example is still not very efficient.  I’m fetching all properties and sifting through to return only one of them.  It would be more efficient to change the query statement to something like “select ” & Property & ” from ” & ClassName.  This would shorten the collection size, reducing the iteration time within For each.

You can expand on this function wrapping approach as well.  For example, you could change the single-instance property approach to using a list of property names.  Whether you choose to implement that using an Array or a delimited string is of minor concern.  The biggest change to the function would be how you associate the return values to the associated property names.  You could pass them back as a Dictionary object (hash construct), or a delimited string (INI or XML construct), and so on.  I personally prefer using an ADO RecordSet object, but that’s arbitrary.

Here is an example using a simple delimited-string method for passing in the list of property names, as well as returning the results in a nested delimited-string format.  This is essentially a “poor man’s hash table”, but it works fairly well:

Function Get_WMI_Properties (Computer, ClassName, PropertyNames)
  Dim objWMI, colItems, objItem, PropertyName, cs, row
  Dim query, row, val, result : result = ""
  cs = "winmgmts:{impersonationLevel=impersonate}\\" & _
    Computer & "\root\cimv2"
  query = "select " & PropertyNames & " from " & ClassName
  On Error Resume Next
  Set objWMI = GetObject(cs)
  If err.Number = 0 Then
    Set colItems = objWMI.ExecQuery(query,,48)
    For each objItem in colItems
      For each PropertyName in Split(PropertyNames, ",")
        val = objItem.Properties_.Item(PropertyName)
        row = PropertyName & "=" & val
        If result <> "" Then
          result = result & vbCRLF & row
        Else
          result = row
        End If
        row = ""
      Next
    Next
  Else
    wscript.echo "error: access denied or computer unavailable"
  End If
  Get_WMI_Properties = result
End Function

Example usage:

test = Get_WMI_Properties (".", "Win32_ComputerSystem", "Manufacturer,Model,SystemType")
wscript.echo test

Long story / short = This is really is not intended as a reason to avoid porting the code to PowerShell.  It’s intended for maintenance and optimization only.  VBScript is still around, and will remain so for a long, long time.  Windows 10 and Office 365 still rely on it (slmgr.vbs, ospp.vbs, etc.), and System Center Configuration Manager 2012 R2 still uses an HTA installer package, which relies on VBScript.

So no matter what anyone might claim otherwise, even the maker hasn’t killed the language just yet.  A lot of developers are still required to maintain legacy code, so my opinion is that it helps to find ways to make it less painful.

Notes:

  1. I assigned the WMI connection string to variable “cs” only to shorten the string length on each line (to avoid wrapping)
  2. “{impersonationLevel=impersonate}” is arbitrary.  Depending up on the situation, you could remove it, or use a different impersonation method.

Additional Resources

Advertisements

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s