VMCO.psm1

<#
    .NOTES
        Author: Mark McGill, VMware
        Last Edit: 2/8/2021
        Version 3.0.0.4
    .SYNOPSIS
        Calculates the optimal vCPU (sockets & cores) based on the current VM and Host architecture
    .DESCRIPTION
        Uses the logic from Mark Achtemichuk's blog "Virtual Machine vCPU and vNUMA Rightsizing – Guidelines"
        https://blogs.vmware.com/performance/2017/03/virtual-machine-vcpu-and-vnuma-rightsizing-rules-of-thumb.html

        If no -vmName is specified, Get-OptimalvCPU will get all VMs from connected vCenters
        Only the VM information will be returned unless the -full option is specified
    .EXAMPLE
        Get-OptimalvCPU #Gets all VMs from currently connected vCenters
    .EXAMPLE
        Get-OptimalvCPU | Export-CSV -path "c:\temp\vNUMA.csv" -NoTypeInformation #Exports results to csv
        Get-OptimalvCPU | Out-GridView #Opens results in a grid window - Windows OS only
    .EXAMPLE
        Get-OptimalvCPU -vmName "MyVmName" #Gets results on only the VM named "MyVmName"
    .EXAMPLE
        Get-OptimalvCPU -vmName (Get-VM -Name "*NY-DC*") #Gets results on any VM with "NY-DC" in its name
    .EXAMPLE
        Get-OptimalvCPU -full #Returns all vCenter, Cluster, and VMHost information
   .EXAMPLE
        Get-OptimalvCPU -tdmJsonFile <FilePath> #Generates report based on VMware TAM TDM reports in JSON format
    .OUTPUTS
        Object containing vCenter,Cluster,Host and VM information, as well as optimal vCPU recommendations
#>

