Private/ConvertTo-WinClatIPv6Prefix.ps1

Function ConvertTo-WinClatIPv6Prefix {

    <#

    .SYNOPSIS
        Returns the prefix of an IPv6 address in CIDR notation.

    .DESCRIPTION
        Keeps the first PrefixLength bits of the address, clears the rest, and returns the result as a string such as '2001:579:6024:21d::/64'. Used by the CLAT address check of Test-WinClat (DESIGN.md, section 8.3), which compares the /64 of the CLAT IPv6 address with the /64 of each SLAAC address. The /64 is used rather than each address's own PrefixLength, because Windows reports temporary SLAAC addresses with a PrefixLength of 128 (section 10).

        Returns $null when the address cannot be parsed or is not IPv6.

        This function must also be available in the remote session. See DESIGN.md, section 4.3.

    .PARAMETER Address
        The IPv6 address, for example '2001:579:6024:21d:f574:f7a:4040:4260'.

    .PARAMETER PrefixLength
        The number of leading bits to keep. The default is 64.

    .INPUTS
        None. This function does not accept pipeline input.

    .OUTPUTS
        System.String.

    .EXAMPLE
        ConvertTo-WinClatIPv6Prefix -Address '2001:579:6024:21d:f574:f7a:4040:4260'

        Returns '2001:579:6024:21d::/64'.

    .LINK
        https://www.rfc-editor.org/rfc/rfc4291

    .NOTES
        Version: 1.0.0
        Creation Date: October 28, 2026
        Last Updated: October 28, 2026
        Author: Richard Hicks
        Organization: Richard M. Hicks Consulting, Inc.
        Contact: rich@richardhicks.com
        Website: https://www.richardhicks.com/

    #>


    [CmdletBinding()]
    [OutputType([string])]

    Param (

        [AllowNull()]
        [AllowEmptyString()]
        [string]$Address,

        [ValidateRange(0, 128)]
        [int]$PrefixLength = 64

    )

    $Parsed = $null

    If (-not $Address -or -not [System.Net.IPAddress]::TryParse($Address, [ref]$Parsed) -or $Parsed.AddressFamily -ne [System.Net.Sockets.AddressFamily]::InterNetworkV6) {

        Return $null

    }

    $Bytes = $Parsed.GetAddressBytes()

    For ($Index = 0; $Index -lt 16; $Index++) {

        $Keep = [Math]::Min([Math]::Max($PrefixLength - ($Index * 8), 0), 8)
        $Bytes[$Index] = $Bytes[$Index] -band [byte](((0xFF -shl (8 - $Keep)) -band 0xFF))

    }

    "$(New-Object -TypeName System.Net.IPAddress -ArgumentList (, $Bytes))/$PrefixLength"

}