Private/Initialize-TagCategory.ps1

function Initialize-TagCategory {
    <#
    .SYNOPSIS
        Ensures a vSphere tag category exists, creating it if necessary.
    .DESCRIPTION
        Idempotent function that checks whether a vSphere tag category with the
        given name already exists. If it does, returns the existing category object.
        If not, creates a new tag category with the specified cardinality and
        description.

        This is called by Sync-VMTags before processing VMs to guarantee that all
        categories defined in the tag profile exist in vCenter.

        Cardinality options:
        - Single: Only one tag from this category can be assigned per entity.
        - Multiple: Multiple tags from this category can be assigned per entity.
    .PARAMETER Name
        The name for the tag category (e.g., "OS-Family", "CPU-Tier").
    .PARAMETER Cardinality
        The cardinality for the tag category. Valid values: Single, Multiple.
        Default: Single.
    .PARAMETER Description
        A description for the tag category. If not specified, a default
        description is generated.
    .EXAMPLE
        $cat = Initialize-TagCategory -Name "OS-Family" -Cardinality "Single" -Description "Operating system family"
    .EXAMPLE
        $cat = Initialize-TagCategory -Name "Network" -Cardinality "Multiple"
    .NOTES
        Author: Larry Roberts
        Requires: VMware.PowerCLI module (connected to vCenter)
        Part of the VM-AutoTagger module.
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [string]$Name,

        [Parameter()]
        [ValidateSet('Single', 'Multiple')]
        [string]$Cardinality = 'Single',

        [Parameter()]
        [string]$Description
    )

    process {
        if ([string]::IsNullOrWhiteSpace($Description)) {
            $Description = "Auto-managed by VM-AutoTagger: $Name"
        }

        # Check if category already exists
        $existing = $null
        try {
            $existing = Get-TagCategory -Name $Name -ErrorAction SilentlyContinue
        }
        catch {
            Write-Verbose "Category '$Name' not found, will create."
        }

        if ($existing) {
            Write-Verbose "Tag category '$Name' already exists (Cardinality: $($existing.Cardinality))."

            # Warn if cardinality mismatch but do not change it (could break existing assignments)
            if ($existing.Cardinality -ne $Cardinality) {
                Write-Warning "Category '$Name' exists with Cardinality '$($existing.Cardinality)' but profile specifies '$Cardinality'. Existing cardinality will be preserved."
            }

            return $existing
        }

        # Create the category
        Write-Verbose "Creating tag category: $Name (Cardinality: $Cardinality)"
        try {
            $newCategory = New-TagCategory -Name $Name `
                -Cardinality $Cardinality `
                -Description $Description `
                -EntityType 'VirtualMachine' `
                -ErrorAction Stop

            Write-Verbose "Created tag category: $Name"
            return $newCategory
        }
        catch {
            Write-Error "Failed to create tag category '$Name': $_"
            return $null
        }
    }
}

function Initialize-Tag {
    <#
    .SYNOPSIS
        Ensures a vSphere tag exists within a given category, creating it if necessary.
    .DESCRIPTION
        Idempotent function that checks whether a tag with the given name exists
        in the specified category. If it does, returns the existing tag. If not,
        creates a new tag.
    .PARAMETER Name
        The name for the tag (e.g., "Windows", "1-2 vCPU", "Production").
    .PARAMETER Category
        The tag category object (from Get-TagCategory or Initialize-TagCategory)
        in which the tag should exist.
    .EXAMPLE
        $cat = Get-TagCategory -Name "OS-Family"
        $tag = Initialize-Tag -Name "Windows" -Category $cat
    .NOTES
        Author: Larry Roberts
        Requires: VMware.PowerCLI module (connected to vCenter)
        Part of the VM-AutoTagger module.
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [string]$Name,

        [Parameter(Mandatory)]
        [object]$Category
    )

    process {
        # Check if tag already exists
        $existing = $null
        try {
            $existing = Get-Tag -Name $Name -Category $Category -ErrorAction SilentlyContinue
        }
        catch {
            Write-Verbose "Tag '$Name' not found in category '$($Category.Name)', will create."
        }

        if ($existing) {
            Write-Verbose "Tag '$($Category.Name)/$Name' already exists."
            return $existing
        }

        # Create the tag
        Write-Verbose "Creating tag: $($Category.Name)/$Name"
        try {
            $newTag = New-Tag -Name $Name `
                -Category $Category `
                -Description "Auto-created by VM-AutoTagger" `
                -ErrorAction Stop

            Write-Verbose "Created tag: $($Category.Name)/$Name"
            return $newTag
        }
        catch {
            Write-Error "Failed to create tag '$($Category.Name)/$Name': $_"
            return $null
        }
    }
}