Public/New-VMTagProfile.ps1
|
function New-VMTagProfile { <# .SYNOPSIS Creates or exports a YAML tag profile configuration file for VM-AutoTagger. .DESCRIPTION Generates a YAML tag profile that defines how VMs should be tagged. The profile specifies tag categories, their data sources, matching rules (wildcards, tiers, regex patterns), and cardinality settings. Two modes of operation: 1. Generate a default/example profile with all built-in categories and optionally custom category examples. 2. Export the current tag structure from a live vCenter server as a profile. The generated YAML file can be customized and passed to Sync-VMTags via the -ProfilePath parameter to control tag behavior. .PARAMETER OutputPath The file path where the YAML profile will be saved. .PARAMETER FromServer If specified, connects to this vCenter server and exports the existing tag categories and tags as a profile. This is useful for capturing an existing tag structure as a starting point for customization. .PARAMETER Credential PSCredential for vCenter authentication when using -FromServer. .PARAMETER IncludeCustom Include example custom tag category definitions (Department, Environment, Backup-Policy, Cost-Center, SLA-Tier) in the generated profile. These demonstrate advanced matching features like regex extraction and VM name pattern matching. .PARAMETER Hypervisor The hypervisor platform to use when exporting from a live server via -FromServer. Valid values: 'VMware', 'HyperV'. Default: 'VMware'. When set to 'HyperV', reads existing tags from VM Notes across all VMs instead of reading vSphere tag categories. .EXAMPLE New-VMTagProfile -OutputPath .\my-profile.yml Creates a default profile with the 10 standard tag categories. .EXAMPLE New-VMTagProfile -OutputPath .\full-profile.yml -IncludeCustom Creates a profile with standard categories plus example custom categories. .EXAMPLE New-VMTagProfile -OutputPath .\exported-profile.yml -FromServer vcenter.contoso.com -Credential (Get-Credential) Exports the existing vCenter tag structure as a YAML profile. .NOTES Author: Larry Roberts Part of the VM-AutoTagger module. #> [CmdletBinding()] param( [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string]$OutputPath, [Parameter()] [string]$FromServer, [Parameter()] [PSCredential]$Credential, [Parameter()] [switch]$IncludeCustom, [Parameter()] [ValidateSet('VMware', 'HyperV')] [string]$Hypervisor = 'VMware' ) process { if ($FromServer) { # Export from live server Write-Verbose "Exporting tag profile from: $FromServer" $yaml = Export-TagProfileFromServer -Server $FromServer -Credential $Credential -Hypervisor $Hypervisor } else { # Generate default profile Write-Verbose "Generating default tag profile" $yaml = Build-DefaultProfileYaml -IncludeCustom:$IncludeCustom } # Save to file try { $outputDir = Split-Path -Path $OutputPath -Parent if ($outputDir -and -not (Test-Path $outputDir)) { New-Item -Path $outputDir -ItemType Directory -Force | Out-Null } $yaml | Set-Content -Path $OutputPath -Encoding UTF8 -Force -NoNewline Write-Host "Tag profile saved to: $OutputPath" -ForegroundColor Green } catch { Write-Error "Failed to save tag profile: $_" return } # Return profile info return [PSCustomObject]@{ Path = (Resolve-Path $OutputPath).Path Source = if ($FromServer) { "Exported from $FromServer" } else { 'Generated default' } IncludeCustom = [bool]$IncludeCustom } } } function Export-TagProfileFromServer { <# .SYNOPSIS Exports existing tag categories and tags as a YAML profile string. .DESCRIPTION For VMware, exports vSphere tag categories and their tags. For Hyper-V, scans all VMs' Notes fields for VM-AutoTagger tag blocks and extracts the unique categories and values found. #> [CmdletBinding()] param( [string]$Server, [PSCredential]$Credential, [string]$Hypervisor = 'VMware' ) $viConnection = $null try { $viConnection = Connect-Hypervisor -Server $Server -Credential $Credential -Hypervisor $Hypervisor } catch { Write-Error "Failed to connect to $Hypervisor host '$Server': $_" return '' } try { $yamlLines = [System.Collections.ArrayList]::new() [void]$yamlLines.Add("# VM-AutoTagger Tag Profile") [void]$yamlLines.Add("# Exported from: $Server ($Hypervisor)") [void]$yamlLines.Add("# Export date: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')") [void]$yamlLines.Add("") [void]$yamlLines.Add("name: exported-$($Server -replace '[^a-zA-Z0-9]', '-')") [void]$yamlLines.Add("description: Tag profile exported from $Server") [void]$yamlLines.Add("") [void]$yamlLines.Add("categories:") if ($Hypervisor -eq 'HyperV') { # Scan all VMs for existing tags in Notes $getVMParams = @{ ErrorAction = 'Stop' } if ($Server -ne 'localhost' -and $Server -ne $env:COMPUTERNAME -and $Server -ne '.') { $getVMParams['ComputerName'] = $Server } $allVMs = @(Hyper-V\Get-VM @getVMParams) # Collect all unique category -> tag value mappings $categoryTags = [ordered]@{} foreach ($vm in $allVMs) { $hvTags = Get-HyperVNotesTags -VM $vm foreach ($key in $hvTags.Keys) { if (-not $categoryTags.Contains($key)) { $categoryTags[$key] = [System.Collections.ArrayList]::new() } $val = $hvTags[$key] if ($val -and $categoryTags[$key] -notcontains $val) { [void]$categoryTags[$key].Add($val) } } } foreach ($catName in ($categoryTags.Keys | Sort-Object)) { [void]$yamlLines.Add(" - name: $catName") [void]$yamlLines.Add(" source: direct") [void]$yamlLines.Add(" cardinality: Single") [void]$yamlLines.Add(" description: `"Exported from Hyper-V VM Notes`"") $tagValues = @($categoryTags[$catName] | Sort-Object) if ($tagValues.Count -gt 0) { [void]$yamlLines.Add(" values:") foreach ($tv in $tagValues) { [void]$yamlLines.Add(" - match: `"$tv`"") [void]$yamlLines.Add(" tag: `"$tv`"") } } [void]$yamlLines.Add("") } } else { $categories = @(Get-TagCategory -Server $Server -ErrorAction Stop) foreach ($cat in ($categories | Sort-Object Name)) { [void]$yamlLines.Add(" - name: $($cat.Name)") [void]$yamlLines.Add(" source: direct") [void]$yamlLines.Add(" cardinality: $($cat.Cardinality)") if ($cat.Description) { [void]$yamlLines.Add(" description: `"$($cat.Description -replace '"', '\"')`"") } $tags = @(Get-Tag -Category $cat -Server $Server -ErrorAction SilentlyContinue) if ($tags.Count -gt 0) { [void]$yamlLines.Add(" values:") foreach ($tag in ($tags | Sort-Object Name)) { [void]$yamlLines.Add(" - match: `"$($tag.Name)`"") [void]$yamlLines.Add(" tag: `"$($tag.Name)`"") } } [void]$yamlLines.Add("") } } return ($yamlLines -join "`n") } finally { if ($viConnection) { try { Disconnect-Hypervisor -Server $Server -Hypervisor $Hypervisor } catch { } } } } function Build-DefaultProfileYaml { <# .SYNOPSIS Generates the default YAML profile content string. #> [CmdletBinding()] param( [switch]$IncludeCustom ) $yaml = @" # VM-AutoTagger Tag Profile # Generated: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') # Documentation: https://github.com/larro1991/VM-AutoTagger name: default description: Default tag profile with standard VM categories categories: # ────────────────────────────────────────────────────────────── # OS-Family: Detected from VMware Tools guest family or GuestId # ────────────────────────────────────────────────────────────── - name: OS-Family source: guest_family cardinality: Single description: "Operating system family detected from VMware Tools" values: - match: "*Windows*" tag: Windows - match: "*Linux*" tag: Linux - match: "*linux*" tag: Linux - match: "*darwin*" tag: macOS - match: "*freebsd*" tag: FreeBSD - default: true tag: Other # ────────────────────────────────────────────────────────────── # OS-Version: Full guest OS name from VMware Tools # ────────────────────────────────────────────────────────────── - name: OS-Version source: guest_fullname cardinality: Single description: "Full OS version string from VMware Tools" # ────────────────────────────────────────────────────────────── # CPU-Tier: vCPU count tiers for capacity planning # ────────────────────────────────────────────────────────────── - name: CPU-Tier source: cpu_count cardinality: Single description: "vCPU count tier for capacity planning" tiers: - max: 2 tag: "1-2 vCPU" - max: 8 tag: "4-8 vCPU" - max: 16 tag: "9-16 vCPU" - default: true tag: "16+ vCPU" # ────────────────────────────────────────────────────────────── # Memory-Tier: RAM allocation tiers for capacity planning # ────────────────────────────────────────────────────────────── - name: Memory-Tier source: memory_gb cardinality: Single description: "Memory allocation tier for capacity planning" tiers: - max: 4 tag: "Small (<=4GB)" - max: 16 tag: "Medium (8-16GB)" - max: 64 tag: "Large (32-64GB)" - default: true tag: "XLarge (64GB+)" # ────────────────────────────────────────────────────────────── # Tools-Status: VMware Tools installation and currency # ────────────────────────────────────────────────────────────── - name: Tools-Status source: tools_status cardinality: Single description: "VMware Tools installation and currency status" values: - match: "toolsOk" tag: Current - match: "toolsOld" tag: Outdated - match: "toolsNotInstalled" tag: Not Installed - match: "toolsNotRunning" tag: Not Running - default: true tag: Not Installed # ────────────────────────────────────────────────────────────── # Power-State: VM power state # ────────────────────────────────────────────────────────────── - name: Power-State source: power_state cardinality: Single description: "VM power state" values: - match: "PoweredOn" tag: Running - match: "PoweredOff" tag: Stopped - match: "Suspended" tag: Suspended - default: true tag: Stopped # ────────────────────────────────────────────────────────────── # Snapshot-Risk: Snapshot presence and age assessment # ────────────────────────────────────────────────────────────── - name: Snapshot-Risk source: snapshot_risk cardinality: Single description: "Snapshot presence and age risk assessment" # ────────────────────────────────────────────────────────────── # VM-Age: Age since VM creation # ────────────────────────────────────────────────────────────── - name: VM-Age source: vm_age cardinality: Single description: "VM age since creation" # ────────────────────────────────────────────────────────────── # Network: Port group assignments (multiple allowed) # ────────────────────────────────────────────────────────────── - name: Network source: network cardinality: Multiple description: "Network port group assignments" # ────────────────────────────────────────────────────────────── # Datastore: Storage locations (multiple allowed) # ────────────────────────────────────────────────────────────── - name: Datastore source: datastore cardinality: Multiple description: "Datastore locations for VM files" "@ if ($IncludeCustom) { $yaml += @" # ══════════════════════════════════════════════════════════════ # CUSTOM CATEGORIES (examples for enterprise use) # ══════════════════════════════════════════════════════════════ # ────────────────────────────────────────────────────────────── # Department: Extracted from VM annotation/notes via regex # Expects notes to contain "Dept: <value>" or "Department: <value>" # ────────────────────────────────────────────────────────────── - name: Department source: annotation cardinality: Single description: "Department extracted from VM notes" pattern: "(?:Dept|Department):\\s*(.+)" # ────────────────────────────────────────────────────────────── # Environment: Determined from VM name prefix convention # Expects naming like PRD-WebServer01, DEV-AppSrv02, etc. # ────────────────────────────────────────────────────────────── - name: Environment source: vm_name cardinality: Single description: "Environment derived from VM naming convention" values: - match: "PRD-*" tag: Production - match: "PROD-*" tag: Production - match: "DEV-*" tag: Development - match: "STG-*" tag: Staging - match: "TST-*" tag: Testing - match: "QA-*" tag: QA - match: "DR-*" tag: Disaster Recovery - default: true tag: Unclassified # ────────────────────────────────────────────────────────────── # Backup-Policy: From vSphere custom attribute "BackupTier" # ────────────────────────────────────────────────────────────── - name: Backup-Policy source: custom_attribute attribute_name: BackupTier cardinality: Single description: "Backup policy tier from custom attribute" # ────────────────────────────────────────────────────────────── # Cost-Center: Extracted from VM annotation/notes via regex # Expects notes to contain "Cost Center: <value>" # ────────────────────────────────────────────────────────────── - name: Cost-Center source: annotation cardinality: Single description: "Cost center extracted from VM notes" pattern: "Cost[- ]?Center:\\s*(\\w+)" # ────────────────────────────────────────────────────────────── # SLA-Tier: From custom attribute or folder path # ────────────────────────────────────────────────────────────── - name: SLA-Tier source: custom_attribute attribute_name: SLATier cardinality: Single description: "SLA tier from custom attribute" "@ } return $yaml } |