PowerShell - Get Network ID and Subnet Info

Written on April 14, 2019

I wanted a simple command that would give me the basic subnet information I commonly want. Identifying the Network ID was tedious, as was converting an IP / Subnet to CIDR notation and vice-versa. Now I have one command that can do it all.

Example 1


Get-IPv4Subnet -IPAddress 192.168.153.0 -SubnetMask 255.255.128.0


CidrID       : 192.168.128.0/17
NetworkID    : 192.168.128.0
SubnetMask   : 255.255.128.0
PrefixLength : 17
HostCount    : 32766
FirstHostIP  : 192.168.128.1
LastHostIP   : 192.168.255.254
Broadcast    : 192.168.255.255

Example 2


Get-IPv4Subnet -IPAddress 10.148.72.201 -PrefixLength 26


CidrID       : 10.148.72.192/26
NetworkID    : 10.148.72.192
SubnetMask   : 255.255.255.192
PrefixLength : 26
HostCount    : 62
FirstHostIP  : 10.148.72.193
LastHostIP   : 10.148.72.254
Broadcast    : 10.148.72.255

The source code will be at the end of the post. Additionally, I have create a PowerShell module for this on GitHub.

The Journey


I have recently starting working more with networking, which is not something I have spent a lot of time doing. I was constantly having to use online subnet calculators to translate what subnet IP addresses belonged to. Getting the prefix length from a subnet mask, and the subnet mask from a prefix length was also something I wanted to look into.

Convert subnet mask to prefix length

How does 255.255.255.0 translate to a prefix length of 24? Its pretty simple really; the first 24 bits of the 32 bits in the IPv4 address are 1, and the remaining bits are all 0.

Ex: 11111111111111111111111100000000

If we can get the bytes from the subnet mask, we should be able to determine how many leading 1s there are and get our prefix length.

# Convert to an IPAddress object
$netMaskIP=[IPAddress]'255.255.255.0'

# Convert to byte array
$netMaskIP.GetAddressBytes()

Now that we have a way of getting the bytes of an IP address, we are almost done.

$netMaskIP=[IPAddress]'255.255.255.0'

$binaryString=[String]::Empty
$netMaskIP.GetAddressBytes() | Foreach {
  # combine each
  $binaryString+=[Convert]::ToString($_, 2)
}

# remove the trailing 0s since we only care about the 1s, and get the length
Write-Output $binaryString.TrimEnd('0').Length

Convert prefix length to subnet mask

This one is pretty easy armed with the knowledge we have from the previous example. If the prefix length is 24, that means the first 24 bits are 1s, and the rest are 0.

$prefixLength=24

# create as many 1s as the prefix length
# use PadRight to add the needed 0s required to make it 32 long
('1' * $prefixLength).PadRight(32, '0')

The only thing left is to convert this string to an IP address. We can split the string into 4 separate strings of 8 to make the required 4 bytes. Then convert the byte strings into the integer equivalent.

$bitString=('1' * $prefixLength).PadRight(32,'0')

$ipString=[String]::Empty

# make 1 string combining a string for each byte and convert to int
for($i=0;$i -lt 32;$i+=8){
  $byteString=$bitString.Substring($i,8)
  $ipString+="$([Convert]::ToInt32($byteString, 2))."
}

Write-Output $ipString.TrimEnd('.')

Get network ID

As long as you have an IP address and a prefix length or subnet mask, you can find the network ID. The subnet mask will determine the number of bits assigned to the network. These same bits in the IP address provided will stay the same in the network Id. The remaining bits will be 0s.

If the subnet mask is 255.255.255.128 and the IP is 192.168.24.71:

Mask: 11111111111111111111111110000000

IP: 11000000101010001100000010001110

NetID: 11000000101010001100000010000000

So the network ID is equivalent to all of the 1s that match between the subnet mask, and the provided IP address. So this means that we can do a 'bitwise AND' comparison of the subnet mask and IP address to get the network ID.

If we convert the subnet mask and the IP address to integers, we can do a bitwise AND comparison, and convert the output back into an IP to get our network ID:

