Hey, Scripting Guy! Question

I have a script that I wrote for the help desk people. It is actually a pretty cool script because it lets the user of the script type in a WMI class name, and it connects to the remote computer and retrieves all the information from the particular WMI class that they typed. The problem is that some of the help desk people do not always get the WMI class name typed in properly. When this happens, the script often takes a one way trip to la-la land. This is not a good user experience, even if it was the fault of the help desk person. I do not want to limit them to only querying a certain number of classes, because I want them to be able to do their job. Patiently waiting for you to fix my problem…


-- TC

Hey, Scripting Guy! AnswerHello TC,

It might be coincidental, but as I was reading your e-mail to scripter@microsoft.com, Rory Block began singing "Tallahatchie Blues" on my Zune.  Man, what a strong, clean sound. If you are singing the blues, you could do much worse than sounding like she does. Speaking of blues, some of the deepest blues I have ever seen were at the rim of Haleakala Crater on Maui, Hawaii. I was up there a few years ago taking pictures; one of my favorite pictures is shown just below. Because this volcano is 10,023 feet (3,055 meters) above sea level, most of the time you are driving through the clouds. And when you are above the clouds, the sky is generally deep blue and the colors are so vivid it seems as if you are on another planet. I love taking my laptop up there because it is peaceful and a great place to work on a book. (I have finished two different books on the island of Maui, and the Haleakala Crater is one of my favorite spots, if I cannot be under the water. But I do not yet have a good laptop to take with me when I'm scuba diving.)

Image of Haleakala Crater


There are two approaches to ensuring that your users only submit allowed values for the script parameters. The first is to offer only a limited number of values. The second approach allows the user to enter any value for the parameter. That value is then determined if it is valid before it is passed along to the remainder of the script. In the Get-ValidWmiClassFunction.ps1 script, a function named Get-ValidWmiClass is used to determine if the value that is supplied to the script is a legitimate WMI class name. In particular, the Get-ValidWmiClass function is used to determine if the string which is passed via the class parameter can be cast to a valid instance of the System.Management.ManagementClass .NET Framework class. The purpose of using the [wmiclass] type accelerator is to convert a string into an instance of the System.Management.ManagementClass class. As seen here, when you assign a string value to a variable, the variable becomes an instance of the System.String class. The GetType method is used to display information about the type of object that is contained in a variable:

PS C:\> $class = "win32_bio"
PS C:\> $class.GetType()

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     String                                   System.Object

To convert the string to a WMI class, you can use the [wmiclass] type accelerator. The string value must contain the name of a legitimate WMI class. If the WMI class you are trying to create on the computer does not exist, an error is generated. This is seen here:

PS C:\> $class = "win32_bio"
PS C:\> [wmiclass]$class
Cannot convert value "win32_bio" to type "System.Management.ManagementClass".
Error: "Not found "
At line:1 char:16
+ [wmiclass]$class <<<<


The Get-ValidWmiClassFunction.ps1 script begins by creating two command-line parameters. The first is the computer parameter that is used to allow the script to run on a local or remote computer. The second parameter is the class parameter that is used to provide the name of the WMI class that will be queried by the script. The third parameter is used to allow the script to inspect other WMI namespaces. All three parameters are strings. This is seen here:

Param (

   [string]$computer = $env:computername,

   [string]$class,

   [string]$namespace = "root\cimv2"

) #end param


The Get-ValidWmiClass function is used to determine if the value supplied for the class parameter is a valid WMI class on the particular computer. This is important because certain versions of the operating system contain unique WMI classes. For example, Windows XP contains a WMI class named netdiagnostics that does not exist on any other version of Windows. Windows XP does not contain the WMI class Win32_Volume, but Windows Server 2003 and later do have this class. So checking for the existence of a WMI class on a remote computer is a good practice to ensure that the script will run in an expeditious manner.

The first thing the Get-ValidWMiClass function does is retrieve the current value for the $ErrorActionPreference variable. There are four possible values for this variable. The possible enumeration values are SilentlyContinue, Stop, Continue, and Inquire. The error handling behavior of Windows PowerShell is governed by these enumeration values. If the value of $ErrorActionPreference is set to SilentlyContinue, any error that occurs will be skipped and the script will attempt to execute the next line of code in the script. The behavior is similar to using the VBScript setting On Error Resume Next. Normally you do not want to use this setting because it can make troubleshooting scripts very difficult. It can also make the behavior of a script unpredictable and even lead to devastating consequences. Consider the case in which you write a script that first creates a new directory on a remote server. Next it copies all of the files from a directory on your local computer to the remote server. Lastly it deletes the directory and all the files from the local computer. Now you enable $ErrorActionPreference = "SilentlyContinue" and you run the script. The first command fails because the remote server is not available. The second command fails because it could not copy the files, but the third command completes successfullyand you have just deleted all the files you wished to backup instead of actually backing up the files. Hopefully you have a recent backup of your critical data. If you set $ErrorActionPreference to SilentlyContinue, you must handle errors that arise during the course of running the script.

In the Get_ValidWmiClass function, the old $ErrorActionPreference setting is retrieved and stored in the $oldErrorActionPreference variable. Next the $ErrorActionPreference variable is set to SilentlyContinue. This is done because it is entirely possible that in the process of checking for a valid WMI class, name errors will be generated. The next thing is that the error stack is cleared of errors. Here are the three lines of code that do this:

$oldErrorActionPreference = $errorActionPreference
$errorActionPreference = "silentlyContinue"
$Error.Clear()

The value stored in the $class variable is used with the [wmiclass] type accelerator to attempt to create a System.Management.ManagementClass object from the string. Because you will need to run this script on a remote computer as well as on a local computer, the value in the $computer variable is used to provide a complete path to the potential management object. When concatenating the variables to make the path to the WMI class, a trailing colon causes problems with the $namespace variable. To work around this, a subexpression is used to force evaluation of the variable before attempting to concatenate the remainder of the string. The subexpression consists of a leading dollar sign and a pair of parentheses. This is seen here:

[wmiclass]"\\$computer\$($namespace):$class" | out-null

To determine if the conversion from string to ManagementClass was successful, the error record is checked. Because the error record was cleared earlier, any error indicates that the command failed. If an error appears, the Get-ValidWmiClass function returns $false to the calling code. If no error appears, the Get-ValidWmiClass function returns $true. This is seen here:

If($error.count) { Return $false } Else { Return $true }

The last thing to do in the Get-ValidWmiClass function is to clean up. First, the error record is cleared, and then the value of the $ErrorActionPreference variable is set back to the original value. This is seen here:

$Error.Clear()
$ErrorActionPreference =  $oldErrorActionPreference

The next function in the Get-ValidWmiClassFunction.ps1 script is the Get-WmiInformation function. This function accepts the values from the $computer, $class, and $namespace variables and passes them to the Get-WmiObject cmdlet. The resulting ManagementObject is pipelined to the Format-List cmdlet and all properties that begin with the letters a through z are displayed. This is seen here:

Function Get-WmiInformation ([string]$computer, [string]$class, [string]$namespace)
{
  Get-WmiObject -class $class -computername $computer -namespace $namespace|
  Format-List -property [a-z]*
} # end Get-WmiInformation function

The entry point to the script calls the Get-ValidWmiClass function, and if it returns True, it next calls the Get-WmiInformation function. If, on the other hand, the Get-ValidWmiClass function returns False, a message is displayed that details the class name, namespace, and computer name. This information could be used for troubleshooting difficulty in obtaining the WMI information. This is seen here:

If(Get-ValidWmiClass -computer $computer -class $class -namespace $namespace)
  {
    Get-WmiInformation -computer $computer -class $class -namespace $namespace
  }
Else
 {
   "$class is not a valid wmi class in the $namespace namespace on $computer"
 }

The complete Get-ValidWmiClassFunction.ps1 script is seen here.

Get-ValidWmiClassFunction.ps1

Param (
   [string]$computer = $env:computername,
   [string]$class,
   [string]$namespace = "root\cimv2"
) #end param

Function Get-ValidWmiClass([string]$computer, [string]$class, [string]$namespace)
{
 $oldErrorActionPreference = $errorActionPreference
 $errorActionPreference = "silentlyContinue"
 $Error.Clear()
 [wmiclass]"\\$computer\$($namespace):$class" | out-null
 If($error.count) { Return $false } Else { Return $true }
 $Error.Clear()
 $ErrorActionPreference =  $oldErrorActionPreference
} # end Get-ValidWmiClass function

Function Get-WmiInformation ([string]$computer, [string]$class, [string]$namespace)
{
  Get-WmiObject -class $class -computername $computer -namespace $namespace|
  Format-List -property [a-z]*
} # end Get-WmiInformation function

# *** Entry point to script ***

If(Get-ValidWmiClass -computer $computer -class $class -namespace $namespace)
  {
    Get-WmiInformation -computer $computer -class $class -namespace $namespace
  }
Else
 {
   "$class is not a valid wmi class in the $namespace namespace on $computer"
 }

TC, we hope that our discussion of controlling user supplied data has been useful. Join us tomorrow as we dig into our virtual mail bag for questions that do not require an extensive response. That is right! It is time for Quick-Hits Friday! If you want to be among the first to be informed about everything that is happening on the Script Center you can follow us on Twitter. We also make postings over at the Scripting Guys group on Facebook. If you get stuck some night while you are working on a script, you can post on the Official Scripting Guys forum. We have an excellent group of moderators and other active members who are always willing to help other scripters. Of course, if you have other questions, suggestions, or comments, you can always e-mail us at scripter@microsoft.com. Have an awesome day!

 

Ed Wilson and Craig Liebendorfer, Scripting Guys