VMware Capacity Metrics PowerShell

##
## Version 1.9
## Written By Brian Vogel bfvogel@gmail.com 
## MIT License
##
##
param (
[switch]$Excel = $false

)
$ScriptDirectory = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent
try {
    . ("$ScriptDirectory\Config.ps1")
}
catch {
    Write-Host "Error while loading supporting PowerShell Scripts" 
	exit 1
}


$RATIOAlert = 2.5 
$PERCENTILE = 90
$Lookback = 30
$Ready_Threshold = 10000


## Module Load
if (!(Get-Module -Name VMware.VimAutomation.Core) -and (Get-Module -ListAvailable -Name VMware.VimAutomation.Core)) {  
    Write-Output "loading the VMware COre Module..."  
    if (!(Import-Module -Name VMware.VimAutomation.Core -ErrorAction SilentlyContinue)) {  
        # Error out if loading fails  
        Write-Error "`nERROR: Cannot load the VMware Module. Is the PowerCLI installed?"  
     }  
    $Loaded = $True  
    }  
    elseif (!(Get-PSSnapin -Name VMware.VimAutomation.Core -ErrorAction SilentlyContinue) -and !(Get-Module -Name VMware.VimAutomation.Core) -and ($Loaded -ne $True)) {  
        Write-Output "loading the VMware Core Snapin..."  
     if (!(Add-PSSnapin -PassThru VMware.VimAutomation.Core -ErrorAction SilentlyContinue)) {  
     # Error out if loading fails  
     Write-Error "`nERROR: Cannot load the VMware Snapin or Module. Is the PowerCLI installed?"  
	 Write-Host "Try: 'Install-Module VMware.PowerCLI'"
     }  
    }  
#Connect-VIServer $hosts | out-null

