Happy New Year to all

It’s 2025, and a year we used to associate with science fiction has arrived, yet “still no flying cars”!

It’s the beginning of a new year, and like most I’m back at my usual daily efforts; building labs, virtual machines, creating, testing and solving issues for customers, etc. And today, I wanted to add a network adapter to a new VM, and I asked myself “how would I define a MAC address based off a CIDR”?

Well it got me thinking (and chatting with my LLM) and in this post I’m sharing the end result. A function named Convert-CIDRToSubnetMask and within it I try to explain the math and byte handling that is relevant for the overall mechanism. Refer to the Wikipedia article for more information.

Here are the goodies, I hope you find it useful.

function Convert-CIDRToSubnetMask
{
<#
.SYNOPSIS
    Converts a CIDR (Classless Inter-Domain Routing) notation to a subnet mask in dotted decimal format.
.DESCRIPTION
    This function takes an integer representing the CIDR prefix length (0-32) and returns the corresponding subnet mask in dotted decimal format (e.g., 255.255.255.0 for /24).
.PARAMETER CIDR
    The CIDR prefix length, an integer value between 0 and 32.
.OUTPUTS
    String
    The subnet mask in dotted decimal notation (e.g., 255.255.255.0).
.EXAMPLE
    Convert /24 to a subnet mask
    Convert-CIDRToSubnetMask -CIDR 24 # 255.255.255.0
.EXAMPLE
    Convert /16 to a subnet mask
    Convert-CIDRToSubnetMask -CIDR 16 # 255.255.0.0
.EXAMPLE
    Convert /22 to a subnet mask
    Convert-CIDRToSubnetMask -CIDR 22 # 255.255.252.0
.NOTES
    Convert cidr to subnet mask in powershell
    Author: Paul Naughton
    Date: Jan 2025
.LINK
    https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing
#>

    param (
        [Parameter(Mandatory)]
        [ValidateRange(0, 32)]
        [Int]$CIDR
    )

    # Generate the subnet mask as an unsigned integer
    $subnetMask = [uint32]([math]::Pow(2, $CIDR) - 1) -shl (32 - $CIDR)
    <#
        [math]::Pow(2, $CIDR)
            raises 2 to the power of $CIDR, forming the base for the subnet mask calculation.
        -1
            Subtracting 1 converts the power of 2^CIDR into a bitmask with $CIDR number of 1s in binary.
            If $CIDR = 24
            2 ^ 24      = 16777216 = 00000001 00000000 00000000 00000000
            16777216 -1 = 16777215 = 00000000 11111111 11111111 11111111
        [uint32]()
            Ensures the value is treated as a 32-bit unsigned integer. Otherwise its a double which is not ok for the operations that follow.
        -shl (32 - $CIDR)
            Left-shifts the 1s in the bitmask to the most significant bits, forming a valid subnet mask.
            32 - 24 = 8
            16777215 after shift left 8: 11111111 11111111 11111111 00000000
    #>

    # Convert the subnet mask to a byte array
    $subnetMaskBytes = [BitConverter]::GetBytes($subnetMask)

    # Handle Endianness explicitly
    if ([BitConverter]::IsLittleEndian) {
        $subnetMaskBytes = $subnetMaskBytes[3..0]
    }

    # Convert the byte array to dotted decimal notation
    $dottedDecimal = ($subnetMaskBytes -join '.')

    # Output the dotted decimal subnet mask
    Write-Output $dottedDecimal
}

<
Previous Post
Programmatically detecting the members of a ValidateSet attribute within your function.
>
Next Post
Avoid Array Addition - Tip