2013 PowerShell Scripting Games Advanced Event 2 – Why NOT to use the Win32_ComputerSystem WMI Class

The majority of the entries I've seen for the Scripting Games event 2 are using the TotalPhysicalMemory property from the Win32_ComputerSystem WMI class to determine the total amount of physical memory in a server. The property name is TotalPhysicalMemory after all so that's what it should contain, right? Not so fast, keep reading to discover why.

Your manager needs an inventory of all of your company's physical servers that are located in three different data centers which are in different parts of the country. He has assigned you the task of inventorying those servers and wants you to physically log into each server or travel to the location where the servers reside to retrieve the information using the GUI.

You've asked your manager if you could try retrieving this information with PowerShell first. He is skeptical and thinks PowerShell is some kind of Witchcraft Voodoo stuff that must be like EverQuest or WarCraft because people stay up late at night playing some sort of scripting games with it. Reluctantly, he gives you the go ahead to proceed with the use of PowerShell for this project. This is your chance to sell your manager on using PowerShell at your company!

The requirements that your manager wants just happens to be the same as the Scripting Games event 2 requirements. Since you're new to PowerShell, you decide to look at the scripts that were submitted in the advanced event #2 and use the method to retrieve the information that most others used, after all, if most people used it and received good scores, then it must be right? You think "This will be too easy, I can copy one of those 5 star scripts and I'll be the PowerShell hero at my company!"

Well, it just so happens that your company got a good deal on some servers several years ago, before you started with the company, that had 8 gigabytes of RAM in them, but due to application compatibility requirements the prior IT engineers who are no longer with the company had to load an x86 (32 bit) version Windows Server 2003 R2. The company already owned Standard Edition of that OS and they didn't need more than 4 gigabytes of RAM so they loaded that operating system to keep the project within budget.

The awesome 5 star script that you downloaded used what most others in the competition used to retrieve the total amount of physical memory in the servers (TotalPhysicalMemory property from the Win32_ComputerSystem WMI class).

Here are the requirements your manager provided which just happen to be exactly the same as the requirements in the Scripting Games advanced event #2. As you can see, in black and white, he wants the "Amount of Installed Physical Memory" from the servers:

event2blog2b.png

You've completed the task, turned in your amazing results to your manager and saved the company lots of money on labor and travel in the process. Now your manager wants to see you in his office. It must to be because he wants to give you a raise or promotion for doing such an awesome job!

Your manager has checked the manufacturers website for some of the servers you provided the inventory for and he wants to know who stole memory out of the servers to put in their gaming machines at home to play these scripting games because the report shows 4GB of RAM and the manufacturers website shows they shipped with 8GB.

Well, it just so happens that the 5 star script you copied as well as what most of the others are using for the Scripting Games in event #2 to retrieve the Amount of Installed Physical Memory is incorrect! What? Sorry, but it's true.

As you can see in the image below, the TotalPhysicalMemory property of Win32_ComputerSystem WMI class only shows the amount of memory that the OS has available to it and not the amount of physical memory in the servers:

event2blog2a.png

Since the Windows Server 2003 R2 Standard Edition x86 (32 bit) operating system is only able to access 4 gigabytes of RAM, that's what the report you turned into your manager had on it, where as you can see using the Win32_PhysicalMemory WMI class and adding up or summing the capacity for all of the memory chips did indeed retrieve the actual amount of physical memory in the server.

Want to know why not to use the Win32_ComputerSystem WMI class for the number of processor sockets? See my blog titled 2013 PowerShell Scripting Games Advanced Event 2 – Attention to Detail is Everything.

Update 02/09/14:

