IPv4 subnet math with PowerShell

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

Download all the functions and examples at once: Net-SubnetMath.ps1

Convert an IP to binary

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

Function ConvertTo-BinaryIP( [String]$IP ) {

  $IPAddress = [Net.IPAddress]::Parse($IP)

  Return [String]::Join('.',
    $( $IPAddress.GetAddressBytes() | %{
      [Convert]::ToString($_, 2).PadLeft(8, '0') } ))
}

Convert an IP to a 32-bit decimal

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

Function ConvertTo-DecimalIP( [String]$IP ) {

  $IPAddress = [Net.IPAddress]::Parse($IP)
  $i = 3
  $IPAddress.GetAddressBytes() | %{
    $DecimalIP += $_ * [Math]::Pow(256, $i); $i-- }

  Return [UInt32]$DecimalIP
}

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 pair of regular expressions to determine which format is presented.

Function ConvertTo-DottedDecimalIP( [String]$IP ) {

  Switch -RegEx ($IP) {
    "([01]{8}\.){3}[01]{8}" {

      Return [String]::Join('.', $( $IP.Split('.') | %{
        [Convert]::ToInt32($_, 2) } ))
    }
    "\d" {

      $IP = [UInt32]$IP
      $DottedIP = $( For ($i = 3; $i -gt -1; $i--) {
        $Remainder = $IP % [Math]::Pow(256, $i)
        ($IP - $Remainder) / [Math]::Pow(256, $i)
        $IP = $Remainder
       } )

      Return [String]::Join('.', $DottedIP)
    }
    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.

Function ConvertTo-MaskLength( [String]$Mask ) {

  $IPMask = [Net.IPAddress]::Parse($Mask)
  $Bits = "$( $IPMask.GetAddressBytes() | %{
    [Convert]::ToString($_, 2) } )" -Replace "[\s0]"

  Return $Bits.Length
}

Convert a mask length to a subnet mask

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

Function ConvertTo-Mask( [Byte]$MaskLength ) {

  Return ConvertTo-DottedDecimalIP ([Convert]::ToUInt32(
    $(("1" * $MaskLength).PadRight(32, "0")), 2))
}

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.

Function Get-NetworkAddress( [String]$IP, [String]$Mask ) {

  Return ConvertTo-DottedDecimalIP $(
    (ConvertTo-DecimalIP $IP) -BAnd
    (ConvertTo-DecimalIP $Mask))
}

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 C:\> $Value = ConvertTo-DecimalIP 255.255.255.0; $Value
4294967040
PS C:\> $Value.GetType()

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

PS C:\> $Inverted = -BNot $Value; $Inverted
-4294967041
PS C:\> $Inverted.GetType()

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