# The ConvertIPv4ToInt and ConvertIntToIPv4 functions are  in the source code
$maskInt=ConvertIPv4ToInt -IPv4Address 255.255.255.128
$ipInt=ConvertIPv4ToInt -IPv4Address 192.168.24.71

$netIdInt=$maskInt -band $ipInt
ConvertIntToIPv4 -Integer $netIdInt


Source Code


The main function is of course Get-IPv4Subnet. One other function in here that I like is Convert-IPv4AddressToBinaryString. It will visually represent the binary form of an IP address.

Function Convert-IPv4AddressToBinaryString {
  Param(
    [IPAddress]$IPAddress='0.0.0.0'
  )
  $addressBytes=$IPAddress.GetAddressBytes()

  $strBuilder=New-Object -TypeName Text.StringBuilder
  foreach($byte in $addressBytes){
    $8bitString=[Convert]::ToString($byte,2).PadRight(8,'0')
    [void]$strBuilder.Append($8bitString)
  }
  Write-Output $strBuilder.ToString()
}

Function ConvertIPv4ToInt {
  [CmdletBinding()]
  Param(
    [String]$IPv4Address
  )
  Try{
    $ipAddress=[IPAddress]::Parse($IPv4Address)

    $bytes=$ipAddress.GetAddressBytes()
    [Array]::Reverse($bytes)

    [System.BitConverter]::ToUInt32($bytes,0)
  }Catch{
    Write-Error -Exception $_.Exception `
      -Category $_.CategoryInfo.Category
  }
}

Function ConvertIntToIPv4 {
  [CmdletBinding()]
  Param(
    [uint32]$Integer
  )
  Try{
    $bytes=[System.BitConverter]::GetBytes($Integer)
    [Array]::Reverse($bytes)
    ([IPAddress]($bytes)).ToString()
  }Catch{
    Write-Error -Exception $_.Exception `
      -Category $_.CategoryInfo.Category
  }
}

Function Add-IntToIPv4Address {
  Param(
    [String]$IPv4Address,

    [int64]$Integer
  )
  Try{
    $ipInt=ConvertIPv4ToInt -IPv4Address $IPv4Address `
      -ErrorAction Stop
    $ipInt+=$Integer

    ConvertIntToIPv4 -Integer $ipInt
  }Catch{
    Write-Error -Exception $_.Exception `
      -Category $_.CategoryInfo.Category
  }
}

Function CIDRToNetMask {
  [CmdletBinding()]
  Param(
    [ValidateRange(0,32)]
    [int16]$PrefixLength=0
  )
  $bitString=('1' * $PrefixLength).PadRight(32,'0')

  $strBuilder=New-Object -TypeName Text.StringBuilder

  for($i=0;$i -lt 32;$i+=8){
    $8bitString=$bitString.Substring($i,8)
    [void]$strBuilder.Append("$([Convert]::ToInt32($8bitString,2)).")
  }

  $strBuilder.ToString().TrimEnd('.')
}

