<# .SYNOPSIS Daily VMware Cluster metadata report for PowerBI trending and capacity planning. .DESCRIPTION Collects cluster-level metadata from vCenter including DRS/HA configuration, aggregate compute capacity, and effective resource availability. Exports a timestamped CSV and inserts into SQL each run. Append daily runs to build a historical dataset for PowerBI trend analysis. .NOTES Run the following DDL once to create the destination table: DROP TABLE IF EXISTS [dbo].[VMware_Trends_Cluster] CREATE TABLE [dbo].[VMware_Trends_Cluster] ( [ReportDate] DATETIME2 NOT NULL, [ClusterName] NVARCHAR(255) NOT NULL, [Datacenter] NVARCHAR(100) NULL, [DrsEnabled] BIT NOT NULL, [DrsMode] NVARCHAR(50) NULL, [HaEnabled] BIT NOT NULL, [HostCount] INT NULL, [EffectiveHostCount] INT NULL, [TotalCpuCores] INT NULL, [TotalCpuMhz] INT NULL, [EffectiveCpuMhz] INT NULL, [TotalMemoryGB] DECIMAL(10,2) NULL, [EffectiveMemoryGB] DECIMAL(10,2) NULL, [UsedMemoryGB] DECIMAL(10,2) NULL, [VMCount] INT NULL ) TotalCpuMhz, EffectiveCpuMhz, TotalMemoryGB, and EffectiveMemoryGB come from vCenter's cluster summary (real-time aggregate). UsedMemoryGB is summed from each host's current MemoryUsageGB. TotalCpuCores is summed from each host's hardware CpuInfo. #> [CmdletBinding()] param( ) Connect-ITDvCenter -Credential $Secret:ndgov_svcitdvmvcro #region --- Setup --------------------------------------------------------------- [string] $OutputPath = 'C:\temp\VM_Trends\' [string] $ServerInstance = 'itdintsql22p1.nd.gov\INTSQL22P1' [string] $Database = 'ITD-Systems-Automation' [string] $Table = 'VMware_Trends_Cluster' [System.Management.Automation.PSCredential] $SqlCredential = $Secret:sql_itdpsu1 $RunDate = Get-Date $DateStamp = $RunDate.ToString('yyyyMMdd') $Timestamp = $RunDate.ToString('yyyy-MM-dd') if (-not (Test-Path -Path $OutputPath)) { New-Item -ItemType Directory -Path $OutputPath | Out-Null } Start-Transcript -Path (Join-Path $OutputPath "ClusterMetadataReport_$DateStamp.log") -Append #endregion #region --- Build Cluster -> Datacenter Lookup --------------------------------- Write-Verbose 'Building cluster-to-datacenter map...' $ClusterDatacenterMap = @{} Get-Datacenter | ForEach-Object { $DatacenterName = $_.Name Get-Cluster -Location $_ | ForEach-Object { $ClusterDatacenterMap[$_.Name] = $DatacenterName } } #endregion #region --- Collect Cluster Data ----------------------------------------------- Write-Verbose 'Gathering clusters...' $AllClusters = Get-Cluster Write-Verbose "Processing $($AllClusters.Count) clusters..." $Results = foreach ($Cluster in $AllClusters) { $Summary = $Cluster.ExtensionData.Summary # reuse for all summary fields #--- Aggregate host-level stats that vCenter summary does not expose $ClusterHosts = Get-VMHost -Location $Cluster $TotalCpuCores = ($ClusterHosts | ForEach-Object { $_.ExtensionData.Hardware.CpuInfo.NumCpuCores } | Measure-Object -Sum).Sum $UsedMemoryGB = [Math]::Round( ($ClusterHosts | Measure-Object -Property MemoryUsageGB -Sum).Sum, 2 ) $VMCount = ($ClusterHosts | ForEach-Object { ($_.ExtensionData.Vm | Measure-Object).Count } | Measure-Object -Sum).Sum #--- Capacity from cluster summary $TotalCpuMhz = $Summary.TotalCpu # MHz $EffectiveCpuMhz = $Summary.EffectiveCpu # MHz $TotalMemoryGB = [Math]::Round($Summary.TotalMemory / 1GB, 2) # bytes -> GB $EffectiveMemGB = [Math]::Round($Summary.EffectiveMemory / 1KB, 2) # MB -> GB [PSCustomObject]@{ ReportDate = $Timestamp ClusterName = $Cluster.Name Datacenter = $ClusterDatacenterMap[$Cluster.Name] DrsEnabled = $Cluster.DrsEnabled DrsMode = $Cluster.DrsAutomationLevel HaEnabled = $Cluster.HAEnabled HostCount = $Summary.NumHosts EffectiveHostCount = $Summary.NumEffectiveHosts TotalCpuCores = [int]$TotalCpuCores TotalCpuMhz = $TotalCpuMhz EffectiveCpuMhz = $EffectiveCpuMhz TotalMemoryGB = $TotalMemoryGB EffectiveMemoryGB = $EffectiveMemGB UsedMemoryGB = $UsedMemoryGB VMCount = [int]$VMCount } } #endregion #region --- Export CSV --------------------------------------------------------- $OutputFile = Join-Path $OutputPath "ClusterMetadata_$DateStamp.csv" $Results | Export-Csv -Path $OutputFile -NoTypeInformation Write-Verbose "Exported $($Results.Count) cluster records to: $OutputFile" #endregion #region --- SQL Insert --------------------------------------------------------- $DataTable = [System.Data.DataTable]::new() $ColDefs = [ordered]@{ ReportDate = [datetime] ClusterName = [string] Datacenter = [string] DrsEnabled = [bool] DrsMode = [string] HaEnabled = [bool] HostCount = [int] EffectiveHostCount = [int] TotalCpuCores = [int] TotalCpuMhz = [int] EffectiveCpuMhz = [int] TotalMemoryGB = [decimal] EffectiveMemoryGB = [decimal] UsedMemoryGB = [decimal] VMCount = [int] } foreach ($Col in $ColDefs.GetEnumerator()) { $Column = [System.Data.DataColumn]::new($Col.Key, $Col.Value) $Column.AllowDBNull = $true [void]$DataTable.Columns.Add($Column) } foreach ($Row in $Results) { $DataRow = $DataTable.NewRow() foreach ($Col in $ColDefs.Keys) { $Val = $Row.$Col $DataRow[$Col] = if ($null -ne $Val) { $Val } else { [DBNull]::Value } } [void]$DataTable.Rows.Add($DataRow) } $SqlParams = @{ ServerInstance = $ServerInstance DatabaseName = $Database SchemaName = 'dbo' TableName = $Table Credential = $SqlCredential InputData = $DataTable } Write-SqlTableData @SqlParams Write-Verbose "Inserted $($DataTable.Rows.Count) cluster records into [$Database].[dbo].[$Table]" #endregion Disconnect-ITDvCenter #region --- Cleanup ------------------------------------------------------------- Stop-Transcript #endregion