functions/Get-IPCalc.ps1

Function Get-IPCalc {
    <#
    .SYNOPSIS
        Advanced IP subnet calculator that provides detailed network information.
 
    .DESCRIPTION
        IP Calculator for calculation IP Subnet. Provides comprehensive network information including binary representations,
        subnet boundaries, and advanced subnet manipulation methods.
 
    .PARAMETER CIDR
        Specifies the network in CIDR notation (e.g., "192.168.1.0/24")
 
    .PARAMETER IPAddress
        Specifies the IP address to analyze
 
    .PARAMETER Mask
        Specifies the subnet mask (e.g., "255.255.255.0")
 
    .PARAMETER PrefixLength
        Specifies the network prefix length (0-32)
 
    .PARAMETER WildCard
        Specifies the wildcard mask
 
    .EXAMPLE
        Get-IPCalc -CIDR 192.168.0.0/24
 
        Shows complete subnet information including:
        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
 
    .EXAMPLE
        Get-IPCalc -IPAddress 192.168.3.0 -PrefixLength 23
 
        Demonstrates calculation with IP address and prefix length, showing a larger subnet (512 IPs)
 
    .EXAMPLE
        (Get-IPCalc 192.168.99.58/30).GetIPArray()
 
        Returns all IP addresses in the specified subnet:
        192.168.99.56
        192.168.99.57
        192.168.99.58
        192.168.99.59
 
    .EXAMPLE
        (Get-IPCalc 192.168.99.56/28).Compare('192.168.99.50')
 
        Demonstrates the Compare method to check if an IP belongs to a subnet
 
    .EXAMPLE
        (Get-IPCalc 192.168.0.0/25).Overlaps('192.168.0.0/27')
 
        Shows how to check for overlapping subnets
 
    .NOTES
        Advanced Methods Available:
        - Add(): Add IP addresses within the subnet
        - Compare(): Compare IP addresses within the subnet
        - Overlaps(): Check for overlapping subnets
        - GetIParray(): Get all IP addresses in the range
        - isLocal(): Check if IP is on local network
        - GetLocalRoute(): Get routing information for specific IPs
    #>

    [CmdletBinding(DefaultParameterSetName = 'CIDR')]
    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
    )

    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-IPCalc -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-IPCalc -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-IPCalc -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-IPCalc -CIDR $_.DestinationPrefix).Compare($IP) }) | Sort-Object -Property @{Expression = { (Get-IPCalc -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

    $Object
}

Function IsIpAddressInRange {
<#
.EXAMPLE
IsIpAddressInRange '192.168.1.10' '192.168.1.0-192.168.1.100'
True
 
#>

    param(
        [string] $ipAddress,
        [string] $Range
        
    )
    $arrRange = $Range.Split("-")
    [string] $fromAddress = $arrRange[0]
    [string] $toAddress = $arrRange[1]
    $ip = [system.net.ipaddress]::Parse($ipAddress).GetAddressBytes()
    [array]::Reverse($ip)
    $ip = [system.BitConverter]::ToUInt32($ip, 0)
     
    $from = [system.net.ipaddress]::Parse($fromAddress).GetAddressBytes()
    [array]::Reverse($from)
    $from = [system.BitConverter]::ToUInt32($from, 0)
     
    $to = [system.net.ipaddress]::Parse($toAddress).GetAddressBytes()
    [array]::Reverse($to)
    $to = [system.BitConverter]::ToUInt32($to, 0)
     
    $from -le $ip -and $ip -le $to
}