<# .SYNOPSIS Daily VMware Host metadata report for PowerBI trending and hardware capacity planning. .DESCRIPTION Collects ESXi host metadata from vCenter including hardware, compute capacity, and version information. Exports a timestamped CSV and inserts into SQL each run. Append daily runs to build a historical dataset for PowerBI trend analysis and physical hardware purchasing / lifecycle decisions. .NOTES Run the following DDL once to create the destination table: DROP TABLE IF EXISTS [dbo].[VMware_Trends_Host] CREATE TABLE [dbo].[VMware_Trends_Host] ( [ReportDate] DATETIME2 NOT NULL, [HostName] NVARCHAR(255) NOT NULL, [Datacenter] NVARCHAR(100) NULL, [Cluster] NVARCHAR(100) NULL, [ConnectionState] NVARCHAR(50) NULL, [PowerState] NVARCHAR(50) NULL, [InMaintenanceMode] BIT NOT NULL, [Model] NVARCHAR(255) NULL, [ProcessorType] NVARCHAR(255) NULL, [CpuSockets] INT NULL, [CoresPerSocket] INT NULL, [TotalCpuCores] INT NULL, [CpuMhz] INT NULL, [TotalCpuMhz] INT NULL, [MemoryTotalGB] DECIMAL(10,2) NULL, [MemoryUsageGB] DECIMAL(10,2) NULL, [VMCount] INT NULL, [EsxiVersion] NVARCHAR(50) NULL, [EsxiBuild] NVARCHAR(50) NULL, [UptimeDays] DECIMAL(10,2) NULL ) MemoryUsageGB reflects real-time memory consumed by running VMs at the time of collection. UptimeDays will be NULL for hosts that are powered off or disconnected. #> [CmdletBinding()] param( ) #region --- Setup --------------------------------------------------------------- [string] $OutputPath = 'C:\temp\VM_Trends\' [string] $ServerInstance = 'itdintsql22p1.nd.gov\INTSQL22P1' [string] $Database = 'ITD-Systems-Automation' [string] $Table = 'VMware_Trends_Host' [System.Management.Automation.PSCredential] $SqlCredential = $SqlCred #$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 "VMHostMetadataReport_$DateStamp.log") -Append #endregion #region --- Build VMHost -> Cluster/Datacenter Lookups (avoids per-host queries) - Write-Verbose 'Building host-to-cluster and host-to-datacenter maps...' $HostClusterMap = @{} $HostDatacenterMap = @{} Get-Datacenter | ForEach-Object { $DatacenterName = $_.Name Get-VMHost -Location $_ | ForEach-Object { $HostDatacenterMap[$_.Name] = $DatacenterName } } Get-Cluster | ForEach-Object { $ClusterName = $_.Name Get-VMHost -Location $_ | ForEach-Object { $HostClusterMap[$_.Name] = $ClusterName } } #endregion #region --- Collect Host Data -------------------------------------------------- Write-Verbose 'Gathering hosts...' $AllHosts = $HostDatacenterMap.Keys | ForEach-Object { Get-VMHost -Name $_ } Write-Verbose "Processing $($AllHosts.Count) hosts..." $Results = foreach ($VMHost in $AllHosts) { $Ext = $VMHost.ExtensionData # single API object -- reuse for all fields #--- CPU info from hardware summary $CpuSockets = $Ext.Hardware.CpuInfo.NumCpuPackages $TotalCpuCores = $Ext.Hardware.CpuInfo.NumCpuCores $CoresPerSocket = if ($CpuSockets -gt 0) { [int]($TotalCpuCores / $CpuSockets) } else { $null } $CpuMhz = $Ext.Hardware.CpuInfo.Hz / 1000000 # Hz -> MHz $TotalCpuMhz = $Ext.Summary.Hardware.CpuMhz * $TotalCpuCores #--- Memory $MemoryTotalGB = [Math]::Round($VMHost.MemoryTotalGB, 2) $MemoryUsageGB = [Math]::Round($VMHost.MemoryUsageGB, 2) #--- VM count on this host right now $VMCount = ($Ext.Vm | Measure-Object).Count #--- Uptime (null when host is powered off / disconnected) $UptimeDays = $null $BootTime = $Ext.Runtime.BootTime if ($null -ne $BootTime) { $UptimeDays = [Math]::Round(($RunDate - $BootTime).TotalDays, 2) } [PSCustomObject]@{ ReportDate = $Timestamp HostName = $VMHost.Name Datacenter = $HostDatacenterMap[$VMHost.Name] Cluster = $HostClusterMap[$VMHost.Name] ConnectionState = $VMHost.ConnectionState PowerState = $VMHost.PowerState InMaintenanceMode = $VMHost.ExtensionData.Runtime.InMaintenanceMode Model = $Ext.Hardware.SystemInfo.Model ProcessorType = $VMHost.ProcessorType CpuSockets = $CpuSockets CoresPerSocket = $CoresPerSocket TotalCpuCores = $TotalCpuCores CpuMhz = [int]$CpuMhz TotalCpuMhz = $TotalCpuMhz MemoryTotalGB = $MemoryTotalGB MemoryUsageGB = $MemoryUsageGB VMCount = $VMCount EsxiVersion = $VMHost.Version EsxiBuild = $VMHost.Build UptimeDays = $UptimeDays } } #endregion #region --- Export CSV --------------------------------------------------------- $OutputFile = Join-Path $OutputPath "VMHostMetadata_$DateStamp.csv" $Results | Export-Csv -Path $OutputFile -NoTypeInformation Write-Verbose "Exported $($Results.Count) host records to: $OutputFile" #endregion #region --- SQL Insert --------------------------------------------------------- $DataTable = [System.Data.DataTable]::new() $ColDefs = [ordered]@{ ReportDate = [datetime] HostName = [string] Datacenter = [string] Cluster = [string] ConnectionState = [string] PowerState = [string] InMaintenanceMode = [bool] Model = [string] ProcessorType = [string] CpuSockets = [int] CoresPerSocket = [int] TotalCpuCores = [int] CpuMhz = [int] TotalCpuMhz = [int] MemoryTotalGB = [decimal] MemoryUsageGB = [decimal] VMCount = [int] EsxiVersion = [string] EsxiBuild = [string] UptimeDays = [decimal] } 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) host records into [$Database].[dbo].[$Table]" #endregion #region --- Cleanup ------------------------------------------------------------- Stop-Transcript #endregion