Connect-VIServer $hosts -Credential $CREDS | out-null
#Get Date for file naming
$DATE = (get-date).ToString("yyyy-MM-dd")
#
#Get-Cluster | select name,@{n="NumVMHostCPU"; e={(Get-VMHost -Location $_ | Measure-Object NumCpu -Sum).Sum}}
$TotalNumvCPUs = 0
$TotalNumRAMs = 0
$HostNumProvisionedStorage = 0
$HostNumUsedStorage = 0
$ClusterTotalRAM = 0
$Cpurdy = 0
[System.Collections.ArrayList]$ClusterArray = @()
Foreach ($Cluster in (Get-Cluster |Sort Name)){
	$HostNumvCPUs = 0
	$HostNumRAMs = 0
	$HostNumProvisionedStorage = 0
	$HostNumUsedStorage = 0
	$ClusterTotalRAM = 0
	$CpurdyAVG = 0
	$CpurdyMAX  = 0
	$CpurdyMIN = 9999999999
	$CpuRdyVMPERCENTILE = @()
	$ClusterRAMPercentile = @()
	$CPUReadyArrayVMAVG = @()
	Write-Host -NoNewLine "Cluster: $($Cluster.name)"
	$ClusterNumCPUs = (Get-VMHost -Location $Cluster.name | Measure-Object NumCpu -Sum).Sum
	$ClusterTotalRAM = (Get-VMHost -Location $Cluster.name | Measure-Object MemoryTotalGB -Sum).Sum



	Foreach ($ESXHost in ($Cluster |Get-VMHost |Sort Name)){
		Write-Host -NoNewLine -

		$RunningLimit = $null
		#$RunningLimit = ($ESXHost |Get-VMHostAdvancedConfiguration).get_Item(“Misc.RunningVCpuLimit“)
		If ($RunningLimit -eq $null){
			$RunningLimit = 128
		}

		Foreach ($VM in ($ESXHost |Get-VM)){
			$HostNumvCPUs += ($VM).NumCpu
			$HostNumRAMs += ($VM).MemoryGB
			$HostNumProvisionedStorage += ($VM).ProvisionedSpaceGB
			$HostNumUsedStorage += ($VM).UsedSpaceGB
			if ( ($VM).PowerState -eq "PoweredOn" )
			{
				$StatCpurdy = Get-Stat -Entity ($VM) -start (get-date).AddDays(-$Lookback) -Finish (Get-Date)-MaxSamples 10000 -Intervalmins 30 -stat cpu.ready.summation



				$Sequence = $StatCpurdy | Select -ExpandProperty Value
				[int[]]$Sequence = $Sequence | Sort-Object
				#write-host $Sequence
				[int]$LENGTH = $Sequence.Length
				[int]$MIN = $Sequence | select -first 1
				[int]$MAX = $Sequence | select -last 1
				[int]$RANGE = ($MAX - $MIN) + 1
				
				$VMrdyStat =  ($RANGE / 100) * $PERCENTILE
				#Write-host $VM.Name MIN:$MIN MAX:$MAX RANGE:$RANGE PERCENTILE:$VMrdyStat
				#$VMrdyStat
				$CpuRdyVMPERCENTILE += $VMrdyStat
				
				
				
				$CpurdyVMAVG  = ([math]::round(($StatCpurdy | Measure-Object -Property Value -Average).Average,0)) 
				
				$CPUReadyArrayVMAVG += $CpurdyVMAVG 
				

				if ( $CpurdyMAX -lt ($StatCpurdy | select -ExpandProperty Value| sort-object | select -last 1) )
				{
					$CpurdyMAX  = $StatCpurdy | select -ExpandProperty Value| sort-object | select -last 1
				}
				if ( $CpurdyMIN -gt ($StatCpurdy | select -ExpandProperty Value| sort-object | select -first 1) )
				{
					$CpurdyMIN  = $StatCpurdy | select -ExpandProperty Value| sort-object | select -first 1
				}
			}
		}
#Write-Host “ Number of vCPU on host: $($HostNumvCPUs)“
		$TotalNumvCPUs += $HostNumvCPUs
		$TotalNumRAMs += $HostNumRAMs
		$TotalNumProvisionedStorage += $HostNumProvisionedStorage
		$TotalNumUsedStorage += $HostNumUsedStorage
#$Cpurdy += $StatCpurdy | Measure-Object -Property value -Average -Maximum -Minimum 
#Gather Host MIN/Max RAM
		$HostStatRAM = Get-Stat -Entity ($ESXHost) -start (get-date).AddDays(-$Lookback) -Finish (Get-Date)-MaxSamples 10000 -Intervalmins 30 -stat mem.usage.average

		$HostStatRAMMax =  ($HostStatRAM | Sort -Property Value | Select -last 1).Value
		#Get the average and count CPU Ready fo all VM's across cluster
		foreach($i in $HostStatRAM.Value){
		$RunningTotal += $i
		}
		$HostStatRAMAvg  =  ([decimal]($RunningTotal) / [decimal]($HostStatRAM.Length));
		$RunningTotal = $null
		#
		$HostStatRAMMin =  ($HostStatRAM | Sort -Property Value | Select -first 1).Value

		$TotalNumRAMMax += $HostStatRAMMax
		$TotalNumRAMAvg += $HostStatRAMAvg
		$TotalNumRAMMin += $HostStatRAMMin     
		
		#Get RAMPercentaile of collective vm ready percentiles
	
	[int[]]$Sequence = ($ESXHost| Get-Stat -Stat mem.usage.average -MaxSamples 10000 -Intervalmins 30 -Start (get-date).AddDays(-$Lookback) -Finish (Get-Date)).Value
	
	[int[]]$Sequence = $Sequence | Sort-Object
	#write-host $Sequence
	[int]$LENGTH = $Sequence.Length
	#[int]$MIN = $Sequence | select -first 1
	[int]$MIN = 0
	[int]$MAX = $Sequence | select -last 1
	[int]$RANGE = ($MAX - $MIN) + 1
	
	$HostRAMPercentile =  ($RANGE / 100) * $PERCENTILE
	$ClusterRAMPercentile += $HostRAMPercentile
		$HostNumvCPUs = 0
		$HostNumRAMs = 0
		$HostNumProvisionedStorage = 0
		$HostNumUsedStorage = 0
		$VMrdyStat = 0
		$CpurdyVMAVG  = 0
		$HostStatRAM = 0

		$HostStatRAMMax = 0
		$HostStatRAMMin = 0
		$HostStatRAMAvg= 0
		$HostRAMPercentile = 0

	}
#Get the average and count CPU Ready fo all VM's across cluster
		foreach($i in $CPUReadyArrayVMAVG){
		$RunningTotal += $i
		}
		$CpurdyAVG  =  ([decimal]($RunningTotal) / [decimal]($CPUReadyArrayVMAVG.Length));
		$RunningTotal = $null
		
		#$CpurdyAVG
		
$RatioCPU = $TotalNumvCPUs / $ClusterNumCPUs


$TotalNumRAMMax = $TotalNumRAMMax / ($Cluster | Get-VMHost).Count
$TotalNumRAMAvg = $TotalNumRAMAvg / ($Cluster | Get-VMHost).Count
$TotalNumRAMMin = $TotalNumRAMMin / ($Cluster | Get-VMHost).Count


$ClusterNumRAMs = [math]::round(($Cluster | Get-Stat -Stat mem.usage.average -MaxSamples 10000 -Intervalmins 30  -Start (get-date).AddDays(-$Lookback) -Finish (Get-Date) | Measure-Object -Property Value -Average).Average,0)


#Get CPUReady Percentaile of collective vm ready percentiles
	$Sequence = $CpuRdyVMPERCENTILE
	[int[]]$Sequence = $Sequence | Sort-Object
	#write-host $Sequence
	[int]$LENGTH = $Sequence.Length
	[int]$MIN = $Sequence | select -first 1
	[int]$MAX = $Sequence | select -last 1
	[int]$RANGE = ($MAX - $MIN) + 1
	
	$ClusterPercentile =  ($RANGE / 100) * $PERCENTILE
	#Write-host $ESXHost.Name MIN:$MIN MAX:$MAX RANGE:$RANGE PERCENTILE:$ClusterPercentile


#Get RAMPercentaile of collective Host percentiles
	
	[int[]]$Sequence = $ClusterRAMPercentile
	
	[int[]]$Sequence = $Sequence | Sort-Object
	$Sequence
	#write-host $Sequence
	[int]$LENGTH = $Sequence.Length
	#[int]$MIN = $Sequence | select -first 1
	#MIN needs to be 0 as we are factoring a percentile of percentages
	[int]$MIN = 0
	[int]$MAX = $Sequence | select -last 1
	[int]$RANGE = ($MAX - $MIN) + 1
	
	$ClusterRAMPercentile =  ($RANGE / 100) * $PERCENTILE
## STDDEV Math

	if ($CPUReadyArrayVMAVG -match '\d+' -and $CPUReadyArrayVMAVG.Count -gt 1) {

		#Variables used later
		[decimal]$newNumbers  = $Null
		[decimal]$stdDev      = $null
		
		#Get the average and count 
		foreach($i in $CPUReadyArrayVMAVG){
		$RunningTotal += $i
		}
		$avgValue  =  ([decimal]($RunningTotal) / [decimal]($CPUReadyArrayVMAVG.Length));
		$RunningTotal = $null
		
		$avgCount             = $CPUReadyArrayVMAVG.Length
		
		#Iterate through each of the numbers and get part of the variance via some PowerShell math.
		ForEach($number in $CPUReadyArrayVMAVG) {

			$newNumbers += [Math]::Pow(($number - $avgValue), 2)

		}

		#Finish the variance calculation, and get the square root to finally get the standard deviation.
		$ClusterstdDev = [math]::Sqrt($($newNumbers / ($avgCount - 1)))

		
		}





$ClusterObject = [PSCustomObject]@{

			Name     			= $Cluster.name
			VMHost_Count     		= ($Cluster | Get-VMHost).Count
			pCPU_Count        		= $ClusterNumCPUs
			vCPU_Count                = $TotalNumvCPUs
			CPU_Prov_Ratio			= [math]::Round($RatioCPU,2)
			
			#
			#https://kb.vmware.com/s/article/2002181
			#
			CPU_Contention_PCT			= [math]::Round(($CpurdyAVG / (300 * 1000)) * 1000,0)
			CPU_Ready_PCTILE_ms			= [math]::Round($ClusterPercentile,0) 
			CPU_Ready_AVG_ms			= [math]::Round($CpurdyAVG,0)
			CPU_Ready_STDDEV_ms			= [math]::Round($ClusterstdDev,0)
			CPU_Ready_MAX_ms			= $CpurdyMAX
			CPU_Ready_MIN_ms			= $CpurdyMIN
			'RAM_Total_GB'         = [math]::Round($ClusterTotalRAM,0)
			'RAM_Provisioned_GB'        = [math]::Round($TotalNumRAMs,0)
			'RAM_PCTILE'			=	$ClusterRAMPercentile
			'RAM_Active_PCT'    = $ClusterNumRAMs
			'RAM_Utilized_MAX_PCT'	= [math]::Round($TotalNumRAMMax,0)
			'RAM_Utilized_MIN_PCT'	= [math]::Round($TotalNumRAMMin,0)
			'RAM_Utilized_AVG_PCT'	= [math]::Round($TotalNumRAMAvg,0)
			Prov_Storage_GB         = [math]::Round($TotalNumProvisionedStorage,0)
			Used_Storage_GB         = [math]::Round($TotalNumUsedStorage,0)

			
		}

$ClusterArray += $ClusterObject

Write-Host "|"
$ClusterNumCPUs = 0
$TotalNumvCPUs = 0
$TotalNumRAMs = 0
$ClusterTotalRAM = 0
$ClusterNumRAMs = 0
$TotalNumProvisionedStorage = 0
$TotalNumUsedStorage = 0
$CpurdyAVG = 0
$CpurdyMAX  = 0
$CpurdyMIN = 9999999999
$CpurdyPCTILE = 0
$CpuRdyVMPERCENTILE = $null
$ClusterPercentile  = 0
$ClusterstdDev = 0
$CPUReadyArrayVMAVG = $null
$CpurdyVMAVG  = 0
$CPUReadyArrayVMAVG = $null
$TotalNumRAMMax = 0
$TotalNumRAMAvg = 0
$TotalNumRAMMin = 0
$ClusterRAMPercentile = 0
$RAMPERCENTILE  = 0
}
$ClusterArray | Export-csv -path $OUTPUTDIR\Capacity-$DATE.csv

