Hey, Scripting Guy! Question

Hey, Scripting Guy! I am interested in using Windows PowerShell to collect processor information, but I do not need to see the raw data. I would rather see calculated results such as the maximum, minimum, and average processor utilization. I know I can do this, but that seems like writing a lot of math formulas, and I just am not in the mood for all that work. Will you do it for me?

- LA

SpacerHey, Scripting Guy! Answer

Hi LA,

Guess what? It is late at night on a Friday night (as I am writing this), I am sipping a nice cup of green tea with lemon and a cinnamon stick in it, munching on some raw almonds, savoring a nice hunk of artisan cheese, and I am listening to Harry James on my Zune. I am not in the mood for a bunch of math formulas either. Luckily for both of us, Windows PowerShell has the Measure-Object cmdlet that will do the calculations for us. Because it is nearly the weekend, I will throw in a progress indicator for free. Without much ado (or Scrappy-Doo) here is the CalculateAverageUtilization.ps1 script:

Function CreateEmptyArray($ubound)
{
 [int[]]$script:aryProp = [array]::CreateInstance("int",$ubound)
} #end CreateEmptyArray
Function GetWmiPerformanceData()
{
 For($i = 0 ; $i -le $reps -1 ; $i++)
  {
   $aryProp[$i] +=([wmi]"\\$computer\root\cimv2:$class.$key='$instance'").$Property
   Write-Progress -Activity "Obtaining Processor info" -Status "% complete: " `
   -PercentComplete $i
   Start-Sleep -Seconds $delay
  } #end for
}#end GetWmiPerformanceData
Function EvaluateObject()
{
 $aryProp | 
 Measure-Object -Average -Maximum -Minimum |
 Format-Table -Property `
  @{ Label = "Data Points" ; Expression = {$_.count} }, 
  average, Maximum, Minimum -autosize
} #End EvaluateObject
# *** Entry Point ***
$computer = "."
$delay = 1
$reps = 10
$class = "Win32_PerfFormattedData_PerfOS_Processor"
$key = "name"
$instance = "_Total"
$property = "PercentProcessorTime"
CreateEmptyArray($reps)
GetWmiPerformanceData
EvaluateObject

The main body of the script consists of three separate functions. The first function that is called creates an empty array of integers and assigns it to a script level variable named $aryProp. The second function called gathers the WMI performance information and writes it to the $aryProp variable. The last function evaluates the information in the $aryProp variable and generates the output. It is a best practice to order the functions in a script in the order in which they will be called. While this is not always possible, it is still a worthy goal because it will make the script easier to read.

The CreateEmptyArray function creates an empty array that will be used to store the processor utilization information. When it is called, the value that is stored in the $reps variable is passed to it. This value becomes the upper boundary of the array. To create the array we use the static CreateInstance method from the system.array .NET Framework class. When we use the CreateInstance method, we need to give it the type of data it will hold and the upper boundary of the array. We store the empty array in a script-level variable named $aryProp. A script-level variable will be available for use outside of the function. We use the [int[]] type constraint to make sure the variable will only hold an array of integers. The CreateEmptyArray function is seen here:

Function CreateEmptyArray($ubound)
{
 [int[]]$script:aryProp = [array]::CreateInstance("int",$ubound)
} #end CreateEmptyArray

The GetWmiPerformanceData function is the main function in the script. It begins with a for loop that starts counting at 0 until it is one minus the number stored in the $reps variable. The for loop increments the enumerator by one. The $i variable is used to not only control the number of repetitions through the code block, but also the element the data point gets stored in. The $i++ syntax increments the $i enumerator by one—this is the same as $i = $i +1. This section of the code is shown here:

For($i = 0 ; $i -le $reps -1 ; $i++)

Inside the code block, the [WMI] type accelerator is used to make a connection to the computer specified in the $computer variable to the class that is stored in the $class variable. The $key and $instance variables are used to connect to the key property of a specific instance of the WMI performance class. The property stored in the $property variable is the data point we are interested in obtaining. When the data is retrieved, it is stored in the element number $i in the array $aryProp. This line of code is seen here:

$aryProp[$i] +=([wmi]"\\$computer\root\cimv2:$class.$key='$instance'").$Property

Because it is conceivable that the script could run for an extended period of time, the Write-Progress cmdlet is used to display a progress indicator bar:

Image of the progress indicator bar

 

The top line displays the value that is specified in the –Activity parameter. In this script, it states "Obtaining processor info." The next line that is displayed is the data you supply for the e-Status parameter. Here it says "% complete:" followed by a progress bar that indicates the percent complete of the value $i. This section of code is shownhere:

   Write-Progress -Activity "Obtaining Processor info" -Status "% complete: " `
   -PercentComplete $i

The last thing we do is pause the execution of the script for a short period of time that is specified by the $delay value. To do this, we use the Start-Sleep cmdlet, which is similar to the VBScript command Wscript.Sleep except that it will take both seconds and milliseconds for the pause interval. The default parameter is seconds, and therefore the –seconds parameter does not need to be specified. However, as a best practice I recommend including the full parameter name because it will improve the readability of the script. This is doubly important because the interval used for Wscript.Sleep was milliseconds, and confusion could easily arise with the missing parameter. This section of code is shown here:

Start-Sleep -Seconds $delay

The complete GetWmiPerformanceData function is seen here:

Function GetWmiPerformanceData()
{
 For($i = 0 ; $i -le $reps -1 ; $i++)
  {
   $aryProp[$i] +=([wmi]"\\$computer\root\cimv2:$class.$key='$instance'").$Property
   Write-Progress -Activity "Obtaining Processor info" -Status "% complete: " `
   -PercentComplete $i
   Start-Sleep -Seconds $delay
  } #end for
}#end GetWmiPerformanceData

The last function to look at is the EvaluateObject function. The EvaluateObject function begins by pipelining the data stored in the $aryProp variable to the Measure-Object cmdlet where we specify that we are interested in the –Average, –Maximum, and the –Minimum values contained in the $aryProp variable. We then pipeline the results of that calculation to the Format-Table to display a table of the results. Because the count property that is reported by the Measure-Object cmdlet is a bit obtuse, we decide to create a custom label named "Data Points." To do this, we need to use a special hash table format.

The first element in the hash table is called Label, and the value of the label element is "Data Points." The second element in the hash table is named Expression and the value of the expression element is $_.count. The $_ variable is an automatic variable that represents the current item on the pipeline—in this case, the object coming across from the Measure-Object cmdlet. We are interested in the count property. We then choose the remaining properties from the Measure-Object cmdlet that we are interested in displaying: average, maximum, and minimum. We use the –autosize parameter from Format-Table cmdlet to shrink the display to a more space-conscious output of the data:

Image of a space-conscious output of the data

 

here:

Function EvaluateObject()
{
 $aryProp | 
 Measure-Object -Average -Maximum -Minimum |
 Format-Table -Property `
  @{ Label = "Data Points" ; Expression = {$_.count} }, 
  average, Maximum, Minimum -autosize
} #End EvaluateObject

The only thing left in the script is to declare the values for the variables and call the functions. This is seen here:

$computer = "."
$delay = 1
$reps = 10
$class = "Win32_PerfFormattedData_PerfOS_Processor"
$key = "name"
$instance = "_Total"
$property = "PercentProcessorTime"

CreateEmptyArray($reps)
GetWmiPerformanceData
EvaluateObject

See, LA, neither of us needed to do any math calculations thanks to the Measure-Object cmdlet. Now that we are done, it’s time for more tea.

Ed Wilson and Craig Liebendorfer, Scripting Guys