Private/Read-TagProfile.ps1
|
function Read-TagProfile { <# .SYNOPSIS Parses a YAML tag profile file into a PowerShell object. .DESCRIPTION Reads a VM-AutoTagger YAML tag profile and converts it to a structured PowerShell object suitable for use by Sync-VMTags and other functions. If the powershell-yaml module is installed, it will be used for parsing. Otherwise, the built-in ConvertFrom-BasicYaml function is used as a fallback. The returned object includes the profile name, description, and an array of category definitions with their matching rules, tiers, patterns, and other configuration. .PARAMETER Path The file system path to the YAML profile file. .PARAMETER YamlContent Raw YAML string content to parse instead of reading from a file. .EXAMPLE $profile = Read-TagProfile -Path "C:\Profiles\default.yml" $profile.categories | ForEach-Object { $_.name } .EXAMPLE $yaml = Get-Content .\enterprise.yml -Raw $profile = Read-TagProfile -YamlContent $yaml .NOTES Author: Larry Roberts Part of the VM-AutoTagger module. #> [CmdletBinding()] param( [Parameter(Mandatory, ParameterSetName = 'File')] [ValidateScript({ Test-Path $_ -PathType Leaf })] [string]$Path, [Parameter(Mandatory, ParameterSetName = 'Content')] [string]$YamlContent ) process { # Get the raw YAML content if ($PSCmdlet.ParameterSetName -eq 'File') { Write-Verbose "Reading tag profile from: $Path" $YamlContent = Get-Content -Path $Path -Raw -ErrorAction Stop } # Attempt to use powershell-yaml if available, fall back to built-in parser $parsed = $null $usedModule = $false if (Get-Module -ListAvailable -Name 'powershell-yaml' -ErrorAction SilentlyContinue) { try { Import-Module powershell-yaml -ErrorAction Stop $parsed = ConvertFrom-Yaml -Yaml $YamlContent -ErrorAction Stop $usedModule = $true Write-Verbose "Parsed YAML using powershell-yaml module." } catch { Write-Verbose "powershell-yaml failed, falling back to built-in parser: $_" } } if (-not $usedModule -or $null -eq $parsed) { $parsed = ConvertFrom-BasicYaml -YamlContent $YamlContent Write-Verbose "Parsed YAML using built-in ConvertFrom-BasicYaml." } # Normalize the parsed structure into a consistent profile object $profile = [PSCustomObject]@{ Name = if ($parsed.name) { $parsed.name } else { 'unnamed' } Description = if ($parsed.description) { $parsed.description } else { '' } Categories = [System.Collections.ArrayList]::new() } # Parse categories array $rawCategories = $null if ($parsed.categories) { $rawCategories = $parsed.categories } elseif ($parsed.Contains('categories')) { $rawCategories = $parsed['categories'] } if ($null -ne $rawCategories) { foreach ($cat in $rawCategories) { $categoryObj = [PSCustomObject]@{ Name = $cat.name Source = if ($cat.source) { $cat.source } else { 'direct' } Cardinality = if ($cat.cardinality) { $cat.cardinality } else { 'Single' } Description = if ($cat.description) { $cat.description } else { "Auto-tagged by VM-AutoTagger: $($cat.name)" } Values = @() Tiers = @() Pattern = if ($cat.pattern) { $cat.pattern } else { $null } AttributeName = if ($cat.attribute_name) { $cat.attribute_name } else { $null } NamePattern = if ($cat.name_pattern) { $cat.name_pattern } else { $null } FolderMapping = if ($cat.folder_mapping) { $cat.folder_mapping } else { $null } } # Parse values (wildcard/match-based) if ($cat.values) { $valList = [System.Collections.ArrayList]::new() foreach ($v in $cat.values) { $valObj = [PSCustomObject]@{ Match = if ($v.match) { $v.match } else { $null } Tag = if ($v.tag) { $v.tag } else { '' } Default = if ($v.default) { [bool]$v.default } else { $false } } [void]$valList.Add($valObj) } $categoryObj.Values = $valList.ToArray() } # Parse tiers (numeric threshold-based) if ($cat.tiers) { $tierList = [System.Collections.ArrayList]::new() foreach ($t in $cat.tiers) { $tierObj = [PSCustomObject]@{ Max = if ($null -ne $t.max) { [double]$t.max } else { [double]::MaxValue } Min = if ($null -ne $t.min) { [double]$t.min } else { 0 } Tag = if ($t.tag) { $t.tag } else { '' } Default = if ($t.default) { [bool]$t.default } else { $false } } [void]$tierList.Add($tierObj) } $categoryObj.Tiers = $tierList.ToArray() } [void]$profile.Categories.Add($categoryObj) } } Write-Verbose "Profile '$($profile.Name)' loaded with $($profile.Categories.Count) categories." return $profile } } function Get-DefaultTagProfile { <# .SYNOPSIS Returns the built-in default tag profile when no YAML file is specified. .DESCRIPTION Provides the standard 10-category tag profile used by Sync-VMTags when no custom profile path is provided. Covers OS-Family, OS-Version, CPU-Tier, Memory-Tier, Tools-Status, Power-State, Snapshot-Risk, VM-Age, Network, and Datastore. .EXAMPLE $defaults = Get-DefaultTagProfile .NOTES Author: Larry Roberts #> [CmdletBinding()] param() $defaultProfilePath = Join-Path -Path $script:ProfileDir -ChildPath 'default.yml' if (Test-Path $defaultProfilePath) { return Read-TagProfile -Path $defaultProfilePath } # Hardcoded fallback if profile file is missing Write-Verbose "Default profile file not found, using hardcoded defaults." $profile = [PSCustomObject]@{ Name = 'default' Description = 'Default tag profile with standard VM categories' Categories = [System.Collections.ArrayList]::new() } # OS-Family [void]$profile.Categories.Add([PSCustomObject]@{ Name = 'OS-Family' Source = 'guest_family' Cardinality = 'Single' Description = 'Operating system family detected from VMware Tools' Values = @( [PSCustomObject]@{ Match = '*Windows*'; Tag = 'Windows'; Default = $false } [PSCustomObject]@{ Match = '*Linux*'; Tag = 'Linux'; Default = $false } [PSCustomObject]@{ Match = '*darwin*'; Tag = 'macOS'; Default = $false } [PSCustomObject]@{ Match = '*freebsd*'; Tag = 'FreeBSD'; Default = $false } [PSCustomObject]@{ Match = $null; Tag = 'Other'; Default = $true } ) Tiers = @() Pattern = $null AttributeName = $null NamePattern = $null FolderMapping = $null }) # OS-Version [void]$profile.Categories.Add([PSCustomObject]@{ Name = 'OS-Version' Source = 'guest_fullname' Cardinality = 'Single' Description = 'Full OS version string from VMware Tools' Values = @() Tiers = @() Pattern = $null AttributeName = $null NamePattern = $null FolderMapping = $null }) # CPU-Tier [void]$profile.Categories.Add([PSCustomObject]@{ Name = 'CPU-Tier' Source = 'cpu_count' Cardinality = 'Single' Description = 'vCPU count tier for capacity planning' Values = @() Tiers = @( [PSCustomObject]@{ Max = 2; Min = 0; Tag = '1-2 vCPU'; Default = $false } [PSCustomObject]@{ Max = 8; Min = 0; Tag = '4-8 vCPU'; Default = $false } [PSCustomObject]@{ Max = 16; Min = 0; Tag = '9-16 vCPU'; Default = $false } [PSCustomObject]@{ Max = [double]::MaxValue; Min = 0; Tag = '16+ vCPU'; Default = $true } ) Pattern = $null AttributeName = $null NamePattern = $null FolderMapping = $null }) # Memory-Tier [void]$profile.Categories.Add([PSCustomObject]@{ Name = 'Memory-Tier' Source = 'memory_gb' Cardinality = 'Single' Description = 'Memory allocation tier for capacity planning' Values = @() Tiers = @( [PSCustomObject]@{ Max = 4; Min = 0; Tag = 'Small (<=4GB)'; Default = $false } [PSCustomObject]@{ Max = 16; Min = 0; Tag = 'Medium (8-16GB)'; Default = $false } [PSCustomObject]@{ Max = 64; Min = 0; Tag = 'Large (32-64GB)'; Default = $false } [PSCustomObject]@{ Max = [double]::MaxValue; Min = 0; Tag = 'XLarge (64GB+)'; Default = $true } ) Pattern = $null AttributeName = $null NamePattern = $null FolderMapping = $null }) # Tools-Status [void]$profile.Categories.Add([PSCustomObject]@{ Name = 'Tools-Status' Source = 'tools_status' Cardinality = 'Single' Description = 'VMware Tools installation and currency status' Values = @( [PSCustomObject]@{ Match = 'toolsOk'; Tag = 'Current'; Default = $false } [PSCustomObject]@{ Match = 'toolsOld'; Tag = 'Outdated'; Default = $false } [PSCustomObject]@{ Match = 'toolsNotInstalled'; Tag = 'Not Installed'; Default = $false } [PSCustomObject]@{ Match = 'toolsNotRunning'; Tag = 'Not Running'; Default = $false } [PSCustomObject]@{ Match = $null; Tag = 'Not Installed'; Default = $true } ) Tiers = @() Pattern = $null AttributeName = $null NamePattern = $null FolderMapping = $null }) # Power-State [void]$profile.Categories.Add([PSCustomObject]@{ Name = 'Power-State' Source = 'power_state' Cardinality = 'Single' Description = 'VM power state' Values = @( [PSCustomObject]@{ Match = 'PoweredOn'; Tag = 'Running'; Default = $false } [PSCustomObject]@{ Match = 'PoweredOff'; Tag = 'Stopped'; Default = $false } [PSCustomObject]@{ Match = 'Suspended'; Tag = 'Suspended'; Default = $false } [PSCustomObject]@{ Match = $null; Tag = 'Stopped'; Default = $true } ) Tiers = @() Pattern = $null AttributeName = $null NamePattern = $null FolderMapping = $null }) # Snapshot-Risk [void]$profile.Categories.Add([PSCustomObject]@{ Name = 'Snapshot-Risk' Source = 'snapshot_risk' Cardinality = 'Single' Description = 'Snapshot presence and age risk assessment' Values = @() Tiers = @() Pattern = $null AttributeName = $null NamePattern = $null FolderMapping = $null }) # VM-Age [void]$profile.Categories.Add([PSCustomObject]@{ Name = 'VM-Age' Source = 'vm_age' Cardinality = 'Single' Description = 'VM age since creation' Values = @() Tiers = @() Pattern = $null AttributeName = $null NamePattern = $null FolderMapping = $null }) # Network [void]$profile.Categories.Add([PSCustomObject]@{ Name = 'Network' Source = 'network' Cardinality = 'Multiple' Description = 'Network port group assignments' Values = @() Tiers = @() Pattern = $null AttributeName = $null NamePattern = $null FolderMapping = $null }) # Datastore [void]$profile.Categories.Add([PSCustomObject]@{ Name = 'Datastore' Source = 'datastore' Cardinality = 'Multiple' Description = 'Datastore locations for VM files' Values = @() Tiers = @() Pattern = $null AttributeName = $null NamePattern = $null FolderMapping = $null }) return $profile } |