2013/05/08

Scripting Games 2013 - Advanced Event 2 - An Inventory Intervention

Now that event 2 is closed for new entries. Here is the solution I proposed.

Instruction 
Download the instruction here [skydrive]

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.

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 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.

Key Points

  • Some Remote Server don't have PowerShell
  • Different OS versions (oldest is Window Server 2000)
  • Domain Environment, Domain Admin credential.
  • Output of the script: ServerName, Version of Windows, Amount of Physical Memory, Processors Count, Sockets Count, Cores Count.
  • Script can receive ComputerName injected via the pipeline


Steps to my solution

Using WMI you can retrieve most of the information on remote machines (WMI Service is present since Windows 2000, Assuming WMI Service is running.. of course...)

Why not CIM ?
I could had play with Get-CIMInstance (which use WSman in the background by default), but for my old servers without PowerShell and WSman, I would had to create an Explicit session with DCOM protocol, then use this session with Get-CimInstance.... I think it is a bit heavy to do this... so I stick to Get-WmiObject.

Operating System Name (Caption Property), Operating System Version (Version property) and ComputerName (Properties: PSComputerName, __SERVER or CSName) were all in the WMI class Win32_OperatingSystem.



For the Memory installed... I based my script on Win32_ComputerSystem with the property TotalPhysicalMemory

However, I noticed that some other competitor were using the class Win32_PhysicalMemory, doing a SUM on the Capacity properties outputted. I Chose to show the value in GB.



Processors
I used the classes Win32_ComputerSystem and Win32_Processor

For Windows Server 2008 and above, you can get some information about the Processor(s) and the Core(s) using the properties NumberOfLogicalProcessors and NumberOfProcessors

However if I query Windows Server 2000 or 2003 I only get the property NumberOfProcessors


Sockets and Cores
For this part, I looked at the property SocketDesignation in the Win32_processor class.


I used the following code to count my sockets and cores:

FOREACH ($Proc in $Processors){
    IF($Proc.numberofcores -eq $null){
        IF ($Proc.SocketDesignation -ne $null){$Sockets++}
        $Cores++
    }ELSE {
        $Sockets++
        $Cores += $proc.numberofcores
    }#ELSE
}#FOREACH $Proc in $Processors


Note: For the Processor part, I think Mike F Robbins got a better approached, Check his post here.


Vote for me! And Comment!
Please vote for me and let me know what you liked or not. I'm still learning and want to improve my code.
Thank you!
You will need to be Logged On first on http://scriptinggames.org


Script

#requires -Version 3

