Private/Get-VMMetadata.ps1
|
function Get-VMMetadata { <# .SYNOPSIS Collects all relevant metadata from a VMware VM into a normalized object. .DESCRIPTION Pulls VM properties including OS information, hardware configuration, VMware Tools status, network adapters, datastores, snapshot information, creation date, power state, notes, and custom attributes. Returns a normalized PSCustomObject for use by Sync-VMTags, Get-VMCompliance, and other module functions. This function expects a VM object from Get-VM (VMware.VimAutomation.ViCore). It makes additional API calls to retrieve snapshots, events (for creation date), and extended properties. .PARAMETER VM A VMware VM object from Get-VM. .PARAMETER IncludeCreationDate If specified, queries vCenter events to determine the VM creation date. This can be slow on large environments; omit for faster metadata collection. .EXAMPLE $vm = Get-VM -Name "WebServer01" $meta = Get-VMMetadata -VM $vm $meta.NumCPU .EXAMPLE Get-VM | ForEach-Object { Get-VMMetadata -VM $_ -IncludeCreationDate } .NOTES Author: Larry Roberts Part of the VM-AutoTagger module. #> [CmdletBinding()] param( [Parameter(Mandatory, ValueFromPipeline)] [object]$VM, [Parameter()] [switch]$IncludeCreationDate ) process { Write-Verbose "Collecting metadata for VM: $($VM.Name)" # --- Basic properties --- $vmName = $VM.Name $powerState = [string]$VM.PowerState $numCPU = [int]$VM.NumCpu $memoryGB = [double]$VM.MemoryGB $notes = if ($VM.Notes) { $VM.Notes } else { '' } $vmId = if ($VM.Id) { $VM.Id } else { '' } # --- Guest OS info --- $guestFamily = '' $guestFullName = '' $guestId = '' if ($VM.Guest) { $guestFamily = if ($VM.Guest.GuestFamily) { [string]$VM.Guest.GuestFamily } else { '' } $guestFullName = if ($VM.Guest.GuestFullName) { [string]$VM.Guest.GuestFullName } else { '' } } # Fall back to config-level GuestId for family detection if ($VM.ExtensionData -and $VM.ExtensionData.Config) { $guestId = if ($VM.ExtensionData.Config.GuestId) { [string]$VM.ExtensionData.Config.GuestId } else { '' } if ([string]::IsNullOrWhiteSpace($guestFullName) -and $VM.ExtensionData.Config.GuestFullName) { $guestFullName = [string]$VM.ExtensionData.Config.GuestFullName } } # If guest family is still empty, derive from GuestId if ([string]::IsNullOrWhiteSpace($guestFamily) -and -not [string]::IsNullOrWhiteSpace($guestId)) { if ($guestId -match 'windows') { $guestFamily = 'windowsGuest' } elseif ($guestId -match 'linux|ubuntu|centos|rhel|debian|sles|oracle|photon|amazon') { $guestFamily = 'linuxGuest' } elseif ($guestId -match 'darwin') { $guestFamily = 'darwinGuest' } elseif ($guestId -match 'freebsd') { $guestFamily = 'freebsdGuest' } } # --- VMware Tools --- $toolsStatus = 'toolsNotInstalled' $toolsVersion = '' if ($VM.ExtensionData -and $VM.ExtensionData.Guest) { $toolsStatus = if ($VM.ExtensionData.Guest.ToolsStatus) { [string]$VM.ExtensionData.Guest.ToolsStatus } else { 'toolsNotInstalled' } $toolsVersion = if ($VM.ExtensionData.Guest.ToolsVersion) { [string]$VM.ExtensionData.Guest.ToolsVersion } else { '' } } elseif ($VM.Guest) { # Some versions expose ToolsVersion on the Guest property if ($VM.Guest.PSObject.Properties['ToolsVersion']) { $toolsVersion = [string]$VM.Guest.ToolsVersion } } # --- IP Addresses --- $ipAddresses = @() if ($VM.Guest -and $VM.Guest.IPAddress) { $ipAddresses = @($VM.Guest.IPAddress | Where-Object { -not [string]::IsNullOrWhiteSpace($_) }) } # --- Network adapters --- $networks = @() try { $netAdapters = Get-NetworkAdapter -VM $VM -ErrorAction SilentlyContinue if ($netAdapters) { $networks = @($netAdapters | ForEach-Object { if ($_.NetworkName) { $_.NetworkName } else { 'Disconnected' } }) } } catch { Write-Verbose "Could not retrieve network adapters for $vmName : $_" } # --- Datastores --- $datastores = @() try { $dsObjects = Get-Datastore -VM $VM -ErrorAction SilentlyContinue if ($dsObjects) { $datastores = @($dsObjects | ForEach-Object { $_.Name }) } } catch { Write-Verbose "Could not retrieve datastores for $vmName : $_" } # --- Storage --- $provisionedSpaceGB = 0.0 $usedSpaceGB = 0.0 if ($VM.ProvisionedSpaceGB) { $provisionedSpaceGB = [Math]::Round([double]$VM.ProvisionedSpaceGB, 2) } if ($VM.UsedSpaceGB) { $usedSpaceGB = [Math]::Round([double]$VM.UsedSpaceGB, 2) } # --- Snapshots --- $snapshotCount = 0 $oldestSnapshotAge = $null $snapshotDetails = @() try { $snapshots = Get-Snapshot -VM $VM -ErrorAction SilentlyContinue if ($snapshots) { $snapshotCount = @($snapshots).Count $snapshotDetails = @($snapshots | ForEach-Object { [PSCustomObject]@{ Name = $_.Name Created = $_.Created SizeGB = [Math]::Round($_.SizeGB, 2) AgeDays = [Math]::Round(((Get-Date) - $_.Created).TotalDays, 1) } }) $oldest = $snapshots | Sort-Object Created | Select-Object -First 1 if ($oldest -and $oldest.Created) { $oldestSnapshotAge = [Math]::Round(((Get-Date) - $oldest.Created).TotalDays, 1) } } } catch { Write-Verbose "Could not retrieve snapshots for $vmName : $_" } # --- Creation date (from events) --- $createdDate = $null if ($IncludeCreationDate) { try { $events = Get-VIEvent -Entity $VM -MaxSamples 1000 -ErrorAction SilentlyContinue | Where-Object { $_ -is [VMware.Vim.VmCreatedEvent] -or $_ -is [VMware.Vim.VmClonedEvent] -or $_ -is [VMware.Vim.VmDeployedEvent] } | Sort-Object CreatedTime | Select-Object -First 1 if ($events) { $createdDate = $events.CreatedTime } } catch { Write-Verbose "Could not retrieve creation events for $vmName : $_" } } # If creation date still unknown, fall back to ExtensionData if ($null -eq $createdDate -and $VM.ExtensionData -and $VM.ExtensionData.Config -and $VM.ExtensionData.Config.CreateDate) { $createDateVal = $VM.ExtensionData.Config.CreateDate if ($createDateVal -and $createDateVal -ne [datetime]::MinValue) { $createdDate = $createDateVal } } # --- Last powered on --- $lastPoweredOn = $null if ($VM.ExtensionData -and $VM.ExtensionData.Runtime -and $VM.ExtensionData.Runtime.BootTime) { $lastPoweredOn = $VM.ExtensionData.Runtime.BootTime } # For powered off VMs, check events if ($null -eq $lastPoweredOn -and $powerState -ne 'PoweredOn') { try { $powerEvents = Get-VIEvent -Entity $VM -MaxSamples 100 -ErrorAction SilentlyContinue | Where-Object { $_.GetType().Name -match 'VmPoweredOffEvent|VmSuspendedEvent|VmPoweredOnEvent' } | Sort-Object CreatedTime -Descending | Select-Object -First 1 if ($powerEvents) { $lastPoweredOn = $powerEvents.CreatedTime } } catch { Write-Verbose "Could not retrieve power events for $vmName : $_" } } # --- Host, Cluster, Datacenter, Folder --- $hostName = '' $clusterName = '' $datacenterName = '' $folderPath = '' if ($VM.VMHost) { $hostName = [string]$VM.VMHost.Name } try { $vmCluster = Get-Cluster -VM $VM -ErrorAction SilentlyContinue if ($vmCluster) { $clusterName = [string]$vmCluster.Name } } catch { Write-Verbose "Could not retrieve cluster for $vmName : $_" } try { $vmDC = Get-Datacenter -VM $VM -ErrorAction SilentlyContinue if ($vmDC) { $datacenterName = [string]$vmDC.Name } } catch { Write-Verbose "Could not retrieve datacenter for $vmName : $_" } if ($VM.Folder) { $folderPath = [string]$VM.Folder.Name } # --- Custom Attributes --- $customAttributes = @{} if ($VM.CustomFields) { foreach ($field in $VM.CustomFields) { if ($field.Key -and $field.Value) { $customAttributes[$field.Key] = $field.Value } } } # --- Compute snapshot risk --- $snapshotRisk = 'Clean' if ($snapshotCount -gt 0) { if ($null -ne $oldestSnapshotAge -and $oldestSnapshotAge -gt 7) { $snapshotRisk = 'Stale Snapshots' } else { $snapshotRisk = 'Has Snapshots' } } # --- Compute VM age category --- $vmAgeCategory = 'Unknown' if ($null -ne $createdDate) { $ageDays = ((Get-Date) - $createdDate).TotalDays if ($ageDays -lt 30) { $vmAgeCategory = 'Less than 30d' } elseif ($ageDays -lt 90) { $vmAgeCategory = '30-90d' } elseif ($ageDays -lt 365) { $vmAgeCategory = '90d-1yr' } else { $vmAgeCategory = 'Over 1yr' } } # --- Build and return the metadata object --- $metadata = [PSCustomObject]@{ Name = $vmName PowerState = $powerState GuestFamily = $guestFamily GuestFullName = $guestFullName GuestId = $guestId NumCPU = $numCPU MemoryGB = $memoryGB ToolsStatus = $toolsStatus ToolsVersion = $toolsVersion IPAddresses = $ipAddresses Networks = $networks Datastores = $datastores SnapshotCount = $snapshotCount SnapshotDetails = $snapshotDetails OldestSnapshotAge = $oldestSnapshotAge SnapshotRisk = $snapshotRisk CreatedDate = $createdDate VMAge = $vmAgeCategory LastPoweredOn = $lastPoweredOn Notes = $notes CustomAttributes = $customAttributes ProvisionedSpaceGB = $provisionedSpaceGB UsedSpaceGB = $usedSpaceGB Host = $hostName Cluster = $clusterName Datacenter = $datacenterName Folder = $folderPath VMId = $vmId } $metadata.PSObject.TypeNames.Insert(0, 'VMAutoTagger.VMMetadata') return $metadata } } |