public/Network/Convert-IPCalc.ps1

Function Convert-IPCalc {
    <#
    .SYNOPSIS
        Calculate IP subnet information
    .COMPONENT
        Network
    .DESCRIPTION
        This function calculates the IP subnet information based upon the entered IP address and subnet.
        It can accept either CIDR notation or a subnet mask in dotted decimal format.
        The output includes details such as the network address, broadcast address, host range, and the number of hosts per subnet.
        Optional parameters allow for the inclusion of binary representations of the IP addresses and a list of all possible host addresses within the subnet.
    .EXAMPLE
        PS> Convert-IPCalc 10.10.100.5/24
 
        Address : 10.10.100.5
        Address32 : 168453125
        Netmask : 255.255.255.0
        Wildcard : 0.0.0.255
        Network : 10.10.100.0/24
        Broadcast : 10.10.100.255
        HostMin : 10.10.100.1
        HostMax : 10.10.100.254
        Hosts/Net : 254
    .EXAMPLE
        PS> Convert-IPCalc -IPAddress 10.100.100.1 -NetMask 255.255.255.0
 
        Address : 10.100.100.1
        Address32 : 174351361
        Netmask : 255.255.255.0
        Wildcard : 0.0.0.255
        Network : 10.100.100.0/24
        Broadcast : 10.100.100.255
        HostMin : 10.100.100.1
        HostMax : 10.100.100.254
        Hosts/Net : 254
    .EXAMPLE
        PS> Convert-IPCalc 192.168.0.1/24 -IncludeBinaryOutput
 
        Address : 192.168.0.1
        Address32 : 3232235521
        Netmask : 255.255.255.0
        Wildcard : 0.0.0.255
        Network : 192.168.0.0/24
        Broadcast : 192.168.0.255
        HostMin : 192.168.0.1
        HostMax : 192.168.0.254
        Hosts/Net : 254
        AddressBinary : 11000000101010000000000000000001
        NetmaskBinary : 11111111111111111111111100000000
        WildcardBinary : 00000000000000000000000011111111
        NetworkBinary : 11000000101010000000000000000000
        HostMinBinary : 11000000101010000000000000000001
        HostMaxBinary : 11000000101010000000000011111110
        BroadcastBinary : 11000000101010000000000011111111
    .EXAMPLE
        PS> Convert-IPCalc 192.168.0.1/24 -IncludeHostList
 
        Address : 192.168.0.1
        Address32 : 3232235521
        Netmask : 255.255.255.0
        Wildcard : 0.0.0.255
        Network : 192.168.0.0/24
        Broadcast : 192.168.0.255
        HostMin : 192.168.0.1
        HostMax : 192.168.0.254
        Hosts/Net : 254
        HostList : {192.168.0.1, 192.168.0.2, 192.168.0.3, 192.168.0.4…}
    .NOTES
        Inspired by Jason Wasser
    #>

    [CmdletBinding(SupportsShouldProcess, HelpUri="https://github.com/pagebox/brickBOX/wiki/Convert-IPCalc")]
    [OutputType([PSCustomObject])]
    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
    }
}

# SIG # Begin signature block
# MIIFsAYJKoZIhvcNAQcCoIIFoTCCBZ0CAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCAZfzO0DJumZZJY
# +elxpDcmghh0D9tkYFzo33nK8qgZoKCCAxwwggMYMIICAKADAgECAhB9XzvMF9qV
# mE6NV/UQOTeSMA0GCSqGSIb3DQEBCwUAMCQxIjAgBgNVBAMMGXBhZ2VCT1ggQ29k
# ZSBTaWduaW5nIENlcnQwHhcNMjUwMzAyMTYzODA3WhcNMjYwMzAyMTY1ODA3WjAk
# MSIwIAYDVQQDDBlwYWdlQk9YIENvZGUgU2lnbmluZyBDZXJ0MIIBIjANBgkqhkiG
# 9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuUDEhiZHE+UCgzTjpgGWX2yRIRbWBxrG0cos
# stAM29Euk+IsbqVUlYk9/oOWHTm1G+h1uSJrsdtobERzogZo6RyYxH2wEQN+qaBc
# 8rn9bc9K+jgnth5mdy4k4VsjVoGVCmjDDSvf1I6iwtvLUgc16KGYp6T/occVu3LD
# qb90y2H53sw/+0k5V6yuE2SD3lKp3WtM4q51fPLmcsgOB5LjIUQmJV7e9wSMPn4A
# jGQSOCgUCIEC+2CB22NE7JPKeP2o1euspimz3VhaHMGYJCY98jYGjCgUjA9DBN8k
# 6h/US1yzlxsZLnnDNGkMQBCjBiTiDZVNSJR7qii7uJaWDo0ruQIDAQABo0YwRDAO
# BgNVHQ8BAf8EBAMCB4AwEwYDVR0lBAwwCgYIKwYBBQUHAwMwHQYDVR0OBBYEFOYq
# A5c0jwfTLfmIHxx+TPuJ44jcMA0GCSqGSIb3DQEBCwUAA4IBAQA7b6EZZtvZISJC
# cE88X9qlaTCUSj0WPfbm90Mj1luN4wQMUrWwSxxMzrrMrXSQBXAA8CCtK02kkeZm
# I35QTf4wVcwU9rZcTJs/WXwRXUDQw9Er02ILTP6BeE/V8NyQCijEziE2hammMKP7
# yaE7uCNyDEi+LIVugSlSotZZE2I4ca9cpqYUiYWNMJbiKKMkmvqUMsQegI+CVu3r
# HPyy7I58ZX2nIJpibGXFAb/8RO/b6H2fQm0Lylx8MHBCeaO0w3W6T9LTKBOlmEs2
# LJbdYyqkuMh8I/IPlnWRfpRx7CGdBcdWSt8BgTiz7kUaKLhqZPfRQPZeL3+TPNEY
# Qt9HBhbZMYIB6jCCAeYCAQEwODAkMSIwIAYDVQQDDBlwYWdlQk9YIENvZGUgU2ln
# bmluZyBDZXJ0AhB9XzvMF9qVmE6NV/UQOTeSMA0GCWCGSAFlAwQCAQUAoIGEMBgG
# CisGAQQBgjcCAQwxCjAIoAKAAKECgAAwGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcC
# AQQwHAYKKwYBBAGCNwIBCzEOMAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIE
# IHc63Z2FIX6r+5IROsbGwKNa/fnTn/PKj43M2IbbiKATMA0GCSqGSIb3DQEBAQUA
# BIIBAKQA384XRhpxi7MUlN+9yCSLmSRS/Ih3rsVuvoA/kOJMkE9QP0x4/KueoPjW
# Clj5zq6QUhg+/YXv/h+YHUprBE0tVXxkvAY+q75STttzcEmgsg0Yf/XVmMwRKP2E
# ErsN1bvMrwD40TGcLXBJ5EhfRl5O1RzgyQyYa6GDAN2pAvXLN86HKTTPxKNH/vT4
# uMYTAvyib20eUPcxFVaAqnVeyx9OzMfG9RyzdAyGCkWyixzauJcN1FssYdXKiLKz
# Ce4IlBC8dIN2kt9xV42gjS1rBm/96+UHoaBjmHENPWOtNd1jtOKDJNB/WI7QQa8M
# 9D3RWkMBM6/TnXgXoHA7mZJDuZA=
# SIG # End signature block