Skip to content

More IPv4 subnet maths with PowerShell

I’ve been extended and upgrading my library of network functions for some time now, I thought I’d post the functions that work directly with my original collection.

These functions are also included in my NetShell module, I need to post an update to that very soon as well.

ConvertTo-HexIP

Sometimes being able to convert to a hexadecimal format is useful, this little function does exactly that. The body is very simple; it uses the built in formatters.

Function ConvertTo-HexIP {
  <#
    .Synopsis
      Converts a dotted decimal IP address into a hexadecimal string.
    .Description
      ConvertTo-HexIP takes a dotted decimal IP and returns a single hexadecimal string value.
    .Parameter IPAddress
      An IP Address to convert.
  #>

  [CmdLetBinding()]
  Param(
    [Parameter(Mandatory = $True, Position = 0, ValueFromPipeline = $True)]
    [Net.IPAddress]$IPAddress
  )

  Process {
    "$($IPAddress.GetAddressBytes() | ForEach-Object { '{0:x2}' -f $_ })" -Replace '\s'
  }
}

ConvertFrom-HexIP

And back again, using System.Convert.ToUInt32 with a base-16 value to get to UInt32, then a call to ConvertTo-DottedDecimalIP (from the original set).

Function ConvertFrom-HexIP {
  <#
    .Synopsis
      Converts a hexadecimal IP address into a dotted decimal string.
    .Description
      ConvertFrom-HexIP takes a hexadecimal string and returns a dotted decimal IP address.
    .Parameter IPAddress
      An IP Address to convert.
  #>

  [CmdLetBinding()]
  Param(
    [Parameter(Mandatory = $True, Position = 0, ValueFromPipeline = $True)]
    [ValidatePattern('^[0-9a-f]{8}$')]
    [String]$IPAddress
  )

  Process {
    ConvertTo-DottedDecimalIP ([Convert]::ToUInt32($IPAddress, 16))
  }
}

Get-NetworkSummary

Get-NetworkSummary gets a bit of an improvement.

Function Get-NetworkSummary {
  <#
    .Synopsis
      Generates a summary of a network range
    .Description
      Get-NetworkSummary uses most of the IP conversion CmdLets to provide a summary of a network
      range from any IP address in the range and a subnet mask.
    .Parameter IPAddress
      Any IP address within the network range.
    .Parameter Network
      A network description in the format 1.2.3.4/24
    .Parameter SubnetMask
      The subnet mask for the network.
  #>

  [CmdLetBinding(DefaultParameterSetName = "IPAndMask")]
  Param(
    [Parameter(Mandatory = $True, Position = 0, ParameterSetName = "IPAndMask", ValueFromPipeline = $True)]
    [Net.IPAddress]$IPAddress,

    [Parameter(Mandatory = $True, Position = 1, ParameterSetName = "IPAndMask")]
    [Alias("Mask")]
    [Net.IPAddress]$SubnetMask,

    [Parameter(Mandatory = $True, ParameterSetName = "CIDRNotation", ValueFromPipeline = $True)]
    [ValidatePattern('^(\d{1,3}\.){3}\d{1,3}[\\/]\d{1,2}$')]
    [String]$Network
  )

  Process {
    If ($PsCmdLet.ParameterSetName -eq 'CIDRNotation') {
      $Temp = $Network.Split("\/")
      $IPAddress = $Temp[0]
      $SubnetMask = ConvertTo-Mask $Temp[1]
    }

    $DecimalIP = ConvertTo-DecimalIP $IPAddress
    $DecimalMask = ConvertTo-DecimalIP $SubnetMask
    $DecimalNetwork =  $DecimalIP -BAnd $DecimalMask
    $DecimalBroadcast = $DecimalIP -BOr ((-BNot $DecimalMask) -BAnd [UInt32]::MaxValue)

    $NetworkSummary = New-Object PSObject -Property @{
      "NetworkAddress"   = (ConvertTo-DottedDecimalIP $DecimalNetwork);
      "NetworkDecimal"   = $DecimalNetwork
      "BroadcastAddress" = (ConvertTo-DottedDecimalIP $DecimalBroadcast);
      "BroadcastDecimal" = $DecimalBroadcast
      "Mask"             = $SubnetMask;
      "MaskLength"       = (ConvertTo-MaskLength $SubnetMask);
      "MaskHexadecimal"  = (ConvertTo-HexIP $SubnetMask);
      "HostRange"        = "";
      "NumberOfHosts"    = ($DecimalBroadcast - $DecimalNetwork - 1);
      "Class"            = "";
      "IsPrivate"        = $False}

    If ($NetworkSummary.MaskLength -lt 31) {
      $NetworkSummary.HostRange = [String]::Format("{0} - {1}",
        (ConvertTo-DottedDecimalIP ($DecimalNetwork + 1)),
        (ConvertTo-DottedDecimalIP ($DecimalBroadcast - 1)))
    }

    Switch -RegEx ($(ConvertTo-BinaryIP $IPAddress)) {
      "^1111"              { $NetworkSummary.Class = "E" }
      "^1110"              { $NetworkSummary.Class = "D" }
      "^11000000.10101000" { $NetworkSummary.Class = "C"; $NetworkSummary.IsPrivate = $True }
      "^110"               { $NetworkSummary.Class = "C" }
      "^10101100.0001"     { $NetworkSummary.Class = "B"; $NetworkSummary.IsPrivate = $True }
      "^10"                { $NetworkSummary.Class = "B" }
      "^00001010"          { $NetworkSummary.Class = "A"; $NetworkSummary.IsPrivate = $True }
      "^0"                 { $NetworkSummary.Class = "A" }
    }   

    Return $NetworkSummary
  }
}

