IP.Tools.psm1
## Running A Build Will Compile This To A Single PSM1 File Containing All Module Code ## ## If Importing Module Source Directly, This Will Dynamically Build Root Module ## # Get list of private functions and public functions to import, in order. $Private = @(Get-ChildItem -Path $PSScriptRoot\private -Recurse -Filter "*.ps1") | Sort-Object Name $Public = @(Get-ChildItem -Path $PSScriptRoot\public -Recurse -Filter "*.ps1") | Sort-Object Name $AllMask = [Math]::Pow(2, 32) New-Variable -Name AllMask -Value $AllMask -Scope Script -Force Class IpNetwork { [ipaddress]$IpAddress [ipaddress]$IpNetmask IpNetwork([string]$CIDR) { ($IP, $MaskLen) = $CIDR.Split('/') $IP = Get-IpNetworkBase -CIDR $CIDR Write-Verbose "IP: $($IP); Mask: $($MaskLen)" $this.IpAddress = [ipaddress]($IP) $this.IpNetmask = [ipaddress](Convert-MaskLenToIp($MaskLen)) } IpNetwork([IpAddress]$IP, [IpAddress] $Mask) { Write-Verbose "IP: $($IP); Mask: $($Mask)" $IP = Get-IpNetworkBase -IP $IP -Mask $Mask $this.IpAddress = $IP $this.IpNetmask = $Mask } [system.net.ipaddress]GetStartAddress() { return Get-IpNetworkStartIp -Network $this } [system.net.ipaddress]GetEndAddress() { return Get-IpNetworkEndIP -Network $this } } # Dot source the private function files. foreach ($ImportItem in $Private) { try { . $ImportItem.FullName Write-Verbose -Message ("Imported private function {0}" -f $ImportItem.FullName) } catch { Write-Error -Message ("Failed to import private function {0}: {1}" -f $ImportItem.FullName, $_) } } # Dot source the public function files. foreach ($ImportItem in $Public) { try { . $ImportItem.FullName Write-Verbose -Message ("Imported public function {0}" -f $ImportItem.FullName) } catch { Write-Error -Message ("Failed to import public function {0}: {1}" -f $ImportItem.FullName, $_) } } # Export the public functions. Export-ModuleMember -Function $Public.BaseName # Private Function Example - Replace With Your Function function Add-PrivateFunction { [CmdletBinding()] Param ( # Your parameters go here... ) # Your function code goes here... Write-Output "Your private function ran!" } function Get-IpBogon { <# .SYNOPSIS Return a list of IP Bogons with [ipaddress] objects representing the IP and Mask .DESCRIPTION Return a list of IP Bogons (Internet non-routable addresses) with [ipaddress] objects representing both IP and Mask. IP Bogons are Internet non-routable addresses that are either reserved for Private use (RFC1918), or are otherwise not available. .INPUTS None .OUTPUTS Array of [ipaddress] objects representing IP Bogons. .EXAMPLE PS> $Bogons = Get-IpBogon.ps1 .LINK https://github.com/jberkers42/ip.tools #> [CmdletBinding()] $Bogons = @() $Bogons += '0.0.0.0/8' # RFC1122 "This" network $Bogons += '10.0.0.0/8' # RFC1918 Private-use networks $Bogons += '100.64.0.0/10' # RFC6598 Carrier-grade NAT $Bogons += '127.0.0.0/8' # RFC1122 IPv4 Loopback $Bogons += '169.254.0.0/16' # RFC3927 IPv4 Link local $Bogons += '172.16.0.0/12' # RFC1918 Private-use networks $Bogons += '192.0.0.0/24' # RFC5736 IETF protocol assignments $Bogons += '192.0.2.0/24' # RFC5737 TEST-NET-1 $Bogons += '192.168.0.0/16' # RFC1918 Private-use networks $Bogons += '198.18.0.0/15' # RFC2544 Network interconnect device benchmark testing $Bogons += '198.51.100.0/24' # RFC5737 TEST-NET-2 $Bogons += '203.0.113.0/24' # RFC5737 TEST-NET-3 $Bogons += '224.0.0.0/4' # RFC1112 Multicast $Bogons += '240.0.0.0/4' # Reserved for future use $BogonIps = @() foreach ($Bogon in $Bogons) { $BogonIp = [IpNetwork]::new($Bogon) $BogonIPs += $BogonIp } Write-Output $BogonIps } function Remove-IpBogon { # <# .SYNOPSIS Remove any Bogon IPs from the list of provided IPs. .DESCRIPTION Remove any Bogon IPs from the list of provided IPs. Each IP is expected to be either a dotted quad notation string representation of the address, or an [ipaddress] object. .INPUTS [IpAddress] or [string] values to filter .OUTPUTS Array of [ipaddress] objects that have had IP Bogons removed. .EXAMPLE PS> $IPs = Get-IpFromSomeSource PS> $IPs | Remove-IpBogon Obtain a list of IPs from some source (like a SIEM), and filter out the Bogon addresses .LINK https://github.com/jberkers42/ip.tools #> [cmdletbinding(SupportsShouldProcess)] param ( [Parameter(Mandatory=$true, ValueFromPipeLine=$true)] [ipaddress] $Address ) Begin { $Me = $MyInvocation.MyCommand.Name Write-Verbose $Me } Process { if ($PSCmdlet.ShouldProcess($Address, "Remove IP if Bogon")) { if (!(Test-IpBogon -Address $Address)) { Write-Output $Address } } } End { } } function Test-IpBogon { [cmdletbinding()] param ( [Parameter(Mandatory=$true, ValueFromPipeLine=$true)] [ipaddress] $Address ) Begin { $Me = $MyInvocation.MyCommand.Name Write-Verbose $Me $IpBogons = Get-IpBogon } Process { $IsBogon = $false foreach ($Bogon in $IpBogons) { if (Test-IpInNetwork -Address $Address -Network $Bogon) { $IsBogon = $true } } Write-Output $IsBogon } End { Clear-Variable 'IpBogons' } } function Convert-IpToMaskLen { <# .SYNOPSIS Convert a Subnet Mask to PrefixLength .DESCRIPTION Convert a Subnet Mask to a Prefix Length .EXAMPLE PS C:\> Convert-IpToMaskLen 255.255.0.0 16 This example counts the relevant network bits of the dotted SubnetMask 255.255.0.0. .INPUTS [string] .OUTPUTS [string] #> [CmdletBinding()] param ( # SubnetMask to convert [Parameter(Mandatory)] [System.Net.IpAddress]$SubnetMask ) Begin { $Me = $MyInvocation.MyCommand.Name Write-Verbose "$($Me): $($SubnetMask)" } Process { $Octets = $SubnetMask.IPAddressToString.Split('.') Write-Verbose "$($Me): Octets: $($Octets)" foreach($Octet in $Octets) { while(0 -ne $Octet){ $Octet = ($Octet -shl 1) -band [byte]::MaxValue $result++ } } $TestMask = Convert-MaskLenToIp -MaskLen $result if ($TestMask -ne $SubnetMask) { throw "Invalid Netmask Supplied" } else { $result -as [string] } } End { } } function Convert-MaskLenToIp { <# .SYNOPSIS Convert MaskLen from CIDR Notation (IP/MaskLen) to an IP Address object .DESCRIPTION Convert MaskLen from CIDR Notation (IP/MaskLen) to an IP Address object .PARAMETER MaskLen Integer value representing the mask length .INPUTS Integer Mask Lenth .OUTPUTS [ipaddress] object representing the Netmask .EXAMPLE Pass a single MaskLen as a parameter PS> Convert-MaskLenToIp -MaskLen 24 AddressFamily : InterNetwork ScopeId : IsIPv6Multicast : False IsIPv6LinkLocal : False IsIPv6SiteLocal : False IsIPv6Teredo : False IsIPv6UniqueLocal : False IsIPv4MappedToIPv6 : False Address : 16777215 IPAddressToString : 255.255.255.0 .EXAMPLE Use Pipeline to convert MaskLen to IP Address PS> 27 | Convert-MaskLenToIp AddressFamily : InterNetwork ScopeId : IsIPv6Multicast : False IsIPv6LinkLocal : False IsIPv6SiteLocal : False IsIPv6Teredo : False IsIPv6UniqueLocal : False IsIPv4MappedToIPv6 : False Address : 3774873599 IPAddressToString : 255.255.255.224 .LINK https://github.com/jberkers42/ip.tools #> [CmdletBinding()] [OutputType([ipaddress])] param( [Parameter(Mandatory=$true, ValueFromPipeLine=$true)] [ValidateRange([int]0, [int]32)] [int]$MaskLen ) Begin { $Me = $MyInvocation.MyCommand.Name Write-Verbose $Me } Process { [ipaddress]($AllMask - ($AllMask -shr $MaskLen)) } End { } } function Get-IpNetworkBase { <# .SYNOPSIS Get the Base Network address for the supplied IP/Mask .DESCRIPTION Get the Base Network address for the supplied IP/Mask .PARAMETER CIDR [string] containing CIDR notation for network address .PARAMETER IP [IPAddress] object or Dotted Quad notation string representing the IP address .PARAMETER Mask [IpNetwork] Object or Dotted Quad notation string representing the Netmask .INPUTS CIDR Strings .OUTPUTS [IpAddress] Object .EXAMPLE PS> (Get-IpNetworkBase -CIDR 192.168.100.20/24).IPAddressToString 192.168.100.0 .EXAMPLE Use Pipeline to convert MaskLen to IP Address PS> 27 | Convert-MaskLenToIp .LINK https://github.com/jberkers42/ip.tools #> [CmdletBinding(DefaultParameterSetName = 'CIDR')] [OutputType([IpAddress])] param( [Parameter(ParameterSetName='CIDR', Mandatory=$true, Position = 0, ValueFromPipeline=$true)] [string] $CIDR, [Parameter(ParameterSetName='IpMask', Mandatory=$true, Position = 0)] [IpAddress] $IP, [Parameter(ParameterSetName='IpMask', Mandatory=$true, Position = 1)] [IpAddress] $Mask ) Begin { $Me = $MyInvocation.MyCommand.Name Write-Verbose $Me } Process { If ($PSCmdlet.ParameterSetName -eq "CIDR") { ([IpAddress]$IP, $MaskLen) = $CIDR -split '/' [IpAddress]$Mask = Convert-MaskLenToIp $MaskLen } Write-Verbose "IP: $($IP); Mask: $($Mask);" [IpAddress]$Net = ($IP.Address -band $Mask.Address) return $Net } End { } } function Get-IpNetworkEndIP { [CmdletBinding(DefaultParameterSetName = 'CIDR')] [OutputType([ipaddress])] param ( [Parameter(ParameterSetName='CIDR', Mandatory=$true, ValueFromPipeline=$true)] [string]$CIDR, [Parameter(ParameterSetName='IpMask', Mandatory=$true, Position = 0)] [string] $IP, [Parameter(ParameterSetName='IpMask', Mandatory=$true, Position = 1)] [string] $Mask, [Parameter(ParameterSetName='Network', Mandatory=$true, Position = 1)] [IpNetwork] $Network ) Begin { $Me = $MyInvocation.MyCommand.Name Write-Verbose $Me } Process { Switch ($PSCmdlet.ParameterSetName) { "CIDR" { Write-Verbose "$($Me): Invoked with CIDR" $Network = [IpNetwork]::new($CIDR) } "IpMask" { Write-Verbose "$($Me): Invoked with IP/Mask" $Network = [IpNetwork]::new($IP, $Mask) } "Network" { Write-Verbose "$($Me): Invoked with IpNetwork Object" } } $NetworkIP = $Network.IPAddress.GetAddressBytes() Write-Verbose "$($Me): NetworkIP: $($NetworkIP)" [Array]::Reverse($NetworkIP) Write-Verbose "$($Me): NetworkIP (reversed): $($NetworkIP)" $NetworkIP = ([ipaddress]($NetworkIP -join ".")).Address $MaskLen = Convert-IpToMaskLen -SubnetMask $Network.IpNetmask $NumIPs = ([System.Math]::Pow(2,(32 -$MaskLen))) $EndIP = $NetworkIP + $NumIPs - 1 # Convert to Double If (($EndIP.GetType()).Name -ine "double") { $EndIP = [Convert]::ToDouble($EndIP) } $EndIP = [ipaddress]$EndIP Return $EndIP } End { } } function Get-IpNetworkStartIP { [CmdletBinding(DefaultParameterSetName = 'CIDR')] [OutputType([ipaddress])] param ( [Parameter(ParameterSetName='CIDR', Mandatory=$true, ValueFromPipeline=$true, Position = 1)] [String]$CIDR, [Parameter(ParameterSetName='IpMask', Mandatory=$true, Position = 0)] [string] $IP, [Parameter(ParameterSetName='IpMask', Mandatory=$true, Position = 1)] [string] $Mask, [Parameter(ParameterSetName='Network', Mandatory=$true, ValueFromPipeline=$true, Position = 1)] [IpNetwork] $Network, [Parameter(Mandatory=$false)] [switch]$ExcludeNetwork ) Begin { $Me = $MyInvocation.MyCommand.Name Write-Verbose $Me } Process { Switch ($PSCmdlet.ParameterSetName) { "CIDR" { Write-Verbose "$($Me): Invoked with CIDR" $Network = [IpNetwork]::new($CIDR) } "IpMask" { Write-Verbose "$($Me): Invoked with IP/Mask" $Network = [IpNetwork]::new($IP, $Mask) } "Network" { Write-Verbose "$($Me): Invoked with IpNetwork Object" } } $NetworkIP = $Network.IPAddress.GetAddressBytes() [Array]::Reverse($NetworkIP) $NetworkIP = ([ipaddress]($NetworkIP -join ".")).Address if($ExcludeNetwork) { $StartIP = $NetworkIP + 1 } else { $StartIP = $NetworkIP } # Convert to Double If (($StartIP.GetType()).Name -ine "double") { $StartIP = [Convert]::ToDouble($StartIP) } $StartIP = [ipaddress]$StartIP Return $StartIP } End { } } function New-IpNetwork { <# .SYNOPSIS Create a new IpNetwork object with the specified CIDR or IP and Mask parameters .DESCRIPTION Create a new IpNetwork object with the specified CIDR or IP and Mask parameters .PARAMETER CIDR [string] containing CIDR notation for network .PARAMETER IP [IPAddress] object or Dotted Quad notation string representing the IP address .PARAMETER Mask [IpNetwork] Object or Dotted Quad notation string representing the Netmask .INPUTS CIDR Strings .OUTPUTS [IpNetwork] Object .EXAMPLE PS> $Network = PS> Test-IpInNetwork -Address 192.168.100.20 -Network .EXAMPLE Use Pipeline to convert MaskLen to IP Address PS> 27 | Convert-MaskLenToIp .LINK https://github.com/jberkers42/ip.tools #> [CmdletBinding(DefaultParameterSetName = 'CIDR', SupportsShouldProcess)] param( [Parameter(ParameterSetName='CIDR', Mandatory=$true, Position = 0, ValueFromPipeline=$true)] [string] $CIDR, [Parameter(ParameterSetName='IpMask', Mandatory=$true, Position = 0)] [string] $IP, [Parameter(ParameterSetName='IpMask', Mandatory=$true, Position = 1)] [string] $Mask ) Begin { $Me = $MyInvocation.MyCommand.Name Write-Verbose $Me } Process { if ($PSCmdlet.ShouldProcess("IpNetwork", "Return new IP Network from CIDR or IP and Mask")) { Switch ($PSCmdlet.ParameterSetName) { "CIDR" { return [IpNetwork]::new($CIDR) } "IpMask" { return [IpNetwork]::new($IP, $Mask) } } } } End { } } function Test-IpInNetwork { <# .SYNOPSIS Test if an IP address exists in the specified network .DESCRIPTION Test if an IP address exists in the specified network .PARAMETER Address [IPAddress] object or Dotted Quad notation string representing the IP address to test .PARAMETER Network [IpNetwork] Object consisting of IpAddress and IpNetmask to check if IP belongs. If supplied as string, will be converted to IpNetwork before testing. .INPUTS IP Addresses and networks. .OUTPUTS [bool] result of test .EXAMPLE PS> $Network = New-IpNetwork "192.168.100.0/24" PS> Test-IpInNetwork -Address 192.168.100.20 -Network $Network .EXAMPLE Use Pipeline to convert MaskLen to IP Address PS> 27 | Convert-MaskLenToIp .LINK https://github.com/jberkers42/ip.tools #> [cmdletbinding()] param ( [Parameter(Mandatory=$true, ValueFromPipeLine=$true)] [ipaddress] $Address, [Parameter(Mandatory=$true, ValueFromPipeline=$true)] [ValidateScript({ $TypeName = $_ | Get-Member | Select-Object -ExpandProperty TypeName -Unique if ($TypeName -eq 'System.String' -and $_ -match "(\d{1,3}\.){3}\d{1,3}\/\d{1,2}") { Write-Verbose "Convert From String: $_" New-IpNetwork $_ } elseif ($TypeName -eq 'IpNetwork') { Write-Verbose "Taken as IpNetwork Object $_" $_ } })] $Network ) Begin { $Me = $MyInvocation.MyCommand.Name Write-Verbose $Me } Process { Write-Verbose "Network To Test: $Network" if ($Network.GetType().Name -eq 'String') { $Network = New-IpNetwork -CIDR $Network } if ($Network.IpAddress.Address -eq ($Address.Address -band $Network.IpNetmask.Address)) { Write-Output $true } else { Write-Output $false } } End { } } function Test-IpRangeIsSubnet { <# .SYNOPSIS Test if supplied IP Range is a valid Subnet .DESCRIPTION Test if supplied IP Range is a valid Subnet .PARAMETER StartAddress [IPAddress] object or Dotted Quad notation string representing the Start IP address of the range to test .PARAMETER EndAddress [IPAddress] object or Dotted Quad notation string representing the End IP address of the range to test .INPUTS IP Addresses .OUTPUTS [bool] False if the IP Range does not represent a valid subnet [ipaddress] representation of the Subnet Mask .EXAMPLE PS> $StartAddress = 192.168.1.0 PS> $EndAddress = 192.168.1.15 PS> Test-IpRangeIsSubnet -StartAddress $StartAddress -EndAddress $EndAddress AddressFamily : InterNetwork ScopeId : IsIPv6Multicast : False IsIPv6LinkLocal : False IsIPv6SiteLocal : False IsIPv6Teredo : False IsIPv6UniqueLocal : False IsIPv4MappedToIPv6 : False Address : 3774873599 IPAddressToString : 255.255.255.224 .LINK https://github.com/jberkers42/ip.tools #> [cmdletbinding()] param ( [Parameter(Mandatory=$true, ValueFromPipeLine=$true)] [ipaddress] $StartAddress, [Parameter(Mandatory=$true, ValueFromPipeLine=$true)] [ipaddress] $EndAddress ) Begin { $Me = $MyInvocation.MyCommand.Name Write-Verbose $Me } Process { Write-Verbose "Address Range To Test: $StartAddress - $EndAddress" $StartAddressBytes = $StartAddress.GetAddressBytes() $EndAddressBytes = $EndAddress.GetAddressBytes() $AddressDiffBytes = @() for (($octet = 0); ($octet -lt $StartAddressBytes.Count); $octet++) { Write-verbose "Octet $octet" $AddressDiffBytes += $StartAddressBytes[$octet] -bxor $EndAddressBytes[$octet] } Write-Verbose "$($AddressDiffBytes -join '.')" [ipaddress]$AddressDiff = $AddressDiffBytes -join '.' Write-Verbose $AddressDiff [ipaddress]$MaskDiff = ($AllMask - 1) - $AddressDiff.Address Write-Verbose $MaskDiff # Reverse the bytes $Octets = $MaskDiff.IPAddressToString.Split('.') [Array]::Reverse($Octets) $Mask = [IpAddress]($Octets -join '.') if (Test-ValidMask ($Mask)) { Write-Output $Mask } else { Write-Output $false } } End { } } function Test-ValidMask { <# .SYNOPSIS Test if an IP address represents a valid network mask .DESCRIPTION Test if an IP address represents a valid network mask. I.E. the binary representation contains all 1's followed by all 0' .PARAMETER Address [IPAddress] object or Dotted Quad notation string representing the IP address to test .INPUTS IP Address .OUTPUTS [bool] result of test .EXAMPLE PS> $Mask = "255.255.192.0" PS> Test-ValidMask -Address $Mask .EXAMPLE Use Pipeline to convert MaskLen to IP Address PS> 27 | Convert-MaskLenToIp .LINK https://github.com/jberkers42/ip.tools #> [cmdletbinding()] param ( [Parameter(Mandatory=$true, ValueFromPipeLine=$true)] [ipaddress] $Address ) Begin { $Me = $MyInvocation.MyCommand.Name Write-Verbose $Me } Process { Write-Verbose "Mask To Test: $Address" # Convert Address to binary representation $binAddress = ($Address.GetAddressBytes() | ForEach-Object {[System.Convert]::ToString($_,2).PadLeft(8,'0')}) -join '' $AddressBits = $binAddress.ToCharArray() $Last = 1 $ValidMask = $true Foreach ($bit in $AddressBits) { if ($bit -eq '1') { if ($Last -ne '1') { $ValidMask = $false } } $Last = $bit } Write-Output $ValidMask } End { } } Export-ModuleMember -Function Get-IpBogon, Remove-IpBogon, Test-IpBogon, Convert-IpToMaskLen, Convert-MaskLenToIp, Get-IpNetworkBase, Get-IpNetworkEndIP, Get-IpNetworkStartIP, New-IpNetwork, Test-IpInNetwork, Test-IpRangeIsSubnet, Test-ValidMask |