Current Version:
# HyperVCapacityReport.ps1
# Version 0.0.2
# Report parameters
$workingDirectory='C:\scripts\googleSheets'
$selectFields='node,model,os,cores,cpuUtilizationPercent,ramGb,ramUsedPercent'
$domainObjects=@(
@{domain='intranet.kimconnect.com';dc='lax-dc02.intranet.kimconnect.com';username='intranet\clusterReport';password=$env:clusterReportPass}
@{domain='intranet.dragoncoin.com';dc='lax-dc02.intranet.dragoncoin.com';username='intranet\clusterReport';password=$env:clusterReportPass}
)
# Update this Google Sheets
$spreadsheetID='SOMEHASHHERE'
$spreadsheetUrl='https://docs.google.com/spreadsheets/d/'+$spreadsheetID
# Google API Authozation
$scope = "https://www.googleapis.com/auth/spreadsheets https://www.googleapis.com/auth/drive https://www.googleapis.com/auth/drive.file"
$certPath = 'C:\scripts\googleSheets\plated-campaign-SOMEHASHERE.p12'
$iss = '[email protected]'
$certPassword = 'notasecret'
# Email parameters
$emailFrom='[email protected]'
$emailTo='[email protected]','[email protected]'
$subject='Hyper-V Hosts Capacity Report'
$smtpRelayServer='relay02.dragoncoin.com'
$emailDay='Monday'
function getAllHyperVClusters($domainObjects){
function getHyperVHosts($domainObjects){
function includeRSAT{
$ErrorActionPreference='stop'
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
#$rsatWindows7x32='https://download.microsoft.com/download/4/F/7/4F71806A-1C56-4EF2-9B4F-9870C4CFD2EE/Windows6.1-KB958830-x86-RefreshPkg.msu'
$rsatWindows7x64='https://download.microsoft.com/download/4/F/7/4F71806A-1C56-4EF2-9B4F-9870C4CFD2EE/Windows6.1-KB958830-x64-RefreshPkg.msu'
$rsatWindows81='https://download.microsoft.com/download/1/8/E/18EA4843-C596-4542-9236-DE46F780806E/Windows8.1-KB2693643-x64.msu'
$rsat1709 = "https://download.microsoft.com/download/1/D/8/1D8B5022-5477-4B9A-8104-6A71FF9D98AB/WindowsTH-RSAT_WS_1709-x64.msu"
$rsat1803 = "https://download.microsoft.com/download/1/D/8/1D8B5022-5477-4B9A-8104-6A71FF9D98AB/WindowsTH-RSAT_WS_1803-x64.msu"
$rsatWs2016 = "https://download.microsoft.com/download/1/D/8/1D8B5022-5477-4B9A-8104-6A71FF9D98AB/WindowsTH-RSAT_WS2016-x64.msu"
# This command does not work on Windows 2012R2
#$releaseId=(Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion" -Name ReleaseId).ReleaseId
#Get-ItemProperty : Property ReleaseId does not exist at path HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows
#NT\CurrentVersion.
#At line:1 char:2
#+ (Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion" -Na ...
#+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# + CategoryInfo : InvalidArgument: (ReleaseId:String) [Get-ItemProperty], PSArgumentException
# + FullyQualifiedErrorId : System.Management.Automation.PSArgumentException,Microsoft.PowerShell.Commands.GetItemPropertyCommand
$releaseId=(Get-Item "HKLM:SOFTWARE\Microsoft\Windows NT\CurrentVersion").GetValue('ReleaseID')
$osVersion=[System.Environment]::OSVersion.Version
[double]$osVersionMajorMinor="$($osVersion.Major).$($osVersion.Minor)"
$osName=(Get-WmiObject Win32_OperatingSystem).Name
#$osType=switch ((Get-CimInstance -ClassName Win32_OperatingSystem).ProductType){
# 1 {'client'}
# 2 {'domaincontroller'}
# 3 {'memberserver'}
# }
$windowsVersion=(Get-CimInstance Win32_OperatingSystem).Version
switch ($releaseId){
1607{write-host 'Windows Server 2016 Release 1607 detected';$link=$rsatWs2016;break}
1709{write-host 'Windows Server 2016 Release 1709 detected';$link=$rsat1709;break}
1803{write-host 'Windows Server 2016 Release 1803 detected';$link=$rsat1803}
}
switch ($osVersionMajorMinor){
{$_ -eq 6.0}{write-host 'Windows Server 2008 or Windows Vista detected';$link=$rsat1709;break}
{$_ -eq 6.1}{write-host 'Windows Server 2008 R2 or Windows 7 detected';$link=$rsatWindows7x64;break}
{$_ -eq 6.2}{write-host 'Windows Server 2012 or Windows 8.1 detected';$link=$rsatWindows81;break}
{$_ -eq 6.3}{write-host 'Windows Server 2012 R2 detected';$link=$rsatWindows81}
}
if (!(Get-Module -ListAvailable -Name ActiveDirectory -EA SilentlyContinue)){
Write-host "Prerequisite checks: module ActiveDirectory NOT currently available on this system. Please wait while the program adds that plugin..."
try{
# If OS is Windows Server, then install RSAT using a different method
if ($osName -match "^Microsoft Windows Server") {
# This sequence has confirmed to be valid on Windows Server 2008 R2 and above
Write-Verbose "Importing Windows Feature: RSAT-AD-PowerShell"
Import-Module ServerManager
Add-WindowsFeature RSAT-AD-PowerShell
}
else{
Write-Verbose "This sequence targets Windows Client versions"
$destinationFile= ($ENV:USERPROFILE) + "\Downloads\" + (split-path $link -leaf)
Write-Host "Downloading RSAT from $link..."
Start-BitsTransfer -Source $link -Destination $destinationFile
$fileCheck=Get-AuthenticodeSignature $destinationFile
if($fileCheck.status -ne "valid") {write-host "$destinationFile is not valid. Please try again...";break}
$wusaCommand = $destinationFile + " /quiet"
Write-host "Installing RSAT - please wait..."
Start-Process -FilePath "C:\Windows\System32\wusa.exe" -ArgumentList $wusaCommand -Wait
}
return $true
}
catch{
write-warning "$($error[0].Exception)"
return $false
}
}else{
Write-host "Prerequisite checks: module ActiveDirectory IS currently available on this system." -ForegroundColor Green
return $true
}
}
function listAllHyperVNodes($domainObjects){
try{
#if(!$domains){$domains=(Get-ADForest).Name|%{(Get-ADForest -Identity $_).Name}}
$allHyperVNodes=@()
foreach ($domainObject in $domainObjects){
#[string]$dc=(get-addomaincontroller -DomainName "$domain" -Discover -NextClosestSite).HostName
$domain=$domainObject.domain
$username=$domainObject.username
$password=$domainObject.password
$encryptedPassword=ConvertTo-securestring $password -AsPlainText -Force
$credential=New-Object -TypeName System.Management.Automation.PSCredential -Args $username,$encryptedPassword
$dc=if(!($domainObject.dc)){
$domainNode=.{try{get-addomain -Server $domain}catch{$false}}
if (!$domainNode){return $null}
if($domainNode.ReplicaDirectoryServers[1]){
$domainNode.ReplicaDirectoryServers[1]
}else{
$domainNode.PDCEmulator
}
}else{
$domainObject.dc
}
$session=.{
try{
new-pssession -computername $dc -credential $credential -EA Stop
write-host "Connected to $dc..."
}catch{
write-warning $_
return $false
}
}
if($session.state -eq 'Opened'){
write-host "Collecting all Hyper-V Clusters in $domain. This may take a while, depending on cluster sizes."
$allClusters=.{
$clusters=invoke-command -session $session -scriptblock{
$rsatClusteringPowershell=get-WindowsFeature RSAT-Clustering-PowerShell
if(!$rsatClusteringPowershell.Installed){Add-WindowsFeature RSAT-Clustering-PowerShell}
(get-cluster -domain $env:USERDNSDOMAIN).Name
}
return $clusters
}
Remove-PSSession $session
foreach ($cluster in $allClusters){
$clusterName="$cluster.$domain"
$retries=3
do{
$newSession=.{
try{
new-pssession -computername $clusterName -credential $credential -EA Stop
write-host "Connected to $clusterName..."
}catch{
return $_
}
}
$retries--
}until($newSession.state -eq 'Opened' -OR $retries -le 0)
if($newSession.state -eq 'Opened'){
write-host "Checking $clusterName"
try{
$nodes=try{
invoke-command -session $newSession -scriptblock{
param($clustername)
#$rsatClusteringPowershell=get-WindowsFeature RSAT-Clustering-PowerShell
#if(!$rsatClusteringPowershell.Installed){Add-WindowsFeature RSAT-Clustering-PowerShell}
$x=Get-ClusterNode -Cluster $clustername -ea SilentlyContinue
if($x){
$x|Where-Object{$_.State -eq 'Up'}|Select-Object Name,@{name='Cluster';e={"$clustername.$env:USERDNSDOMAIN"}}
}else{
$false
}
} -Args $cluster -EA Stop|select Name,Cluster
}catch{
write-warning $_
}
if($nodes){
$allHyperVNodes+=$nodes
}else{
write-warning "$cluster is skipped as program is unable to gather node names"
}
}catch{
write-warning $_
}
remove-pssession $newSession
}else{
write-warning $newSession
write-warning "$env:computername cannot connect to $clustername as required to gather Hyper-V node names."
}
}
}else{
write-warning "$env:computername cannot connect to $dc as required to gather domain objects data."
}
}
return $allHyperVNodes
}catch{
Write-Error $_
return $false
}
}
try{
#$null=includeRSAT;
#$rsatClusteringPowershell=get-WindowsFeature RSAT-Clustering-PowerShell
#if(!$rsatClusteringPowershell.Installed){Add-WindowsFeature RSAT-Clustering-PowerShell}
$hyperVHosts=listAllHyperVNodes $domainObjects
$hyperVHostNames=$hyperVHosts|sort -property Cluster
return $hyperVHostNames
}catch{
Write-Error $_
return $false
}
}
function sortArrayStringAsNumbers([string[]]$names){
$hashTable=@{}
$maxLength=($names | Measure-Object -Maximum -Property Length).Maximum
foreach ($name in $names){
#[int]$x=.{[void]($name -match '(?:.(\d+))+$');$matches[1]}
#$x=.{[void]($name -match '(?:.(\d+)+)$');@($name.substring(0,$name.length-$matches[1].length),$matches[1])}
$originalName=$name
$x=.{
[void]($name -match '(?:.(\d+)+)\w{0,}$');
if($matches){
[int]$trailingNonDigits=([regex]::match($name,'\D+$').value).length
if($trailingNonDigits){
$name=$name.substring(0,$name.length-$trailingNonDigits)
}
return ($name.substring(0,$name.length-$matches[1].length))+$matches[1].PadLeft($maxLength,'0');
}else{
return $name+''.PadLeft($maxLength,'0');
}}
$hashTable.Add($originalName,$x)
}
$sorted=foreach($item in $hashTable.GetEnumerator() | Sort Value){$item.Name}
return $sorted
}
#write-host "Obtaining cluster names and associated hosts..."
$hyperVHostsInForest=getHyperVHosts $domainObjects
$sortedArray=@()
$clusters=$hyperVHostsInForest|Group-Object -Property Cluster
foreach($cluster in $clusters){
$clusterName=$cluster.Name
write-host $clusterName
$sortedHostnames=sortArrayStringAsNumbers $cluster.Group.Name
$sortedHostnames|%{$sortedArray+=New-Object -TypeName psobject -Property @{hostname=$_; cluster=$clusterName}}
}
return $sortedArray
}
function getQuickStats($computername=$env:computername){
$ErrorActionPreference='stop'
try{
# write-host "Checking $env:computername`:"
# Server Model
$biosRegKey="REGISTRY::HKEY_LOCAL_MACHINE\Hardware\Description\System\Bios"
$bios=Get-ItemProperty $biosRegKey
$model=$bios.SystemProductName
# RAM (IN GB)
write-host " system memory..." -NoNewline
$ramInfo=get-wmiobject win32_physicalmemory|Select-Object *
$ramInfo=get-CimInstance win32_physicalmemory|Select-Object *
$ramGb=.{$sum=0;$ramInfo.Capacity|%{ $sum += $_};return $sum/1GB}
# RAM Module size
#$ramModuleSize=($ramInfo.Capacity|select -unique)|%{$_/1GB}
$ramGroups=$ramInfo.Capacity|Group|select Count,@{name='ramSize';e={($_.Group|select -Unique)/1GB}}
$ramModuleSize=$ramGroups|%{"($($_.Count)) $($_.ramSize)"}
# Ram Speed (Mhz)
# $ramSpeed=$ramInfo.Speed|select -unique
$ramSpeedGroups=$ramInfo.Speed|Group|select Count,@{name='ramSize';e={$_.Group|select -Unique}}
$ramSpeed=$ramSpeedGroups|%{"($($_.Count)) $($_.ramSize)"}
# ramConfiguredClockSpeed
#$ramConfiguredSpeed=$ramInfo.ConfiguredClockSpeed|select -unique
$ramConfigSpeedGroups=$ramInfo.ConfiguredClockSpeed|Group|select Count,@{name='ramSize';e={$_.Group|select -Unique}}
$ramConfiguredSpeed=$ramConfigSpeedGroups|%{"($($_.Count)) $($_.ramSize)"}
# RAM in use
$osObject=get-wmiobject Win32_OperatingSystem
$os=$osObject.Caption
$ramPercentFree=$osObject.FreePhysicalMemory/$osObject.TotalVisibleMemorySize
$ramUsed=[math]::round($ramGb-($ramPercentFree*$ramGb),2)
# Slots filled
$ramSlotsFilled=($ramInfo|?{$_.Capacity}).count
# Slots available
$ramSlotsAvailable=$ramInfo.count
# Cores
write-host " system CPU..."
$cpu=Get-CimInstance Win32_processor
$cores=.{$total=0;$cpu.NumberOfCores|%{$total+=$_};return $total}
# Proc count
$sockets=$cpu.count
# Cpu Type
$cpuType=$cpu.Name|select -unique
# Clock speed
$cpuClock=($cpu.MaxClockSpeed|Measure-Object -Average).Average
$cpuBandwidth=$cpuClock*$cores
$cpuUtilizationPercent=($cpu|Measure-Object -property LoadPercentage -Average).Average
$cpuUtilization=($cpuUtilizationPercent/100)*$cpuBandwidth
#$cpuUtilizationPercent=((Get-WmiObject -Class Win32_PerfFormattedData_PerfOS_Processor).PercentProcessorTime|measure-object -Average).Average
# $cpuUtilizationPercent=.{#$rawValues=wmic cpu get loadpercentage
# $rawValues=$cpu.LoadPercentage
# $selectNumbers=$rawValues|?{$_ -match '\d+'}
# ($selectNumbers|Measure-Object -Average).Average
# }
# $cpuUtilizationPercent=(get-counter -Counter "\Processor(*)\% Processor Time" -SampleInterval 1 -MaxSamples 2 |`
# select -ExpandProperty countersamples | select -ExpandProperty cookedvalue | Measure-Object -Average).average
# $cpuUtilization=((Get-process|Select-Object CPU).CPU|Measure-Object -sum).Sum
# $cpuUtilizationPercent=($cpuUtilization/$cpuBandwidth)*100
# $cpuProperties=@(
# @{Name="processName"; Expression = {$_.name}},
# @{Name="cpuPercent"; Expression = {$_.PercentProcessorTime}},
# @{Name="MemoryGb"; Expression = {[Math]::Round(($_.workingSetPrivate / 1GB),2)}}
# )
# $totalCpuUsage=(Get-WmiObject -class Win32_PerfFormattedData_PerfProc_Process |
# Select-Object $cpuProperties)|?{$_.'processName' -eq '_Total'}
# Other methods not being used
#$cs = Get-WmiObject -class Win32_ComputerSystem
#$Sockets=$cs.numberofprocessors
#$Cores=$cs.numberoflogicalprocessors
# write-host " Hyper-V Guest VMs..." -NoNewline
# $guestVms = Get-VM|Where-Object {$_.State -eq 'Running'}
# $vmsCount = $guestVms.count
}catch{
write-warning $_
continue
}
return [PSCustomObject][ordered]@{
node=$computername
model=$model
cpuType=$cpuType
sockets=$sockets
cores=$cores
cpuBandwidth=$cpuBandwidth
cpuUtilization=[math]::round($cpuUtilization,2)
cpuUtilizationPercent=[math]::round($cpuUtilizationPercent,2)
ramGb=$ramGb
ramUsedGb=[math]::round($ramUsed,2)
ramUsedPercent=[math]::round(($ramUsed/$ramGb)*100,2)
ramModuleSizeGb=$ramModuleSize
ramSlotsAvailable=$ramSlotsAvailable
ramSlotsFilled=$ramSlotsFilled
ramSpeed=$ramSpeed
ramConfigSpeed=$ramConfiguredSpeed
#vmsCount=$vmsCount
os=$os
}
}
function checkSystem($computername=$env:computername,$credential,$retries=3){
do{
$session=try{
if($credential){
New-PSSession $computername -credential $credential
}else{
New-PSSession $computername
}
}catch{
$retries--
write-warning "There's a problem connecting to $computername ... Retrying $retries more times..."
$null
}
}while(!$session -or $retries -gt 0)
if($session.State -eq 'Opened'){
$quickStats=invoke-command -session $session -scriptblock{
param($getQuickStats)
[scriptblock]::create($getQuickStats).invoke()
} -Args ${function:getQuickStats}
Remove-PSSession $session
}else{
Write-Warning "$computername is not Reachable from $env:computername via WinRM"
$quickStats=$false
}
if($quickStats){
return $quickStats|Select-Object -Property * -ExcludeProperty PSComputerName,RunspaceId,PSShowComputerName
}else{
return [PSCustomObject]@{
computerName = $computername+' Unreachable'
}
}
}
function generateEmailContent{
param(
$arrayObjects,
$selectFields='node,model,cpuType,sockets,cores,cpuBandwidth,cpuUtilizationPercent,ramGb,ramUsedPercent',
$reportName,
$summary,
$css="
<style>
.h1 {
font-size: 18px;
height: 40px;
padding-top: 80px;
margin: auto;
text-align: center;
}
.h5 {
font-size: 22px;
text-align: center;
}
.th {text-align: center;}
.table {
padding:7px;
border:#4e95f4 1px solid;
background-color: white;
margin-left: auto;
margin-right: auto;
width: 100%
}
.colgroup {}
.th { background: #0046c3; color: #fff; padding: 5px 10px; }
.td { font-size: 11px; padding: 5px 20px; color: #000;
width: 1px;
white-space: pre;
}
.tr { background: #b8d1f3;}
.tr:nth-child(even) {
background: #dae5f4;
width: 1%;
white-space: nowrap
}
.tr:nth-child(odd) {
background: #b8d1f3;
width: 1%;
white-space: nowrap
}
</style>
"
)
$filteredArray=invoke-expression "`$arrayObjects|select-object $selectFields"
$report=$filteredArray|ConvertTo-Html -Fragment|Out-String
$reportHtml=$report -replace '\<(?<item>\w+)\>', '<${item} class=''${item}''>'
$emailContent='<html><head>'+$css+"</head><body><h1>$reportName</h1>`n<h5>$summary</h5>"+$reportHtml+'</body></html>'
return $emailContent
}
function generateHtmlFragment($arrayObjects,$selectFields,$reportName,$summary){
$filteredArray=invoke-expression "`$arrayObjects|select-object $selectFields"
$htmlFragment=$filteredArray|ConvertTo-Html -Fragment|Out-String
$customHtml=$htmlFragment -replace '\<(?<item>\w+)\>', '<${item} class=''${item}''>'
$customHtmlFragment="<h1>$reportName</h1>`n<h5>$summary</h5>"+$customHtml
return $customHtmlFragment
}
function packageHtmlEmailContent{
param(
$htmlBody,
$footer,
$css="
<style>
.h1 {
font-size: 18px;
height: 40px;
padding-top: 80px;
margin: auto;
text-align: center;
}
.h5 {
font-size: 22px;
text-align: center;
}
.th {text-align: center;}
.table {
padding:7px;
border:#4e95f4 1px solid;
background-color: white;
margin-left: auto;
margin-right: auto;
width: 100%
}
.colgroup {}
.th { background: #0046c3; color: #fff; padding: 5px 10px; }
.td { font-size: 11px; padding: 5px 20px; color: #000;
width: 1px;
white-space: pre;
}
.tr { background: #b8d1f3;}
.tr:nth-child(even) {
background: #dae5f4;
width: 1%;
white-space: nowrap
}
.tr:nth-child(odd) {
background: #b8d1f3;
width: 1%;
white-space: nowrap
}
</style>
"
)
$emailContent='<html><head>'+$css+"</head><body>$htmlBody"+$footer+'</body></html>'
return $emailContent
}
function emailReport{
param(
$emailFrom,
$emailTo,
$subject,
$emailContent,
$smtpRelayServer
)
Send-MailMessage -From $emailFrom `
-To $emailTo `
-Subject $subject `
-Body $emailContent `
-BodyAsHtml `
-SmtpServer $smtpRelayServer
}
function updateGoogleSheets{
param(
$spreadsheetID,
$sheetName=$env:USERDNSDOMAIN,
$reportCsv,
$certPath,
$certPassword='notasecret',
$iss,
$scope="https://www.googleapis.com/auth/spreadsheets https://www.googleapis.com/auth/drive https://www.googleapis.com/auth/drive.file"
)
# Set security protocol to TLS 1.2 to avoid TLS errors
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
Import-Module UMN-Google
# obtain token if necessary
$accessToken = Get-GOAuthTokenService -scope $scope -certPath $certPath -certPswd $certPassword -iss $iss
# Create new sheet, if not already exists
try{Add-GSheetSheet -accessToken $accessToken -sheetName $sheetName -spreadSheetID $spreadsheetID}catch{}
# Upload CSV data to Google Sheets with Set-GSheetData
$inputCsv = Import-Csv $reportCsv
$header = $inputCsv[0].psobject.properties.name|select -first 26 # Google default 26 columns max
$import = new-Object System.Collections.ArrayList
$import.Add($header) | Out-Null
$inputCsv | ForEach-Object {
$row=@(foreach($label in $header){$_."$($label)"})
$import.Add($row) | Out-Null
}
$import.Add(@()) | Out-Null
$import.Add(@( $summary -split '\r?\n'|%{$_.Trim('<br>$')}|out-string )) | Out-Null
$columnsCount=$header.count
$columnLetter=if($columnsCount -le 25){
[char](65+$columnsCount)
}else{
'Z'
}
$range="A1:$columnLetter$($import.Count)"
try{
Set-GSheetData -accessToken $accessToken `
-rangeA1 $range `
-sheetName $sheetName `
-spreadSheetID $spreadsheetID `
-values $import #-Debug -Verbose
return $true
}catch{
write-warning $_
return $false
}
}
$hyperVClusters=getAllHyperVClusters $domainObjects
$clusterGroups=$hyperVClusters|Group-Object -Property cluster
$emailFragments=''
foreach($group in $clusterGroups){
$clusterName=$group.Name
$domain=[regex]::matches($clusterName,'\.(.*)').captures.groups[1].value
$username=($domainObjects|?{$_.domain.toupper() -eq $domain.toupper()}).username
$password=($domainObjects|?{$_.domain.toupper() -eq $domain.toupper()}).password
$encryptedPassword=ConvertTo-securestring $password -AsPlainText -Force
$credential=New-Object -TypeName System.Management.Automation.PSCredential -Args $username,$encryptedPassword
write-host $clusterName
$timeStamp=(get-date).tostring()
$results=@()
$hyperVHosts=$group.Group.hostname
$hostCount=$hyperVHosts.Count
for ($i=0;$i -lt $hostCount;$i++){
$hostname="$($hyperVHosts[$i]).$domain"
write-host "Scanning $($i+1) of $hostCount`: $($hyperVHosts[$i])..."
$retries=3
do{
$result=checkSystem $hostname $credential
$retries--
if(!$result){
$nslookupTime=measure-command {nslookup $hostname}
write-warning "It has taken $([math]::round($nslookupTime.totalseconds,2)) seconds for $env:computername to lookup $hostname"
}
}until ($result -or $retries -eq 0)
if($result){$results+=$result}
}
$totalRam=.{$total=0;$results.ramGb|%{$total+=$_};$total}
$usedRam=.{$total=0;$results.ramUsedGb|%{$total+=$_};$total}
$usedRamPercent=[math]::round($usedRam/$totalRam*100,2)
$availableRam=[math]::round($totalRam-$usedRam,2)
$totalCpuBandwidth=.{$total=0;$results.cpuBandwidth|%{$total+=$_};$total}
$usedCpuBandwidth=.{$total=0;$results.cpuUtilization|%{$total+=$_};$total}
$usedCpuPercent=[math]::round($usedCpuBandwidth/$totalCpuBandwidth*100,2)
$summary="Data Timestamp: $timeStamp<br>
Hosts Count: $($hyperVHosts.count)<br>
Total CPU Bandwidth: $([math]::round($totalCpuBandwidth/1000)) Ghz<br>
Used CPU Percent: $usedCpuPercent %<br>
Total RAM: $totalRam GB<br>
Used RAM: $usedRam GB<br>
Used RAM Percent: $usedRamPercent %<br>
Available RAM: $availableRam GB<br>"
$reportName="Hyper-V Capacity Report for Cluster $clustername"
$emailFragment=generateHtmlFragment $results $selectFields $reportName $summary
$emailFragments+=$emailFragment
$reportCsvPath=join-path $workingDirectory "$clustername.csv"
$null=$results|export-csv $reportCsvPath -NoTypeInformation
updateGoogleSheets $spreadsheetID $clusterName $reportCsvPath $certPath $certPassword $iss $scope
}
$today=(get-date).DayOfWeek
if ($today -eq $emailDay){
$footer="<br>GoogleSheets URL: $spreadsheetUrl<br>"
$emailContent=packageHtmlEmailContent $emailFragments $footer
emailReport $emailFrom $emailTo $subject $emailContent $smtpRelayServer
}
Previous Version:
# hyperVCapacityReport.ps1
# Version: 0.1
# Report parameters
$workingDirectory='C:\scripts\hyperVReports'
$selectFields='node,model,os,cpuType,sockets,cores,ramGb,ramUsedPercent,vmsCount'
$domainObjects=@(
@{domain='kimconnect.com';dc='dc2.kimconnect.com';username='testAdmin';password='password'}
)
# Update this Google Sheets
$spreadsheetID='abcdefghijklmnopqstvwzyz1234567890'
$spreadsheetUrl='https://docs.google.com/spreadsheets/d/'+$spreadsheetID
# Google API Authozation
$scope = "https://www.googleapis.com/auth/spreadsheets https://www.googleapis.com/auth/drive https://www.googleapis.com/auth/drive.file"
$certPath = 'C:\scripts\googleSheets\googleApiCert12345.p12'
$iss = '[email protected]'
$certPassword = 'notasecret'
# Email parameters
$emailFrom='[email protected]'
$emailTo='[email protected]'
$subject='Hyper-V Hosts Capacity Report'
$smtpRelayServer='smtprelay.kimconnect.com'
$emailDay='Monday'
function getAllHyperVClusters($domainObjects){
function getHyperVHosts($domainObjects){
function includeRSAT{
$ErrorActionPreference='stop'
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
#$rsatWindows7x32='https://download.microsoft.com/download/4/F/7/4F71806A-1C56-4EF2-9B4F-9870C4CFD2EE/Windows6.1-KB958830-x86-RefreshPkg.msu'
$rsatWindows7x64='https://download.microsoft.com/download/4/F/7/4F71806A-1C56-4EF2-9B4F-9870C4CFD2EE/Windows6.1-KB958830-x64-RefreshPkg.msu'
$rsatWindows81='https://download.microsoft.com/download/1/8/E/18EA4843-C596-4542-9236-DE46F780806E/Windows8.1-KB2693643-x64.msu'
$rsat1709 = "https://download.microsoft.com/download/1/D/8/1D8B5022-5477-4B9A-8104-6A71FF9D98AB/WindowsTH-RSAT_WS_1709-x64.msu"
$rsat1803 = "https://download.microsoft.com/download/1/D/8/1D8B5022-5477-4B9A-8104-6A71FF9D98AB/WindowsTH-RSAT_WS_1803-x64.msu"
$rsatWs2016 = "https://download.microsoft.com/download/1/D/8/1D8B5022-5477-4B9A-8104-6A71FF9D98AB/WindowsTH-RSAT_WS2016-x64.msu"
# This command does not work on Windows 2012R2
#$releaseId=(Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion" -Name ReleaseId).ReleaseId
#Get-ItemProperty : Property ReleaseId does not exist at path HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows
#NT\CurrentVersion.
#At line:1 char:2
#+ (Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion" -Na ...
#+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# + CategoryInfo : InvalidArgument: (ReleaseId:String) [Get-ItemProperty], PSArgumentException
# + FullyQualifiedErrorId : System.Management.Automation.PSArgumentException,Microsoft.PowerShell.Commands.GetItemPropertyCommand
$releaseId=(Get-Item "HKLM:SOFTWARE\Microsoft\Windows NT\CurrentVersion").GetValue('ReleaseID')
$osVersion=[System.Environment]::OSVersion.Version
[double]$osVersionMajorMinor="$($osVersion.Major).$($osVersion.Minor)"
$osName=(Get-WmiObject Win32_OperatingSystem).Name
#$osType=switch ((Get-CimInstance -ClassName Win32_OperatingSystem).ProductType){
# 1 {'client'}
# 2 {'domaincontroller'}
# 3 {'memberserver'}
# }
$windowsVersion=(Get-CimInstance Win32_OperatingSystem).Version
switch ($releaseId){
1607{write-host 'Windows Server 2016 Release 1607 detected';$link=$rsatWs2016;break}
1709{write-host 'Windows Server 2016 Release 1709 detected';$link=$rsat1709;break}
1803{write-host 'Windows Server 2016 Release 1803 detected';$link=$rsat1803}
}
switch ($osVersionMajorMinor){
{$_ -eq 6.0}{write-host 'Windows Server 2008 or Windows Vista detected';$link=$rsat1709;break}
{$_ -eq 6.1}{write-host 'Windows Server 2008 R2 or Windows 7 detected';$link=$rsatWindows7x64;break}
{$_ -eq 6.2}{write-host 'Windows Server 2012 or Windows 8.1 detected';$link=$rsatWindows81;break}
{$_ -eq 6.3}{write-host 'Windows Server 2012 R2 detected';$link=$rsatWindows81}
}
if (!(Get-Module -ListAvailable -Name ActiveDirectory -EA SilentlyContinue)){
Write-host "Prerequisite checks: module ActiveDirectory NOT currently available on this system. Please wait while the program adds that plugin..."
try{
# If OS is Windows Server, then install RSAT using a different method
if ($osName -match "^Microsoft Windows Server") {
# This sequence has confirmed to be valid on Windows Server 2008 R2 and above
Write-Verbose "Importing Windows Feature: RSAT-AD-PowerShell"
Import-Module ServerManager
Add-WindowsFeature RSAT-AD-PowerShell
}
else{
Write-Verbose "This sequence targets Windows Client versions"
$destinationFile= ($ENV:USERPROFILE) + "\Downloads\" + (split-path $link -leaf)
Write-Host "Downloading RSAT from $link..."
Start-BitsTransfer -Source $link -Destination $destinationFile
$fileCheck=Get-AuthenticodeSignature $destinationFile
if($fileCheck.status -ne "valid") {write-host "$destinationFile is not valid. Please try again...";break}
$wusaCommand = $destinationFile + " /quiet"
Write-host "Installing RSAT - please wait..."
Start-Process -FilePath "C:\Windows\System32\wusa.exe" -ArgumentList $wusaCommand -Wait
}
return $true
}
catch{
write-warning "$($error[0].Exception)"
return $false
}
}else{
Write-host "Prerequisite checks: module ActiveDirectory IS currently available on this system." -ForegroundColor Green
return $true
}
}
function listAllHyperVNodes($domainObjects){
try{
#if(!$domains){$domains=(Get-ADForest).Name|%{(Get-ADForest -Identity $_).Name}}
$allHyperVNodes=@()
foreach ($domainObject in $domainObjects){
#[string]$dc=(get-addomaincontroller -DomainName "$domain" -Discover -NextClosestSite).HostName
$domain=$domainObject.domain
$username=$domainObject.username
$password=$domainObject.password
$encryptedPassword=ConvertTo-securestring $password -AsPlainText -Force
$credential=New-Object -TypeName System.Management.Automation.PSCredential -Args $username,$encryptedPassword
$dc=if(!($domainObject.dc)){
$domainNode=.{try{get-addomain -Server $domain}catch{$false}}
if (!$domainNode){return $null}
if($domainNode.ReplicaDirectoryServers[1]){
$domainNode.ReplicaDirectoryServers[1]
}else{
$domainNode.PDCEmulator
}
}else{
$domainObject.dc
}
$session=.{
try{
new-pssession -computername $dc -credential $credential -EA Stop
write-host "Connected to $dc..."
}catch{
write-warning $_
return $false
}
}
if($session){
write-host "Collecting all Hyper-V Clusters in $domain. This may take a while, depending on cluster sizes."
$allClusters=.{
$clusters=invoke-command -session $session -scriptblock{
$rsatClusteringPowershell=get-WindowsFeature RSAT-Clustering-PowerShell
if(!$rsatClusteringPowershell.Installed){Add-WindowsFeature RSAT-Clustering-PowerShell}
(get-cluster -domain $env:USERDNSDOMAIN).Name
}
return $clusters
}
foreach ($cluster in $allClusters){
write-host "Checking $cluster"
try{
$nodes=invoke-command -computername "$cluster.$domain" -credential $credential -scriptblock{
param($clustername)
#$rsatClusteringPowershell=get-WindowsFeature RSAT-Clustering-PowerShell
#if(!$rsatClusteringPowershell.Installed){Add-WindowsFeature RSAT-Clustering-PowerShell}
$x=Get-ClusterNode -Cluster $clustername -ea SilentlyContinue
if($x){
$x|Where-Object{$_.State -eq 'Up'}|Select-Object Name,@{name='Cluster';e={"$clustername.$env:USERDNSDOMAIN"}}
}else{
$false
}
} -Args $cluster -EA Stop|select Name,Cluster
if($nodes){$allHyperVNodes+=$nodes}
}catch{
write-warning "$cluster is skipped..."
}
}
Remove-PSSession $session
}else{
write-warning "$env:computername cannot connect to $dc..."
}
}
return $allHyperVNodes
}catch{
Write-Error $_
return $false
}
}
try{
#$null=includeRSAT;
#$rsatClusteringPowershell=get-WindowsFeature RSAT-Clustering-PowerShell
#if(!$rsatClusteringPowershell.Installed){Add-WindowsFeature RSAT-Clustering-PowerShell}
$hyperVHosts=listAllHyperVNodes $domainObjects
$hyperVHostNames=$hyperVHosts|sort -property Cluster
return $hyperVHostNames
}catch{
Write-Error $_
return $false
}
}
function sortArrayStringAsNumbers([string[]]$names){
$hashTable=@{}
$maxLength=($names | Measure-Object -Maximum -Property Length).Maximum
foreach ($name in $names){
#[int]$x=.{[void]($name -match '(?:.(\d+))+$');$matches[1]}
#$x=.{[void]($name -match '(?:.(\d+)+)$');@($name.substring(0,$name.length-$matches[1].length),$matches[1])}
$originalName=$name
$x=.{
[void]($name -match '(?:.(\d+)+)\w{0,}$');
if($matches){
[int]$trailingNonDigits=([regex]::match($name,'\D+$').value).length
if($trailingNonDigits){
$name=$name.substring(0,$name.length-$trailingNonDigits)
}
return ($name.substring(0,$name.length-$matches[1].length))+$matches[1].PadLeft($maxLength,'0');
}else{
return $name+''.PadLeft($maxLength,'0');
}}
$hashTable.Add($originalName,$x)
}
$sorted=foreach($item in $hashTable.GetEnumerator() | Sort Value){$item.Name}
return $sorted
}
#write-host "Obtaining cluster names and associated hosts..."
$hyperVHostsInForest=getHyperVHosts $domainObjects
$sortedArray=@()
$clusters=$hyperVHostsInForest|Group-Object -Property Cluster
foreach($cluster in $clusters){
$clusterName=$cluster.Name
write-host $clusterName
$sortedHostnames=sortArrayStringAsNumbers $cluster.Group.Name
$sortedHostnames|%{$sortedArray+=New-Object -TypeName psobject -Property @{hostname=$_; cluster=$clusterName}}
}
return $sortedArray
}
function getQuickStats($computername=$env:computername){
try{
# Server Model
$biosRegKey="REGISTRY::HKEY_LOCAL_MACHINE\Hardware\Description\System\Bios"
$bios=Get-ItemProperty $biosRegKey
$model=$bios.SystemProductName
# RAM (IN GB)
$ramInfo=get-wmiobject win32_physicalmemory|Select-Object *
$ramInfo=get-CimInstance win32_physicalmemory|Select-Object *
$ramGb=.{$sum=0;$ramInfo.Capacity|%{ $sum += $_};return $sum/1GB}
# RAM Module size
#$ramModuleSize=($ramInfo.Capacity|select -unique)|%{$_/1GB}
$ramGroups=$ramInfo.Capacity|Group|select Count,@{name='ramSize';e={($_.Group|select -Unique)/1GB}}
$ramModuleSize=$ramGroups|%{"($($_.Count)) $($_.ramSize)"}
# Ram Speed (Mhz)
# $ramSpeed=$ramInfo.Speed|select -unique
$ramSpeedGroups=$ramInfo.Speed|Group|select Count,@{name='ramSize';e={$_.Group|select -Unique}}
$ramSpeed=$ramSpeedGroups|%{"($($_.Count)) $($_.ramSize)"}
# ramConfiguredClockSpeed
#$ramConfiguredSpeed=$ramInfo.ConfiguredClockSpeed|select -unique
$ramConfigSpeedGroups=$ramInfo.ConfiguredClockSpeed|Group|select Count,@{name='ramSize';e={$_.Group|select -Unique}}
$ramConfiguredSpeed=$ramConfigSpeedGroups|%{"($($_.Count)) $($_.ramSize)"}
# RAM in use
$osObject=get-wmiobject Win32_OperatingSystem
$os=$osObject.Caption
$ramPercentFree=$osObject.FreePhysicalMemory/$osObject.TotalVisibleMemorySize
$ramUsed=[math]::round($ramGb-($ramPercentFree*$ramGb),2)
# Slots filled
$ramSlotsFilled=($ramInfo|?{$_.Capacity}).count
# Slots available
$ramSlotsAvailable=$ramInfo.count
# Cores
$cpu=Get-CimInstance Win32_processor
$cores=.{$total=0;$cpu.NumberOfCores|%{$total+=$_};return $total}
# Proc count
$sockets=$cpu.count
# Cpu Type
$cpuType=$cpu.Name|select -unique
# Clock speed
$cpuClock=($cpu.MaxClockSpeed|Measure-Object -Average).Average
$cpuBandwidth=$cpuClock*$cores
$cpuUtilizationPercent=($cpu|Measure-Object -property LoadPercentage -Average).Average
$cpuUtilization=($cpuUtilizationPercent/100)*$cpuBandwidth
#$cpuUtilizationPercent=((Get-WmiObject -Class Win32_PerfFormattedData_PerfOS_Processor).PercentProcessorTime|measure-object -Average).Average
# $cpuUtilizationPercent=.{#$rawValues=wmic cpu get loadpercentage
# $rawValues=$cpu.LoadPercentage
# $selectNumbers=$rawValues|?{$_ -match '\d+'}
# ($selectNumbers|Measure-Object -Average).Average
# }
# $cpuUtilizationPercent=(get-counter -Counter "\Processor(*)\% Processor Time" -SampleInterval 1 -MaxSamples 2 |`
# select -ExpandProperty countersamples | select -ExpandProperty cookedvalue | Measure-Object -Average).average
# $cpuUtilization=((Get-process|Select-Object CPU).CPU|Measure-Object -sum).Sum
# $cpuUtilizationPercent=($cpuUtilization/$cpuBandwidth)*100
# $cpuProperties=@(
# @{Name="processName"; Expression = {$_.name}},
# @{Name="cpuPercent"; Expression = {$_.PercentProcessorTime}},
# @{Name="MemoryGb"; Expression = {[Math]::Round(($_.workingSetPrivate / 1GB),2)}}
# )
# $totalCpuUsage=(Get-WmiObject -class Win32_PerfFormattedData_PerfProc_Process |
# Select-Object $cpuProperties)|?{$_.'processName' -eq '_Total'}
# Other methods not being used
#$cs = Get-WmiObject -class Win32_ComputerSystem
#$Sockets=$cs.numberofprocessors
#$Cores=$cs.numberoflogicalprocessors
$guestVms = Get-VM|Where-Object {$_.State -eq 'Running'}
$vmsCount = $guestVms.count
}catch{}
return [PSCustomObject][ordered]@{
node=$computername
model=$model
cpuType=$cpuType
sockets=$sockets
cores=$cores
cpuBandwidth=$cpuBandwidth
cpuUtilization=[math]::round($cpuUtilization,2)
cpuUtilizationPercent=[math]::round($cpuUtilizationPercent,2)
ramGb=$ramGb
ramUsedGb=[math]::round($ramUsed,2)
ramUsedPercent=[math]::round(($ramUsed/$ramGb)*100,2)
ramModuleSizeGb=$ramModuleSize
ramSlotsAvailable=$ramSlotsAvailable
ramSlotsFilled=$ramSlotsFilled
ramSpeed=$ramSpeed
ramConfigSpeed=$ramConfiguredSpeed
vmsCount=$vmsCount
os=$os
}
}
function checkSystem($computername=$env:computername,$credential){
$session=if($credential){
New-PSSession $computername -credential $credential
}else{
New-PSSession $computername
}
if($session.State -eq 'Opened'){
$quickStats=invoke-command -session $session -scriptblock{
param($getQuickStats)
[scriptblock]::create($getQuickStats).invoke()
} -Args ${function:getQuickStats}
Remove-PSSession $session
}else{
Write-Warning "$computername is not Reachable from $env:computername via WinRM"
$quickStats=$false
}
if($quickStats){
return $quickStats|Select-Object -Property * -ExcludeProperty PSComputerName,RunspaceId,PSShowComputerName
}else{
return [PSCustomObject]@{
computerName = $computername+' Unreachable'
}
}
}
function generateEmailContent{
param(
$arrayObjects,
$selectFields='node,model,cpuType,sockets,cores,cpuBandwidth,cpuUtilizationPercent,ramGb,ramUsedPercent',
$reportName,
$summary,
$css="
<style>
.h1 {
font-size: 18px;
height: 40px;
padding-top: 80px;
margin: auto;
text-align: center;
}
.h5 {
font-size: 22px;
text-align: center;
}
.th {text-align: center;}
.table {
padding:7px;
border:#4e95f4 1px solid;
background-color: white;
margin-left: auto;
margin-right: auto;
width: 100%
}
.colgroup {}
.th { background: #0046c3; color: #fff; padding: 5px 10px; }
.td { font-size: 11px; padding: 5px 20px; color: #000;
width: 1px;
white-space: pre;
}
.tr { background: #b8d1f3;}
.tr:nth-child(even) {
background: #dae5f4;
width: 1%;
white-space: nowrap
}
.tr:nth-child(odd) {
background: #b8d1f3;
width: 1%;
white-space: nowrap
}
</style>
"
)
$filteredArray=invoke-expression "`$arrayObjects|select-object $selectFields"
$report=$filteredArray|ConvertTo-Html -Fragment|Out-String
$reportHtml=$report -replace '\<(?<item>\w+)\>', '<${item} class=''${item}''>'
$emailContent='<html><head>'+$css+"</head><body><h1>$reportName</h1>`n<h5>$summary</h5>"+$reportHtml+'</body></html>'
return $emailContent
}
function generateHtmlFragment($arrayObjects,$selectFields,$reportName,$summary){
$filteredArray=invoke-expression "`$arrayObjects|select-object $selectFields"
$htmlFragment=$filteredArray|ConvertTo-Html -Fragment|Out-String
$customHtml=$htmlFragment -replace '\<(?<item>\w+)\>', '<${item} class=''${item}''>'
$customHtmlFragment="<h1>$reportName</h1>`n<h5>$summary</h5>"+$customHtml
return $customHtmlFragment
}
function packageHtmlEmailContent{
param(
$htmlBody,
$footer,
$css="
<style>
.h1 {
font-size: 18px;
height: 40px;
padding-top: 80px;
margin: auto;
text-align: center;
}
.h5 {
font-size: 22px;
text-align: center;
}
.th {text-align: center;}
.table {
padding:7px;
border:#4e95f4 1px solid;
background-color: white;
margin-left: auto;
margin-right: auto;
width: 100%
}
.colgroup {}
.th { background: #0046c3; color: #fff; padding: 5px 10px; }
.td { font-size: 11px; padding: 5px 20px; color: #000;
width: 1px;
white-space: pre;
}
.tr { background: #b8d1f3;}
.tr:nth-child(even) {
background: #dae5f4;
width: 1%;
white-space: nowrap
}
.tr:nth-child(odd) {
background: #b8d1f3;
width: 1%;
white-space: nowrap
}
</style>
"
)
$emailContent='<html><head>'+$css+"</head><body>$htmlBody"+$footer+'</body></html>'
return $emailContent
}
function emailReport{
param(
$emailFrom,
$emailTo,
$subject,
$emailContent,
$smtpRelayServer
)
Send-MailMessage -From $emailFrom `
-To $emailTo `
-Subject $subject `
-Body $emailContent `
-BodyAsHtml `
-SmtpServer $smtpRelayServer
}
function updateGoogleSheets{
param(
$spreadsheetID,
$sheetName=$env:USERDNSDOMAIN,
$reportCsv,
$certPath,
$certPassword='notasecret',
$iss,
$scope="https://www.googleapis.com/auth/spreadsheets https://www.googleapis.com/auth/drive https://www.googleapis.com/auth/drive.file"
)
# Set security protocol to TLS 1.2 to avoid TLS errors
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
Import-Module UMN-Google
# obtain token if necessary
$accessToken = Get-GOAuthTokenService -scope $scope -certPath $certPath -certPswd $certPassword -iss $iss
# Create new sheet, if not already exists
try{Add-GSheetSheet -accessToken $accessToken -sheetName $sheetName -spreadSheetID $spreadsheetID}catch{}
# Upload CSV data to Google Sheets with Set-GSheetData
$inputCsv = Import-Csv $reportCsv
$header = $inputCsv[0].psobject.properties.name|select -first 26 # Google default 26 columns max
$import = new-Object System.Collections.ArrayList
$import.Add($header) | Out-Null
$inputCsv | ForEach-Object {
$row=@(foreach($label in $header){$_."$($label)"})
$import.Add($row) | Out-Null
}
$import.Add(@()) | Out-Null
$import.Add(@( $summary -split '\r?\n'|%{$_.Trim('<br>$')}|out-string )) | Out-Null
$columnsCount=$header.count
$columnLetter=if($columnsCount -le 25){
[char](65+$columnsCount)
}else{
'Z'
}
$range="A1:$columnLetter$($import.Count)"
try{
$null=Clear-GSheetSheet -spreadSheetID $spreadsheetID -SheetName $sheetName -accessToken $accessToken
$null=Set-GSheetData -accessToken $accessToken `
-rangeA1 $range `
-sheetName $sheetName `
-spreadSheetID $spreadsheetID `
-values $import #-Debug -Verbose
write-host "Google Sheet $sheetName range $range has been updated."
return $true
}catch{
write-warning $_
return $false
}
}
$hyperVClusters=getAllHyperVClusters $domainObjects
$clusterGroups=$hyperVClusters|Group-Object -Property cluster
$emailFragments=''
foreach($group in $clusterGroups){
$clusterName=$group.Name
$domain=[regex]::matches($clusterName,'\.(.*)').captures.groups[1].value
$username=($domainObjects|?{$_.domain.toupper() -eq $domain.toupper()}).username
$password=($domainObjects|?{$_.domain.toupper() -eq $domain.toupper()}).password
$encryptedPassword=ConvertTo-securestring $password -AsPlainText -Force
$credential=New-Object -TypeName System.Management.Automation.PSCredential -Args $username,$encryptedPassword
write-host $clusterName
$timeStamp=(get-date).tostring()
$results=@()
$hyperVHosts=$group.Group.hostname
$hostCount=$hyperVHosts.Count
for ($i=0;$i -lt $hostCount;$i++){
write-host "Scanning $($i+1) of $hostCount`: $($hyperVHosts[$i])..."
$results+=checkSystem "$($hyperVHosts[$i]).$domain" $credential
}
$totalRam=.{$total=0;$results.ramGb|%{$total+=$_};$total}
$usedRam=.{$total=0;$results.ramUsedGb|%{$total+=$_};$total}
$usedRamPercent=[math]::round($usedRam/$totalRam*100,2)
$availableRam=[math]::round($totalRam-$usedRam,2)
$totalCpuBandwidth=.{$total=0;$results.cpuBandwidth|%{$total+=$_};$total}
$usedCpuBandwidth=.{$total=0;$results.cpuUtilization|%{$total+=$_};$total}
$usedCpuPercent=[math]::round($usedCpuBandwidth/$totalCpuBandwidth*100,2)
$summary="Data Timestamp: $timeStamp<br>
Hosts Count: $($hyperVHosts.count)<br>
Total CPU Bandwidth: $([math]::round($totalCpuBandwidth/1000)) Ghz<br>
Used CPU Percent: $usedCpuPercent %<br>
Total RAM: $totalRam GB<br>
Used RAM: $usedRam GB<br>
Used RAM Percent: $usedRamPercent %<br>
Available RAM: $availableRam GB<br>"
$reportName="Hyper-V Capacity Report for Cluster $clustername"
$emailFragment=generateHtmlFragment $results $selectFields $reportName $summary
$emailFragments+=$emailFragment
$reportCsvPath=join-path $workingDirectory "$clustername.csv"
$null=$results|export-csv $reportCsvPath -NoTypeInformation
updateGoogleSheets $spreadsheetID $clusterName $reportCsvPath $certPath $certPassword $iss $scope
}
$today=(get-date).DayOfWeek
if ($today -eq $emailDay){
$footer="<br>GoogleSheets URL: $spreadsheetUrl<br>"
$emailContent=packageHtmlEmailContent $emailFragments $footer
emailReport $emailFrom $emailTo $subject $emailContent $smtpRelayServer
}
Categories: