List and validate IPv4 subnet masks using PowerShell

From Svendsen Tech PowerShell Wiki
Jump to: navigation, search

In this article I show you how to generate a list of all valid IPv4 subnet masks, and how to validate that a provided subnet mask is valid. Generating the list of valid IPv4 subnet masks in dotted decimal notation can be done in several ways, as can validating a supplied subnet mask in both binary and dotted-decimal form.

With the small number of possible subnets, an array and the -Contains operator should be fine for most purposes (32 possible network lengths - or 33 if you toss in 0), but I'll show you other ways that will perform better too, for the sake of elucidation, and potentially to fulfill a rare need (if you have such a need you probably don't need this article).




Example screenshot of all IPv4 subnet masks

Enumerate-and-validate-IPv4-subnet-masks-list.png

Example function for listing IPv4 subnet masks

Enumerate-IPv4-subnet-masks-example-with--contains.png

Here's the source code for the function in the screenshot above.

function Get-IPv4SubnetMask {
    param([Parameter(ValueFromPipeline=$true)][int[]] $NetworkLength)
    process {
        foreach ($Length in $NetworkLength) {
            $MaskBinary = ('1' * $Length).PadRight(32, '0')
            $DottedMaskBinary = $MaskBinary -replace '(.{8}(?!\z))', '${1}.'
            $SubnetMask = ($DottedMaskBinary.Split('.') | foreach { [Convert]::ToInt32($_, 2) }) -join '.'
            $SubnetMask
        }
    }
}

This code can be used to validate the subnet masks as you can see with the array and -contains, but I also demonstrate another way that should be a bit more efficient below. It really will not matter in 99.n % of the cases, and using the -Contains operator on an array should be enough.

I also talk a bit more about binary addresses if you read on.

PS C:\> $AllSubnetMasks = 1..32 | Get-IPv4SubnetMask

PS C:\> $AllSubnetMasks -contains '255.0.0.0'
True

PS C:\> $AllSubnetMasks -contains '255.0.0.1'
False

Validating a little more efficiently

Here's a pretty cool function for validating IPv4 subnet masks that returns true or false for whether it's respectively a valid or an invalid IPv4 subnet mask. Use the -Verbose parameter to see why the tests fail, if they fail.

It can parse and validate both a binary subnet mask and a dotted-decimal subnet mask (possibly in the same run).

 function Test-IPv4SubnetMask {
    [CmdletBinding()]
    param(
        [string] $SubnetMaskBinary,
        [string] $SubnetMaskDottedDecimal)
    if ($SubnetMaskBinary) {
        if ($SubnetmaskBinary -match '01') {
            Write-Verbose -Message "Invalid binary IPv4 subnet mask: '$SubnetMaskBinary'. Matched pattern '01'."
            $false
        } elseif ($SubnetMaskBinary.Length -ne 32) {
            Write-Verbose -Message "Invalid binary IPv4 subnet mask: '$SubnetMaskBinary'. Length was different from 32."
            $false
        } elseif ($SubnetMaskBinary -match '[^01]') {
            Write-Verbose -Message "Invalid binary IPv4 subnet mask: '$SubnetMaskBinary'. Was not all ones and zeroes."
            $false
        } else {
            $true
        }
    }
    if ($SubnetMaskDottedDecimal) {
        function Convert-IPToBinary {
            param([string] $IP)
            $IP = $IP -replace '\s+' # remove whitespace for fun
            try
            {
                return ($IP.Split('.') | ForEach-Object { [System.Convert]::ToString([byte] $_, 2).PadLeft(8, '0') }) -join ''
            }
            catch
            {
                Write-Warning -Message "Error converting '$IP' to a binary string: $_"
                return $Null
            }
        }
        $Binary = Convert-IPToBinary -IP $SubnetMaskDottedDecimal
        if ($Binary) {
            Test-IPv4SubnetMask -SubnetMaskBinary $Binary
        } else {
            $false
        }
    }
}

Example use

And here's how it works.

PS C:\> Test-IPv4SubnetMask -SubnetMaskBinary '11' -Verbose
VERBOSE: Invalid binary IPv4 subnet mask: '11'. Length was different from 32.
False

PS C:\> Test-IPv4SubnetMask -SubnetMaskBinary (('1' * 30) + '00') -Verbose
True

PS C:\> Test-IPv4SubnetMask -SubnetMaskBinary (('1' * 30) + '01') -Verbose
VERBOSE: Invalid binary IPv4 subnet mask: '11111111111111111111111111111101'. Matched pattern '01'.
False

PS C:\> Test-IPv4SubnetMask -SubnetMaskBinary (('1' * 30) + 'xx') -Verbose
VERBOSE: Invalid binary IPv4 subnet mask: '111111111111111111111111111111xx'. Was not all ones and zeroes.
False

PS C:\> Test-IPv4SubnetMask -SubnetMaskDottedDecimal 128.0.0.0
True

PS C:\> Test-IPv4SubnetMask -SubnetMaskDottedDecimal 128.0.0.1 -Verbose
VERBOSE: Invalid binary IPv4 subnet mask: '10000000000000000000000000000001'. Matched pattern '01'.
False

PS C:\> Test-IPv4SubnetMask -SubnetMaskDottedDecimal 128.0.0.x -Verbose
WARNING: Error converting '128.0.0.x' to a binary string: Cannot convert value "x" to type "System.Byte".
Error: "Input string was not in a correct format."
False

Verifying our own results

Why not throw one function at the other and see how it pans out? We should get 32 times "True" (stringified $true - a boolean value) for this, if everything is working correctly. Let's see.

PS C:\> Get-IPv4SubnetMask 1 | foreach { Test-IPv4SubnetMask -SubnetMaskDottedDecimal $_ }
True

PS C:\> Get-IPv4SubnetMask (1..32) | foreach { Test-IPv4SubnetMask -SubnetMaskDottedDecimal $_ }
True
True
True
True
True
True
True
True
True
True
True
True
True
True
True
True
True
True
True
True
True
True
True
True
True
True
True
True
True
True
True
True

A whole lot of truth!

A missing piece of the puzzle?

And for someone who might want to create a list of binary subnet masks, I leave that as an exercise for the reader, but here's an example "IPToBinary" function I conveniently ripped (and modified slightly) from my PSipcalc script, to help you along the way (use it on the generated list of dotted-decimal subnet masks in a foreach, or similar).

function Convert-IPToBinary {
    param([string] $IP)
    $IP = $IP -replace '\s+' # remove whitespace for fun/flexibility
    try
    {
        return ($IP.Split('.') | ForEach-Object { [System.Convert]::ToString([byte] $_, 2).PadLeft(8, '0') }) -join ''
    }
    catch
    {
        Write-Warning -Message "Error converting '$IP' to a binary string: $_"
        return $null
    }
}