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:
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:
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}
µ