function Get-ComputerInfo
{

<#
.SYNOPSIS
   This function query some basic Operating System and Hardware Information from
   a local or remote machine.

.DESCRIPTION
   This function query some basic Operating System and Hardware Information from
   a local or remote machine.
   It requires PowerShell version 3 for the Ordered Hashtable.

   The properties returned are the Computer Name (ComputerName),the Operating 
   System Name (OSName), Operating System Version (OSVersion), Memory Installed 
   on the Computer in GigaBytes (MemoryGB), the Number of 
   Processor(s) (NumberOfProcessors), Number of Socket(s) (NumberOfSockets),
   and Number of Core(s) (NumberOfCores).

   This function as been tested against Windows Server 2000, 2003, 2008 and 2012

.PARAMETER ComputerName
   Specify a ComputerName or IP Address. Default is Localhost.

.PARAMETER ErrorLog
   Specify the full path of the Error log file. Default is .\Errors.log.

.EXAMPLE
   Get-ComputerInfo

   ComputerName       : XAVIER
   OSName             : Microsoft Windows 8 Pro
   OSVersion          : 6.2.9200
   MemoryGB           : 4
   NumberOfProcessors : 1
   NumberOfSockets    : 1
   NumberOfCores      : 4

   This example return information about the localhost. By Default, if you don't
   specify a ComputerName, the function will run against the localhost.

.EXAMPLE
   Get-ComputerInfo -ComputerName SERVER01

   ComputerName       : SERVER01
   OSName             : Microsoft Windows Server 2012
   OSVersion          : 6.2.9200
   MemoryGB           : 4
   NumberOfProcessors : 1
   NumberOfSockets    : 1
   NumberOfCores      : 4

   This example return information about the remote computer SERVER01.

.EXAMPLE
   Get-Content c:\ServersList.txt | Get-ComputerInfo
    
   ComputerName       : DC
   OSName             : Microsoft Windows Server 2012
   OSVersion          : 6.2.9200
   MemoryGB           : 8
   NumberOfProcessors : 1
   NumberOfSockets    : 1
   NumberOfCores      : 4

   ComputerName       : FILESERVER
   OSName             : Microsoft Windows Server 2008 R2 Standard 
   OSVersion          : 6.1.7601
   MemoryGB           : 2
   NumberOfProcessors : 1
   NumberOfSockets    : 1
   NumberOfCores      : 1

   ComputerName       : SHAREPOINT
   OSName             : Microsoft(R) Windows(R) Server 2003 Standard x64 Edition
   OSVersion          : 5.2.3790
   MemoryGB           : 8
   NumberOfProcessors : 8
   NumberOfSockets    : 8
   NumberOfCores      : 8

   ComputerName       : FTP
   OSName             : Microsoft Windows 2000 Server
   OSVersion          : 5.0.2195
   MemoryGB           : 4
   NumberOfProcessors : 2
   NumberOfSockets    : 2
   NumberOfCores      : 2

   This example show how to use the function Get-ComputerInfo in a Pipeline.
   Get-Content Cmdlet Gather the content of the ServersList.txt and send the
   output to Get-ComputerInfo via the Pipeline.

.EXAMPLE
   Get-ComputerInfo -ComputerName FILESERVER,SHAREPOINT -ErrorLog d:\MyErrors.log.

   ComputerName       : FILESERVER
   OSName             : Microsoft Windows Server 2008 R2 Standard 
   OSVersion          : 6.1.7601
   MemoryGB           : 2
   NumberOfProcessors : 1
   NumberOfSockets    : 1
   NumberOfCores      : 1

   ComputerName       : SHAREPOINT
   OSName             : Microsoft(R) Windows(R) Server 2003 Standard x64 Edition
   OSVersion          : 5.2.3790
   MemoryGB           : 8
   NumberOfProcessors : 8
   NumberOfSockets    : 8
   NumberOfCores      : 8

   This example show how to use the function Get-ComputerInfo against multiple
   Computers. Using the ErrorLog Parameter, we send the potential errors in the
   file d:\Myerrors.log.

.INPUTS
   System.String

.OUTPUTS
   System.Management.Automation.PSCustomObject

.NOTES
   Scripting Games 2013 - Advanced Event #2
#>

 [CmdletBinding()]

 PARAM(
  [Parameter(ValueFromPipeline=$true)]
  [String[]]$ComputerName = "LocalHost",

  [String]$ErrorLog = ".\Errors.log"
    )

    BEGIN {}#PROCESS BEGIN

    PROCESS{
        FOREACH ($Computer in $ComputerName) {
            Write-Verbose -Message "PROCESS - Querying $Computer ..."
                
            TRY{
                $Everything_is_OK = $true
                Write-Verbose -Message "PROCESS - $Computer - Testing Connection"
                Test-Connection -Count 1 -ComputerName $Computer -ErrorAction Stop -ErrorVariable ProcessError | Out-Null

                # Query WMI class Win32_OperatingSystem
                Write-Verbose -Message "PROCESS - $Computer - WMI:Win32_OperatingSystem"
                $OperatingSystem = Get-WmiObject -Class Win32_OperatingSystem -ComputerName $Computer -ErrorAction Stop -ErrorVariable ProcessError
                    
                # Query WMI class Win32_ComputerSystem
                Write-Verbose -Message "PROCESS - $Computer - WMI:Win32_ComputerSystem"
                $ComputerSystem = Get-WmiObject -Class win32_ComputerSystem -ComputerName $Computer -ErrorAction Stop -ErrorVariable ProcessError

                # Query WMI class Win32_Processor
                Write-Verbose -Message "PROCESS - $Computer - WMI:Win32_Processor"
                $Processors = Get-WmiObject -Class win32_Processor -ComputerName $Computer -ErrorAction Stop -ErrorVariable ProcessError

                # Processors - Determine the number of Socket(s) and core(s)
                # The following code is required for some old Operating System where the
                # property NumberOfCores does not exist.
                Write-Verbose -Message "PROCESS - $Computer - Determine the number of Socket(s)/Core(s)"
                $Cores = 0
                $Sockets = 0
                FOREACH ($Proc in $Processors){
                    IF($Proc.numberofcores -eq $null){
                        IF ($Proc.SocketDesignation -ne $null){$Sockets++}
                        $Cores++
                    }ELSE {
                        $Sockets++
                        $Cores += $proc.numberofcores
                    }#ELSE
                }#FOREACH $Proc in $Processors

            }CATCH{
                $Everything_is_OK = $false
                Write-Warning -Message "Error on $Computer"
                $Computer | Out-file -FilePath $ErrorLog -Append -ErrorAction Continue
                $ProcessError | Out-file -FilePath $ErrorLog -Append -ErrorAction Continue
                Write-Warning -Message "Logged in $ErrorLog"

            }#CATCH


            IF ($Everything_is_OK){
                Write-Verbose -Message "PROCESS - $Computer - Building the Output Information"
                $Info = [ordered]@{
                    "ComputerName" = $OperatingSystem.__Server;
                    "OSName" = $OperatingSystem.Caption;
                    "OSVersion" = $OperatingSystem.version;
                    "MemoryGB" = $ComputerSystem.TotalPhysicalMemory/1GB -as [int];
                    "NumberOfProcessors" = $ComputerSystem.NumberOfProcessors;
                    "NumberOfSockets" = $Sockets;
                    "NumberOfCores" = $Cores}

                $output = New-Object -TypeName PSObject -Property $Info
                $output
            } #end IF Everything_is_OK
        }#end Foreach $Computer in $ComputerName
    }#PROCESS BLOCK
    END{
        # Cleanup
        Write-Verbose -Message "END - Cleanup Variables"
        Remove-Variable -Name output,info,ProcessError,Sockets,Cores,OperatingSystem,ComputerSystem,Processors,
        ComputerName, ComputerName, Computer, Everything_is_OK -ErrorAction SilentlyContinue
        
        # End
        Write-Verbose -Message "END - Script End !"
    }#END BLOCK
}#function

