SubnetTools.psm1
function Find-ValidSubnet { <# .SYNOPSIS Takes an IPv4 network address and creates subnets based on hosts and networks needed. .DESCRIPTION This command calculates all possible subnet masks that will accommodate the required subnets and hosts/subnet. Once the masks are found it will then produce all of the possible subnet ranges per subnet mask. It also has the option to show only the smallest subnets in regard to hosts/subnet (largest CIDR mask). These is also an option to show all subnets and this is very useful when trying to plan for VLSM subnets. This is a basic example: Find-ValidSubnet -CIDRSubnetAddress 172.16.0.0/16 -SubnetsRequired 4 -HostsPerSubnetRequired 6000 | Format-Table -GroupBy Mask Mask: 18 Mask SubnetID FirstValidIP LastValidIP BroadcastIP HostsPerSubnet Subnet TotalSubnets ---- -------- ------------ ----------- ----------- -------------- ------ ------------ 18 172.16.0.0 172.16.0.1 172.16.63.254 172.16.63.255 16382 1 4 18 172.16.64.0 172.16.64.1 172.16.127.254 172.16.127.255 16382 2 4 18 172.16.128.0 172.16.128.1 172.16.191.254 172.16.191.255 16382 3 4 18 172.16.192.0 172.16.192.1 172.16.255.254 172.16.255.255 16382 4 4 Mask: 19 Mask SubnetID FirstValidIP LastValidIP BroadcastIP HostsPerSubnet Subnet TotalSubnets ---- -------- ------------ ----------- ----------- -------------- ------ ------------ 19 172.16.0.0 172.16.0.1 172.16.31.254 172.16.31.255 8190 1 8 19 172.16.32.0 172.16.32.1 172.16.63.254 172.16.63.255 8190 2 8 19 172.16.64.0 172.16.64.1 172.16.95.254 172.16.95.255 8190 3 8 19 172.16.96.0 172.16.96.1 172.16.127.254 172.16.127.255 8190 4 8 19 172.16.128.0 172.16.128.1 172.16.159.254 172.16.159.255 8190 5 8 19 172.16.160.0 172.16.160.1 172.16.191.254 172.16.191.255 8190 6 8 19 172.16.192.0 172.16.192.1 172.16.223.254 172.16.223.255 8190 7 8 19 172.16.224.0 172.16.224.1 172.16.255.254 172.16.255.255 8190 8 8 It will also make sure the original network address is a network address and if it is not it will AND the given address with the oiginal mask to find the network address. It is suggested that the output be formated into a table with a -groupby Mask parameter. .EXAMPLE Find-ValidSubnet -CIDRSubnetAddress 172.16.0.0/16 -SubnetsRequired 4 -HostsPerSubnetRequired 4000 | Format-Table -GroupBy Mask Using the 172.16.0.0/16 network as a base this will find all subnet masks that will allow for a minimum of 4 subnets, while still allowing 4000 hosts per subnet. The subnets willl be listed for each subnet mask discovered .EXAMPLE Find-ValidSubnet -CIDRSubnetAddress 192.168.20.0/24 -AllSubnetsVLSM | Format-Table -GroupBy Mask Using the 192.168.20.0/24 network as a base this will find all subnets that are possible, this is very handy when trying to plan VLSM subnets. .PARAMETER CIDRSubnetAddress This parameter requires the network address to be entered with the CIDR mask as well. In this format 172.16.0.0/16 .PARAMETER SubnetsRequired This parameter declares how many subnets the CIDR network will need to be broken into as a minimum. Because this is a minimum, this command will also look for all valid subnets as long as it still allows for the number of hosts per subnet, -HostsPerSubnetRequired parameter value. .PARAMETER HostsPerSubnetRequired This parameter dictates the minimum amount of hosts that are required per subnet. .PARAMETER SmallestSubnets This parameter only shows the smallest subnets, those with the biggest subnet mask value. .PARAMETER LargestSubnets This parameter only shows the largest subnets, those with the smallest subnet mask value. .PARAMETER AllSubnetsVLSM This parameter show all possible subnets which can be very handy when planning VLSM subnets. .NOTES General notes Created by: Brent Denny Created on: 09 Mar 2021 Last Modified: 16 Mar 2021 Version: 0.9.3 #> [cmdletbinding(DefaultParameterSetName='Default',PositionalBinding=$false)] Param ( [Parameter(Mandatory=$true,ParameterSetName='VLSM')] [Parameter(Mandatory=$true,ParameterSetName='Subnet')] [string]$CIDRSubnetAddress, [Parameter(Mandatory=$true,ParameterSetName='Subnet')] [int]$SubnetsRequired, [Parameter(Mandatory=$true,ParameterSetName='Subnet')] [int]$HostsPerSubnetRequired, [Parameter(ParameterSetName='Subnet')] [switch]$SmallestSubnets, [Parameter(ParameterSetName='Subnet')] [switch]$LargestSubnets, [Parameter(ParameterSetName='VLSM')] [switch]$AllSubnetsVLSM ) function ConvertTo-IPAddressObject { [cmdletbinding(DefaultParameterSetName='Default')] Param ( [int]$BitCount, [Parameter(ParameterSetName='IPAddress')] [string]$IPAddress, [Parameter(ParameterSetName='DecAddress')] [int64]$DecAddress ) # this function will take a bit count, IPaddress or decimal address, and # convert any of them into an object that contains a forward and reverse versions # of the IPAddresses and their decimal values if ($PSCmdlet.ParameterSetName -eq 'Default') { $BinaryString = '1' * $BitCount + '0' * (32 - $BitCount) $IPObj = [ipaddress]([convert]::ToInt64($BinaryString,2)) $FwdAddrIPObj = [ipaddress]($IPObj.IPAddressToString -replace '^(\d+)\.(\d+)\.(\d+)\.(\d+)$','$4.$3.$2.$1') $RevAddrIPObj = $IPObj } elseif ($PSCmdlet.ParameterSetName -eq 'IPAddress') { $FwdAddrIPObj = [ipaddress]$IPAddress $RevAddrIPObj = [ipaddress]($IPAddress -replace '^(\d+)\.(\d+)\.(\d+)\.(\d+)$','$4.$3.$2.$1') } elseif ($PSCmdlet.ParameterSetName -eq 'DecAddress') { $IPObj = [ipaddress]$DecAddress $FwdAddrIPObj = [ipaddress]($IPObj.IPAddressToString -replace '^(\d+)\.(\d+)\.(\d+)\.(\d+)$','$4.$3.$2.$1') $RevAddrIPObj = $IPObj } $ObjProp = [ordered]@{ FwdAddrIP = $FwdAddrIPObj.IPAddressToString FwdAddrDec = $FwdAddrIPObj.Address RevAddrIP = $RevAddrIPObj.IPAddressToString RevAddrDec = $RevAddrIPObj.Address } New-object -TypeName psobject -Property $ObjProp } function Find-IPSubnetRange { [cmdletbinding()] Param ( [string]$IPAddress, [int]$InitialMask, [int]$SubnetMask ) # This function will find all of the valid subnets for a subnetted mask, and list the following # Mask,Subnet,FirstValidIP,LastValidIP,BroadcastIP,HostsPerSubnet and Subnet Number $MaxSubnetIndex = [math]::Pow(2,$SubnetMask - $InitialMask) - 1 $JumpValue = [math]::Pow(2,8-$SubnetMask % 8) $JumpIndex = [math]::Truncate($SubnetMask / 8) if ($JumpValue -eq 256) { $JumpValue = 1 $JumpIndex = $JumpIndex - 1 } [int[]]$JumpIPArray = 0,0,0,0 $JumpIPArray[$JumpIndex] = $JumpValue $JumpIPAddr = $JumpIPArray -join '.' $JumpIPAddressSet = ConvertTo-IPAddressObject -IPAddress $JumpIPAddr $IPAddressSet = ConvertTo-IPAddressObject -IPAddress $IPAddress foreach ($SubnetIndex in (0..$MaxSubnetIndex)) { # The ...RevDec refers to the IP addresses decimal value, it 'turns out' that the # [IPAddress] object reverses the decimal value of the IP, so by reversing the # reverse we get the actual decimal value. This is why you see this everywhere # within this function $ThisSubnetRevDec = $IPAddressSet.RevAddrDec + ($SubnetIndex * $JumpIPAddressSet.RevAddrDec) $ThisSubnetSet = ConvertTo-IPAddressObject -DecAddress $ThisSubnetRevDec $FirstValidRevDec = $ThisSubnetRevDec + 1 $LastValidRevDec = $ThisSubnetRevDec + $JumpIPAddressSet.RevAddrDec - 2 $BroadCastRevDec = $ThisSubnetRevDec + $JumpIPAddressSet.RevAddrDec - 1 $FirstValidSet = ConvertTo-IPAddressObject -DecAddress $FirstValidRevDec $LastValidSet = ConvertTo-IPAddressObject -DecAddress $LastValidRevDec $BroadCastSet = ConvertTo-IPAddressObject -DecAddress $BroadCastRevDec $ObjProp = [ordered]@{ Mask = $SubnetMask SubnetID = $ThisSubnetSet.FwdAddrIP FirstValidIP = $FirstValidSet.FwdAddrIP LastValidIP = $LastValidSet.FwdAddrIP BroadcastIP = $BroadCastSet.FwdAddrIP HostsPerSubnet = [math]::Pow(2,32 -$SubnetMask) - 2 Subnet = $SubnetIndex + 1 TotalSubnets = $MaxSubnetIndex + 1 } New-Object -TypeName psobject -Property $ObjProp } } ## MAIN Function BODY if ($AllSubnetsVLSM -eq $true) { $SubnetsRequired = 1 $HostsPerSubnetRequired = 1 } $CIDRParts = $CIDRSubnetAddress -split '\/' $SubnetID = $CIDRParts[0] -as [string] $InitialMask = $CIDRParts[1] -as [int] $HostBitsRequired = [math]::Ceiling([math]::Log($HostsPerSubnetRequired+2)/[math]::log(2)) # +2 to cater for NetworkId and BroadcastID addresses $NetworkBitsRequired = [math]::Floor([math]::Log($SubnetsRequired)/[math]::log(2)) $TotalBitsRequired = $InitialMask + $HostBitsRequired + $NetworkBitsRequired # Make sure the given IP addres is an IP Address if ($CIDRSubnetAddress -notmatch '^([1-9]|[1-9][0-9]|1[01][0-9]|12[0-6]|12[89]|1[3-9][0-9]|2[0-2][0-3])(\.([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])){3}\/([2-9]|[12][0-9]|30)$') { write-warning "$CIDRSubnetAddress - is not a valid address please enter the address and mask, for example: 164.12.0.0/16" break } elseif ($TotalBitsRequired -gt 32) { Write-Warning "The solution you requested requires $TotalBitsRequired bits in the address, we only have 32 bits in IPv4" break } else { $MaskSet = ConvertTo-IPAddressObject -BitCount $InitialMask $SubnetSet = ConvertTo-IPAddressObject -IPAddress $SubnetID # ANDing the Network address with original mask to produce a real network address # this is just in case an address that was entered was a host address and # not the network address. $NetBinAndMaskDec = $SubnetSet.RevAddrDec -band $MaskSet.RevAddrDec # Fixed IP uses ANDing to make sure the subnet address is the actual address of the subnet and not a host address # the subnet. $ActualNetworkAddrSet = ConvertTo-IPAddressObject -DecAddress $NetBinAndMaskDec $SubnetingBitsArray = 0..(32 - $TotalBitsRequired ) | ForEach-Object { # Finding how many subnet bits are required for the number of subnets requested [math]::Ceiling([math]::Log($SubnetsRequired)/[math]::log(2)) + $_ + $InitialMask } $SubnetResults = foreach ($SubnettedBits in $SubnetingBitsArray) { # Go find the valid subnet ranges per valid subnet mask Find-IPSubnetRange -IPAddress $ActualNetworkAddrSet.FwdAddrIP -InitialMask $InitialMask -SubnetMask $SubnettedBits } if ($SmallestSubnets -eq $true) {$SubnetResults | Where-Object {$_.Mask -eq $SubnetResults[-1].Mask}} if ($LargestSubnets -eq $true) {$SubnetResults | Where-Object {$_.Mask -eq $SubnetResults[0].Mask}} if ($SmallestSubnets -ne $true -and $LargestSubnets -ne $true) {$SubnetResults} } } function Show-IPFwdAndRev { <# .SYNOPSIS Given an IP address in one of different forms it shows forward and reverse IP address information .DESCRIPTION You can enter the IP address in one of five forms: Reverse IP Address Forward IP Address Reverse Decimal IP Address Forward Decimal IP Address CIDR Mask integer .EXAMPLE Show-IPFwdAndRev -FwdIPAddress 192.168.20.13 This will show the following information: ForwardIP : 192.168.20.13 FwdDecIP : 3232240653 FwdBinary : 11000000101010000001010000001101 ReverseIP : 13.20.168.192 RevDecIP : 219457728 RevBinary : 00001101000101001010100011000000 .PARAMETER RevIPAddress This will take an IP that has been reversed and show all forms of that IP address .PARAMETER FwdIPAddress This will take an IP and show all forms of that IP address .PARAMETER FwdDecIP This will take the decimal equivilent of the IP address and show all forms of the IP address .PARAMETER RevDecIP This will take the reverse decimal equivilent of the IP address and show all forms of the IP address .PARAMETER CIDRMask This will take a CIDR Mask integer and show it in all the address forms .NOTES General notes Created by: Brent Denny Created on: 25 Mar 2021 Modified on: 25 Mar 2021 #> [CmdletBinding(DefaultParameterSetName='Default')] Param ( [Parameter(ParameterSetName='ReverseIP')] [string]$RevIPAddress, [Parameter(ParameterSetName='ForwardIP')] [string]$FwdIPAddress, [Parameter(ParameterSetName='ForwardDecIP')] [string]$FwdDecIP, [Parameter(ParameterSetName='ReverseDecIP')] [string]$RevDecIP, [Parameter(ParameterSetName='CIDRMask')] [int]$CIDRMask ) if ($PSCmdlet.ParameterSetName -eq 'ReverseIP') { $IPOctetsArray = $RevIPAddress -split '\.' $FwdArray = 3..0 $RevArray = 0..3 } elseif ($PSCmdlet.ParameterSetName -eq 'ForwardIP') { $IPOctetsArray = $FwdIPAddress -split '\.' $FwdArray = 0..3 $RevArray = 3..0 } elseif ($PSCmdlet.ParameterSetName -eq 'ForwardDecIP') { $IPObj = [ipaddress]$FwdDecIP $IPOctetsArray = $IPObj.IPAddressToString -split '\.' $FwdArray = 0..3 $RevArray = 3..0 } elseif ($PSCmdlet.ParameterSetName -eq 'ReverseDecIP') { $IPObj = [ipaddress]$RevDecIP $IPOctetsArray = $IPObj.IPAddressToString -split '\.' $FwdArray = 3..0 $RevArray = 0..3 } elseif ($PSCmdlet.ParameterSetName -eq 'CIDRMask') { $BinaryMask = ('1' * $CIDRMask) + ('0' * (32 - $CIDRMask)) $DecimalMask = [convert]::ToInt64($BinaryMask,2) $IPObj = [ipaddress]$DecimalMask $IPOctetsArray = $IPObj.IPAddressToString -split '\.' $FwdArray = 3..0 $RevArray = 0..3 } $FwdIP = $IPOctetsArray[$FwdArray] -join '.' $FwdBinary = ($FwdArray | ForEach-Object { $Bin = [convert]::ToString($IPOctetsArray[$_],2) ('0' * (8 - $Bin.Length)) + $Bin }) -join '' $RevIP = $IPOctetsArray[$RevArray] -join '.' $RevBinary = ($RevArray | ForEach-Object { $Bin = [convert]::ToString($IPOctetsArray[$_],2) ('0' * (8 - $Bin.Length)) + $Bin }) -join '' $FnObjProp = [ordered]@{ ForwardIP = $FwdIP FwdDecIP = ([ipaddress]$RevIP).Address FwdBinary = $FwdBinary ReverseIP = $RevIP RevDecIP = ([ipaddress]$FwdIP).Address RevBinary = $RevBinary } New-Object -TypeName psobject -Property $FnObjProp } function Convert-CIDRMask { <# .SYNOPSIS Takes a CIDR subnet mask integer and displays it in many different formats .DESCRIPTION Using the CIDR mask provided, this will convert it to the following formats: CIDR Mask Binary Mask IPV4 Mask (forward and reverse) Decimal Mask (forward and reverse) Jump information (the count from subnet to subnet) .EXAMPLE Convert-CIDRMask -CIDRMask 20 This would produce this output, the reverse values are useful in PowerShell IP calulations CidrMask : 20 BinaryMask : 11111111111111111111000000000000 IPv4FwdMask : 255.255.240.0 DecimalFwdMask : 4294963200 IPv4RevMask : 0.240.255.255 DecimalRevMask : 15794175 JumpIPs : 0.0.16.0 JumpDecIP : 4096 JumpRevIPs : 0.16.0.0 JumpDecRevIP : 1048576 JumpValue : 16 .PARAMETER CIDRMask Enter an integer for the CIDR subnet mask, for example if the mask is 255.255.240.0 the mask you would enter would be 20. .PARAMETER IPV4Mask Enter an IPV4 subnet mask, for example 255.255.240.0 .NOTES General notes Created by: Brent Denny Created on: 25 Mar 2021 Modified on: 25 Mar 2021 #> [cmdletbinding(DefaultParameterSetName='default')] Param ( [Parameter(ParameterSetName='CIDR')] [int]$CIDRMask, [Parameter(ParameterSetName='IP')] [string]$IPV4Mask ) if ($PSCmdlet.ParameterSetName -eq 'IP') { if ($IPV4Mask -notmatch '^(255\.(0|128|192|224|240|248|252|254|255)(\.0){2}|(255\.){2}(0|128|192|224|240|248|252|254|255)\.0|(255\.){3}(0|128|192|224|240|248|252|254|255))$') { Write-Warning 'Not a valid subnet mask' break } $Binary = (Show-IPFwdAndRev -FwdIPAddress $IPV4Mask).FwdBinary $CIDRMask = ($Binary -replace '^(1+)0+$','$1').length } else { if ($CIDRMask -notin @(8..30)) { Write-Warning 'Not a valid subnet mask' break } } $IPAddrInfo = Show-IPFwdAndRev -CIDRMask $CIDRMask $JumpRevDec = (Show-IPFwdAndRev -CIDRMask ($CIDRMask)).RevDecIP - (Show-IPFwdAndRev -CIDRMask ($CIDRMask-1)).RevDecIP $JumpInfo = Show-IPFwdAndRev -RevDecIP $JumpRevDec $JumpVal = [math]::Pow(2,(8-($CIDRMask % 8))) $FnObjProp = [ordered]@{ CidrMask = $CIDRMask BinaryMask = $IPAddrInfo.FwdBinary BinaryGroups = $IPAddrInfo.FwdBinary -replace '(\d{8})(\d{8})(\d{8})(\d{8})','$1 $2 $3 $4' IPv4FwdMask = $IPAddrInfo.ForwardIP DecimalFwdMask = $IPAddrInfo.FwdDecIP IPv4RevMask = $IPAddrInfo.ReverseIP DecimalRevMask = $IPAddrInfo.RevDecIP JumpIPs = $JumpInfo.ForwardIP JumpDecIP = $JumpInfo.FwdDecIP JumpRevIPs = $JumpInfo.ReverseIP JumpDecRevIP = $JumpInfo.RevDecIP JumpValue = $JumpVal } New-Object -TypeName psobject -Property $FnObjProp } function New-SubnetPSSessions { <# .SYNOPSIS Creates PS Sessions with every host in subnet .DESCRIPTION Creates PS sessions with all machines that are in the subnet .EXAMPLE New-SubnetPSSessions After the command runs use $Sessions = Get-PSSession to collect the sessions into a single variable .NOTES General notes Created by: Brent Denny Created on: 25 Mar 2021 Modified on: 25 Mar 2021 #> Set-Item -Path WSMan:\localhost\Client\TrustedHosts -Value * -Force $MyIp = ((ipconfig /all).trim() | Select-String -Pattern '^IPV4 Address' ) -replace '(^.+\:\s|\(Preferred\))','' | Sort-Object @{e={($_ -replace '.*\.','') -as [int] }} $MySubnetMask = ((ipconfig /all).trim() | Select-String -Pattern '^Subnet Mask' ) -replace '^.+\:\s','' $IPRawText = (ipconfig /all).trim() | Select-String -Pattern '^(DHCP Server|Default Gateway|DNS Servers|IPV4 Address)' $IPArrayToExclude = $IPRawText -replace '(^.+\:\s|\(Preferred\))','' | Sort-Object @{e={($_ -replace '.*\.','') -as [int] }} $Mask = (Convert-CIDRMask -IPV4Mask $MySubnetMask).CIDRMask $MaskFormats = Convert-CIDRMask -CIDRMask $Mask $JumpSize = $MaskFormats.JumpValue $ValidIPJump = $JumpSize - 2 $IP = [ipaddress]$MyIp $DecSubnetID = $IP.Address -band $MaskFormats.DecimalRevMask $SubnetIDIP = Show-IPFwdAndRev -FwdIPAddress ([ipaddress]$DecSubnetID).IPAddressToString $FirstDecIP = [ipaddress](1 + $SubnetIDIP.FwdDecIP) $FirstIP = Show-IPFwdAndRev -RevIPAddress $FirstDecIP.IPAddressToString $ClassRoomIPs = foreach ($LastOctet in (1..$ValidIPJump)) { $NewIP = $FirstIP.ForwardIP -replace '\.\d{1,3}$',".$LastOctet" if ($NewIP -notin $IPArrayToExclude) { $NewIP } } New-PSSession -ComputerName $ClassRoomIPs *> $null } |