Expert Commentary: 2012 Scripting Games Beginner Event 8

Expert Commentary: 2012 Scripting Games Beginner Event 8

  • Comments 1
  • Likes

Summary: Microsoft PFE, Chris Bellee, provides expert commentary for 2012 Scripting Games Beginner Event 8.

Microsoft Scripting Guy, Ed Wilson, is here. Chris Bellee is the expert commentator for Beginner Event 8.

Image of Chris Bellee

Chris is a premier field engineer (PFE) based in Sydney, Australia. He teaches many Windows PowerShell workshops and custom classes, as well as works on IP development that's delivered worldwide to Microsoft Premier Customers. He also trains and certifies other field engineers to deliver Windows PowerShell workshops in the APAC region.

Chris is also a directory services PFE with a passion for helping customers monitor and automate their Active Directory infrastructure. He has developed many sample Windows PowerShell solutions for customers to enable them monitor and manage Active Directory.

Having previously worked as a systems engineer, Chris has extensive experience developing solutions by using a variety of languages other than Windows PowerShell, such as VBScript, Perl, JavaScript, and C# to aid his daily tasks.

In beginner event 8, you are required to determine if a computer is a desktop computer or a laptop computer:

Event scenario

You are the desktop manager at a midsized enterprise company. It is inventory time, and you need to get a count of the number of laptop computers and the number of desktop computers. You decide to write a Windows PowerShell script that will count the number of desktop computers and the number of laptop computers on the network. To permit auditing, your report should include the computer name, and whether or not it is a desktop or a laptop machine.

Design points

  • For the purposes of this scenario, your script only needs to write to the console.
  • Your script only needs to determine laptop or desktop from a hardware perspective. You do not need to determine the version of the operating system.
  • Your code only needs to run on a local computer.
  • If your code requires admin rights, you should detect if the code is running as an admin or as a standard user. If your code works without requiring admin rights, you do not need to make this check.
  • Extra points for writing a simple function that only returns a Boolean value.

Solution

For this challenge we need to find a way to determine whether a computer is a laptop or desktop. Because the scenario states that the script should collect information from remote machines, our obvious choice to employ Windows Management Instrumentation (WMI) for the job.

The first decision is which WMI class contains the data that we are interested in. In this case, there are two classes that fit the bill: Win32_ComputerSystem and Win32_SystemEnclosure. Because Windows PowerShell provides first class support for the WMI object model, it is relatively trivial to query these classes and browse the available properties by using the Get-WMIObject cmdlet.

Get-WmiObject -Class Win32_ComputerSystem | Format-List *

Get-WmiObject -Class Win32_SystemEnclosure | Format-List *

The PCSystemType property in Win32_ComputerSystem contains a number that represents the form-factor of the machine. This seems to be a perfect fit until you read the MSDN Win32_ComputerSystem class documentation. The PCSystemType property is not available on computers running Windows XP, Windows Server 2003, Windows 2000, or Windows NT Server 4.0, so this may be less than perfect for organizations where Windows XP is still prevalent.

In contrast, the Win32_SystemEnclosure class is supported on down-level operating systems. Its ChassisTypes property contains just the information we need. If you look at the MSDN class documentation, there are a large number of possible numeric values for this property, it also holds an array data type, so attention has to be given as to how we work with it.

Now that we have the correct piece of data, we can create a function that returns a Boolean value depending on whether the target machine has a laptop or desktop form-factor. The function is named Is-Laptop and it returns a Boolean value. It has a single input parameter consisting of the remote computer name to query and a single output variable called $result. The output variable is set to $false at the beginning of the function body.

<#

.Synopsis

   helper function used by Get-HardwareType

.EXAMPLE

   Example of how to use this cmdlet

#>

 

function Is-Laptop {

 

[CmdletBinding()]

[OutputType([boolean])]

 

    param

    (

    [Parameter(Mandatory=$true)]

    [string]

    $strHostName

    )

 

    $blnResult = $false

  

        $objWMI = Get-WmiObject -ComputerName $strHostName -Query $query -ErrorAction Stop

 

          switch ($objWMI.ChassisTypes) {

       

            9  {$blnResult = $true} # Laptop

            10 {$blnResult = $true} # Notebook

            12 {$blnResult = $true} # Docking Station

            14 {$blnResult = $true} # Sub Notebook

            default {}

          }

          return $blnResult

     } # end function

Looking at the documentation for the Win32_SystemEnclosure class, you can see that there is more than one numerical value that determines if a machine is “portable.” For this reason I chose to use a switch statement to determine which of the four values that we are interested in are set. Another bonus to using a switch instead of an ‘if’ statement is that it can automatically deal with arrays. The ChassisTypes property holds an array of numbers. Although most of the time the property holds a single value, it is possible that it can hold many values. The switch statement acts like a pipeline—each item in the array flows through the statement one-by-one.

Although the Is-Laptop function does all the work of executing the WMI query and returning a Boolean value, we define a second function that executes it. This makes the code more legible and abstracts the actual WMI query from the name resolution and error handling functionality implemented in the Get-HardwareType function.

Notice the process {} scriptblock that contains most of the code in the Get-HardwareType function. This allows the function to process machine names as pipeline input.

"pc01","pc02","pc03" | Get-HardwareType

Next, we try to resolve the short computer name to a fully qualified DNS host name by using the GetHostByName() .NET static method. A new object instance is not needed to use the method. We can simply reference the type name and call the method by using the double-colon (::) accessor syntax.

$objHostName = [system.net.dns]::GetHostByName($hostName)