The link to my solution is no longer valid so I'm posting it here since I've received some requests for it:

  1#Requires -Version 3.0
  2
  3function Get-SystemInventory {
  4
  5<#
  6.SYNOPSIS
  7Retrieves hardware and operating system information for systems running Windows 2000 and higher.
  8.DESCRIPTION
  9Get-SystemInventory is a function that retrieves the computer name, operating system version, amount of physical memory
 10(RAM) in megabytes, and number of processors (CPU's) sockets from one or more hosts specified via the ComputerName parameter
 11or via pipeline input. The TotalPhysicalMemory property of Win32_ComputerSystem was initially used, but that property is not
 12the total amount of physical system memory. It is the amount of system memory available to Windows after the OS takes some
 13out for video if necessary or if the OS doesn't support the amount of RAM in the machine which is common when running an older
 1432 bit operating system on hardware with a lot of physical memory. Displaying the amount of memory in megabytes was chosen
 15because this function can be run against older hosts where less than half a gigabyte of memory is common and casting 256MB for
 16example to an integer in gigabytes would display zero which is not useful information. The amount of processor sockets was
 17chosen for similar reasons because older operating systems aren't aware of CPU cores and that information wouldn't be reliably
 18provided across all operating systems this function could be run against. Initially, the NumberOfProcessors property in the
 19Win32_ComputerSystem class was used, but it does not provide an accurate count of CPU sockets on older operating systems with
 20multi-core processors per this MSDN article: http://msdn.microsoft.com/en-us/library/windows/desktop/aa394102(v=vs.85).aspx
 21The Cim cmdlets have been used to gain maximum efficiency so only one connection will be made to each computer. This tool has
 22also been future proofed so as more hosts are upgraded to newer Windows operating systems, they will be able to take advantage
 23of the WSMAN protocol because DCOM is blocked by default in the firewall of newer operating systems and possibly on firewalls
 24between the computer running this tool and the destination host. A ShowProtocol switch parameter has been provided so the
 25results can be filtered (Where-Object) or sorted (Sort-Object) to determine which computers are not being communicated with
 26using WSMAN which is another means of finding older hosts. A Credential parameter has been provided because it's best practice
 27to run PowerShell as a non-domain admin and provide the domain admin credentials specified in the scenario on an as needed per
 28individual command basis. This function requires PowerShell version 3 on the computer it is being run from, but PowerShell is
 29not required to be installed or enabled on the remote computers that it is being run against.
 30.PARAMETER ComputerName
 31Specifies the name of a target computer(s). The local computer is the default. You can also pipe this parameter value.
 32.PARAMETER Credential
 33Specifies a user account that has permission to perform this action. The default is the current user.
 34.EXAMPLE
 35Get-SystemInventory
 36.EXAMPLE
 37Get-SystemInventory -ComputerName 'Server1'
 38.EXAMPLE
 39Get-SystemInventory -ComputerName 'Server1', 'Server2' -Credential 'Domain\UserName'
 40.EXAMPLE
 41'Server1', 'Server2' | Get-SystemInventory
 42.EXAMPLE
 43(Get-Content c:\ComputerNames.txt) | Get-SystemInventory
 44.EXAMPLE
 45(Get-Content c:\ComputerNames.txt, c:\ServerNames.txt) | Get-SystemInventory -Credential (Get-Credential) -ShowProtocol
 46.INPUTS
 47System.String
 48.OUTPUTS
 49System.Management.Automation.PSCustomObject
 50#>
 51
 52    param(
 53        [Parameter(ValueFromPipeline=$True)]
 54        [ValidateNotNullorEmpty()]
 55        [string[]]$ComputerName = $env:COMPUTERNAME,
 56
 57        [System.Management.Automation.Credential()]$Credential = [System.Management.Automation.PSCredential]::Empty,
 58
 59        [switch]$ShowProtocol
 60    )
 61
 62    BEGIN {
 63        $Opt = New-CimSessionOption -Protocol Dcom
 64    }
 65
 66    PROCESS {
 67        foreach ($Computer in $ComputerName) {
 68
 69            Write-Verbose "Attempting to Query $Computer"
 70            $Problem = $false
 71            $SessionParams = @{
 72                ComputerName  = $Computer
 73                ErrorAction = 'Stop'
 74            }
 75
 76            If ($PSBoundParameters['Credential']) {
 77               $SessionParams.credential = $Credential
 78            }
 79
 80            if ((Test-WSMan -ComputerName $Computer -ErrorAction SilentlyContinue).productversion -match 'Stack: 3.0') {
 81                try {
 82                    $CimSession = New-CimSession @SessionParams
 83                    $CimProtocol = $CimSession.protocol
 84                    Write-Verbose "Successfully created a CimSession to $Computer using the $CimProtocol protocol."
 85                }
 86                catch {
 87                    $Problem = $True
 88                    Write-Verbose "Unable to connect to $Computer using the WSMAN protocol. Verify your credentials and try again."
 89                }
 90            }
 91
 92            elseif (Test-Connection -ComputerName $Computer -Count 1 -Quiet -ErrorAction SilentlyContinue) {
 93                $SessionParams.SessionOption = $Opt
 94                try {
 95                    $CimSession = New-CimSession @SessionParams
 96                    $CimProtocol = $CimSession.protocol
 97                    Write-Verbose "Successfully created a CimSession to $Computer using the $CimProtocol protocol."
 98                }
 99                catch {
100                    $Problem = $True
101                    Write-Verbose  "Unable to connect to $Computer using the DCOM protocol. Verify your credenatials and that DCOM is allowed in the firewall on the remote host."
102                }
103            }
104
105            else {
106                $Problem = $True
107                Write-Verbose "Unable to connect to $Computer using the WSMAN or DCOM protocol. Verify $Computer is online and try again."
108            }
109
110            if (-not($Problem)) {
111                $OperatingSystem = Get-CimInstance -CimSession $CimSession -Namespace root/CIMV2 -ClassName Win32_OperatingSystem -Property CSName, Caption, Version
112                $PhysicalMemory = Get-CimInstance -CimSession $CimSession -Namespace root/CIMV2 -ClassName Win32_PhysicalMemory -Property Capacity |
113                                  Measure-Object -Property Capacity -Sum
114                $Processor = Get-CimInstance -CimSession $CimSession -Namespace root/CIMV2 -ClassName Win32_Processor -Property SocketDesignation |
115                             Select-Object -Property SocketDesignation -Unique
116                Remove-CimSession -CimSession $CimSession -ErrorAction SilentlyContinue
117            }
118
119            else {
120                $OperatingSystem = @{
121                    CSName= $Computer
122                    Caption = 'Failed to connect to computer'
123                    Version = 'Unknown'
124                }
125                $PhysicalMemory = @{
126                    Sum = 0
127                }
128                $Processor = @{
129                }
130                $CimProtocol = 'NA'
131            }
132
133            $SystemInfo = [ordered]@{
134                ComputerName = $OperatingSystem.CSName
135                'OS Name' = $OperatingSystem.Caption
136                'OS Version' = $OperatingSystem.Version
137                'Memory(MB)' =  $PhysicalMemory.Sum/1MB -as [int]
138                'CPU Sockets' = $Processor.SocketDesignation.Count
139            }
140
141            If ($PSBoundParameters['ShowProtocol']) {
142               $SystemInfo.'Connection Protocol' = $CimProtocol
143            }
144
145            New-Object PSObject -Property $SystemInfo
146        }
147    }
148}

µ