IPv4 subnet maths with PowerShell

Written to complement the VbScript version of this post. This collection of functions handles subnet maths in PowerShell.

The functions described here have been updated since posting this article. The updated functions are available as part of the script Indented.Net.IP. The module may be installed from the PS gallery:

Install-Module Indented.Net.IP

Convert an IP to binary

This function uses System.Convert to change each octet of an IP address to binary form. PadLeft is used to add leading zero’s if required.

filter ConvertTo-BinaryIP {
    [CmdletBinding()]
    [OutputType([String])]
    param (
        [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
        [IPAddress]$IPAddress
    )
    
    $binaryOctets = $IPAddress.GetAddressBytes() | ForEach-Object { 
        [Convert]::ToString($_, 2).PadLeft(8, '0')
    }
    $binaryOctets -join '.'
}

Convert an IP to a 32-bit decimal

Allows conversion of an IP Address to an unsigned 32-bit integer.

filter ConvertTo-DecimalIP {
    [CmdletBinding()]
    [OutputType([UInt32])]
    param (
        [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
        [IPAddress]$IPAddress
    )
    
    $bytes = $IPAddress.GetAddressBytes()
    $decimal = 0;
    for ($i = 0; $i -le 3; $i++) {
        $decimal += $bytes[$i] * [Math]::Pow(256, 3 - $i)
    }

    [UInt32]$decimal
}

Convert a decimal number or binary IP to a dotted IP

Used to switch a decimal or binary IP back to the more common dotted decimal format. The function uses a simple set of regular expressions to determine which format is presented.

filter ConvertTo-DottedDecimalIP {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
        [String]$IPAddress
    )
        
    switch -regex ($IPAddress) {
        '^([01]{8}.){3}[01]{8}$' {
            [Byte[]]$bytes = $IPAddress -split '\.' | ForEach-Object {
                [Convert]::ToByte($_, 2)
            }
            [IPAddress]$bytes
        }
        '^\d+$' {
            $IPAddress = [UInt32]$IPAddress
            [Byte[]]$bytes = for ($i = 3; $i -ge 0; $i--) {
                $remainder = [UInt32]$IPAddress % [Math]::Pow(256, $i)
                [UInt32]$IPAddress - $remainder
                $IPAddress = $remainder
            }
            [IPAddress]$bytes
        }
        default { Write-Error "Cannot convert this format" }
    }
}

Convert a subnet mask to a mask length

Occasionally it is desirable to calculate the subnet mask bit length. This can be done using the following.

filter ConvertTo-MaskLength {
    [CmdletBinding()]
    [OutputType([Int32])]
    param (
        [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
        [Alias("Mask")]
        [IPAddress]$SubnetMask
    )
    
    $binaryOctets = $SubnetMask.GetAddressBytes() | ForEach-Object { 
        [Convert]::ToString($_, 2)
    }
    ($binaryOctets -join '').Trim('0').Length
}

Convert a mask length to a subnet mask

To a dotted decimal IP via binary and an unsigned 32-bit integer.

filter ConvertTo-Mask {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
        [Alias("Length")]
        [ValidateRange(0, 32)]
        [Int32]$MaskLength
    )
    
    $binaryIP = ("1" * $MaskLength).PadRight(32, "0")
    $decimalIP = [Convert]::ToUInt32($binaryIP, 2)
    [Byte[]]$bytes = for ($i = 3; $i -ge 0; $i--) {
        $remainder = $decimalIP % [Math]::Pow(256, $i)
        Write-Host ($decimalIP - $remainder)
        $decimalIP - $remainder
        $decimalIP = $remainder
    }
    [IPAddress]$bytes
}

Calculate the subnet network address

The functions above can be used with along with a bitwise AND operation against an IP address and subnet mask to calculate the network address.

Note that this function includes calls to both ConvertTo-DottedDecimalIP and ConvertTo-DecimalIP.

filter Get-NetworkAddress {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
        [IPAddress]$IPAddress,
        
        [Parameter(Mandatory = $true, Position = 1)]
        [Alias("Mask")]
        [IPAddress]$SubnetMask
    )
    
    ConvertTo-DottedDecimalIP ((ConvertTo-DecimalIP $IPAddress) -band (ConvertTo-DecimalIP $SubnetMask))
}

Calculate the subnet broadcast address

The function is right at the bottom, the explanation is, of course, entirely optional.

Getting to the Broadcast Address is a bit more complicated than the Network Address. A Bitwise Or is executed against an Inverted Subnet Mask. For example, the Inverted form of 255.255.255.0 is 0.0.0.255.

