Private/Set-HyperVNotesTags.ps1

function Set-HyperVNotesTags {
    <#
    .SYNOPSIS
        Stores VM-AutoTagger tags as a JSON block in a Hyper-V VM's Notes field.
    .DESCRIPTION
        Since Hyper-V does not have a native tag system like vSphere, this function
        stores tags as a structured JSON block appended to (or replacing in) the VM
        Notes field. The format uses a separator line to distinguish user notes from
        auto-tagger tags:
 
            [any existing notes text]
            ---VM-AUTOTAGGER-TAGS---
            {"OS-Family":"Windows","CPU-Tier":"4-8 vCPU","Memory-Tier":"Medium (8-16GB)"}
 
        If an existing tag block is found, it is replaced. User notes above the
        separator are preserved.
    .PARAMETER VM
        A Hyper-V VM object from Get-VM.
    .PARAMETER Tags
        A hashtable of category-name to tag-value pairs to store.
    .PARAMETER Server
        The Hyper-V host name (used for -ComputerName on remote hosts).
    .EXAMPLE
        $tags = @{ 'OS-Family' = 'Windows'; 'CPU-Tier' = '4-8 vCPU' }
        Set-HyperVNotesTags -VM $vm -Tags $tags
    .EXAMPLE
        Set-HyperVNotesTags -VM $vm -Tags @{ 'Power-State' = 'Running' } -Server 'hyperv01'
    .NOTES
        Author: Larry Roberts
        Part of the VM-AutoTagger module.
    #>

    [CmdletBinding(SupportsShouldProcess)]
    param(
        [Parameter(Mandatory)]
        [object]$VM,
        [Parameter(Mandatory)]
        [hashtable]$Tags,
        [string]$Server
    )

    $separator = "`n---VM-AUTOTAGGER-TAGS---`n"
    $currentNotes = if ($VM.Notes) { $VM.Notes } else { '' }

    # Strip existing tag block if present
    $sepIndex = $currentNotes.IndexOf('---VM-AUTOTAGGER-TAGS---')
    if ($sepIndex -ge 0) {
        $currentNotes = $currentNotes.Substring(0, $sepIndex).TrimEnd()
    }

    $tagJson = $Tags | ConvertTo-Json -Compress
    $newNotes = if ([string]::IsNullOrWhiteSpace($currentNotes)) {
        $separator.TrimStart() + $tagJson
    } else {
        $currentNotes + $separator + $tagJson
    }

    if ($PSCmdlet.ShouldProcess($VM.Name, "Set VM Notes with tags")) {
        $setParams = @{ VM = $VM; Notes = $newNotes; ErrorAction = 'Stop' }
        if ($Server -and $Server -ne 'localhost' -and $Server -ne $env:COMPUTERNAME -and $Server -ne '.') {
            $setParams['ComputerName'] = $Server
        }
        Hyper-V\Set-VM @setParams
    }
}

function Get-HyperVNotesTags {
    <#
    .SYNOPSIS
        Reads VM-AutoTagger tags from a Hyper-V VM's Notes field.
    .DESCRIPTION
        Parses the VM Notes field looking for the VM-AutoTagger tag separator line
        and extracts the JSON tag block. Returns a hashtable of category-name to
        tag-value pairs.
 
        If no tag block is found, returns an empty hashtable.
    .PARAMETER VM
        A Hyper-V VM object from Get-VM.
    .EXAMPLE
        $tags = Get-HyperVNotesTags -VM $vm
        $tags['OS-Family'] # Returns 'Windows'
    .EXAMPLE
        $vm = Hyper-V\Get-VM -Name 'WebServer01'
        $tags = Get-HyperVNotesTags -VM $vm
        if ($tags.Count -eq 0) { Write-Host 'No tags found' }
    .NOTES
        Author: Larry Roberts
        Part of the VM-AutoTagger module.
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [object]$VM
    )

    $notes = if ($VM.Notes) { $VM.Notes } else { return @{} }
    $sepIndex = $notes.IndexOf('---VM-AUTOTAGGER-TAGS---')
    if ($sepIndex -lt 0) { return @{} }

    $jsonStart = $sepIndex + '---VM-AUTOTAGGER-TAGS---'.Length
    $jsonStr = $notes.Substring($jsonStart).Trim()

    if ([string]::IsNullOrWhiteSpace($jsonStr)) { return @{} }

    try {
        $parsed = $jsonStr | ConvertFrom-Json -ErrorAction Stop
        $result = @{}
        foreach ($prop in $parsed.PSObject.Properties) {
            $result[$prop.Name] = $prop.Value
        }
        return $result
    }
    catch {
        Write-Verbose "Failed to parse tags from VM Notes: $_"
        return @{}
    }
}