Recommend-Sizing.psm1

function Recommend-Sizing {
<#
    .NOTES
    ===========================================================================
    Created by: Markus Kraus
    Twitter: @VMarkus_K
    Private Blog: mycloudrevolution.com
    ===========================================================================
    Changelog:
    2016.11 ver 1.0 Base Release
    2016.11 ver 1.1 Optional Stats Collection
    2016.11 ver 1.2 VM Stats from Realtime Data and new Counters
    ===========================================================================
    External Code Sources:
    http://www.lucd.info/2011/04/22/get-the-maximum-iops/
    https://communities.vmware.com/thread/485386
    ===========================================================================
    Tested Against Environment:
    vSphere Version: 5.5 U2, 6.0
    PowerCLI Version: PowerCLI 6.3 R1, PowerCLI 6.5 R1
    PowerShell Version: 4.0, 5.0
    OS Version: Windows 8.1, Server 2012 R2
    ===========================================================================
    Keywords vSphere, ESXi, VM, Storage, Sizing
    ===========================================================================
 
    .DESCRIPTION
    This Function collects Basic vSphere Informations for a Hardware Sizing Recommandation. Focus is in Compute Ressources.
 
    .Example
    Recommend-Sizing -ClusterNames Cluster01, Cluster02 -Stats -StatsRange 60 -Verbose
 
    .Example
    Recommend-Sizing -ClusterNames Cluster01, Cluster02
 
    .Example
    Recommend-Sizing -ClusterNames Cluster01
 
    .PARAMETER ClusterNames
    List of your vSphere Cluser Names to process.
 
    .PARAMETER Stats
    Enables Stats Collection.
 
    Warning: At the moment this is only fully tested with vSphere 5.5 and vSphere 6.5!
 
    .PARAMETER StatsRange
    Time Range in Minutes for the Stats Collection.
    Default is 24h.
 
#Requires PS -Version 4.0
#Requires -Modules VMware.VimAutomation.Core, @{ModuleName="VMware.VimAutomation.Core";ModuleVersion="6.3.0.0"}
#>


[CmdletBinding()]
param( 
    [Parameter(Mandatory=$True, ValueFromPipeline=$False, Position=0)]
        [Array] $ClusterNames,
    [Parameter(Mandatory=$False, ValueFromPipeline=$False, Position=1, ParameterSetName = "Stats")]
        [switch] $Stats,
     [Parameter(Mandatory=$False, ValueFromPipeline=$False, Position=2, ParameterSetName = "Stats")]
        [int] $StatsRange = 1440      
        
)
Begin {
     if ($Stats) {
        Write-Warning "Stats Collection requested.`nAt the moment this is only fully tested with vSphere 5.5 and vSphere 6.5"
        [int]$TimeRange = "-" + $StatsRange
     }

    $Validate = $True
    #region: Check Clusters
    Write-Verbose "$(Get-Date -Format G) Starting Cluster Validation..." 
    foreach ($ClusterName in $ClusterNames) {
        $TestCluster = Get-Cluster -Name $ClusterName -ErrorAction SilentlyContinue -Verbose:$False
        if(!($TestCluster)){
            Write-Warning "No Custer found wth Name $ClusterName!"
            $Validate = $False
        }
        elseif ($TestCluster.count -gt 1) {
            Write-Warning "Multiple Custers found wth Name $ClusterName!`nUse a List of explicit Cluster Names: Recommend-Sizing -ClusterNames Cluster01, Cluster02 "
            $Validate = $False
        }
    }
    Write-Verbose "$(Get-Date -Format G) Cluster Validation completed" 
    #endregion
}

Process {
    $MyView = @()
    if ($Validate -eq $True) {
        foreach ($ClusterName in $ClusterNames) {
            #region: Get Cluster Objects
            Write-Verbose "$(Get-Date -Format G) Collect $ClusterName Cluster Objects..." 
            $Cluster =  Get-Cluster -Name $ClusterName -Verbose:$False
            $ClusterVMs = $Cluster | Get-VM -Verbose:$False
            $ClusterVMsPoweredOn = $ClusterVMs | where {$_.PowerState -eq "PoweredOn"}
            $ClusterDatastores = $Cluster | Get-Datastore -Verbose:$False
            $ClusterHosts = $Cluster | Get-VMHost -Verbose:$False
            $HostsAverageMemoryUsageGB = [math]::round( ($ClusterHosts | Measure-Object -Average -Property MemoryUsageGB).Average,1 )
            $HostsAverageMemoryUsage = $([math]::round( (($ClusterHosts | Measure-Object -Average -Property MemoryUsageGB).Average / ($ClusterHosts | Measure-Object -Average -Property MemoryTotalGB).Average) * 100,1 ))
            $HostsAverageCpuUsageMhz = [math]::round( ($ClusterHosts | Measure-Object -Average -Property CpuUsageMhz).Average,1 )
            $HostsAverageCpuUsage = $([math]::round( (($ClusterHosts | Measure-Object -Average -Property CpuUsageMhz).Average / ($ClusterHosts | Measure-Object -Average -Property CpuTotalMhz).Average) * 100,1 ))
            Write-Verbose "$(Get-Date -Format G) Collect $($Cluster.name) Cluster Objects completed" 
            #endregion

            #region: CPU Calculation
            Write-Verbose "$(Get-Date -Format G) Collect $($Cluster.name) CPU Details..." 
            $VMvCPUs = ($ClusterVMs | Measure-Object -Sum -Property NumCpu).sum
            $LogicalThreads = $Cluster.ExtensionData.Summary.NumCpuThreads
            $CpuCores = $Cluster.ExtensionData.Summary.NumCpuCores
            $vCPUpCPUratio = [math]::round( $VMvCPUs / $LogicalThreads,1 )
            Write-Verbose "$(Get-Date -Format G) Collect $($Cluster.name) CPU Details completed." 
            #endregion

            #region: Memory Calculation
            Write-Verbose "$(Get-Date -Format G) Collect $($Cluster.name) Memory Details..." 
            $AllocatedVMMemoryGB = [math]::round( ($ClusterVMs | Measure-Object -Sum -Property MemoryGB).sum )
            $PhysicalMemory = [math]::round( $Cluster.ExtensionData.Summary.TotalMemory / 1073741824,1 )
            $MemoryUsage = [math]::round( ($AllocatedVMMemoryGB / $PhysicalMemory) * 100 ,1 )
            Write-Verbose "$(Get-Date -Format G) Collect $($Cluster.name) Memory Details completed" 
            #endregion

            if ($Stats) {
                #region: Creating VM Stats
                Write-Verbose "$(Get-Date -Format G) Create $($Cluster.name) VM Stats..."
                $VMMetrics = "disk.numberwrite.summation","disk.numberread.summation","cpu.usage.average", "mem.usage.average"
                $Start = (Get-Date).AddMinutes($TimeRange)
                $VMStats = Get-Stat -Realtime -Stat $VMMetrics -Entity $ClusterVMsPoweredOn -Start $Start -Verbose:$False
                Write-Verbose "$(Get-Date -Format G) Create $($Cluster.name) VM Stats completed"
                #endregion
                
                #region: Creating VM Stats Report
                Write-Verbose "$(Get-Date -Format G) Process $($Cluster.name) VM Stats Report..."
                $ReportVMPerf = @() 
                $ReportVMPerf = $VMStats | Group-Object -Property {$_.Entity.Name},Instance | %{
                                    New-Object PSObject -Property @{
                                    IOPSWriteAvg = ($_.Group | `
                                    where{$_.MetricId -eq "disk.numberwrite.summation"} | `
                                    Measure-Object -Property Value -Average).Average
                                    IOPSReadAvg = ($_.Group | `
                                    where{$_.MetricId -eq "disk.numberread.summation"} | `
                                    Measure-Object -Property Value -Average).Average
                                    CPUUsageAvg = ($_.Group | `
                                    where{$_.MetricId -eq "cpu.usage.average"} | `
                                    Measure-Object -Property Value -Average).Average
                                    MEMUsageAvg = ($_.Group | `
                                    where{$_.MetricId -eq "mem.usage.average"} | `
                                    Measure-Object -Property Value -Average).Average
                    }
                }
                Write-Verbose "$(Get-Date -Format G) Process $($Cluster.name) VM Stats Report completed"
                #endregion
            }
            else {
                Write-Verbose "$(Get-Date -Format G) Stats Collection skipped..."
            }

            #region: Create VM Disk Space Report
            Write-Verbose "$(Get-Date -Format G) Process $($Cluster.name) VM Disk Space Report..."
            $reportDiskSpace = @()
            foreach ($ClusterVM in $ClusterVMs){
                $VMDKs = $ClusterVM | get-HardDisk -Verbose:$False
                foreach ($VMDK in $VMDKs) {
                    if ($VMDK -ne $null){
                        [int]$CapacityGB = $VMDK.CapacityKB/1024/1024
                        $Report = [PSCustomObject] @{
                                CapacityGB = $CapacityGB
                            }
                            $reportDiskSpace += $Report
                        }   
                    }
                }
            Write-Verbose "$(Get-Date -Format G) Process $($Cluster.name) VM Disk Space Report completed"
            #endregion

            #region: Create Datastore Space Report
            Write-Verbose "$(Get-Date -Format G) Process $($Cluster.name) Datastore Space Report..."
            $DatastoreReport = @($ClusterDatastores | Select-Object @{N="CapacityGB";E={[math]::Round($_.CapacityGB,2)}}, @{N="FreeSpaceGB";E={[math]::Round($_.FreeSpaceGB,2)}}, @{N="UsedSpaceGB";E={[math]::Round($_.CapacityGB - $_.FreeSpaceGB,2)}})
            Write-Verbose "$(Get-Date -Format G) Process $($Cluster.name) Datastore Space Report completed"
            #endregion

            #region: Create Global Report
            Write-Verbose "$(Get-Date -Format G) Process Global Report..."
            $SizingReport = [PSCustomObject] @{
                Cluster = $Cluster.name
                HAEnabled = $Cluster.HAEnabled
                DrsEnabled = $Cluster.DrsEnabled
                Hosts = $Cluster.ExtensionData.Summary.NumHosts
                HostsAverageMemoryUsageGB = $HostsAverageMemoryUsageGB
                HostsAverageMemoryUsage = "$HostsAverageMemoryUsage %" 
                HostsAverageCpuUsageMhz = $HostsAverageCpuUsageMhz
                HostsAverageCpuUsage = "$HostsAverageCpuUsage %" 
                PhysicalCPUCores = $CpuCores
                LogicalCPUThreads = $LogicalThreads
                VMs =  $ClusterVMs.count
                ActiveVMs =  $ClusterVMsPoweredOn.count
                VMvCPUs = $VMvCPUs
                vCPUpCPUratio = "$vCPUpCPUratio : 1"
                PhysicalMemoryGB = $PhysicalMemory
                AllocatedVMMemoryGB = $AllocatedVMMemoryGB        
                ClusterMemoryUsage = "$MemoryUsage %"
                SumVMDiskSpaceGB = [math]::round( ($reportDiskSpace | Measure-Object -Sum -Property CapacityGB).sum, 1 )
                SumDatastoreSpaceGB = [math]::round( ($DatastoreReport | Measure-Object -Sum -Property CapacityGB).sum, 1 )
                SumDatastoreUsedSpaceGB = [math]::round( ($DatastoreReport | Measure-Object -Sum -Property UsedSpaceGB).sum, 1 )
                AverageVMIOPSWriteAvg = [math]::round( ($ReportVMPerf | Measure-Object -Average -Property IOPSWriteAvg).Average,1 )
                AverageVMIOPSReadAvg = [math]::round( ($ReportVMPerf | Measure-Object -Average -Property IOPSReadAvg).Average,1 )
                AverageVMCPUUsageAvg = "$([math]::round( ($ReportVMPerf | Measure-Object -Average -Property CPUUsageAvg).Average,1 )) %"
                AverageVMMEMUsageAvg = "$([math]::round( ($ReportVMPerf | Measure-Object -Average -Property MEMUsageAvg).Average,1 )) %"
            }
            $MyView += $SizingReport
            Write-Verbose "$(Get-Date -Format G) Process Global Report completed"
            #endregion
        }

        }
        Else {
            Write-Error "Validation Failed! Processing Skipped"
        }
        
    }

    End {
        $MyView
    }

}