Here’s my approach to the 2013 PowerShell Scripting Games Advanced Event 2:
When I start one of the Scripting Games events, I read and re-read the scenario because if you don’t understand the requirements, you can’t write an effect script, function, command, tool, etc. It’s not a bad idea to print out the event scenario and highlight the high-points.
Here’s the scenario for Advanced Event 2 -An Inventory Intervention, I’ll place the items in bold that I would normally highlight on my printout:
Dr. Scripto finally has the budget to buy a few new virtualization host servers, but he needs to make some room in the data center to accommodate them. He thinks it makes sense to get rid of his lowest-powered old servers first… but he needs to figure out which ones those are.
This is just the first wave, too – there’s more budget on the horizon so it’s possible he’ll need to run this little report a few times. Better make a reusable tool.
The phrase in bold in the previous paragraph means it needs to be a script, function, or maybe even part of a module that has error checking to prevent others you may hand this ‘tool” off to from having issues with it (not a one-liner).
All of the virtualization hosts run Windows Server, but some of them don’t have Windows PowerShell installed, and they’re all running different OS versions. The oldest OS version is Windows 2000 Server (he knows, and he’s embarrassed but he’s just been so darn busy). The good news is that they all belong to the same domain, and that you can rely on having a Domain Admin account to work with.
The important points are support OS’s as old as Windows 2000, some without PowerShell, they’re all in the same domain and we have a domain admin account to use for access. It’s best practice to run PowerShell as a standard user and specify alternate credentials when running tools such as this need additional rights instead of running PowerShell as a domain admin so we’ll need to add a credential parameter to our tool.
The good Doctor has asked you to write a PowerShell tool that can show him each server’s name, installed version of Windows, amount of installed physical memory, and number of installed processors. For processors, he’ll be happy getting a count of cores, or sockets, or even both – whatever you can reliably provide across all these different versions of Windows. He has a few text files with computer names – he’d like to pipe the computer names, as strings, to you tool, and have your tool query those computers.
That’s a lot of information. It’s a good thing I have a few highlighters that are different colors. My first thought: “I’ll need to use WMI to retrieve this information”. The great thing about WMI is that it’s been around forever and you could even find a VBScript that uses WMI to accomplish what you want and pull the WMI class out of it and use it in PowerShell.
Now to determine what WMI classes we need to use:
Windows Version: Win32_OperatingSystem. This one was too easy. You can search WMI using the Get-WMIObject cmdlet to find classes that may contain the data you’re looking for. The class you want is going to begin with win32_ and normally your not going to want the classes that start with win32_perf so filter those out like so:
Based on these results, there’s only two classes that might contain the data we’re looking for: Win32_OperatingSystem or Win32_SystemOperatingSystem
I’ll switch to the Get-CimInstance cmdlet at this point since I’m running PowerShell version 3 and it tab expands namespaces and classes so there’s less typing. I’ll pipe to “Format-List -Property * -Force” because by default only a fraction of the properties are displayed. I added the -Force switch parameter because I want to see the properties where whoever wrote this is trying to protect me from myself. I can see the Caption is the OS name, and the CSName is the ComputerName:
Towards the bottom of the results, the Version property is the OS Version:
Test against all the different OS’s we need to support and validate that class exists and returns the correct data for each OS starting with Windows 2000. I personally ran my commands against over 30 machines so I would receive different results from a wide variety of different hardware and operating systems. Don’t run your commands in your company’s production environment though.
Ok, that one class gives us the server name, and OS version which are two of the items we need. I’ll also include the OS Name since OS’s like Windows 8 and Server 2012 or Windows Server 2003 and XP have the same “version” numbers. That way if this tool is ever run on workstations and servers, we’ll be able to differentiate between the two.
I’ve found in the scripting games it’s generally ok to include a little more information as long as it still meets the requirements and works, but with each item you add that’s not required, you open up another chance for problems. Add something that’s not required and if it doesn’t work, it’s much worse that not adding that item or feature at all.
Now we need the memory and processor information. I’ll do another WMI search to see what classes exist that might contain that information just like before:
The Win32_ComputerSystem class looks promising.
It’s also a good idea to test the classes as you go to make sure they do indeed retrieve the data you want. While the Win32_ComputerSystem appeared to return the correct information on multiple modern operating systems, on Windows 2003 it doesn’t provide the correct information as shown below. I know that this Dell PE 1950 Server has two physical processors with four cores each so the NumberOfProcessors property can’t be used to retrieve the number of physical CPU sockets since this should be 2:
I also have a question for you, first read this again: For processors, he’ll be happy getting a count of cores, or sockets, or even both - whatever you can reliably provide across all these different versions of Windows.
What does reliably mean? The online dictionary I used says “giving the same result on successive trials”. Can you provide a count of processor cores RELIABLY across all the different versions of Windows beginning with Windows 2000? My answer is no so I did not provide this information. Same question for processor sockets: Can you RELIABLY provide sockets across all the different versions? Yes, so I provided that information.
It’s also a good idea to lookup the WMI classes you’ve chosen to use on MSDN to see if there are any notes about the properties that you’re planning to use. Doing this for the Win32_ComputerSystem class and viewing the NumberOfProcessors property would have allowed you to discover that it wouldn’t display the processor sockets correctly for Windows 2000 and Windows 2003 machines:
Image Source: http://msdn.microsoft.com/en-us/library/windows/desktop/aa394102(v=vs.85).aspx
There’s a hotfix to correct this problem on Windows 2003, but not on 2000: http://support.microsoft.com/kb/932370
Here’s a trick to accurately retrieve the correct number of physical processors from any of the systems this tool is suppose to run against:
The TotalPhysicalMemory property in Win32_ComputerSystem is also incorrect. It shows what’s available to Windows after things such as memory for video has been taken out or if the OS doesn’t support the amount of physical memory in the server, it would only show what the OS is able to access.
Using this class and property would only return 4091MB of RAM on the same Dell PE 1950 as shown in the following image. I didn’t know they made physcial memory chips that would add up to that amount (4091MB)? That’s because they don’t. Notice in the second example, it’s actually very easy to determine the true amount of physical memory using the Win32_PhysicalMemory class. You’ll need to Sum or add up all of the memory chips to retrieve this information, but it’s not that difficult:
Oh, and in case he forgets how to use it – make sure this tool of yours has a full help display available.
The help is self explanatory. Run “help about_Comment_Based_Help” in PowerShell to see examples.
Are you casting the amount of memory as an integer in gigabytes? Guess what? It’s not uncommon for a Windows 2000 Server to have less than half a gigabyte of RAM. Cast 256MB as an integer in gigabytes and you’ll end up with zero. Is zero useful information? It’s not. You could either leave a couple of decimal places (don’t cast it as an integer), or cast it to megabytes as I did.
Here’s the process I use to write my PowerShell scripts, functions, commands, tools, etc:
#1 Write pieces of the script, function, command, etc that pulls the necessary data and verify accuracy. If the data isn’t accurate it doesn’t matter how fancy your PowerShell code is (period).
#2 Tune your command for efficiency. Don’t make multiple calls across the network to the same machine unless absolutely necessary. Only go to the well once and get all you need. Need all you get as well (don’t pull data from a remote machine that you don’t need – filter at the source, not the destination). Store the data in a variable and work with the variable instead of constantly creating network traffic back and forth. Same goes for calls to disk. They’re expensive. Get what you need and store it in a variable. Be sure to only retrieve what you need. A wise man once told me don’t buy the whole box of Whitman’s Samplers if all you like is chocolate covered peanuts.
#3 Do step #1 again and make sure you’re data is still accurate (Accuracy counts!)
#4 Apply best practices once you’ve completed #1, #2, and #3. Add error checking etc.
Want to see my solution for Scripting Games advanced event 2? Click this link: Advanced 2: An Inventory Intervention. You’ll have to be signed in to see the solution. If you like my script, please vote for it! Click on the star all the way to the right:
Want to know more or talk about the Scripting Games event #2? Join us for an unofficial meeting of the Mississippi PowerShell User Group tonight at 8:30pm CDT. Use this link to join the Lync meeting. See our Attendee Info page for the requirements if needed.