Inverting the decimal value of the subnet mask can be performed using BNot, the Complement Operator (see Get-Help About_Comparison_Operators).

Unfortunately -bnot only returns a signed 64-bit integer (in the range -9223372036854775808 to 9223372036854775807).

PS> $Value = ConvertTo-DecimalIP 255.255.255.0
PS> $Value

4294967040

PS> $Value.GetType()

IsPublic IsSerial Name   BaseType
-------- -------- ----   --------
True     True     UInt32 System.ValueType

PS> $Inverted = -bnot $Value
PS> $Inverted

-4294967041

PS> $Inverted.GetType()

IsPublic IsSerial Name   BaseType
-------- -------- ----   --------
True     True     Int64  System.ValueType

PS> # Convert to Binary (Base 2)
PS> [Convert]::ToString($Inverted, 2)

1111111111111111111111111111111100000000000000000000000011111111

The first 32 bits have are 1 (feel free to count). This happens because of the implicit conversion between UInt32 and Int64. Those 32 leading 1’s must go.

PS C:\> $Inverted -band [UInt32]::MaxValue

255

The operation takes advantage of the implicit conversion between types to get [UInt32]::MaxValue and to an Int64 type. This process is shown in binary below:

Int64 value:     11111111 11111111 11111111 11111111 00000000 00000000 00000000 11111111
UInt32.MaxValue: 00000000 00000000 00000000 00000000 11111111 11111111 11111111 11111111
Result of -band: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 11111111

Finally, the resulting value can be implicitly (or explicitly) converted back to UInt32.

A very long explanation for a very short function.

filter Get-BroadcastAddress {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
        [IPAddress]$IPAddress,
        
        [Parameter(Mandatory = $true, Position = 1)]
        [Alias("Mask")]
        [IPAddress]$SubnetMask
    )
    
    $decimalIP = ConvertTo-DecimalIP $IPAddress
    $invertedMask = -bnot (ConvertTo-DecimalIP $SubnetMask) -band [UInt32]::MaxValue
    ConvertTo-DottedDecimalIP ($decimalIP -bor $invertedMask)
}

Get-NetworkSummary

Putting the functions above to work, providing a summary of an IP address range.

function Get-NetworkSummary {
    param (
        [String]$IPAddress,
        
        [String]$SubnetMask
    )
    
    if ($IPAddress.Contains("/")) {
        $IPAddress, $SubnetMask = $IPAddress.Split("/")
    }
    if (-not $SubnetMask.Contains('.')) {
        $SubnetMask = ConvertTo-Mask $SubnetMask
    }
    
    $DecimalIP = ConvertTo-DecimalIP $IPAddress
    $DecimalMask = ConvertTo-DecimalIP $SubnetMask

    $Network = $DecimalIP -band $DecimalMask
    $Broadcast = $DecimalIP -bor (-bnot $DecimalMask -band [UInt32]::MaxValue)
    
    $NetworkAddress = ConvertTo-DottedDecimalIP $Network 
    
    $RangeStart = ConvertTo-DottedDecimalIP ($Network + 1) 
    $RangeEnd = ConvertTo-DottedDecimalIP ($Broadcast - 1) 
    $BroadcastAddress = ConvertTo-DottedDecimalIP $Broadcast 
    
    $MaskLength = ConvertTo-MaskLength $SubnetMask
    $BinaryIP = ConvertTo-BinaryIP $IPAddress
    $Private = $false
    
    switch -regex ($BinaryIP) {
        "^1111" { $Class = "E"; $SubnetBitMap = "1111"; break }
        "^1110" { $Class = "D"; $SubnetBitMap = "1110"; break }
        "^110" {
            $Class = "C"
            if ($BinaryIP -match "^11000000.10101000") {
                $Private = $true
            }
            break
        }
        "^10" {
            $Class = "B"
            if ($BinaryIP -match "^10101100.0001") {
                $Private = $true
            }
            break
        }
        "^0" {
            $Class = "A"
            if ($BinaryIP -match "^0000101") {
                $Private = $true
            }
        }
    }
    $NetInfo = New-Object Object
    Add-Member NoteProperty "Network" -Input $NetInfo -Value $NetworkAddress 
    Add-Member NoteProperty "Broadcast" -Input $NetInfo -Value $BroadcastAddress
    Add-Member NoteProperty "Range" -Input $NetInfo -Value "$RangeStart - $RangeEnd"
    Add-Member NoteProperty "Mask" -Input $NetInfo -Value $SubnetMask
    Add-Member NoteProperty "MaskLength" -Input $NetInfo -Value $MaskLength 
    Add-Member NoteProperty "Hosts" -Input $NetInfo -Value ($Broadcast - $Network - 1)
    Add-Member NoteProperty "Class" -Input $NetInfo -Value $Class
    Add-Member NoteProperty "IsPrivate" -Input $NetInfo -Value $Private
    
    return $NetInfo
}

