Private/Compare-Version.ps1

function Compare-Version {
    <#
    .SYNOPSIS
    Order two version strings, returning -1, 0, or 1.
 
    .DESCRIPTION
    Coerce both version strings to a common comparable type and compare them via
    [IComparable]. SemanticVersion is tried first so pre-release ordering is
    honoured (e.g. 1.0.0-alpha sorts below 1.0.0); System.Version is the
    fallback so four-part versions (1.2.3.4) still compare. Missing System.Version
    components are normalised to 0 so 1.2.3 and 1.2.3.0 compare equal. If neither
    type can parse both inputs, fall back to an ordinal string comparison.
 
    Both operands must coerce to the same type - a SemanticVersion cannot be
    compared to a System.Version - so each branch requires both inputs to parse.
 
    .PARAMETER ReferenceVersion
    The version on the left of the comparison.
 
    .PARAMETER DifferenceVersion
    The version on the right of the comparison.
 
    .EXAMPLE
    Compare-Version -ReferenceVersion '1.2.0' -DifferenceVersion '1.2.3'
 
    Returns -1 (1.2.0 is less than 1.2.3).
 
    .EXAMPLE
    Compare-Version -ReferenceVersion '1.0.0' -DifferenceVersion '1.0.0-beta'
 
    Returns 1 (a release sorts above its pre-release).
    #>

    [CmdletBinding()]
    [OutputType([int])]
    param(
        [string]$ReferenceVersion,
        [string]$DifferenceVersion
    )

    # SemanticVersion first: it orders pre-release labels correctly.
    [System.Management.Automation.SemanticVersion]$refSemVer = $null
    [System.Management.Automation.SemanticVersion]$diffSemVer = $null
    if (
        [System.Management.Automation.SemanticVersion]::TryParse($ReferenceVersion, [ref]$refSemVer) -and
        [System.Management.Automation.SemanticVersion]::TryParse($DifferenceVersion, [ref]$diffSemVer)
    ) {
        return $refSemVer.CompareTo($diffSemVer)
    }

    # System.Version fallback handles four-part versions SemVer rejects.
    # Normalise absent components (-1) to 0 so 1.2.3 equals 1.2.3.0.
    [System.Version]$refVer = $null
    [System.Version]$diffVer = $null
    if (
        [System.Version]::TryParse($ReferenceVersion, [ref]$refVer) -and
        [System.Version]::TryParse($DifferenceVersion, [ref]$diffVer)
    ) {
        $refNormalised = [System.Version]::new(
            [Math]::Max($refVer.Major, 0),
            [Math]::Max($refVer.Minor, 0),
            [Math]::Max($refVer.Build, 0),
            [Math]::Max($refVer.Revision, 0)
        )
        $diffNormalised = [System.Version]::new(
            [Math]::Max($diffVer.Major, 0),
            [Math]::Max($diffVer.Minor, 0),
            [Math]::Max($diffVer.Build, 0),
            [Math]::Max($diffVer.Revision, 0)
        )
        return $refNormalised.CompareTo($diffNormalised)
    }

    # Neither type parses both: ordinal string comparison, clamped to -1/0/1.
    # Trace this: a wrong answer here (e.g. a typo'd '1.2.x') is otherwise silent.
    Write-Verbose "Compare-Version falling back to ordinal string comparison for [$ReferenceVersion] vs [$DifferenceVersion]"
    return [Math]::Sign(
        [string]::Compare($ReferenceVersion, $DifferenceVersion, [System.StringComparison]::OrdinalIgnoreCase)
    )
}