Thanks for Reading! If you have any questions, leave a comment or send email to me at fxcat@lazywinadmin.com. I invite you to follow me: Twitter: @lazywinadm

12 comments:

  1. Another awesome script! I've been trying to find the scripts people posted for the 2013 inventory intervention and can't get into the site, although I've been registered for a couple years, just not ready to contribute (I don't want anyone to lose because of me :)
    I tried finding the link to the post you mentioned where someone else might have handled the processors a bit better, although I'm just going with your script. I can't find it, what was the difference in how they did it and with your script it appears to work fine with getting physical cpus and then cores, and that's across 2012, 2008r2, 2008, 2003, and even 2000, is that right?

    ReplyDelete
    Replies
    1. Hello! Thanks for your comment!

      You should definitely participate to the scripting games! You will learn a LOT! Winning should not be your priority ;-) I really mean that!

      For my script, the CPU/cores information is gathered using Win32_Processors.

      But if you check the following MSDN Article: http://msdn.microsoft.com/en-us/library/windows/desktop/aa394373(v=vs.85).aspx

      #Remarks#
      To determine the total number of processor instances associated with a computer system object, use the Win32_ComputerSystemProcessor association class.
      To determine if hyperthreading is enabled for the processor, compare NumberOfLogicalProcessors and NumberOfCores. If hyperthreading is enabled in the BIOS for the processor, then NumberOfCores is less than NumberOfLogicalProcessors. For example, a dual-processor system that contains two processors enabled for hyperthreading can run four threads or programs or simultaneously. In this case, NumberOfCores is 2 and NumberOfLogicalProcessors is 4.
      The Win32_Processor class is derived from CIM_Processor.

      #Requirements#
      Minimum supported client: Windows XP
      Minimum supported server: Windows Server 2003



      Hope this help, and thanks again for your comment
      FX

      Delete
    2. Btw I uploaded some of the entries from the Scripting Games 2013 here

      https://skydrive.live.com/#cid=60886DE0176E604A&id=60886DE0176E604A%2116653

      Delete
  2. This is great, how can I get it to prompt me for creds?

    ReplyDelete
    Replies
    1. Hi Steve, you would have to either:
      -launch powershell with different credential
      or
      -add a new parameter for credential, to have the option to change the current credential

      Delete
    2. I upload another version here with credential support:
      http://gallery.technet.microsoft.com/Get-ComputerInformation-ecaa5cc9

      Hope this help

      Delete
  3. Thanks for the quick reply, can you offer some guidance on adding a param to use a different credential? I've looked at launching PowerShell as a different credential, but it gets very confusing with the number of methods that are out there. I'm simply trying to run this against systems in different domains or not in a domain.

    Thanks SteveL

    ReplyDelete
    Replies
    1. Hey Steve,

      I upload another version here with credential support:
      http://gallery.technet.microsoft.com/Get-ComputerInformation-ecaa5cc9

      Hope this help

      Delete
  4. You ROCK!! Awesome! Thank you!

    ReplyDelete
    Replies
    1. Thank you! Rate the script on technet if it is helpful :-)

      http://gallery.technet.microsoft.com/Get-ComputerInformation-ecaa5cc9

      Delete
  5. Hi, I'm using this and it is great, but what can I do about 2003 servers that are not patched?

    ReplyDelete
    Replies
    1. Hey Calley,

      Are you refering to this patch/fix: http://support.microsoft.com/kb/932370 ?
      If yes, well you will have to patch them in order to get the good processor information :-/

      Fx

      Delete