Private/Yaml.ps1

# Copyright (c) 2026 Broadcom. All Rights Reserved.
# Broadcom Confidential. The term "Broadcom" refers to Broadcom Inc.
# and/or its subsidiaries.
#
# =============================================================================
#
# SOFTWARE LICENSE AGREEMENT
#
# Copyright (c) CA, Inc. All rights reserved.
#
# You are hereby granted a non-exclusive, worldwide, royalty-free license
# under CA, Inc.'s copyrights to use, copy, modify, and distribute this
# software in source code or binary form for use in connection with CA, Inc.
# products.
#
# This copyright notice shall be included in all copies or substantial
# portions of the software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
# DEALINGS IN THE SOFTWARE.
#
# =============================================================================
#
#region Private — YAML serialization and deserialization
function ConvertFrom-Yaml {

    <#
    .SYNOPSIS
        Converts YAML content to PowerShell objects using native PowerShell parsing.

    .DESCRIPTION
        The ConvertFrom-Yaml function parses YAML content and converts it into PowerShell hashtables
        and arrays. This is a native PowerShell implementation that doesn't require external dependencies.
        It returns an array containing hashtables representing the parsed YAML structure.

        Key features:
        - Native PowerShell implementation (no external dependencies)
        - Supports nested objects and arrays
        - Handles multi-document YAML (separated by ---)
        - Returns structured PowerShell objects for easy property access
        - Comprehensive error handling with detailed error messages

    .PARAMETER IndentSize
        Number of spaces per indentation level in the YAML source. Default is 2. Pass 4 when
        the input file uses 4-space indentation.

    .PARAMETER YamlContent
        The YAML content as a string to be parsed. This can be single or multi-document YAML.

    .EXAMPLE
        $yaml = @"
        name: John Doe
        age: 30
        address:
          street: 123 Main St
          city: New York
        "@
        $result = ConvertFrom-Yaml -YamlContent $yaml
        $result[0].name # Returns: John Doe

    .OUTPUTS
        System.Array
        Returns an array containing hashtables representing the parsed YAML structure.

    .NOTES
        This function is designed to work with the internal ConvertFrom-YamlInternal function
        which handles the actual parsing logic. The function uses PowerShell's pipeline
        capabilities for efficient processing of YAML content.

    #>


    [CmdletBinding()]
    [OutputType([System.Object[]])]
    Param (
        [Parameter(Mandatory = $false)] [ValidateRange(1, 8)] [Int]$IndentSize = 2,
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [AllowEmptyString()] [String]$YamlContent
    )

    begin {
        $yamlLines = [System.Collections.Generic.List[String]]::new()
    }

    process {
        # Normalize to LF and split so the parser receives clean lines on all platforms.
        $lines = $YamlContent -split "`n"
        foreach ($line in $lines) {
            $yamlLines.Add($line.TrimEnd("`r"))
        }
    }

    end {
        try {
            $validLines = [System.Collections.Generic.List[String]]::new()
            foreach ($line in $yamlLines) {
                if ($null -ne $line -and $line.Trim() -ne "") {
                    $validLines.Add($line)
                }
            }

            if ($validLines.Count -eq 0) {
                Write-LogMessage -Type DEBUG -Message "YAML content contains no valid lines. Returning empty array."
                return @()
            }

            return ConvertFrom-YamlInternal -IndentSize $IndentSize -YamlLines $validLines
        } catch {
            return Write-ErrorAndReturn -ErrorMessage "YAML parsing failed: $($_.Exception.Message)" -ErrorCode "ERR_YAML_PARSE"
        }
    }
}
function ConvertTo-Yaml {

    <#
    .SYNOPSIS
        Converts a PowerShell object to YAML format.

    .DESCRIPTION
        The ConvertTo-Yaml function converts PowerShell objects (hashtables, arrays, PSCustomObjects)
        into YAML format. It supports nested objects, arrays, and various data types including
        strings, numbers, booleans, and null values.

    .PARAMETER InputObject
        The PowerShell object to be converted to YAML format. This can be a hashtable,
        PSCustomObject, array, or any other PowerShell object.

    .PARAMETER IndentSize
        The number of spaces to use for indentation in the YAML output. Default is 2.

    .EXAMPLE
        $object = @{
            name = "John Doe"
            age = 30
            skills = @("PowerShell", "Python")
            address = @{
                street = "123 Main St"
                city = "New York"
            }
        }
        ConvertTo-Yaml -InputObject $object

    .EXAMPLE
        $array = @("item1", "item2", "item3")
        ConvertTo-Yaml -InputObject $array -IndentSize 4

    .OUTPUTS
        System.String
        Returns the YAML representation of the input object.

    .NOTES
        Delegates recursive serialization to ConvertTo-YamlInternal.

    #>



    [CmdletBinding()]
    [OutputType([String])]
    Param (
        [Parameter()] [ValidateRange(1, [Int]::MaxValue)] [Int]$IndentSize = 2,
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [Object]$InputObject
    )

    begin {
        Write-LogMessage -Type DEBUG -Message "Entered ConvertTo-Yaml function..."

        $yamlContent = [System.Collections.Generic.List[String]]::new()
    }

    process {
        $lines = ConvertTo-YamlInternal -InputObject $InputObject -IndentSize $IndentSize -CurrentIndent 0
        foreach ($line in $lines) {
            $yamlContent.Add($line)
        }
    }

    end {
        return $yamlContent -join "`n"
    }
}
function ConvertFrom-YamlInternal {

    <#
    .SYNOPSIS
        Internal function that parses YAML lines into a PowerShell array.

    .DESCRIPTION
        The ConvertFrom-YamlInternal function is an internal helper function that processes
        an array of YAML lines and converts them into a PowerShell array containing a hashtable. It handles
        nested objects, arrays, and various YAML structures using a stack-based approach
        to maintain proper indentation levels.

    .PARAMETER IndentSize
        Number of spaces per indentation level in the YAML source. Default is 2. Must match the
        indentation used in the file — 4-space YAML requires IndentSize = 4. Mismatch causes
        incorrect nesting depth resolution.

    .PARAMETER YamlLines
        An array of strings representing the YAML content, where each string is a line
        from the YAML document.

    .EXAMPLE
        $yamlLines = @(
            "name: John Doe",
            "age: 30",
            "address:",
            " street: 123 Main St",
            " city: New York"
        )
        $result = ConvertFrom-YamlInternal -YamlLines $yamlLines

    .OUTPUTS
        System.Array
        Returns an array containing a hashtable representing the parsed YAML structure.

    .NOTES
        This is an internal function used by ConvertFrom-Yaml. It should not be called
        directly in most scenarios.
    #>


    [CmdletBinding()]
    [OutputType([Object[]])]
    Param (
        [Parameter(Mandatory = $false)] [ValidateRange(1, 8)] [Int]$IndentSize = 2,
        [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [String[]]$YamlLines
    )

    $result = @{}
    $stack = [System.Collections.Generic.List[Hashtable]]::new()
    $currentObject = $result
    $lineNumber = 0

    foreach ($line in $YamlLines) {
        $lineNumber++
        $trimmedLine = $line.TrimEnd()

        # Skip empty lines and comments.

        if ([String]::IsNullOrWhiteSpace($trimmedLine) -or $trimmedLine.StartsWith('#')) {
            continue
        }

        $currentIndentLevel = ($line.Length - $line.TrimStart().Length) / $IndentSize

        while ($stack.Count -gt 0) {
            $lastItem = $stack[$stack.Count - 1]
            if ($lastItem.IndentLevel -ge $currentIndentLevel) {
                $stack.RemoveAt($stack.Count - 1)
            } else {
                break
            }
        }

        $parsedItem = Get-YamlLine -Line $trimmedLine.TrimStart()

        if ($null -ne $parsedItem) {
            if ($stack.Count -eq 0) {
                $currentObject = $result
            } else {
                $currentObject = $stack[$stack.Count - 1].Object
            }

            if ($parsedItem.Type -eq "KeyValue") {
                Add-ObjectProperty -Object $currentObject -Path $parsedItem.Key -Value $parsedItem.Value
            }
            elseif ($parsedItem.Type -eq "ArrayItem") {
                if (-not $currentObject.ContainsKey($parsedItem.Key)) {
                    $currentObject[$parsedItem.Key] = [System.Collections.Generic.List[Object]]::new()
                }
                $currentObject[$parsedItem.Key].Add($parsedItem.Value)
            }
            elseif ($parsedItem.Type -eq "ObjectStart") {
                $newObject = @{}
                Add-ObjectProperty -Object $currentObject -Path $parsedItem.Key -Value $newObject
                $stack.Add(@{
                    Object = $newObject
                    IndentLevel = $currentIndentLevel
                })
            }
            elseif ($parsedItem.Type -eq "ArrayStart") {
                $newArray = [System.Collections.Generic.List[Object]]::new()
                Add-ObjectProperty -Object $currentObject -Path $parsedItem.Key -Value $newArray
                $stack.Add(@{
                    Object = $newArray
                    IndentLevel = $currentIndentLevel
                    IsArray = $true
                })
            }
        }
    }

    return [Object[]]@($result)
}
function Get-YamlLine {

    <#
    .SYNOPSIS
        Parses a single YAML line and returns a structured object representing its content.

    .DESCRIPTION
        The Get-YamlLine function analyzes a single YAML line and determines its type
        (key-value pair, array item, object start, or array start). It returns a hashtable
        with type information and parsed values that can be used by the YAML parser.

    .PARAMETER Line
        The YAML line to be parsed. Should be trimmed of leading/trailing whitespace.

    .EXAMPLE
        $result = Get-YamlLine -Line "name: John Doe"
        # Returns: @{ Type = "KeyValue"; Key = "name"; Value = "John Doe" }

    .EXAMPLE
        $result = Get-YamlLine -Line "- item1"
        # Returns: @{ Type = "ArrayItem"; Key = ""; Value = "item1" }

    .EXAMPLE
        $result = Get-YamlLine -Line "address:"
        # Returns: @{ Type = "ObjectStart"; Key = "address"; Value = $null }

    .OUTPUTS
        System.Collections.Hashtable
        Returns a hashtable with the following possible properties:
        - Type: "KeyValue", "ArrayItem", "ObjectStart", or "ArrayStart"
        - Key: The key name (empty for array items)
        - Value: The parsed value (null for object/array starts)

    .NOTES
        This is an internal function used by ConvertFrom-YamlInternal. It should not be
        called directly in most scenarios.

        Parsing limitation: YAML values must be separated from their key by ": " (colon then space).
        The form "key:value" (no space) is treated as an ObjectStart (nested object) rather than a
        KeyValue pair. Hand-authored YAML files should always use ": " separators. All YAML produced
        by ConvertTo-Yaml in this module uses proper spacing.
    #>


    [CmdletBinding()]
    [OutputType([Hashtable])]
    Param (
        [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$Line
    )

    if ($Line.StartsWith('- ')) {
        $value = $Line.Substring(2).Trim()
        return @{
            Type = "ArrayItem"
            Key = ""
            Value = ConvertFrom-YamlValue -Value $value
        }
    }

    if ($Line.Contains(':')) {
        $colonIndex = $Line.IndexOf(':')
        $key = $Line.Substring(0, $colonIndex).Trim()
        $value = $Line.Substring($colonIndex + 1).Trim()

        if ([String]::IsNullOrWhiteSpace($value)) {
            return @{
                Type = "ObjectStart"
                Key = $key
                Value = $null
            }
        }
        elseif ($value -eq '[]') {
            return @{
                Type = "ArrayStart"
                Key = $key
                Value = $null
            }
        }
        else {
            return @{
                Type = "KeyValue"
                Key = $key
                Value = ConvertFrom-YamlValue -Value $value
            }
        }
    }

    return $null
}
function ConvertFrom-YamlValue {

    <#
    .SYNOPSIS
        Converts a YAML value string to its appropriate PowerShell data type.

    .DESCRIPTION
        The ConvertFrom-YamlValue function takes a YAML value string and converts it to
        the appropriate PowerShell data type. It handles strings, numbers, booleans, null
        values, and removes quotes when appropriate.

    .PARAMETER Value
        The YAML value string to be converted to a PowerShell object.

    .EXAMPLE
        $result = ConvertFrom-YamlValue -Value "John Doe"
        # Returns: "John Doe" (string)

    .EXAMPLE
        $result = ConvertFrom-YamlValue -Value "30"
        # Returns: 30 (integer)

    .EXAMPLE
        $result = ConvertFrom-YamlValue -Value "true"
        # Returns: $true (boolean)

    .EXAMPLE
        $result = ConvertFrom-YamlValue -Value "null"
        # Returns: $null

    .EXAMPLE
        $result = ConvertFrom-YamlValue -Value '"quoted string"'
        # Returns: "quoted string" (unquoted string)

    .OUTPUTS
        System.Object
        Returns the converted value as the appropriate PowerShell data type:
        - String (unquoted)
        - Int64 (for integer strings — Int64 avoids Int32 overflow on large values such as Unix timestamps)
        - Double (for decimal strings)
        - Boolean (for true/false values)
        - Null (for null/empty values)

    .NOTES
        This is an internal function used by Get-YamlLine. It should not be called
        directly in most scenarios.
    #>


    [CmdletBinding()]
    [OutputType([Int64], [Double], [Bool], [String])]
    Param (
        [Parameter(Mandatory = $false)] [AllowEmptyString()] [AllowNull()] [String]$Value
    )

    if ([String]::IsNullOrWhiteSpace($Value)) {
        return $null
    }

    if (($Value.StartsWith('"') -and $Value.EndsWith('"')) -or
        ($Value.StartsWith("'") -and $Value.EndsWith("'"))) {
        return $Value.Substring(1, $Value.Length - 2)
    }

    switch -Regex ($Value) {
        '^-?\d+$' { return [Int64]$Value }
        '^-?\d+\.\d+$' { return [Double]$Value }
    }

    if ($Value -ieq 'true')  { return $true }
    if ($Value -ieq 'false') { return $false }
    if ($Value -ieq 'null' -or $Value -eq '~') { return $null }

    return $Value
}
function Add-ObjectProperty {

    <#
    .SYNOPSIS
        Adds a property to a hashtable object with the specified key and value.

    .DESCRIPTION
        The Add-ObjectProperty function adds a property to a hashtable object using the
        specified key (path) and value. This is a simple helper function used internally
        by the YAML parser to set properties on objects during parsing.

    .PARAMETER Object
        The hashtable object to which the property will be added.

    .PARAMETER Path
        The key name for the property to be added to the object.

    .PARAMETER Value
        The value to be assigned to the property.

    .EXAMPLE
        $obj = @{}
        Add-ObjectProperty -Object $obj -Path "name" -Value "John Doe"
        # $obj now contains: @{ name = "John Doe" }

    .EXAMPLE
        $obj = @{}
        Add-ObjectProperty -Object $obj -Path "age" -Value 30
        # $obj now contains: @{ age = 30 }

    .EXAMPLE
        $obj = @{}
        Add-ObjectProperty -Object $obj -Path "address" -Value @{ street = "123 Main St" }
        # $obj now contains: @{ address = @{ street = "123 Main St" } }

    .OUTPUTS
        None
        This function modifies the input object in place and does not return a value.

    .NOTES
        This is an internal function used by ConvertFrom-YamlInternal. It should not be
        called directly in most scenarios.
    #>


    [CmdletBinding()]
    Param (
        [Parameter(Mandatory = $true)] [ValidateNotNull()] [Hashtable]$Object,
        [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$Path,
        [Parameter(Mandatory = $false)] [AllowNull()] [Object]$Value
    )

    if ([String]::IsNullOrWhiteSpace($Path)) {
        return
    }

    $Object[$Path] = $Value
}
function ConvertTo-YamlInternal {

    <#
    .SYNOPSIS
        Internal helper function that recursively converts PowerShell objects to YAML format with proper indentation.

    .DESCRIPTION
        The ConvertTo-YamlInternal function is an internal helper that performs recursive conversion of PowerShell
        objects (hashtables, PSCustomObjects, arrays, and primitive types) into properly formatted YAML lines
        with appropriate indentation. This function is the core engine behind the ConvertTo-Yaml cmdlet and
        handles the complex logic of traversing nested object structures while maintaining proper YAML formatting.

        The function processes different object types as follows:
        - Hashtables and PSCustomObjects: Converts properties to key-value pairs with nested indentation
        - Arrays and ArrayLists: Converts items to YAML list format with dash prefixes
        - Nested objects: Recursively processes with increased indentation levels
        - Primitive values: Delegates to ConvertTo-YamlValue for proper type conversion

        The function maintains proper YAML indentation by calculating spaces based on the current nesting level
        and the specified indent size, ensuring the output conforms to YAML specification standards.

    .PARAMETER InputObject
        The PowerShell object to be converted to YAML format. This can be any type of object including:
        - Hashtables containing key-value pairs
        - PSCustomObjects with properties
        - Arrays or ArrayLists containing multiple items
        - Primitive types (strings, numbers, booleans)
        - Nested combinations of the above types

    .PARAMETER IndentSize
        The number of spaces to use for each level of indentation in the YAML output.
        This parameter controls the visual formatting and nesting structure of the generated YAML.
        Common values are 2 or 4 spaces per indentation level to match YAML conventions.

    .PARAMETER CurrentIndent
        The current indentation level for the object being processed. This parameter is used
        internally during recursive calls to maintain proper nesting depth. The actual number
        of spaces used for indentation is calculated as (CurrentIndent * IndentSize).
        This parameter starts at 0 for root-level objects and increments for each nesting level.

    .EXAMPLE
        # This function is typically called internally by ConvertTo-Yaml.

        $hashtable = @{
            name = "John Doe"
            age = 30
            skills = @("PowerShell", "Python")
            address = @{
                street = "123 Main St"
                city = "New York"
            }
        }
        $yamlLines = ConvertTo-YamlInternal -InputObject $hashtable -IndentSize 2 -CurrentIndent 0

        This example would produce YAML lines with proper indentation:
        name: John Doe
        age: 30
        skills:
          - PowerShell
          - Python
        address:
          street: 123 Main St
          city: New York

    .EXAMPLE
        # Processing an array at indentation level 1.

        $array = @("item1", "item2", "item3")
        $yamlLines = ConvertTo-YamlInternal -InputObject $array -IndentSize 2 -CurrentIndent 1

        This would produce:
          - item1
          - item2
          - item3

    .OUTPUTS
        System.Collections.Generic.List[String]
        Returns a List[String] where each string represents a line of YAML output
        with appropriate indentation. The caller can join these lines with newline characters to
        create the final YAML document.

    .NOTES
        This is an internal function used by ConvertTo-Yaml and should not be called directly.
        IDictionary types (Hashtable, OrderedDictionary) are iterated via GetEnumerator()/Keys
        to avoid PSObject.Properties returning type members instead of dictionary entries.

    #>


    [CmdletBinding()]
    [OutputType([System.Collections.Generic.List[String]])]
    Param (
        [Parameter(Mandatory = $true)] [ValidateRange(0, [Int]::MaxValue)] [Int]$CurrentIndent,
        [Parameter(Mandatory = $true)] [ValidateRange(1, [Int]::MaxValue)] [Int]$IndentSize,
        [Parameter(Mandatory = $true)] [AllowNull()] [Object]$InputObject
    )

    $yamlLines = [System.Collections.Generic.List[String]]::new()
    $indent = " " * ($CurrentIndent * $IndentSize)

    # IDictionary (Hashtable, OrderedDictionary) must be enumerated via GetEnumerator() —
    # PSObject.Properties on these types exposes the type's own members (Count, Keys, Values…)
    # rather than the dictionary entries.
    if ($InputObject -is [System.Collections.IDictionary] -or $InputObject -is [PSCustomObject]) {
        $isDict = $InputObject -is [System.Collections.IDictionary]
        $keys = if ($isDict) { @($InputObject.Keys) } else { $InputObject.PSObject.Properties.Name }
        foreach ($key in $keys) {
            $value = if ($isDict) { $InputObject[$key] } else { $InputObject.$key }

            if ($value -is [array] -or $value -is [System.Collections.IList]) {
                $yamlLines.Add("$indent$key`:")
                foreach ($item in $value) {
                    $yamlLines.Add("$indent - $(ConvertTo-YamlValue -Value $item -IndentSize $IndentSize -CurrentIndent ($CurrentIndent + 1))")
                }
            }
            elseif ($value -is [System.Collections.IDictionary] -or $value -is [PSCustomObject]) {
                $yamlLines.Add("$indent$key`:")
                $subLines = ConvertTo-YamlInternal -InputObject $value -IndentSize $IndentSize -CurrentIndent ($CurrentIndent + 1)
                foreach ($line in $subLines) {
                    $yamlLines.Add($line)
                }
            }
            else {
                $yamlLines.Add("$indent$key`: $(ConvertTo-YamlValue -Value $value -IndentSize $IndentSize -CurrentIndent $CurrentIndent)")
            }
        }
    }
    elseif ($InputObject -is [array] -or $InputObject -is [System.Collections.IList]) {
        foreach ($item in $InputObject) {
            $yamlLines.Add("$indent- $(ConvertTo-YamlValue -Value $item -IndentSize $IndentSize -CurrentIndent $CurrentIndent)")
        }
    }
    else {
        $yamlLines.Add("$indent$(ConvertTo-YamlValue -Value $InputObject -IndentSize $IndentSize -CurrentIndent $CurrentIndent)")
    }

    return $yamlLines
}
function ConvertTo-YamlValue {

    <#
    .SYNOPSIS
        Converts a PowerShell object to its YAML string representation.

    .DESCRIPTION
        The ConvertTo-YamlValue function converts a PowerShell object to its appropriate
        YAML string representation. It handles various data types including strings,
        numbers, booleans, null values, hashtables, arrays, and complex objects.

    .PARAMETER Value
        The PowerShell object to be converted to YAML format.

    .PARAMETER IndentSize
        The number of spaces to use for indentation in nested structures.

    .PARAMETER CurrentIndent
        The current indentation level for proper formatting.

    .EXAMPLE
        $result = ConvertTo-YamlValue -Value "Hello World" -IndentSize 2 -CurrentIndent 0
        # Returns: "Hello World"

    .EXAMPLE
        $result = ConvertTo-YamlValue -Value 42 -IndentSize 2 -CurrentIndent 0
        # Returns: "42"

    .EXAMPLE
        $result = ConvertTo-YamlValue -Value $true -IndentSize 2 -CurrentIndent 0
        # Returns: "true"

    .EXAMPLE
        $result = ConvertTo-YamlValue -Value $null -IndentSize 2 -CurrentIndent 0
        # Returns: "null"

    .EXAMPLE
        $result = ConvertTo-YamlValue -Value @("item1", "item2") -IndentSize 2 -CurrentIndent 0
        # Returns: Multi-line YAML array representation.


    .OUTPUTS
        System.String
        Returns the YAML string representation of the input object.

    .NOTES
        This is an internal function used by ConvertTo-YamlInternal. It should not be
        called directly in most scenarios.

    #>


    [CmdletBinding()]
    [OutputType([String])]
    Param (
        [Parameter(Mandatory = $true)] [ValidateRange(0, [Int]::MaxValue)] [Int]$CurrentIndent,
        [Parameter(Mandatory = $true)] [ValidateRange(1, [Int]::MaxValue)] [Int]$IndentSize,
        [Parameter(Mandatory = $false)] [AllowNull()] [Object]$Value
    )

    if ($null -eq $Value) {
        return "null"
    }
    elseif ($Value -is [bool]) {
        return $Value.ToString().ToLower()
    }
    elseif ($Value -is [string]) {
        if ($Value.Contains(':') -or $Value.Contains('"') -or $Value.Contains("'") -or
            $Value.StartsWith(' ') -or $Value.EndsWith(' ') -or
            $Value -match '^[0-9]' -or $Value -match '^(true|false|null)$') {
            return "`"$($Value.Replace('"', '\"'))`""
        }
        return $Value
    }
    elseif ($Value -is [int] -or $Value -is [long] -or $Value -is [double] -or $Value -is [decimal]) {
        return $Value.ToString()
    }
    elseif ($Value -is [System.Collections.IDictionary] -or $Value -is [PSCustomObject]) {
        $subYaml = ConvertTo-YamlInternal -InputObject $Value -IndentSize $IndentSize -CurrentIndent ($CurrentIndent + 1)
        return "`n$subYaml"
    }
    elseif ($Value -is [array] -or $Value -is [System.Collections.IList]) {
        $arrayItems = [System.Collections.Generic.List[String]]::new()
        foreach ($item in $Value) {
            $itemValue = ConvertTo-YamlValue -Value $item -IndentSize $IndentSize -CurrentIndent $CurrentIndent
            $arrayItems.Add($itemValue)
        }
        return "`n$($arrayItems -join "`n")"
    }
    else {
        return $Value.ToString()
    }
}
function ConvertTo-YamlLiteralBlock {

    <#
        .SYNOPSIS
        Converts a file's contents into an indented YAML literal block scalar string.

        .DESCRIPTION
        Reads the file at FilePath, normalizes line endings to LF, trims trailing whitespace,
        and returns a YAML literal block scalar in the form:

          <KeyIndent><KeyName>: |
          <KeyIndent> <line 1>
          <KeyIndent> <line 2>
          ...

        This format is suitable for embedding PEM certificate and private key content into a YAML
        configuration file, as required by the Harbor tlsCertificate block.

        .PARAMETER FilePath
        Full path to the file whose contents will form the YAML literal block value.

        .PARAMETER KeyIndentSpaces
        Number of leading spaces for the key line. Content lines receive two additional spaces of
        indentation (e.g., 2 spaces for the key means 4 spaces for each content line).

        .PARAMETER KeyName
        The YAML key name to emit (e.g., "tls.crt", "tls.key", "ca.crt").

        .OUTPUTS
        [String] The YAML literal block scalar string, including a trailing newline.

        .EXAMPLE
        $block = ConvertTo-YamlLiteralBlock -FilePath "/etc/ssl/tls.crt" -KeyName "tls.crt" -KeyIndentSpaces 2

        Returns a string such as:
          tls.crt: |
            -----BEGIN CERTIFICATE-----
            MIIByTCCAW6g...
            -----END CERTIFICATE-----
    #>



    [CmdletBinding()]
    [OutputType([String])]
    Param (
        [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$FilePath,
        [Parameter(Mandatory = $true)] [ValidateRange(0, 20)] [Int]$KeyIndentSpaces,
        [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$KeyName
    )

    if (-not (Test-Path -LiteralPath $FilePath -PathType Leaf)) {
        throw [VcfDeploymentException]::new("ConvertTo-YamlLiteralBlock: File not found: '$FilePath'")
    }
    $fileContent = Get-Content -LiteralPath $FilePath -Raw -ErrorAction Stop
    # Defensive: -Raw should always return a single string for a single literal path.
    # Guard against unexpected multi-value returns (e.g., some PowerShell 5.1 edge cases).
    if ($fileContent -is [System.Array]) { $fileContent = $fileContent -join "" }
    $fileContent = ($fileContent -replace '\r\n', "`n").TrimEnd()
    $keyIndent = " " * $KeyIndentSpaces
    $contentIndent = " " * ($KeyIndentSpaces + 2)
    $lines = $fileContent -split '\n'
    $indentedLines = $lines | ForEach-Object { $contentIndent + $_ }
    return "$keyIndent${KeyName}: |`n$($indentedLines -join "`n")`n"
}
function ConvertTo-YamlSingleQuotedScalar {

    <#
        .SYNOPSIS
        Wraps a string value in YAML single quotes with proper escaping.

        .DESCRIPTION
        Returns a YAML-safe single-quoted scalar for the given string. Single quoting is the
        safest way to inject user-controlled values into YAML: no indicator characters
        ({, [, >, |, *, &, !, %, @, `) can be misinterpreted, and colons followed by a
        space are inert inside single quotes. The only character that requires escaping in a
        YAML single-quoted scalar is the single quote itself, which is represented as ''.

        .PARAMETER Value
        The plain-text string to encode.

        .OUTPUTS
        [String] The value wrapped in YAML single quotes, with any embedded single quotes doubled.

        .EXAMPLE
        ConvertTo-YamlSingleQuotedScalar -Value "pass'word{}"
        Returns: 'pass''word{}'
    #>


    [CmdletBinding()]
    [OutputType([String])]
    Param (
        [Parameter(Mandatory = $true)] [AllowEmptyString()] [ValidateNotNull()] [String]$Value
    )

    return "'$($Value.Replace("'", "''"))'"
}