Skip to main content

Calculate IP subnet information including network address, broadcast address, subnet mask, and other subnet details.

function Get-IPSubnet
{
    <#
    .DESCRIPTION
        Calculate IP subnet information including network address, broadcast address, subnet mask, and other subnet details.

    .EXAMPLE
        PS > Get-IPSubnet -CIDR 192.168.0.0/24

        IP           : 192.168.0.0
        Mask         : 255.255.255.0
        PrefixLength : 24
        WildCard     : 0.0.0.255
        IPcount      : 256
        Subnet       : 192.168.0.0
        Broadcast    : 192.168.0.255
        CIDR         : 192.168.0.0/24
        ToDecimal    : 3232235520
        IPBin        : 11000000.10101000.00000000.00000000
        MaskBin      : 11111111.11111111.11111111.00000000
        SubnetBin    : 11000000.10101000.00000000.00000000
        BroadcastBin : 11000000.10101000.00000000.11111111

        Calculate subnet information using CIDR notation.

    .EXAMPLE
        PS > Get-IPSubnet -IPAddress 192.168.0.0 -Mask 255.255.255.0

        IP           : 192.168.0.0
        Mask         : 255.255.255.0
        PrefixLength : 24
        WildCard     : 0.0.0.255
        IPcount      : 256
        Subnet       : 192.168.0.0
        Broadcast    : 192.168.0.255
        CIDR         : 192.168.0.0/24
        ToDecimal    : 3232235520
        IPBin        : 11000000.10101000.00000000.00000000
        MaskBin      : 11111111.11111111.11111111.00000000
        SubnetBin    : 11000000.10101000.00000000.00000000
        BroadcastBin : 11000000.10101000.00000000.11111111

        Calculate subnet information using IP address and subnet mask.

    .EXAMPLE
        PS > Get-IPSubnet -IPAddress 192.168.3.0 -PrefixLength 23

        IP           : 192.168.3.0
        Mask         : 255.255.254.0
        PrefixLength : 23
        WildCard     : 0.0.1.255
        IPcount      : 512
        Subnet       : 192.168.2.0
        Broadcast    : 192.168.3.255
        CIDR         : 192.168.2.0/23
        ToDecimal    : 3232236288
        IPBin        : 11000000.10101000.00000011.00000000
        MaskBin      : 11111111.11111111.11111110.00000000
        SubnetBin    : 11000000.10101000.00000010.00000000
        BroadcastBin : 11000000.10101000.00000011.11111111

        Calculate subnet information using IP address and prefix length.

    .EXAMPLE
        PS > (Get-IPSubnet -IPAddress (Get-IPSubnet 192.168.99.56/28).Subnet -PrefixLength 32).Add(1).IPAddress
        192.168.99.49

        Add 1 to the subnet network address to get the next IP address.

    .EXAMPLE
        PS > (Get-IPSubnet 192.168.99.56/28).Compare('192.168.99.50')
        True

        Test if an IP address belongs to a specific subnet.

    .EXAMPLE
        PS > (Get-IPSubnet 192.168.99.58/30).GetIPArray()

        192.168.99.56
        192.168.99.57
        192.168.99.58
        192.168.99.59

        Get all IP addresses within a subnet range.

    .EXAMPLE
        PS > Get-NetRoute -AddressFamily IPv4 | ? {(Get-IPSubnet -CIDR $_.DestinationPrefix).Compare('8.8.8.8')} | Sort-Object -Property @(@{Expression = {$_.DestinationPrefix.Split('/')[1]}; Asc = $false},'RouteMetric','ifMetric')

        ifIndex DestinationPrefix                              NextHop                                  RouteMetric ifMetric PolicyStore
        ------- -----------------                              -------                                  ----------- -------- -----------
        22      0.0.0.0/0                                      192.168.0.1                                        0 25       ActiveStore

        Find the routing table entry that would be used to reach a specific IP address.

    .EXAMPLE
        PS > (Get-IPSubnet 0.0.0.0/0).GetLocalRoute('127.0.0.1')

        ifIndex DestinationPrefix                              NextHop                                  RouteMetric ifMetric PolicyStore
        ------- -----------------                              -------                                  ----------- -------- -----------
        1       127.0.0.0/8                                    0.0.0.0                                          256 75       ActiveStore

        Find the most specific local route for an IP address.

    .EXAMPLE
        PS > (Get-IPSubnet 0.0.0.0/0).GetLocalRoute('127.0.0.1', 2)

        ifIndex DestinationPrefix                              NextHop                                  RouteMetric ifMetric PolicyStore
        ------- -----------------                              -------                                  ----------- -------- -----------
        1       127.0.0.1/32                                   0.0.0.0                                          256 75       ActiveStore
        1       127.0.0.0/8                                    0.0.0.0                                          256 75       ActiveStore

        Get multiple local routes for an IP address, ordered by specificity.

    .EXAMPLE
        PS > (Get-IPSubnet 192.168.0.0/25).Overlaps('192.168.0.0/27')
        True

        Check if two subnets overlap with each other.

    .LINK
        https://jonlabelle.com/snippets/view/powershell/ip-subnet-calculator

    .LINK
        https://github.com/jonlabelle/pwsh-profile/blob/main/Functions/Get-IPSubnet.ps1

    .NOTES
        Original Author: saw-friendship@yandex.ru
        Description: IP Subnet Calculator WildCard CIDR
        URL: https://sawfriendship.wordpress.com/
    #>
    [CmdletBinding(DefaultParameterSetName = 'CIDR')]
    [OutputType('NetWork.IPCalcResult')]
    param(
        [Parameter(Mandatory = $true, ParameterSetName = 'CIDR', ValueFromPipelineByPropertyName = $true, Position = 0)]
        [ValidateScript({ $array = ($_ -split '\\|\/'); ($array[0] -as [IPAddress]).AddressFamily -eq [System.Net.Sockets.AddressFamily]::InterNetwork -and [string[]](0..32) -contains $array[1] })]
        [Alias('DestinationPrefix')]
        [string]$CIDR,

        [parameter(ParameterSetName = 'Mask')][parameter(ParameterSetName = ('PrefixLength'), ValueFromPipelineByPropertyName = $true)][parameter(ParameterSetName = ('WildCard'))]
        [ValidateScript({ ($_ -as [IPAddress]).AddressFamily -eq [System.Net.Sockets.AddressFamily]::InterNetwork })]
        [Alias('IP')]
        [IPAddress]$IPAddress,

        [Parameter(Mandatory = $true, ParameterSetName = 'Mask')]
        [IPAddress]$Mask,

        [parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'PrefixLength')]
        [ValidateRange(0, 32)]
        [int]$PrefixLength,

        [parameter(Mandatory = $true, ParameterSetName = 'WildCard')]
        [IPAddress]$WildCard
    )

    process
    {
        if ($CIDR)
        {
            [IPAddress]$IPAddress = ($CIDR -split '\\|\/')[0]
            [int]$PrefixLength = ($CIDR -split '\\|\/')[1]
            [IPAddress]$Mask = [IPAddress]([string](4gb - ([System.Math]::Pow(2, (32 - $PrefixLength)))))
        }

        if ($PrefixLength -and !$Mask)
        {
            [IPAddress]$Mask = [IPAddress]([string](4gb - ([System.Math]::Pow(2, (32 - $PrefixLength)))))
        }

        if ($WildCard)
        {
            [IPAddress]$Mask = $WildCard.GetAddressBytes().ForEach({ 255 - $_ }) -join '.'
        }

        if (!$PrefixLength -and $Mask)
        {
            $PrefixLength = 32 - ($Mask.GetAddressBytes().ForEach({ [System.Math]::Log((256 - $_), 2) }) | Measure-Object -Sum).Sum
        }

        [int[]]$splitIPAddress = $IPAddress.GetAddressBytes()
        [int64]$toDecimal = $splitIPAddress[0] * 16mb + $splitIPAddress[1] * 64kb + $splitIPAddress[2] * 256 + $splitIPAddress[3]

        [int[]]$splitMask = $Mask.GetAddressBytes()
        $ipBin = ($splitIPAddress.ForEach({ [System.Convert]::ToString($_, 2).PadLeft(8, '0') })) -join '.'
        $maskBin = ($splitMask.ForEach({ [System.Convert]::ToString($_, 2).PadLeft(8, '0') })) -join '.'

        if ((($maskBin -replace '\.').TrimStart('1').Contains('1')) -and (!$WildCard))
        {
            Write-Warning 'Mask Length error, you can try put WildCard'; break
        }
        if (!$WildCard)
        {
            [IPAddress]$WildCard = $splitMask.ForEach({ 255 - $_ }) -join '.'
        }
        if ($WildCard)
        {
            [int[]]$splitWildCard = $WildCard.GetAddressBytes()
        }

        [IPAddress]$subnet = $IPAddress.Address -band $Mask.Address
        [int[]]$splitSubnet = $subnet.GetAddressBytes()
        [string]$subnetBin = $splitSubnet.ForEach({ [System.Convert]::ToString($_, 2).PadLeft(8, '0') }) -join '.'
        [IPAddress]$broadcast = @(0..3).ForEach({ [int]($splitSubnet[$_]) + [int]($splitWildCard[$_]) }) -join '.'
        [int[]]$splitBroadcast = $broadcast.GetAddressBytes()
        [string]$broadcastBin = $splitBroadcast.ForEach({ [System.Convert]::ToString($_, 2).PadLeft(8, '0') }) -join '.'
        [string]$CIDR = "$($subnet.IPAddressToString)/$PrefixLength"
        [int64]$ipCount = [System.Math]::Pow(2, $(32 - $PrefixLength))

        $object = [PSCustomObject][Ordered]@{
            IPAddress = $IPAddress.IPAddressToString
            Mask = $Mask.IPAddressToString
            PrefixLength = $PrefixLength
            WildCard = $WildCard.IPAddressToString
            IPcount = $ipCount
            Subnet = $subnet
            Broadcast = $broadcast
            CIDR = $CIDR
            ToDecimal = $toDecimal
            IPBin = $ipBin
            MaskBin = $maskBin
            SubnetBin = $subnetBin
            BroadcastBin = $broadcastBin
            PSTypeName = 'NetWork.IPCalcResult'
        }

        [string[]]$defaultProperties = @('IPAddress', 'Mask', 'PrefixLength', 'WildCard', 'Subnet', 'Broadcast', 'CIDR', 'ToDecimal')

        Add-Member -InputObject $object -MemberType AliasProperty -Name IP -Value IPAddress

        Add-Member -InputObject $object -MemberType:ScriptMethod -Name Add -Value {
            param([int]$add, [int]$PrefixLength = $This.PrefixLength)
            Get-IPSubnet -IPAddress ([IPAddress]([String]$($This.ToDecimal + $add))).IPAddressToString -PrefixLength $PrefixLength
        }

        Add-Member -InputObject $object -MemberType:ScriptMethod -Name Compare -Value {
            param ([Parameter(Mandatory = $true)][IPAddress]$ip)
            $ipBin = -join (($ip)).GetAddressBytes().ForEach({ [System.Convert]::ToString($_, 2).PadLeft(8, '0') })
            $subnetBin = $This.SubnetBin.Replace('.', '')
            for ($i = 0; $i -lt $This.PrefixLength; $i += 1) { if ($ipBin[$i] -ne $subnetBin[$i]) { return $false } }
            return $true
        }

        Add-Member -InputObject $object -MemberType:ScriptMethod -Name Overlaps -Value {
            param ([Parameter(Mandatory = $true)][string]$cidr = $This.CIDR)
            $calc = Get-IPSubnet -Cidr $cidr
            $This.Compare($calc.Subnet) -or $This.Compare($calc.Broadcast)
        }

        Add-Member -InputObject $object -MemberType:ScriptMethod -Name GetIParray -Value {
            $w = @($This.Subnet.GetAddressBytes()[0]..$This.Broadcast.GetAddressBytes()[0])
            $x = @($This.Subnet.GetAddressBytes()[1]..$This.Broadcast.GetAddressBytes()[1])
            $y = @($This.Subnet.GetAddressBytes()[2]..$This.Broadcast.GetAddressBytes()[2])
            $z = @($This.Subnet.GetAddressBytes()[3]..$This.Broadcast.GetAddressBytes()[3])
            $w.ForEach({ $wi = $_; $x.ForEach({ $xi = $_; $y.ForEach({ $yi = $_; $z.ForEach({ $zi = $_; $wi, $xi, $yi, $zi -join '.' }) }) }) })
        }

        Add-Member -InputObject $object -MemberType:ScriptMethod -Name isLocal -Value {
            param ([Parameter(Mandatory = $true)][IPAddress]$ip = $This.IPAddress)
            [bool](@(Get-NetIPAddress -AddressFamily IPv4 -AddressState Preferred).Where({ (Get-IPSubnet -IPAddress $_.IPAddress -PrefixLength $_.PrefixLength).Compare($ip) }).Count)
        }

        Add-Member -InputObject $object -MemberType:ScriptMethod -Name GetLocalRoute -Value {
            param ([Parameter(Mandatory = $true)][IPAddress]$ip = $This.IPAddress, [int]$count = 1)
            @(Get-NetRoute -AddressFamily IPv4).Where({ (Get-IPSubnet -CIDR $_.DestinationPrefix).Compare($ip) }) | Sort-Object -Property @{Expression = { (Get-IPSubnet -CIDR $_.DestinationPrefix).PrefixLength } } -Descending | Select-Object -First $count
        }

        Add-Member -InputObject $object -MemberType:ScriptMethod -Force -Name ToString -Value {
            $This.CIDR
        }

        $psPropertySet = New-Object -TypeName System.Management.Automation.PSPropertySet -ArgumentList @('DefaultDisplayPropertySet', $defaultProperties)
        $psStandardMembers = [System.Management.Automation.PSMemberInfo[]]$psPropertySet
        Add-Member -InputObject $object -MemberType MemberSet -Name PSStandardMembers -Value $psStandardMembers

        Write-Output -InputObject $object
    }
}