PS C:\> # Convert to Binary (Base 2)
PS C:\> [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; Back to Binary And.

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. As a result, this is what BAnd did:

1: 11111111 11111111 11111111 11111111 00000000 00000000 00000000 11111111
2: 00000000 00000000 00000000 00000000 11111111 11111111 11111111 11111111
3: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 11111111

Where 1 is the Int64 value from -BNot, 2 is the implicit conversion of [Int32]::MaxValue to Int64, and 3 is the result of the And operation. Finally, the resulting value can be implicitly (or explicitly) converted back to UInt32.

A very long explanation for a very short function.

Function Get-BroadcastAddress( [String]$IP, [String]$Mask ) {

  Return ConvertTo-DottedDecimalIP $(
    (ConvertTo-DecimalIP $IP) -BOr
    ((-BNot (ConvertTo-DecimalIP $Mask)) -BAnd [UInt32]::MaxValue))
}

Get-NetworkInfo

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

Function Get-NetworkInfo( [String]$IP, [String]$Mask ) {
  If ($IP.Contains("/"))
  {
    $Temp = $IP.Split("/")
    $IP = $Temp[0]
    $Mask = $Temp[1]
  }

  If (!$Mask.Contains("."))
  {
    $Mask = ConvertTo-Mask $Mask
  }

  $DecimalIP = ConvertTo-DecimalIP $IP
  $DecimalMask = ConvertTo-DecimalIP $Mask

  $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 $Mask

  $BinaryIP = ConvertTo-BinaryIP $IP; $Private = $False
  Switch -RegEx ($BinaryIP)
  {
    "^1111"  { $Class = "E"; $SubnetBitMap = "1111" }
    "^1110"  { $Class = "D"; $SubnetBitMap = "1110" }
    "^110"   {
      $Class = "C"
      If ($BinaryIP -Match "^11000000.10101000") { $Private = $True } }
    "^10"    {
      $Class = "B"
      If ($BinaryIP -Match "^10101100.0001") { $Private = $True } }
    "^0"     {
      $Class = "A"
      If ($BinaryIP -Match "^00001010") { $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 $Mask
  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( [String]$IP, [String]$Mask ) {
  If ($IP.Contains("/"))
  {
    $Temp = $IP.Split("/")
    $IP = $Temp[0]
    $Mask = $Temp[1]
  }

  If (!$Mask.Contains("."))
  {
    $Mask = ConvertTo-Mask $Mask
  }

  $DecimalIP = ConvertTo-DecimalIP $IP
  $DecimalMask = ConvertTo-DecimalIP $Mask

  $Network = $DecimalIP -BAnd $DecimalMask
  $Broadcast = $DecimalIP -BOr ((-BNot $DecimalMask) -BAnd [UInt32]::MaxValue)

  For ($i = $($Network + 1); $i -lt $Broadcast; $i++) {
    ConvertTo-DottedDecimalIP $i
  }
}

Examples

# Use of Convert Functions
ConvertTo-BinaryIP 192.168.1.1
ConvertTo-DecimalIP 192.168.1.1
ConvertTo-DottedDecimalIP 11000000.10101000.00000001.00000001
ConvertTo-DottedDecimalIP 3232235777
ConvertTo-MaskLength 255.255.128.0
ConvertTo-Mask 17

# Use of Network and Broadcast Address Functions
Get-NetworkAddress 192.168.1.1 255.255.255.0
Get-BroadcastAddress 192.168.1.1 255.255.255.0

# Use of Network Info
Get-NetworkInfo 229.168.1.1 255.255.248.0
Get-NetworkInfo 172.16.1.243 18
Get-NetworkInfo 10.0.0.3/14

# Use of Network Range
Get-NetworkRange 192.168.1.5 255.255.255.248
Get-NetworkRange 172.18.0.23 30
Get-NetworkRange 172.18.0.23/29

No related posts.

Related posts brought to you by Yet Another Related Posts Plugin.

5 Responses to this post.

  1. Posted by Neil Fairall on 23.01.10 at 3:35 pm

    This is beautiful. I have been doing some of this from scratch and you just saved me a lot of time.

    One thing I have noted however is that Int32 can’t handle some of the decimal values generated. Everything works very nicely if you use Int64 instead.

  2. Posted by Chris on 23.01.10 at 3:35 pm

    Thanks :)

    PowerShell 1? It may have some issues with the unsigned integers used by the script, everything was written under PowerShell 2. Otherwise if you happen to have examples of where it’s failing I’ll fix it :)

  3. [...] IP subnet math in PowerShell Tips [...]

  4. Posted by Gareth on 23.01.10 at 3:35 pm

    Great thanks for posting – you just saved me a whole load of pain!!

    I owe you a beer!

    Cheers

    GM

  5. Posted by Andy N on 23.01.10 at 3:35 pm

    These functions have proven supremely useful – I’ve automated a tedious firewall object provisioning process based on your work.. Thanks for sharing!

    Rather thank dot-sourcing, I’ve made a Module from the file by adding ” Export-ModuleMember -Function * ” and saving the script as a .psm1 file in the system Modules directory.

Respond to this post