Public/ConvertTo-DotNetVersionClass.ps1

function ConvertTo-DotNetVersionClass {
    <#
        .SYNOPSIS
            Converts a version string to a four-component .NET Version.
        .DESCRIPTION
            Separators (period, underscore, plus, and hyphen) delimit version components.
            Numeric components must use ASCII digits. Text components are mapped to stable
            SHA-256-derived integers; this permits conversion but does not preserve semantic
            version ordering. Components after the third are summed into the fourth component,
            saturating at Int32.MaxValue.
        .EXTERNALHELP Evergreen-help.xml
    #>

    [OutputType([System.Version])]
    [CmdletBinding()]
    param (
        [Parameter(
            Mandatory = $true,
            Position = 0,
            ValueFromPipeline,
            ValueFromPipelineByPropertyName,
            HelpMessage = "A version string to convert to a standard .NET compliant version class.")]
        [ValidateNotNullOrEmpty()]
        [System.String] $Version
    )

    process {
        if ([System.String]::IsNullOrWhiteSpace($Version)) {
            throw [System.ArgumentException]::new("Version must not be empty or whitespace.", "Version")
        }

        if ($Version -match '(^[._+-]|[._+-]$|[._+-]{2})') {
            throw [System.ArgumentException]::new(
                "Version '$Version' contains an empty segment. Separators must be single and occur between segments.",
                "Version"
            )
        }

        # Normalize into a fixed-size list and reuse one hash instance for text segments.
        $Segments = $Version -split '[._+-]'
        $NormalizedSegments = [System.Collections.Generic.List[System.Int32]]::new()
        $TailSum = [System.Int64]0
        $HashAlgorithm = $null
        $SegmentIndex = 0

        try {
            foreach ($Segment in $Segments) {
                if ($Segment.Length -gt 0 -and
                    $Segment -notmatch '^[0-9]+$' -and
                    $Segment -notmatch '^\p{Nd}+$' -and
                    $null -eq $HashAlgorithm) {
                    $HashAlgorithm = [System.Security.Cryptography.SHA256]::Create()
                }

                $NormalizedSegment = Convert-Segment -Segment $Segment -HashAlgorithm $HashAlgorithm
                if ($SegmentIndex -lt 3) {
                    $NormalizedSegments.Add($NormalizedSegment)
                }
                elseif ($TailSum -lt [System.Int32]::MaxValue) {
                    if ($NormalizedSegment -ge ([System.Int32]::MaxValue - $TailSum)) {
                        $TailSum = [System.Int32]::MaxValue
                    }
                    else {
                        $TailSum += $NormalizedSegment
                    }
                }

                $SegmentIndex++
            }
        }
        finally {
            if ($null -ne $HashAlgorithm) {
                $HashAlgorithm.Dispose()
            }
        }

        if ($SegmentIndex -gt 3) {
            $NormalizedSegments.Add([System.Int32]$TailSum)
        }

        while ($NormalizedSegments.Count -lt 4) {
            $NormalizedSegments.Add(0)
        }

        return [System.Version]::new(
            $NormalizedSegments[0],
            $NormalizedSegments[1],
            $NormalizedSegments[2],
            $NormalizedSegments[3]
        )
    }
}