public/Network/Convert-IPCalc.ps1

Function Convert-IPCalc {
    <#
    .SYNOPSIS
        Convert-IPCalc calculates the IP subnet information based upon the entered IP address and subnet.
    .COMPONENT
        Network
    .EXAMPLE
        Convert-IPCalc 10.10.100.5/24
    .EXAMPLE
        Convert-IPCalc -IPAddress 10.100.100.1 -NetMask 255.255.255.0
    .EXAMPLE
        Convert-IPCalc 192.168.0.1/24 -IncludeBinaryOutput
    .EXAMPLE
        Convert-IPCalc 192.168.0.1/24 -IncludeHostList
    .NOTES
        Inspired by Jason Wasser
    #>

    [CmdletBinding()]
    param (
        # Enter the IP address by itself or with CIDR notation.
        [Parameter(Mandatory=$True,Position=1)][string]$IPAddress,
        # Enter the subnet mask information in dotted decimal form.
        [Parameter(Mandatory=$False,Position=2)][string]$Netmask,
        # Include the binary format of the subnet information.
        [switch]$IncludeBinaryOutput,
        # Include List of all hosts.
        [switch]$IncludeHostList

    )
    process {
        # Function to convert IP address string to binary: "1.2.3.4" => "00000001000000100000001100000100"
        function toBinary ($dottedDecimal) {
            return ($dottedDecimal -split "\." | ForEach-Object { [convert]::ToString($_,2).padleft(8,"0") }) -join ""
        }

        # Function to convert IP address to Int32: "172.22.5.0" => 2887124224
        function toInt32 ([IPAddress]$ip) {
            $bytes = $ip.GetAddressBytes()
            if ([BitConverter]::IsLittleEndian) { [Array]::Reverse($bytes)}
            return [BitConverter]::ToUInt32($bytes, 0)
        }

        # Function to convert binary IP address to dotted decimal string: "00000001000000100000001100000100" => "1.2.3.4"
        function toDottedDecimal ($binary) {
            return (0..3 | ForEach-Object { [string]$([convert]::toInt32($binary.substring($_ * 8, 8), 2)) }) -join "."
        }

        # Function to convert CIDR format to binary: 24 => "11111111111111111111111100000000"
        function CidrToBin ([int]$cidr) {
            return "".PadLeft($cidr,'1').PadRight(32,'0')
        }

        # Function to convert network mask to wildcard format: "11111111111111111111111100000000" => 00000000000000000000000011111111
        function NetMasktoWildcard ($wildcard) {
            return $wildcard -replace 1,2 -replace 0,1 -replace 2,0
        }


        # Check the IP Address format.
        if ($IPAddress -match '^(?<ip>([0-9]{1,3}\.){3}[0-9]{1,3})(/(?<cidr>[1-9]|[12][0-9]|3[012]))?$') { 
            $IPAddress = $Matches['ip'] 
        } else { 
            throw 'The input of the IP Address is invalid!' 
        }
        $cidr = [convert]::ToInt32($Matches['cidr'])

        # check if IP Address is valid.
        $IPAddress.split(".") | ForEach-Object {
            if ([int]$_ -lt 0 -or [int]$_ -gt 255) { throw "IP Address is invalid!" }
        }
        $ipBinary = toBinary $IPAddress

        # check, if the Netmask and CIDR are both set.
        if ($Netmask -and $cidr -gt 0) { throw 'You can not set both Netmask and CIDR!' }

        # check if neither Netmask nor CIDR is set.
        if (!$Netmask -and $cidr -eq 0) { throw 'You must set either Netmask or CIDR!' }
        
        # check if Netmask is valid.
        $smBinary = if ($Netmask) { toBinary $Netmask } else { CidrToBin($cidr) }
        if ($smBinary -notmatch '^1{1,32}0{0,31}$' -or $smBinary.length -ne 32) { throw "Subnet Mask is invalid!" } # Validate the subnet mask
        
        if (!$Netmask) { $Netmask = toDottedDecimal($smBinary) }
        $wildcardbinary = NetMasktoWildcard $smBinary
        $netBits = $smBinary.indexOf("0") # First determine the location of the first zero in the subnet mask in binary (if any)

        # If there is a 0 found then the subnet mask is less than 32 (CIDR).
        if ($netBits -ne -1) {
            $cidr = $netBits
            #identify subnet boundaries
            $networkIDbinary = $ipBinary.substring(0,$netBits).padright(32,"0")
            $networkID = toDottedDecimal $networkIDbinary
            $firstAddressBinary = $($ipBinary.substring(0,$netBits).padright(31,"0") + "1")
            $firstAddress = toDottedDecimal $firstAddressBinary
            $lastAddressBinary = $($ipBinary.substring(0,$netBits).padright(31,"1") + "0")
            $lastAddress = toDottedDecimal $lastAddressBinary
            $broadCastbinary = $ipBinary.substring(0,$netBits).padright(32,"1")
            $broadCast = toDottedDecimal $broadCastbinary
            $wildcard = toDottedDecimal $wildcardbinary
            $Hostspernet = ([convert]::ToInt32($broadCastbinary,2) - [convert]::ToInt32($networkIDbinary,2)) - 1

        } else { # Subnet mask is 32 (CIDR)
            #identify subnet boundaries
            $networkID = toDottedDecimal $ipBinary
            $networkIDbinary = $ipBinary
            $firstAddress = toDottedDecimal $ipBinary
            $firstAddressBinary = $ipBinary
            $lastAddress = toDottedDecimal $ipBinary
            $lastAddressBinary = $ipBinary
            $broadCast = toDottedDecimal $ipBinary
            $broadCastbinary = $ipBinary
            $wildcard = toDottedDecimal $wildcardbinary
            $Hostspernet = 1
            $cidr = 32
        }

        # Output custom object with or without binary information.
        $Output = [PSCustomObject]@{
            Address = $IPAddress
            Address32 = toInt32 ([IPAddress]$IPAddress)
            Netmask = $Netmask
            Wildcard = $wildcard
            Network = "$networkID/$cidr"
            Broadcast = $broadCast
            HostMin = $firstAddress
            HostMax = $lastAddress
            'Hosts/Net' = $Hostspernet
        }
        if ($IncludeBinaryOutput) {
            $Output | Add-Member -MemberType NoteProperty -Name 'AddressBinary'     -Value $ipBinary
            $Output | Add-Member -MemberType NoteProperty -Name 'NetmaskBinary'     -Value $smBinary
            $Output | Add-Member -MemberType NoteProperty -Name 'WildcardBinary'    -Value $wildcardbinary
            $Output | Add-Member -MemberType NoteProperty -Name 'NetworkBinary'     -Value $networkIDbinary
            $Output | Add-Member -MemberType NoteProperty -Name 'HostMinBinary'     -Value $firstAddressBinary
            $Output | Add-Member -MemberType NoteProperty -Name 'HostMaxBinary'     -Value $lastAddressBinary
            $Output | Add-Member -MemberType NoteProperty -Name 'BroadcastBinary'   -Value $broadCastbinary
        }
        if ($IncludeHostList) {
            $hostList = New-Object System.Collections.Generic.List[System.Object]  # @()
            for ($ip = [Convert]::ToInt64($firstAddressBinary, 2); $ip -le [Convert]::ToInt64($lastAddressBinary, 2); $ip++) {
                $hostList.Add((toDottedDecimal ([Convert]::ToString($ip,2)).padleft(32,"0")))
            }
            $Output | Add-Member -MemberType NoteProperty -Name 'HostList' -Value $hostList
        }
        return $Output
    }
}