function Get-OptimalvCPU
{
    #Requires -Version 5.0
    [cmdletbinding()]
    Param
    (
        [Parameter(Mandatory=$false)]$vmName,
        [Parameter(Mandatory=$false)]$tdmJsonFile,
        [Parameter(Mandatory=$false)][switch]$full
    )

    $nameFilter = ""
    $vmFilter = @{'RunTime.ConnectionState'='^(?!disconnected|inaccessible|invalid|orphaned).*$';'Runtime.PowerState'='poweredOn';'Config.Template'='False'}
    $hostFilter = @{'Runtime.ConnectionState'='connected';'Runtime.PowerState'='poweredOn'}
    $results = @()
    $vms = @()
    Write-Verbose "Retrieving VMs information - Skipping Disconnected, Powered Off, or Template VMs"

    Function Get-OptSockets ($vm,$vmMemoryGB,$vmCPUs,$vmHostMemPerChannel,$vmHostCPUs,$vmHostCoresPerSocket)
    {
        #calculations for optimal vCPU
        $i = 0
        try 
        {
            Do 
            {
                $i++
            }
            Until (
                ((($vmMemoryGB / $i -le $vmHostMemPerChannel) -or ($vmCPUs / $i -eq 1) -or ($vmCPUs -eq $vmHostCPUs)) `
                    -and (($vmCPUs / $i -le $vmHostCoresPerSocket) -or ($vmCPUs / $i -eq 1)) `
                    -and (($vmCPUs / $i)  % 2 -eq 0 -or ($vmCPUs / $i)  % 2 -eq 1)) `
                    -or $i -eq $vmHostCPUs / $vmHostCoresPerSocket
                )
            $optSockets = $i
            Return $optSockets           
        }
        Catch
        {
            Return "ERROR calculating Optimal vCPU for $vm : $($_.Exception.Message) at line $($_.InvocationInfo.ScriptLineNumber)"
        }
    }#end Get-OptSockets

    Function Get-vSphereInfo($vmName)
    {
        Try
        {
            If ($VMName -ne $null)
            {
                Switch($vmName.GetType().Name)
                {
                    "Object[]" {$vmName = $vmName.Name}
                    "UniversalVirtualMachineImpl" {$vmName = $vmName.Name}
                    "String" {}
                    Default {Throw "Unrecognized Input for VM"}
                }
                foreach ($name in $VMName)
                {
                    #escapes parentheses in VM name (ie, Tanzu VMs)
                    $name = ($name.Replace('(','\(')).Replace(')','\)')
                    $nameFilter += "^$($name)$|"
                }
                $nameFilter = $nameFilter.TrimEnd("|")
                $vmFilter += @{"Name" = $nameFilter}
            }
            #gets all VMs from connected vCenter servers
            $vms = get-view -ViewType VirtualMachine -Filter $vmFilter -Property Name,Config.Hardware.MemoryMB,Config.Hardware.NumCPU,Config.Hardware.NumCoresPerSocket,Config.CpuHotAddEnabled,Config.Version,Config.ExtraConfig,Runtime.Host | `
                Select Name, @{n='MemoryGB'; e={[math]::Round(($_.Config.Hardware.MemoryMB / 1024),2)}},@{n='Sockets';e={($_.Config.Hardware.NumCPU)/($_.Config.Hardware.NumCoresPerSocket)}},@{n='CoresPerSocket'; 
                e={$_.Config.Hardware.NumCoresPerSocket}},@{n='NumCPU';e={$_.Config.Hardware.NumCPU}},@{n='CpuHotAdd';e={$_.Config.CpuHotAddEnabled}},@{n='HWVersion';e={$_.Config.Version}},@{n='HostId';
                e={$_.Runtime.Host.Value}},@{n='vCenter';e={([uri]$_.client.ServiceUrl).Host}},@{n='NumaVcpuMin';e={($_.Config.ExtraConfig | where {$_.Key -eq "numa.vcpu.min"}).Value}}
            If($vms -eq $null)
            {
                Throw "ERROR retrieving VM Information. No VMs found, or VMs are not powered on, or connected"
            }

            Write-Verbose "Retrieving Host information. Skipping Disconnected or Powered Off Hosts"
            If ($VMName -ne $null)
            {
                $hostsUnique = $vms | Select @{n="Id";e={"HostSystem-" + "$($_.HostId)"}},vCenter | Sort-Object -Property @{e="Id"},@{e="vCenter"} -Unique
                $hostCommand = {get-view -Id $($hostsUnique.Id) -Property Name,Parent,Config.Product.Version,Config.HyperThread,Hardware.MemorySize,Hardware.CpuInfo,Config.PowerSystemInfo.CurrentPolicy.Key,Config.Option}
            }
            Else
            {
                $hostCommand = {get-view -ViewType HostSystem -Filter $hostFilter -Property Name,Parent,Config.Product.Version,Config.HyperThread,Hardware.MemorySize,Hardware.CpuInfo,Config.PowerSystemInfo.CurrentPolicy.Key,Config.Option}
            }
        
            $vmHosts = Invoke-Command $hostCommand | select Name,@{n='Id';e={$_.MoRef.Value}},@{n='Version';e={$_.Config.Product.Version}},@{n='vCenter';e={([uri]$_.Client.serviceurl).Host}},@{n="ClusterId";
                e={$_.Parent | Where{$_.Type -eq "ClusterComputeResource"} | select -expand Value}},@{n='MemoryGB';e={[int](($_.Hardware.MemorySize)/1073741824)}},@{n="MemPerChannel";
                e={[int](($_.Hardware.MemorySize)/1073741824) / ($_.Hardware.CpuInfo.NumCpuPackages)}},@{n='Sockets';e={($_.Hardware.CpuInfo.NumCpuPackages)}},@{n='CoresPerSocket';
                e={($_.Hardware.CpuInfo.NumCPUCores)/$($_.Hardware.CpuInfo.NumCpuPackages)}},@{n='CPUs';e={$_.Hardware.CpuInfo.NumCPUCores}},@{n='CpuThreads';e={($_.Hardware.CpuInfo.NumCpuThreads)}},@{n='HTActive';
                e={$_.Config.HyperThread.Active}},@{n='NumaVcpuMin'; e={$_.Config.Option | where {$_.Key -eq "numa.vcpu.min"}}},@{n='PowerPolicy'; 
                e={
                    switch($_.Config.PowerSystemInfo.CurrentPolicy.Key)
                    {
                        "1" {"HighPerformance"}
                        "2" {"Balanced"}
                        "3" {"LowPower"}
                        "4" {"Custom"}
                    }
                   }
            }

            Write-Verbose "Retrieving Cluster information" 
            $clustersUnique = $vmHosts | Where{$_.ClusterId -ne $null} | Select @{n="Id";e={"ClusterComputeResource-" + "$($_.ClusterId)"}},vCenter | Sort-Object -Property @{e="Id"},@{e="vCenter"} -Unique
            #accounts for hosts with no cluster
            If ($clustersUnique -ne $Null)
            {
                $clusters = get-view -Id $($clustersUnique.Id) -Property Name,Configuration.DrsConfig | Select Name,@{n="Id";e={$_.MoRef.Value}},@{n="vCenter"; e={([uri]$_.Client.serviceurl).Host}},@{n="DRSEnabled"; 
                    e={$_.Configuration.DrsConfig.Enabled}},MinMemoryGB,MinSockets,MinCoresPerSocket -ErrorAction Stop
     
                foreach ($cluster in $clusters)
                {
                    $clusterHosts = $vmHosts | Where{($_.vCenter -eq $cluster.vCenter) -and $_.clusterID -eq $cluster.Id} | select Name,Id,MemoryGB,Sockets,CoresPerSocket
                    $cluster.MinMemoryGB = ($clusterHosts.MemoryGB | measure -Minimum).Minimum
                    $cluster.MinSockets = ($clusterHosts.Sockets | measure -Minimum).Minimum
                    $cluster.MinCoresPerSocket = ($clusterHosts.CoresPerSocket | measure -Minimum).Minimum
                }
            }

            $returnObj = "" | Select vms,vmHosts,clusters
            $returnObj.vms = $vms
            $returnObj.vmHosts = $vmHosts
            $returnObj.clusters = $clusters
            Return $returnObj
        }#end Try
        Catch
        {
            Return "ERROR retrieving VM information: $($_.Exception.Message) at line $($_.InvocationInfo.ScriptLineNumber)"
        }
    } #end Get-vSphereInfo
    Function Import-TDMData ($jsonFile)
    {
        Try
        {
            Write-Host "Importing JSON Data from $jsonFile" -ForegroundColor Green
            $content = get-content -Path $jsonFile -Raw 
            #Accounts for 'duplicates' that are the same text, but different case.
            Do
            {
                Try
                {
                    $json = $content | ConvertFrom-Json -ErrorAction Stop
                    $success = $true
                }
                Catch [System.InvalidOperationException]
                {
                    $duplicateKey = ($_.Exception.Message).Split("'")[1]
                    $replace = $duplicateKey + "_"
                    $content = $content.Replace($duplicateKey,$replace) | Out-String 
                    $success = $false
                }
                Catch
                {
                    Throw "ERROR converting file content to JSON: $($_.Exception.Message)" 
                }
            }
            Until($success)

            Write-Host "Collecting Host Information..." -ForegroundColor Green
            $vmHosts = $json.hosts | select Name, @{n="Id";e={$_.mobId}},Version,vCenter,@{n="ClusterName"; e={$_.Cluster}},ClusterId, @{n='MemoryGB'; 
                e={[int](($_.ram)/1073741824)}},@{n="MemPerChannel";e={([int](($_.ram)/1073741824))/$_.cpus}},@{n="Sockets";e={$_.cpus}},@{n="CoresPerSocket"; e={$_.cores / $_.cpus}}, cpus, @{n="CpuThreads"; e={$_.Threads}},@{n="HTActive"; 
                e={if($_.Threads -gt $_.cores){$true}Else{$false}}}, NumaVcpuMin, @{n="PowerPolicy";e={"N/A"}}

            Write-Host "Collecting vCenter and Cluster information and calculating cluster minimums..." -ForegroundColor Green
            $clusters = $vmHosts | Select @{n="Name"; e={$_.clusterName}},@{n="Id"; e={$_.ClusterId}},@{n="DRSEnabled"; e={"N/A"}},vCenter,MinMemoryGB,MinSockets,MinCoresPerSocket | Where{$_.cluster -ne "n/a"} | Sort-Object -Property @{e="Id"},@{e="vCenter"} -Unique
            foreach ($cluster in $clusters)
            {
                $clusterHosts = $vmHosts | Where{($_.vCenter -eq $cluster.vCenter) -and ($_.clusterID -eq $cluster.Id)} | select Name,Id,MemoryGB,Sockets,CoresperSocket
                $cluster.MinMemoryGB = ($clusterHosts.MemoryGB | measure -Minimum).Minimum
                $cluster.MinSockets = ($clusterHosts.Sockets | measure -Minimum).Minimum
                $cluster.MinCoresPerSocket = ($clusterHosts.CoresPerSocket | measure -Minimum).Minimum
            }
        
            Write-Host "Processing Virtual Machine Information..." -ForegroundColor Green
            $vms = $json.vms | Where{$_.powerState -eq "POWERED_ON" -and $_.isTemplate -eq "no"} | Select Name, @{n="MemoryGB"; e={[math]::Round(($_.memoryInMb/1024),2)}},@{n="Sockets"; e={$_.numCpus/$_.num_cores_per_socket}},@{n="CoresPerSocket";
                e={$_.num_cores_per_socket}},@{n='NumCPU';e={$_.numCpus}},@{n='CpuHotAdd';e={"N/A"}},@{n='HWVersion';e={$_.version}},@{n='HostId';e={$_.parent_esx_mo_id}},@{n='vCenter';e={$_.vCenter}},NumaVcpuMin

            $returnObj = "" | Select vms,vmHosts,clusters
            $returnObj.vms = $vms
            $returnObj.vmHosts = $vmHosts
            $returnObj.clusters = $clusters
            Return $returnObj
        }#end Try
        Catch
        {
            Return "ERROR importing TDM JSON file: $($_.Exception.Message) at line $($_.InvocationInfo.ScriptLineNumber)"
        }
    }#end Import-TDMData

    #Main code body
    Try
    {
        If ($tdmJsonFile -ne $null)
        {
            If (Test-Path $tdmJsonFile)
            {
                $vSphereData = Import-TDMData($tdmJsonFile)
                If($vSphereData -match "ERROR")
                {
                    Throw $vSphereData
                }
            }
            else 
            {
                Throw "Cannot find TDM JSON File: $tdmJsonFile"    
            }
        }
        #if no TDM file is specified
        else 
        {
            $vSphereData = Get-vSphereInfo($vmName)
            If($vSphereData -match "ERROR")
            {
                Throw $vSphereData
            }            
        }

        $vms = $vSphereData.vms
        $vmHosts = $vSphereData.vmHosts
        $clusters = $vSphereData.clusters

        #process VM calculations
        $vmCount = ($vms | Measure-Object).Count
        Write-Verbose "Calculating Optimal vCPU settings for $vmCount VMs"
        $n = 1
    }
    Catch
    {
        Write-Error "$($_.Exception.Message)"
        break
    }
      
    foreach ($vm in $vms)
    {
        Try 
        {
            $vmsPercent = [math]::Round(($n / $vmCount) * 100)
            Write-Progress -Activity "Calculating Optimal vCPU Config for VMs" -Status "$vmsPercent% Complete:" -PercentComplete $vmsPercent -CurrentOperation "Current VM: $($vm.Name)"
                
            $priorities = @() 
            $priorities += 0   
            $details = ""
            $pNumaNotExpDetails = ""
            $pNumaNotExp = $null

            $vmHost = $vmHosts | where {$($_.vCenter) -eq $($vm.vCenter) -and $($_.Id) -eq $($vm.HostId)} | select -first 1

            If ($vmHost.ClusterId -eq $null -or $vmHost.ClusterId -eq "n/a")
            {
                If($vmHost.ClusterId -eq "n/a")
                {
                    $vmHost.ClusterId = $null
                }
                $cluster = "" | Select Name,MinMemoryGB,MinSockets,MinCoresPerSocket
                $clusterInconsistent = $false
            }
            Else
            {
                $cluster = $clusters | Where{($($_.Id) -eq $($vmHost.ClusterId)) -and ($($_.vCenter) -eq $($vmHost.vCenter))} | Select Name,DRSEnabled,MinMemoryGB,MinSockets,MinCoresPerSocket | Select -first 1
                #flags if hosts in a cluster are of different size memory or CPU
                If (($vmHost.MemoryGB -ne $cluster.MinMemoryGB -or $vmHost.Sockets -ne $cluster.MinSockets -or $vmHost.CoresPerSocket -ne $cluster.MinCoresPerSocket) -and $cluster.MinMemoryGB -ne "")
                {
                    $clusterMemPerChannel = $cluster.MinMemoryGB / $cluster.MinSockets
                    $clusterCPUs = $cluster.MinSockets * $cluster.MinCoresPerSocket
                    $clusterInconsistent = $true
                    $details += "Host hardware in the cluster is inconsistent | "
                    $priorities += 1
                }
                else 
                {
                    $clusterInconsistent = $false
                }
            }#end Else

            #flags if vmMemory spans pNUMA node
            If ($vm.MemoryGB -gt $vmHost.MemPerChannel)
            {
                $memWide = $true
                $memWideDetail = "memory"
            }
            Else
            {
                $memWide = $false
                $memWideDetail = ""
            } 
            #flags if vCPUs span pNUMA node
            If ($vm.NumCPU -gt $vmHost.CoresPerSocket)
            {
                $cpuWide = $true
                $cpuWideDetail = "CPU"
            }
            Else
            {
                $cpuWide = $false
                $cpuWideDetail = ""
            }
        
            #if #vCPUs is odd and crosses pNUMA nodes
            If (($memWide -or $cpuWide) -and (($vm.NumCPU % 2) -ne 0))
            {
                $calcVmCPUs = $vm.NumCPU + 1
                $cpuOdd = $true
            }
            Else
            {
                $calcVmCPUs = $vm.NumCPU
                $cpuOdd = $false
            }
            #call function to calculate the optimal sockets based on VM/Host/Cluster info
            $optSockets = Get-OptSockets $vm.Name $vm.MemoryGB $calcVmCPUs $vmHost.MemPerChannel $vmHost.CPUs $vmHost.CoresPerSocket
            If($optSockets -match "Error")
            {
                Throw $optSockets
            }
            If ($clusterInconsistent)
            {
                $clusterOptSockets = Get-OptSockets $vm.Name $vm.MemoryGB $calcVmCPUs $clusterMemPerChannel $clusterCPUs $cluster.MinCoresPerSocket
                If ($clusterOptSockets -ne $optSockets)
                {
                    $optSockets = $clusterOptSockets
                    If ($cluster.DRSEnabled -eq $True)
                    {
                        $details = "Host hardware in the cluster is inconsistent and DRS is enabled. VM will cross pNUMA boundaries on smallest host in the cluster | "
                        $priorities += 4
                    }
                    else 
                    {
                        $details = "Host hardware in the cluster is inconsistent. VM will cross pNUMA boundaries on smallest host in the cluster | "
                        $priorities += 3                        
                    }
                }
            }

            $optCoresPerSocket = $calcVmCPUs / $optSockets

            #flags if adjustments had to be made to the vCPUs
            If (($optSockets -ne $vm.Sockets) -or ($optCoresPerSocket -ne $vm.CoresPerSocket) -or $cpuOdd)
            {
                $cpuOpt = $false
            }
            Else
            {
                $cpuOpt = $true
            }
            #vCPUs are not optimal, but VM is not wide
            If (-not ($memWide -or $cpuWide) -and (-not $cpuOpt))
            {
                $details += "VM does not span pNUMA nodes, but consider configuring it to match pNUMA architecture | "
                $priorities += 2
            }

            ######################################################
            #if crossing pNUMA node(s), additional flags
            If (($memWide -or $cpuWide) -and (-not $cpuOpt))
            {
                If ($memWideDetail -ne "" -and $cpuWideDetail -ne "")
                {
                    $wideDetails = "$memWideDetail and $cpuWideDetail"
                }
                Else
                {
                    $wideDetails = ("$memWideDetail $cpuWideDetail").Trim()
                }
                $details += "VM $wideDetails spans pNUMA nodes and should be distributed evenly across as few as possible | "
                $priorities += 4
                #flags if VM is crossing pNUMA nodes, and vHW version is less than 8 (pNUMA not exposed to guest)
                $vmHWVerNo = [int]$vm.HWVersion.Split("-")[1]
                If($vmHWVerNo -lt 8)
                {
                    $pNumaNotExp = $true
                    $pNumaNotExpDetails = "(vHW < 8) "
                    $priorities += 4
                }
                #flags if VM is crossing pNUMA nodes, and CPUHotAdd is enabled (pNUMA not exposed to guest)
                If($vm.CpuHotAdd -eq $true)
                {
                    $pNumaNotExp = $true
                    $pNumaNotExpDetails = $pNumaNotExpDetails + " (CpuHotAddEnabled = TRUE)"
                    $priorities += 4
                }
                #flags if VM is crossing pNUMA nodes, and vCPUs is less than 9 (pNUMA not exposed to guest)
                If($vm.NumCPU -lt 9 -and $vm.NumaVcpuMin -eq $null -and $vmHost.NumaVcpuMin -eq $null)
                {
                    $pNumaNotExp = $true
                    $pNumaNotExpDetails = $pNumaNotExpDetails + " (vCPUs < 9). Consider modifying advanced setting ""Numa.Vcpu.Min"" to $($vm.NumCPU) or lower. "
                    $priorities += 4 
                }
                #if NumaVcpuMin has been modified
                Elseif($vm.NumaVcpuMin -ne $null -or $vmHost.NumaVcpuMin -ne $null)
                {
                    If($vm.NumaVcpuMin -ne $null)
                    {
                        $modVM = "VMValue: $($vm.NumaVcpuMin) "
                    }
                    ElseIf($vmHost.NumaVcpuMin -ne $null)
                    {
                        $modHost = "HostValue: $($vmHost.NumaVcpuMin)"
                    }
                    $modDetail = ("$modVM, $modHost").Trim(", ")

                    switch($vm.NumaVcpuMin -le $vm.NumCPU -or $vmHost.NumaVcpuMin -le $vm.NumCPU)
                    {
                        $true 
                        {
                            $details += "vCPUs < 9, but advanced setting ""Numa.Vcpu.Min"" has been modified ($modDetail) to expose pNUMA to guest OS | "
                            $priorities += 1
                        }
                        $false 
                        {
                            $pNumaNotExp = $true
                            $pNumaNotExpDetails = $pNumaNotExpDetails + " (Advanced setting ""Numa.Vcpu.Min"" is > VM vCPUs). The setting has been modified ($modDetail), but is still higher than VM vCPUs. Change the value to $($vm.NumCPU) or lower to expose pNUMA to the guest OS"
                            $priorities += 4
                        }
                    }
                }
                If($pNumaNotExp)
                {
                    $pNumaNotExpDetails = $pNumaNotExpDetails.Trim() 
                    $details += "VM spans pNUMA nodes, but pNUMA is not exposed to the guest OS: $pNumaNotExpDetails | "
                    $priorities += 4
                }
             
                #flags if VM has odd # of vCPUs and spans pNUMA nodes
                If ($cpuOdd)
                {
                    $details += "VM has an odd number of vCPUs and spans pNUMA nodes | "
                    $priorities += 4
                }

            }#end if (($memWide -or $cpuWide) -and (-not $cpuOpt))

            #flags VMs with CPU count higher than physical cores
            If($vm.NumCPU -gt ($vmHost.Sockets * $vmHost.CoresPerSocket))
            {
                $optSockets = $vmHost.Sockets
                $optCoresPerSocket = $vmHost.CoresPerSocket
                $priorities += 4
                $details += "VM vCPUs exceed the host's physical cores. Consider reducing the number of vCPUs | "
            }
            #flags if vCPU count is > 8 and Host PowerPolicy is not "HighPerformance"
            If($vm.NumCPU -gt 8 -and $vmHost.PowerPolicy -ne "HighPerformance" -and $vmHost.PowerPolicy -ne "N/A")
            {
                $priorities += 3
                $details += 'Consider changing the host Power Policy to "High Performance" for hosts with VMs larger than 8 vCPUs | '
            } 
  
            #gets highest priority
            $highestPriority = ($priorities | measure -Maximum).Maximum
            Switch($highestPriority)
            {
                0    {$priority = "N/A"}
                1    {$priority = "INFO"}
                2    {$priority = "LOW"}
                3    {$priority = "MEDIUM"}
                4    {$priority = "HIGH"}
            }

            #flags whether the VM is configured optimally or not
            If ($priority -eq "N/A" -or $priority -eq "INFO")
            {
                $vmOptimized = $True
            }
            Else
            {
                $vmOptimized = $False
            }
            #creates object with data to return from function
            If ($full -eq $true)
            {
                $objInfo = [pscustomobject]@{
                    vCenter                  = $($vmHost.vCenter);
                    Cluster                  = $($cluster.Name);
                    ClusterMinMemoryGB       = $($cluster.MinMemoryGB);
                    ClusterMinSockets        = $($cluster.MinSockets);
                    ClusterMinCoresPerSocket = $($cluster.MinCoresPerSocket);
                    DRSEnabled               = $($cluster.DRSEnabled);
                    HostName                 = $($vmHost.Name);
                    ESXi_Version             = $($vmHost.Version);
                    HostMemoryGB             = $($vmHost.MemoryGB);
                    HostSockets              = $($vmHost.Sockets);
                    HostCoresPerSocket       = $($vmHost.CoresPerSocket);
                    HostCpuThreads           = $($vmHost.CpuThreads);
                    HostHTActive             = $($vmHost.HTActive);
                    HostPowerPolicy          = $($vmHost.PowerPolicy);
                    VMName                   = $($vm.Name);
                    VMHWVersion              = $($vm.HWVersion);
                    VMCpuHotAddEnabled       = $($vm.CpuHotAdd).ToString();
                    VMMemoryGB               = $($vm.MemoryGB);
                    VMSockets                = $($vm.Sockets);
                    VMCoresPerSocket         = $($vm.CoresPerSocket);
                    vCPUs                    = $($vm.NumCPU);
                    VMOptimized              = $vmOptimized;
                    OptimalSockets           = $optSockets;
                    OptimalCoresPerSocket    = $optCoresPerSocket;;
                    Priority                 = $priority;
                    Details                  = $details.Trim("| ")
                    } #end pscustomobject
            }#end If
            Else
            {
                $objInfo = [pscustomobject]@{
                    VMName                   = $($vm.Name);
                    VMSockets                = $($vm.Sockets);
                    VMCoresPerSocket         = $($vm.CoresPerSocket);
                    vCPUs                    = $($vm.NumCPU);
                    VMOptimized              = $vmOptimized;
                    OptimalSockets           = $optSockets;
                    OptimalCoresPerSocket    = $optCoresPerSocket;
                    Priority                 = $priority;
                    Details                  = $details.Trim("| ")
                    } #end pscustomobject
            }

            $results += $objInfo
            $n++
        }#end Try
        Catch
        {
            Write-Error "Error calculating optimal CPU for $($vm.Name): $($_.Exception.Message)"
            break
        }
    }#end foreach ($vm in $vms)
    Write-Progress -Activity "Calculating Optimum vCPU Config for VMs" -Completed
    Return $results    
}