Get-NetworkRange

And so does Get-NetworkRange.

Function Get-NetworkRange {
  <#
    .Synopsis
      Generates IP addresses within the specified network.
    .Description
      Get-NetworkRange finds the network and broadcast address as decimal values then starts a
      counter between the two, returning Net.IPAddress for each.
    .Parameter IPAddress
      Any IP address within the network range.
    .Parameter Network
      A network description in the format 1.2.3.4/24
    .Parameter SubnetMask
      The subnet mask for the network.
  #>

  [CmdLetBinding(DefaultParameterSetName = "IPAndMask")]
  Param(
    [Parameter(Mandatory = $True, Position = 0, ParameterSetName = "IPAndMask", ValueFromPipeline = $True)]
    [Net.IPAddress]$IPAddress, 

    [Parameter(Mandatory = $True, Position = 1, ParameterSetName = "IPAndMask")]
    [Alias("Mask")]
    [Net.IPAddress]$SubnetMask,

    [Parameter(Mandatory = $True, ParameterSetName = "CIDRNotation", ValueFromPipeline = $True)]
    [ValidatePattern('^(\d{1,3}\.){3}\d{1,3}[\\/]\d{1,2}$')]
    [String]$Network
  )

  Process {
    If ($PsCmdLet.ParameterSetName -eq 'CIDRNotation') {
      $Temp = $Network.Split("\/")
      $IPAddress = $Temp[0]
      $SubnetMask = ConvertTo-Mask $Temp[1]
    }

    $DecimalIP = ConvertTo-DecimalIP $IPAddress
    $DecimalMask = ConvertTo-DecimalIP $SubnetMask

    $DecimalNetwork = $DecimalIP -BAnd $DecimalMask
    $DecimalBroadcast = $DecimalIP -BOr ((-BNot $DecimalMask) -BAnd [UInt32]::MaxValue)

    For ($i = $($DecimalNetwork + 1); $i -lt $DecimalBroadcast; $i++) {
      ConvertTo-DottedDecimalIP $i
    }
  }
}

Get-Subnets

I ran into a need to calculate a list of subnets of a specific length within a super-net, this function does just that.

Function Get-Subnets {
  <#
    .Synopsis
      Generates a list of subnets for a given network range
    .Description
      Generates a list of subnets for a given network range using either the address class or a
      user-specified value.
    .Parameter NetworkAddress
      Any address in the supernet range.
    .Parameter SubnetMask
      The desired mask, determines the size of the resulting subnet. Must be a valid subnet mask.
    .Parameter SupernetLength
      By default Get-Subnets uses the address class to determine the size of the supernet. Where the
      supernet describes the range of addresses being split.
  #>

  [CmdLetBinding(DefaultParameterSetName = "PS1")]
  Param(
    [Parameter(Mandatory = $True, Position = 0, ParameterSetName = "IPAndMask", ValueFromPipeline = $True)]
    [Net.IPAddress]$IPAddress, 

    [Parameter(Mandatory = $True, Position = 1, ParameterSetName = "IPAndMask")]
    [Alias("Mask")]
    [Net.IPAddress]$SubnetMask,

    [Parameter(Mandatory = $True, ParameterSetName = "CIDRNotation", ValueFromPipeline = $True)]
    [ValidatePattern('^(\d{1,3}\.){3}\d{1,3}[\\/]\d{1,2}$')]
    [String]$Network,

    [ValidateRange(1, 32)]
    [UInt32]$SupernetLength
  )

  Process {
    If ($PsCmdLet.ParameterSetName -eq 'CIDRNotation') {
      $Temp = $Network.Split("\/")
      $IPAddress = $Temp[0]
      $SubnetMask = ConvertTo-Mask $Temp[1]
    } Else {
      $SubnetLength = ConvertTo-MaskLength $SubnetMask
    }

    If (!$SupernetLength) {
      $SupernetLength = Switch -RegEx ($(ConvertTo-BinaryIP $IPAddress)) {
        "^110"  { 24 }
        "^10"   { 16 }
        "^0"    { 8 }
        default { 24 }
      }
    }
    If ($SupernetLength -gt $SubnetLength) {
      Write-Error "Subnet is larger than supernet. Aborting"
    }
    $SupernetMask = ConvertTo-Mask $SupernetLength

    $NumberOfNets = [Math]::Pow(2, ($SubnetLength - $SupernetLength))
    $NumberOfAddresses = [Math]::Pow(2, (32 - $SubnetLength))

    $SupernetAddress = Get-NetworkAddress $IPAddress $SupernetMask
    $DecimalAddress = ConvertTo-DecimalIP $SupernetAddress

    For ($i = 0; $i -lt $NumberOfNets; $i++) {
      $NetworkAddress = ConvertTo-DottedDecimalIP $DecimalAddress 

      "" | Select-Object @{n='NetworkAddress';e={ $NetworkAddress }},
        @{n='BroadcastAddress';e={ Get-BroadcastAddress $NetworkAddress $SubnetMask }},
        @{n='SubnetMask';e={ $SubnetMask }},
        @{n='HostAddresses';e={
          $NumberOfHosts = $NumberOfAddresses - 2
          If ($NumberOfHosts -lt 0) { 0 } Else { $NumberOfHosts } }}

      $DecimalAddress += $NumberOfAddresses
    }
  }
}

No related posts.

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

Post a Comment

Your email is never published nor shared.