Subnet.psm1
|
function Get-NetworkClass { <# .SYNOPSIS Use to determine the network class of a given IP address. .DESCRIPTION Returns A, B, C, D or E depending on the numeric value of the first octet of a given IP address. .PARAMETER IP The IP address to test. .EXAMPLE Get-NetworkClass -IP 172.16.1.2 Result ------ B .EXAMPLE '10.1.1.1' | Get-NetworkClass Result ------ A #> param( [parameter(Mandatory,ValueFromPipeline)] [string] $IP ) process { switch ($IP.Split('.')[0]) { { $_ -in 0..127 } { 'A' } { $_ -in 128..191 } { 'B' } { $_ -in 192..223 } { 'C' } { $_ -in 224..239 } { 'D' } { $_ -in 240..255 } { 'E' } } } } function Get-Subnet { <# .SYNOPSIS Returns subnet details for the local IP address, or a given network address and mask. .DESCRIPTION Use to get subnet details for a given network address and mask, including network address, broadcast address, network class, address range, host addresses and host address count. .PARAMETER IP The network IP address or IP address with subnet mask via slash notation. .PARAMETER MaskBits The numerical representation of the subnet mask. .PARAMETER Force Use to force the return of all host IP addresses regardless of the subnet size (skipped by default for subnets larger than /16). .EXAMPLE Get-Subnet 10.1.2.3/24 Description ----------- Returns the subnet details for the specified network and mask, specified as a single string to the -IP parameter. .EXAMPLE Get-Subnet 192.168.0.1 -MaskBits 23 Description ----------- Returns the subnet details for the specified network and mask. .EXAMPLE Get-Subnet Description ----------- Returns the subnet details for the current local IP. .EXAMPLE '10.1.2.3/24','10.1.2.4/24' | Get-Subnet Description ----------- Returns the subnet details for two specified networks. #> param ( [parameter(ValueFromPipeline)] [string] $IP, [ValidateRange(0, 32)] [Alias('CIDR')] [int] $MaskBits, [switch] $Force ) process { if ($PSBoundParameters.ContainsKey('MaskBits')) { $Mask = $MaskBits } if (-not $IP) { $LocalIP = Get-LocalIPv4Address if (-not $LocalIP) { throw "Unable to determine a local IPv4 address for this system. Please specify the -IP parameter explicitly." } $IP = $LocalIP.IPAddress If ($Mask -notin 0..32) { $Mask = $LocalIP.PrefixLength } } if ($IP -match '/\d') { $IPandMask = $IP -Split '/' $IP = $IPandMask[0] # Without this cast, $Mask stays a string here, and PowerShell's comparison operators then # compare it lexicographically rather than numerically -- e.g. '8' -ge 16 is $true, because # '8' -ge 16 is stringwise, so '8' > '1'. That silently mis-triggers (or skips) the -ge 16 / # -ge 31 checks below for any single-digit mask (/0 - /9). $Mask = [int]$IPandMask[1] } $Class = Get-NetworkClass -IP $IP if ($Mask -notin 0..32) { $Mask = switch ($Class) { 'A' { 8 } 'B' { 16 } 'C' { 24 } default { throw "Subnet mask size was not specified and could not be inferred because the address is Class $Class." } } Write-Warning "Subnet mask size was not specified. Using default subnet size for a Class $Class network of /$Mask." } $IPAddr = [ipaddress]::Parse($IP) $MaskAddr = [ipaddress]::Parse((Convert-Int64toIP -int ([convert]::ToInt64(("1" * $Mask + "0" * (32 - $Mask)), 2)))) $NetworkAddr = [ipaddress]($MaskAddr.address -band $IPAddr.address) $BroadcastAddr = [ipaddress](([ipaddress]::parse("255.255.255.255").address -bxor $MaskAddr.address -bor $NetworkAddr.address)) $Range = "$NetworkAddr ~ $BroadcastAddr" $HostStartAddr = (Convert-IPtoInt64 -ip $NetworkAddr.ipaddresstostring) + 1 $HostEndAddr = (Convert-IPtoInt64 -ip $broadcastaddr.ipaddresstostring) - 1 if ($Mask -ge 16 -or $Force) { Write-Progress "Calcualting host addresses for $NetworkAddr/$Mask.." if ($Mask -ge 31) { $HostAddresses = ,$NetworkAddr if ($Mask -eq 31) { $HostAddresses += $BroadcastAddr } $HostAddressCount = $HostAddresses.Length $NetworkAddr = $null $BroadcastAddr = $null } else { $HostAddresses = for ($i = $HostStartAddr; $i -le $HostEndAddr; $i++) { Convert-Int64toIP -int $i } $HostAddressCount = ($HostEndAddr - $HostStartAddr) + 1 } } else { Write-Warning "Host address enumeration was not performed because it would take some time for a /$Mask subnet. `nUse -Force if you want it to occur." } [pscustomobject]@{ IPAddress = $IPAddr MaskBits = $Mask NetworkAddress = $NetworkAddr BroadcastAddress = $broadcastaddr SubnetMask = $MaskAddr NetworkClass = $Class Range = $Range HostAddresses = $HostAddresses HostAddressCount = $HostAddressCount } } } function Test-PrivateIP { <# .SYNOPSIS Use to determine if a given IP address is within the IPv4 private address space ranges. .DESCRIPTION Returns $true or $false for a given IP address string depending on whether or not is is within the private IP address ranges. .PARAMETER IP The IP address to test. .EXAMPLE Test-PrivateIP -IP 172.16.1.2 Result ------ True #> param( [parameter(Mandatory,ValueFromPipeline)] [string] $IP ) process { if ($IP -Match '(^127\.)|(^192\.168\.)|(^10\.)|(^172\.1[6-9]\.)|(^172\.2[0-9]\.)|(^172\.3[0-1]\.)') { $true } else { $false } } } function Convert-Int64toIP ([int64]$int) { <# .SYNOPSIS Converts a 32-bit integer to its dotted-decimal IPv4 address string representation. #> (([math]::truncate($int / 16777216)).tostring() + "." + ([math]::truncate(($int % 16777216) / 65536)).tostring() + "." + ([math]::truncate(($int % 65536) / 256)).tostring() + "." + ([math]::truncate($int % 256)).tostring() ) } function Convert-IPtoInt64 ($ip) { <# .SYNOPSIS Converts a dotted-decimal IPv4 address string to its 32-bit integer representation. #> $octets = $ip.split(".") [int64]([int64]$octets[0] * 16777216 + [int64]$octets[1] * 65536 + [int64]$octets[2] * 256 + [int64]$octets[3]) } function Convert-SubnetMaskToPrefixLength ([System.Net.IPAddress]$SubnetMask) { <# .SYNOPSIS Converts a dotted-decimal subnet mask (e.g. 255.255.255.0) to its prefix length in bits (e.g. 24). #> $Bits = ($SubnetMask.GetAddressBytes() | ForEach-Object { [Convert]::ToString($_, 2).PadLeft(8, '0') }) -join '' ($Bits.ToCharArray() | Where-Object { $_ -eq '1' }).Count } function Get-LocalIPv4Address { <# .SYNOPSIS Returns the IPv4 address and prefix length of the first active, non-loopback network interface. .NOTES [System.Net.NetworkInformation.NetworkInterface] is part of the base class library, so unlike Get-NetIPAddress (Windows-only) this works the same way on Windows, Linux and macOS. #> $UnicastAddress = [System.Net.NetworkInformation.NetworkInterface]::GetAllNetworkInterfaces() | Where-Object { $_.OperationalStatus -eq 'Up' -and $_.NetworkInterfaceType -ne 'Loopback' } | ForEach-Object { $_.GetIPProperties().UnicastAddresses } | Where-Object { $_.Address.AddressFamily -eq 'InterNetwork' } | Select-Object -First 1 if (-not $UnicastAddress) { return } # .PrefixLength isn't implemented on .NET Framework (Windows PowerShell), so fall back to counting # the bits of .IPv4Mask there instead -- and the reverse is true on Linux/macOS, where .IPv4Mask # throws because those platforms have no concept of a dotted-decimal subnet mask. $PrefixLength = try { $UnicastAddress.PrefixLength } catch { Convert-SubnetMaskToPrefixLength -SubnetMask $UnicastAddress.IPv4Mask } [pscustomobject]@{ IPAddress = $UnicastAddress.Address.IPAddressToString PrefixLength = $PrefixLength } } |