if ($Excel)
{
$FileName = "$OUTPUTDIR\Capacity-$DATE"

# create some CSV data
Get-Process | Export-Csv -Path "$FileName.csv" -NoTypeInformation -Encoding UTF8

# load into Excel
$excel = New-Object -ComObject Excel.Application 
$excel.Visible = $true

# change thread culture
[System.Threading.Thread]::CurrentThread.CurrentCulture = 'en-US'

$excel.Workbooks.Open("$FileName.csv").SaveAs("$FileName.xlsx",51)
$excel.Quit()
}

if ($SMTPSERVER)
{
Send-MailMessage -SmtpServer $SMTPServer -Port $SMTPPort -From $SMTPFrom -To $SMTPTo -Subject "VMWare Capacity Metrics $Date" -Body "These are the capacity metrics for all configured VMWare environments" -Attachments $OUTPUTDIR\Capacity-$DATE.csv -Priority High | out-null
}

if ($Post)
{
$uri = "https://www.nobrandsan.com/filedrop/index.php"
$contentType = "multipart/form-data; boundary=----WebKitFormBoundaryyEmKNDsBKjB7QEqu"
$body = @{
        "uploaded_file" = Get-Content -Path "$OUTPUTDIR\Capacity-$DATE.csv" -Raw
    }
Invoke-WebRequest -UseBasicParsing -Uri $uri -Method Post -ContentType $contentType -Body $body
}
Disconnect-VIServer * -confirm:$false