Notice that the GetHostByName() method call is contained within a Try/Catch block. This will allow any name resolution failures to bypass the rest of the function, simply printing an error to the user and allowing the function to continue execution.

Before executing the WMI query, we use the Test-Connection cmdlet to determine whether the computer is accessible, using an ICMP (ping) packet. If this check is not performed, the WMI query will time-out when a computer is not available, slowing the script execution considerably. The Test-Connection cmdlet returns a Boolean value ($true) if the machine is contacted successfully, allowing us to use it directly within an ‘if’ statement condition.

if (Test-Connection -ComputerName $objHostName.HostName -count 1 -erroraction silentlycontinue)

The final part of the function uses another Try/Catch block to enclose the call to the Is-Laptop function, again allowing the script to recover from a potential terminating error.

$objResult = New-Object -TypeName psobject -Property @{

HostName=$objHostName.HostName;IsLaptop=(Is-Laptop -strHostName $objHostName.HostName)}

The previous line creates a new Windows PowerShell object to store the name of the computer that is being tested and a Boolean value that indicates whether it is a laptop or a desktop machine. Here, we are using –Property to declare a hash table of name and value pairs that populate the new object’s members. The Is-Laptop property stores the result of calling the Is-Laptop function, which is a Boolean value.

The main script execution occurs at the bottom of the script. We declare two counter variables to hold the number of desktops or laptop computers that are found.

$laptopCount = 0

$desktopCount = 0

To provide some input to the Get-HardwareType function, we search the local Active Directory domain for the CN attributes of all computer objects that don’t have the string server within their OperatingSystem attribute, using a DirectorySearcher object. The search filter is specified by the LDAP query language that follows.

(&(objectcategory=computer)(!operatingsystem=*server*))")

The search is executed by calling the FindAll() method of the DirectorySearcher object and the search results are saved in the $arrMachine variable. The variable should now contain an array of short computer names that can be passed through the pipeline to the Get-Hardware function. Each computer will then be connected to and evaluated, and the results will be displayed on the console.

$result = $arrMachineName | Get-HardwareType

$result

The total number of laptop and desktop form-factors can now be calculated. The $result variable contains an array of psobjects that are passed to a ForEach-Object cmdlet that increments the $laptopCount and $desktopCount variables, depending on whether the current object’s islaptop property is $true or $false.

Finally, the totals can be written to the console.

"Laptop Total: $laptopCount"

"Desktop Total: $desktopCount"

Following is the full script.

<#

.Synopsis

   helper function used by Get-HardwareType

.EXAMPLE

   Example of how to use this cmdlet

#>

 

function Is-Laptop {

 

[CmdletBinding()]

[OutputType([boolean])]

 

    param

    (

    [Parameter(Mandatory=$true)]

    [string]

    $strHostName

    )

 

    $blnResult = $false

  

        $objWMI = Get-WmiObject -ComputerName $strHostName -Query $query -ErrorAction Stop

 

          switch ($objWMI.ChassisTypes) {

       

            9  {$blnResult = $true} # Laptop

            10 {$blnResult = $true} # Notebook

            12 {$blnResult = $true} # Docking Station

            14 {$blnResult = $true} # Sub Notebook

            default {}

          }

          return $blnResult

     } # end function

    

<#

.Synopsis

  function to determine chassis type using a WMI query

.DESCRIPTION

   function to determine chassis type using a WMI query

.EXAMPLE

   "pc01","pc02","pc03" | Get-HardwareType

#>

 

function Get-HardwareType {

 

[CmdletBinding()]

[OutputType([psobject])]

 

Param

(

[Parameter(Mandatory=$true,ValueFromPipeline=$true)]

$strHostName

)

 

process {  

 

    try {

 

        $objHostName = [system.net.dns]::GetHostByName($strHostName)

 

        $query = "select __SERVER,ChassisTypes from Win32_SystemEnclosure"

 

            if (Test-Connection -ComputerName $objHostName.HostName -count 1 -erroraction silentlycontinue) {

 

            try {        

                $objResult = New-Object -TypeName psobject -Property @{HostName=$objHostName.HostName;IsLaptop=(Is-Laptop -strHostName $objHostName.HostName)}

                return $objResult

                }

           

          catch {

                "Error trying to query $($objHostName.HostName)"

                }

            }

       else {

            write-host "error connecting to $($objHostName.HostName)"

            }

    }

    catch {

    write-host "Unable to resolve DNS address for $strHostName"

    }

  }

} # end function

 

###################################################

# Main script execution

###################################################

 

$laptopCount = 0

$desktopCount = 0

 

$searcher = new-object directoryservices.directorysearcher([ADSI]"","(&(objectcategory=computer)(!operatingsystem=*server*))")

[void]$searcher.PropertiesToLoad.Add("cn")

$arrMachineName = $searcher.findall() | %{$_.properties.cn}

 

$result = $arrMachineName | Get-HardwareType

$result

$result | ForEach-Object {if ($_.islaptop) {$laptopCount++} else {$desktopCount++}}

 

"Laptop Total: $laptopCount"

"Desktop Total: $desktopCount"

~Chris

2012 Scripting Games Guest Commentator Week Part 2 will continue tomorrow when we will present the scenario for Event 9.

I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

Ed Wilson, Microsoft Scripting Guy 

Your comment has been posted.   Close
Thank you, your comment requires moderation so it may take a while to appear.   Close
Leave a Comment
  • Interesting approach, it is funny to see that there is still some VB naming conventions poking through your code