Get-NetworkRange

Calculate each host address within the network range.

function Get-NetworkRange {
    param (
        [String]$IPAddress,
        
        [String]$SubnetMask
    )
    
    if ($IPAddress.Contains("/")) {
        $IPAddress, $SubnetMask = $IPAddress.Split("/")
    }
    if (-not $SubnetMask.Contains('.')) {
        $SubnetMask = ConvertTo-Mask $SubnetMask
    }
    
    $DecimalIP = ConvertTo-DecimalIP $IPAddress
    $DecimalMask = ConvertTo-DecimalIP $SubnetMask
    
    $Network = $DecimalIP -band $DecimalMask
    $Broadcast = $DecimalIP -bor (-bnot $DecimalMask -band [UInt32]::MaxValue) 
    
    for ($i = $Network + 1; $i -lt $Broadcast; $i++) {
        ConvertTo-DottedDecimalIP $i
    }
}

Examples

Convert functions

PS> ConvertTo-BinaryIP 192.168.1.1

11000000.10101000.00000001.00000001
PS> ConvertTo-DecimalIP 192.168.1.1

3232235777
PS> ConvertTo-DottedDecimalIP 11000000.10101000.00000001.00000001

Address            : 16885952
AddressFamily      : InterNetwork
ScopeId            :
IsIPv6Multicast    : False
IsIPv6LinkLocal    : False
IsIPv6SiteLocal    : False
IsIPv6Teredo       : False
IsIPv4MappedToIPv6 : False
IPAddressToString  : 192.168.1.1
ConvertTo-DottedDecimalIP 3232235777

Address            : 16885952
AddressFamily      : InterNetwork
ScopeId            :
IsIPv6Multicast    : False
IsIPv6LinkLocal    : False
IsIPv6SiteLocal    : False
IsIPv6Teredo       : False
IsIPv4MappedToIPv6 : False
IPAddressToString  : 192.168.1.1
PS> ConvertTo-MaskLength 255.255.128.0

17
PS> ConvertTo-Mask 17

Address            : 8454143
AddressFamily      : InterNetwork
ScopeId            :
IsIPv6Multicast    : False
IsIPv6LinkLocal    : False
IsIPv6SiteLocal    : False
IsIPv6Teredo       : False
IsIPv4MappedToIPv6 : False
IPAddressToString  : 255.255.128.0

Use of Network and Broadcast Address Functions

PS> Get-NetworkAddress 192.168.1.1 255.255.255.0

Address            : 108736
AddressFamily      : InterNetwork
ScopeId            :
IsIPv6Multicast    : False
IsIPv6LinkLocal    : False
IsIPv6SiteLocal    : False
IsIPv6Teredo       : False
IsIPv4MappedToIPv6 : False
IPAddressToString  : 192.168.1.0
PS> Get-BroadcastAddress 192.168.1.1 255.255.255.0

Address            : 4278298816
AddressFamily      : InterNetwork
ScopeId            :
IsIPv6Multicast    : False
IsIPv6LinkLocal    : False
IsIPv6SiteLocal    : False
IsIPv6Teredo       : False
IsIPv4MappedToIPv6 : False
IPAddressToString  : 192.168.1.255

Use of Network Info

PS> Get-NetworkSummary 229.168.1.1 255.255.248.0

Network    : 229.168.0.0
Broadcast  : 229.168.7.255
Range      : 229.168.0.1 - 229.168.7.254
Mask       : 255.255.248.0
MaskLength : 21
Hosts      : 2046
Class      : D
IsPrivate  : False
PS> Get-NetworkSummary 172.16.1.243 18

Network    : 172.16.0.0
Broadcast  : 172.16.63.255
Range      : 172.16.0.1 - 172.16.63.254
Mask       : 255.255.192.0
MaskLength : 18
Hosts      : 16382
Class      : B
IsPrivate  : True
PS> Get-NetworkSummary 10.0.0.3/14

Network    : 10.0.0.0
Broadcast  : 10.3.255.255
Range      : 10.0.0.1 - 10.3.255.254
Mask       : 255.252.0.0
MaskLength : 14
Hosts      : 262142
Class      : A
IsPrivate  : True

Use of Network Range

PS> Get-NetworkRange 192.168.1.5 255.255.255.248
PS> Get-NetworkRange 172.18.0.23 30
PS> Get-NetworkRange 172.18.0.23/29