Function NetMaskToCIDR {
  [CmdletBinding()]
  Param(
    [String]$SubnetMask='255.255.255.0'
  )
  $byteRegex='^(0|128|192|224|240|248|252|254|255)$'
  $invalidMaskMsg="Invalid SubnetMask specified [$SubnetMask]"
  Try{
    $netMaskIP=[IPAddress]$SubnetMask
    $addressBytes=$netMaskIP.GetAddressBytes()

    $strBuilder=New-Object -TypeName Text.StringBuilder

    $lastByte=255
    foreach($byte in $addressBytes){

      # Validate byte matches net mask value
      if($byte -notmatch $byteRegex){
        Write-Error -Message $invalidMaskMsg `
          -Category InvalidArgument `
          -ErrorAction Stop
      }elseif($lastByte -ne 255 -and $byte -gt 0){
        Write-Error -Message $invalidMaskMsg `
          -Category InvalidArgument `
          -ErrorAction Stop
      }

      [void]$strBuilder.Append([Convert]::ToString($byte,2))
      $lastByte=$byte
    }

    ($strBuilder.ToString().TrimEnd('0')).Length
  }Catch{
    Write-Error -Exception $_.Exception `
      -Category $_.CategoryInfo.Category
  }
}

Function Get-IPv4Subnet {
  [CmdletBinding(DefaultParameterSetName='PrefixLength')]
  Param(
    [Parameter(Mandatory=$true,Position=0)]
    [IPAddress]$IPAddress,

    [Parameter(Position=1,ParameterSetName='PrefixLength')]
    [Int16]$PrefixLength=24,

    [Parameter(Position=1,ParameterSetName='SubnetMask')]
    [IPAddress]$SubnetMask
  )
  Begin{}
  Process{
    Try{
      if($PSCmdlet.ParameterSetName -eq 'SubnetMask'){
        $PrefixLength=NetMaskToCidr -SubnetMask $SubnetMask `
          -ErrorAction Stop
      }else{
        $SubnetMask=CIDRToNetMask -PrefixLength $PrefixLength `
          -ErrorAction Stop
      }
      
      $netMaskInt=ConvertIPv4ToInt -IPv4Address $SubnetMask     
      $ipInt=ConvertIPv4ToInt -IPv4Address $IPAddress
      
      $networkID=ConvertIntToIPv4 -Integer ($netMaskInt -band $ipInt)

      $maxHosts=[math]::Pow(2,(32-$PrefixLength)) - 2
      $broadcast=Add-IntToIPv4Address -IPv4Address $networkID `
        -Integer ($maxHosts+1)

      $firstIP=Add-IntToIPv4Address -IPv4Address $networkID -Integer 1
      $lastIP=Add-IntToIPv4Address -IPv4Address $broadcast -Integer -1

      if($PrefixLength -eq 32){
        $broadcast=$networkID
        $firstIP=$null
        $lastIP=$null
        $maxHosts=0
      }

      $outputObject=New-Object -TypeName PSObject 

      $memberParam=@{
        InputObject=$outputObject;
        MemberType='NoteProperty';
        Force=$true;
      }
      Add-Member @memberParam -Name CidrID -Value "$networkID/$PrefixLength"
      Add-Member @memberParam -Name NetworkID -Value $networkID
      Add-Member @memberParam -Name SubnetMask -Value $SubnetMask
      Add-Member @memberParam -Name PrefixLength -Value $PrefixLength
      Add-Member @memberParam -Name HostCount -Value $maxHosts
      Add-Member @memberParam -Name FirstHostIP -Value $firstIP
      Add-Member @memberParam -Name LastHostIP -Value $lastIP
      Add-Member @memberParam -Name Broadcast -Value $broadcast

      Write-Output $outputObject
    }Catch{
      Write-Error -Exception $_.Exception `
        -Category $_.CategoryInfo.Category
    }
  }
  End{}
}

Again, I have put this source code, along with help, on GitHub.

Example Script


Now a quick example of a script to get NIC configuration information on a computer using this function:

$nics=[Net.NetworkInformation.NetworkInterface]::GetAllNetworkInterfaces()

foreach($interface in $nics){
  if($interface.NetworkInterfaceType -eq 'Loopback'){
    continue
  }
  if(!($interface.Supports('IPv4'))){
    continue
  }
  $ipProperties=$interface.GetIPProperties()

  $ipv4Properties=$ipProperties.GetIPv4Properties()

  $ipProperties.UnicastAddresses | Foreach {
    if(!($_.Address.IPAddressToString)){
      continue
    }
    if($ipProperties.GatewayAddresses){
      $gateway=$ipProperties.GatewayAddresses.Address.IPAddressToString
    }else{
      $gateway=$null
    }

    $subnetInfo=Get-IPv4Subnet -IPAddress $_.Address.IPAddressToString `
      -PrefixLength $_.PrefixLength
    New-Object -TypeName PSObject -Property @{
      InterfaceName=$interface.Name;
      InterfaceType=$interface.NetworkInterfaceType;
      NetworkID=$subnetInfo.NetworkID;
      IPAddress=$_.Address.IPAddressToString;
      SubnetMask=$subnetInfo.SubnetMask;
      CidrID=$subnetInfo.CidrID;
      DnsAddresses=$ipProperties.DnsAddresses.IPAddressToString;
      GatewayAddresses=$gateway;
      DhcpEnabled=$ipv4Properties.IsDhcpEnabled;
    }
  }
}

Thanks for reading

PS> exit