Indented.Net.IP.psm1
function ConvertToNetwork { <# .SYNOPSIS Converts IP address formats to a set a known styles. .DESCRIPTION ConvertToNetwork ensures consistent values are recorded from parameters which must handle differing addressing formats. This Cmdlet allows all other the other functions in this module to offload parameter handling. .NOTES Change log: 05/03/2016 - Chris Dent - Refactored and simplified. 14/01/2014 - Chris Dent - Created. #> [CmdletBinding()] [OutputType('Indented.Net.IP.Network')] param ( # Either a literal IP address, a network range expressed as CIDR notation, or an IP address and subnet mask in a string. [Parameter(Mandatory = $true, Position = 1)] [String]$IPAddress, # A subnet mask as an IP address. [Parameter(Position = 2)] [AllowNull()] [String]$SubnetMask ) $validSubnetMaskValues = "0.0.0.0", "128.0.0.0", "192.0.0.0", "224.0.0.0", "240.0.0.0", "248.0.0.0", "252.0.0.0", "254.0.0.0", "255.0.0.0", "255.128.0.0", "255.192.0.0", "255.224.0.0", "255.240.0.0", "255.248.0.0", "255.252.0.0", "255.254.0.0", "255.255.0.0", "255.255.128.0", "255.255.192.0", "255.255.224.0", "255.255.240.0", "255.255.248.0", "255.255.252.0", "255.255.254.0", "255.255.255.0", "255.255.255.128", "255.255.255.192", "255.255.255.224", "255.255.255.240", "255.255.255.248", "255.255.255.252", "255.255.255.254", "255.255.255.255" $network = [PSCustomObject]@{ IPAddress = $null SubnetMask = $null MaskLength = 0 PSTypeName = 'Indented.Net.IP.Network' } # Override ToString $network | Add-Member ToString -MemberType ScriptMethod -Force -Value { '{0}/{1}' -f $this.IPAddress, $this.MaskLength } if (-not $psboundparameters.ContainsKey('SubnetMask') -or $SubnetMask -eq '') { $IPAddress, $SubnetMask = $IPAddress.Split([Char[]]'\/ ', [StringSplitOptions]::RemoveEmptyEntries) } # IPAddress while ($IPAddress.Split('.').Count -lt 4) { $IPAddress += '.0' } if ([IPAddress]::TryParse($IPAddress, [Ref]$null)) { $network.IPAddress = [IPAddress]$IPAddress } else { $errorRecord = [System.Management.Automation.ErrorRecord]::new( [ArgumentException]'Invalid IP address.', 'InvalidIPAddress', 'InvalidArgument', $IPAddress ) $pscmdlet.ThrowTerminatingError($errorRecord) } # SubnetMask if ($null -eq $SubnetMask -or $SubnetMask -eq '') { $network.SubnetMask = [IPAddress]$validSubnetMaskValues[32] $network.MaskLength = 32 } else { $maskLength = 0 if ([Int32]::TryParse($SubnetMask, [Ref]$maskLength)) { if ($MaskLength -ge 0 -and $maskLength -le 32) { $network.SubnetMask = [IPAddress]$validSubnetMaskValues[$maskLength] $network.MaskLength = $maskLength } else { $errorRecord = [System.Management.Automation.ErrorRecord]::new( [ArgumentException]'Mask length out of range (expecting 0 to 32).', 'InvalidMaskLength', 'InvalidArgument', $SubnetMask ) $pscmdlet.ThrowTerminatingError($errorRecord) } } else { while ($SubnetMask.Split('.').Count -lt 4) { $SubnetMask += '.0' } $maskLength = $validSubnetMaskValues.IndexOf($SubnetMask) if ($maskLength -ge 0) { $Network.SubnetMask = [IPAddress]$SubnetMask $Network.MaskLength = $maskLength } else { $errorRecord = [System.Management.Automation.ErrorRecord]::new( [ArgumentException]'Invalid subnet mask.', 'InvalidSubnetMask', 'InvalidArgument', $SubnetMask ) $pscmdlet.ThrowTerminatingError($errorRecord) } } } $network } function GetPermutation { <# .SYNOPSIS Gets permutations of an IP address expansion expression. .DESCRIPTION Gets permutations of an IP address expansion expression. #> [CmdletBinding()] param ( [PSTypeName('ExpansionGroupInfo')] [Object[]]$Group, [String]$BaseAddress, [Int32]$Index ) foreach ($value in $Group[$Index].ReplaceWith) { $octets = $BaseAddress -split '\.' $octets[$Group[$Index].Position] = $value $address = $octets -join '.' if ($Index -lt $Group.Count - 1) { $address = GetPermutation $Group -Index ($Index + 1) -BaseAddress $address } $address } } function ConvertFrom-HexIP { <# .SYNOPSIS Converts a hexadecimal IP address into a dotted decimal string. .DESCRIPTION ConvertFrom-HexIP takes a hexadecimal string and returns a dotted decimal IP address. An intermediate call is made to ConvertTo-DottedDecimalIP. .INPUTS System.String .EXAMPLE ConvertFrom-HexIP c0a80001 Returns the IP address 192.168.0.1. #> [CmdletBinding()] [OutputType([IPAddress])] param ( # An IP Address to convert. [Parameter(Mandatory, Position = 1, ValueFromPipeline)] [ValidatePattern('^(0x)?[0-9a-f]{8}$')] [String]$IPAddress ) process { [IPAddress][UInt64][Convert]::ToUInt32($IPAddress, 16) } } function ConvertTo-BinaryIP { <# .SYNOPSIS Converts a Decimal IP address into a binary format. .DESCRIPTION ConvertTo-BinaryIP uses System.Convert to switch between decimal and binary format. The output from this function is dotted binary. .INPUTS System.Net.IPAddress .EXAMPLE ConvertTo-BinaryIP 1.2.3.4 Convert an IP address to a binary format. #> [CmdletBinding()] [OutputType([String])] param ( # An IP Address to convert. [Parameter(Mandatory, Position = 1, ValueFromPipeline)] [IPAddress]$IPAddress ) process { $binary = foreach ($byte in $IPAddress.GetAddressBytes()) { [Convert]::ToString($byte, 2).PadLeft(8, '0') } $binary -join '.' } } function ConvertTo-DecimalIP { <# .SYNOPSIS Converts a Decimal IP address into a 32-bit unsigned integer. .DESCRIPTION ConvertTo-DecimalIP takes a decimal IP, uses a shift operation on each octet and returns a single UInt32 value. .INPUTS System.Net.IPAddress .EXAMPLE ConvertTo-DecimalIP 1.2.3.4 Converts an IP address to an unsigned 32-bit integer value. #> [CmdletBinding()] [OutputType([UInt32])] param ( # An IP Address to convert. [Parameter(Mandatory, Position = 1, ValueFromPipeline )] [IPAddress]$IPAddress ) process { [UInt32]([IPAddress]::HostToNetworkOrder($IPAddress.Address) -shr 32 -band [UInt32]::MaxValue) } } function ConvertTo-DottedDecimalIP { <# .SYNOPSIS Converts either an unsigned 32-bit integer or a dotted binary string to an IP Address. .DESCRIPTION ConvertTo-DottedDecimalIP uses a regular expression match on the input string to convert to an IP address. .INPUTS System.String .EXAMPLE ConvertTo-DottedDecimalIP 11000000.10101000.00000000.00000001 Convert the binary form back to dotted decimal, resulting in 192.168.0.1. .EXAMPLE ConvertTo-DottedDecimalIP 3232235521 Convert the decimal form back to dotted decimal, resulting in 192.168.0.1. #> [CmdletBinding()] [OutputType([IPAddress])] param ( # A string representation of an IP address from either UInt32 or dotted binary. [Parameter(Mandatory, Position = 1, ValueFromPipeline)] [String]$IPAddress ) process { try { [Int64]$value = 0 if ([Int64]::TryParse($IPAddress, [Ref]$value)) { return [IPAddress]([IPAddress]::NetworkToHostOrder([Int64]$value) -shr 32 -band [UInt32]::MaxValue) } else { [IPAddress][UInt64][Convert]::ToUInt32($IPAddress.Replace('.', ''), 2) } } catch { $errorRecord = [System.Management.Automation.ErrorRecord]::new( [ArgumentException]'Cannot convert this format.', 'UnrecognisedFormat', 'InvalidArgument', $IPAddress ) Write-Error -ErrorRecord $errorRecord } } } function ConvertTo-HexIP { <# .SYNOPSIS Convert a dotted decimal IP address into a hexadecimal string. .DESCRIPTION ConvertTo-HexIP takes a dotted decimal IP and returns a single hexadecimal string value. .PARAMETER IPAddress An IP Address to convert. .INPUTS System.Net.IPAddress .EXAMPLE ConvertTo-HexIP 192.168.0.1 Returns the hexadecimal string c0a80001. #> [CmdletBinding()] [OutputType([String])] param ( [Parameter(Mandatory, Position = 1, ValueFromPipeline)] [IPAddress]$IPAddress ) process { $bytes = $IPAddress.GetAddressBytes() [Array]::Reverse($bytes) '{0:x8}' -f [BitConverter]::ToUInt32($bytes, 0) } } function ConvertTo-Mask { <# .SYNOPSIS Convert a mask length to a dotted-decimal subnet mask. .DESCRIPTION ConvertTo-Mask returns a subnet mask in dotted decimal format from an integer value ranging between 0 and 32. ConvertTo-Mask creates a binary string from the length, converts the string to an unsigned 32-bit integer then calls ConvertTo-DottedDecimalIP to complete the operation. .INPUTS System.Int32 .EXAMPLE ConvertTo-Mask 24 Returns the dotted-decimal form of the mask, 255.255.255.0. #> [CmdletBinding()] [OutputType([IPAddress])] param ( # The number of bits which must be masked. [Parameter(Mandatory, Position = 1, ValueFromPipeline)] [Alias('Length')] [ValidateRange(0, 32)] [Byte]$MaskLength ) process { [IPAddress][UInt64][Convert]::ToUInt32(('1' * $MaskLength).PadRight(32, '0'), 2) } } function ConvertTo-MaskLength { <# .SYNOPSIS Convert a dotted-decimal subnet mask to a mask length. .DESCRIPTION A count of the number of 1's in a binary string. .INPUTS System.Net.IPAddress .EXAMPLE ConvertTo-MaskLength 255.255.255.0 Returns 24, the length of the mask in bits. #> [CmdletBinding()] [OutputType([Int32])] param ( # A subnet mask to convert into length. [Parameter(Mandatory, Position = 1, ValueFromPipeline)] [Alias("Mask")] [IPAddress]$SubnetMask ) process { [Convert]::ToString([IPAddress]::HostToNetworkOrder($SubnetMask.Address), 2).Replace('0', '').Length } } function ConvertTo-Subnet { <# .SYNOPSIS Convert a start and end IP address to the closest matching subnet. .DESCRIPTION ConvertTo-Subnet attempts to convert a starting and ending IP address from a range to the closest subnet. .EXAMPLE ConvertTo-Subnet -Start 0.0.0.0 -End 255.255.255.255 .EXAMPLE ConvertTo-Subnet -Start 192.168.0.1 -End 192.168.0.129 .EXAMPLE ConvertTo-Subnet 10.0.0.23/24 .EXAMPLE ConvertTo-Subnet 10.0.0.23 255.255.255.0 #> [CmdletBinding(DefaultParameterSetName = 'FromIPAndMask')] [OutputType('Indented.Net.IP.Subnet')] param ( # Any IP address in the subnet. [Parameter(Mandatory, Position = 1, ParameterSetName = 'FromIPAndMask')] [String]$IPAddress, # A subnet mask. [Parameter(Position = 2, ParameterSetName = 'FromIPAndMask')] [String]$SubnetMask, # The first IP address from a range. [Parameter(Mandatory, ParameterSetName = 'FromStartAndEnd')] [IPAddress]$Start, # The last IP address from a range. [Parameter(Mandatory, ParameterSetName = 'FromStartAndEnd')] [IPAddress]$End ) if ($pscmdlet.ParameterSetName -eq 'FromIPAndMask') { try { $network = ConvertToNetwork @psboundparameters } catch { $pscmdlet.ThrowTerminatingError($_) } } elseif ($pscmdlet.ParameterSetName -eq 'FromStartAndEnd') { if ($Start -eq $End) { $MaskLength = 32 } else { $DecimalStart = ConvertTo-DecimalIP $Start $DecimalEnd = ConvertTo-DecimalIP $End if ($DecimalEnd -lt $DecimalStart) { $Start = $End } # Find the point the binary representation of each IP address diverges $i = 32 do { $i-- } until (($DecimalStart -band ([UInt32]1 -shl $i)) -ne ($DecimalEnd -band ([UInt32]1 -shl $i))) $MaskLength = 32 - $i - 1 } try { $network = ConvertToNetwork $Start $MaskLength } catch { $pscmdlet.ThrowTerminatingError($_) } } $hostAddresses = [Math]::Pow(2, (32 - $network.MaskLength)) - 2 if ($hostAddresses -lt 0) { $hostAddresses = 0 } $subnet = [PSCustomObject]@{ NetworkAddress = Get-NetworkAddress $network.ToString() BroadcastAddress = Get-BroadcastAddress $network.ToString() SubnetMask = $network.SubnetMask MaskLength = $network.MaskLength HostAddresses = $hostAddresses PSTypeName = 'Indented.Net.IP.Subnet' } $subnet | Add-Member ToString -MemberType ScriptMethod -Force -Value { return '{0}/{1}' -f $this.NetworkAddress, $this.MaskLength } $subnet } function Get-BroadcastAddress { <# .SYNOPSIS Get the broadcast address for a network range. .DESCRIPTION Get-BroadcastAddress returns the broadcast address for a subnet by performing a bitwise AND operation against the decimal forms of the IP address and inverted subnet mask. .INPUTS System.String .EXAMPLE Get-BroadcastAddress 192.168.0.243 255.255.255.0 Returns the address 192.168.0.255. .EXAMPLE Get-BroadcastAddress 10.0.9/22 Returns the address 10.0.11.255. .EXAMPLE Get-BroadcastAddress 0/0 Returns the address 255.255.255.255. .EXAMPLE Get-BroadcastAddress "10.0.0.42 255.255.255.252" Input values are automatically split into IP address and subnet mask. Returns the address 10.0.0.43. #> [CmdletBinding()] [OutputType([IPAddress])] param ( # Either a literal IP address, a network range expressed as CIDR notation, or an IP address and subnet mask in a string. [Parameter(Mandatory, Position = 1, ValueFromPipeline)] [String]$IPAddress, # A subnet mask as an IP address. [Parameter(Position = 2)] [String]$SubnetMask ) process { try { $network = ConvertToNetwork @psboundparameters $networkAddress = [IPAddress]($network.IPAddress.Address -band $network.SubnetMask.Address) return [IPAddress]( $networkAddress.Address -bor -bnot $network.SubnetMask.Address -band -bnot ([Int64][UInt32]::MaxValue -shl 32) ) } catch { Write-Error -ErrorRecord $_ } } } function Get-NetworkAddress { <# .SYNOPSIS Get the network address for a network range. .DESCRIPTION Get-NetworkAddress returns the network address for a subnet by performing a bitwise AND operation against the decimal forms of the IP address and subnet mask. .PARAMETER IPAddress Either a literal IP address, a network range expressed as CIDR notation, or an IP address and subnet mask in a string. .PARAMETER SubnetMask A subnet mask as an IP address. .INPUTS System.String .EXAMPLE Get-NetworkAddress 192.168.0.243 255.255.255.0 Returns the address 192.168.0.0. .EXAMPLE Get-NetworkAddress 10.0.9/22 Returns the address 10.0.8.0. .EXAMPLE Get-NetworkAddress "10.0.23.21 255.255.255.224" Input values are automatically split into IP address and subnet mask. Returns the address 10.0.23.0. #> [CmdletBinding()] [OutputType([IPAddress])] param ( [Parameter(Mandatory, Position = 1, ValueFromPipeline)] [String]$IPAddress, [Parameter(Position = 2)] [String]$SubnetMask ) process { try { $network = ConvertToNetwork @psboundparameters return [IPAddress]($network.IPAddress.Address -band $network.SubnetMask.Address) } catch { Write-Error -ErrorRecord $_ } } } function Get-NetworkRange { <# .SYNOPSIS Get a list of IP addresses within the specified network. .DESCRIPTION Get-NetworkRange finds the network and broadcast address as decimal values then starts a counter between the two, returning IPAddress for each. .INPUTS System.String .EXAMPLE Get-NetworkRange 192.168.0.0 255.255.255.0 Returns all IP addresses in the range 192.168.0.0/24. .EXAMPLE Get-NetworkRange 10.0.8.0/22 Returns all IP addresses in the range 192.168.0.0 255.255.252.0. #> [CmdletBinding(DefaultParameterSetName = 'FromIPAndMask')] [OutputType([IPAddress])] param ( # Either a literal IP address, a network range expressed as CIDR notation, or an IP address and subnet mask in a string. [Parameter(Mandatory, Position = 1, ValueFromPipeline, ParameterSetName = 'FromIPAndMask')] [String]$IPAddress, # A subnet mask as an IP address. [Parameter(Position = 2, ParameterSetName = 'FromIPAndMask')] [String]$SubnetMask, # Include the network and broadcast addresses when generating a network address range. [Parameter(ParameterSetName = 'FromIPAndMask')] [Switch]$IncludeNetworkAndBroadcast, # The start address of a range. [Parameter(Mandatory, ParameterSetName = 'FromStartAndEnd')] [IPAddress]$Start, # The end address of a range. [Parameter(Mandatory, ParameterSetName = 'FromStartAndEnd')] [IPAddress]$End ) process { if ($pscmdlet.ParameterSetName -eq 'FromIPAndMask') { try { $null = $psboundparameters.Remove('IncludeNetworkAndBroadcast') $network = ConvertToNetwork @psboundparameters } catch { $pscmdlet.ThrowTerminatingError($_) } $decimalIP = ConvertTo-DecimalIP $network.IPAddress $decimalMask = ConvertTo-DecimalIP $network.SubnetMask $startDecimal = $decimalIP -band $decimalMask $endDecimal = $decimalIP -bor (-bnot $decimalMask -band [UInt32]::MaxValue) if (-not $IncludeNetworkAndBroadcast) { $startDecimal++ $endDecimal-- } } else { $startDecimal = ConvertTo-DecimalIP $Start $endDecimal = ConvertTo-DecimalIP $End } for ($i = $startDecimal; $i -le $endDecimal; $i++) { [IPAddress]([IPAddress]::NetworkToHostOrder([Int64]$i) -shr 32 -band [UInt32]::MaxValue) } } } function Get-NetworkSummary { <# .SYNOPSIS Generates a summary describing several properties of a network range .DESCRIPTION Get-NetworkSummary uses many of the IP conversion commands to provide a summary of a network range from any IP address in the range and a subnet mask. .INPUTS System.String .EXAMPLE Get-NetworkSummary 192.168.0.1 255.255.255.0 .EXAMPLE Get-NetworkSummary 10.0.9.43/22 .EXAMPLE Get-NetworkSummary 0/0 #> [CmdletBinding()] [OutputType('Indented.Net.IP.NetworkSummary')] param ( # Either a literal IP address, a network range expressed as CIDR notation, or an IP address and subnet mask in a string. [Parameter(Mandatory, Position = 1, ValueFromPipeline)] [String]$IPAddress, # A subnet mask as an IP address. [Parameter(Position = 2)] [String]$SubnetMask ) process { try { $network = ConvertToNetwork @psboundparameters } catch { throw $_ } $decimalIP = ConvertTo-DecimalIP $Network.IPAddress $decimalMask = ConvertTo-DecimalIP $Network.SubnetMask $decimalNetwork = $decimalIP -band $decimalMask $decimalBroadcast = $decimalIP -bor (-bnot $decimalMask -band [UInt32]::MaxValue) $networkSummary = [PSCustomObject]@{ NetworkAddress = $networkAddress = ConvertTo-DottedDecimalIP $decimalNetwork NetworkDecimal = $decimalNetwork BroadcastAddress = ConvertTo-DottedDecimalIP $decimalBroadcast BroadcastDecimal = $decimalBroadcast Mask = $network.SubnetMask MaskLength = $maskLength = ConvertTo-MaskLength $network.SubnetMask MaskHexadecimal = ConvertTo-HexIP $network.SubnetMask CIDRNotation = '{0}/{1}' -f $networkAddress, $maskLength HostRange = '' NumberOfAddresses = $decimalBroadcast - $decimalNetwork + 1 NumberOfHosts = $decimalBroadcast - $decimalNetwork - 1 Class = '' IsPrivate = $false PSTypeName = 'Indented.Net.IP.NetworkSummary' } if ($networkSummary.NumberOfHosts -lt 0) { $networkSummary.NumberOfHosts = 0 } if ($networkSummary.MaskLength -lt 31) { $networkSummary.HostRange = '{0} - {1}' -f @( (ConvertTo-DottedDecimalIP ($decimalNetwork + 1)) (ConvertTo-DottedDecimalIP ($decimalBroadcast - 1)) ) } $networkSummary.Class = switch -regex (ConvertTo-BinaryIP $network.IPAddress) { '^1111' { 'E'; break } '^1110' { 'D'; break } '^11000000\.10101000' { if ($networkSummary.MaskLength -ge 16) { $networkSummary.IsPrivate = $true } } '^110' { 'C'; break } '^10101100\.0001' { if ($networkSummary.MaskLength -ge 12) { $networkSummary.IsPrivate = $true } } '^10' { 'B'; break } '^00001010' { if ($networkSummary.MaskLength -ge 8) { $networkSummary.IsPrivate = $true} } '^0' { 'A'; break } } $networkSummary } } function Get-Subnet { <# .SYNOPSIS Get a list of subnets of a given size within a defined supernet. .DESCRIPTION Generates a list of subnets for a given network range using either the address class or a user-specified value. .EXAMPLE Get-Subnet 10.0.0.0 255.255.255.0 -NewSubnetMask 255.255.255.192 Four /26 networks are returned. .EXAMPLE Get-Subnet 0/22 -NewSubnetMask 24 64 /24 networks are returned. .NOTES Change log: 07/03/2016 - Chris Dent - Cleaned up code, added tests. 12/12/2015 - Chris Dent - Redesigned. 13/10/2011 - Chris Dent - Created. #> [CmdletBinding()] [OutputType('Indented.Net.IP.Subnet')] param ( # Any address in the super-net range. Either a literal IP address, a network range expressed as CIDR notation, or an IP address and subnet mask in a string. [Parameter(Mandatory = $true, Position = 1)] [String]$IPAddress, # The subnet mask of the network to split. Mandatory if the subnet mask is not included in the IPAddress parameter. [Parameter(Position = 2)] [String]$SubnetMask, # Split the existing network described by the IPAddress and subnet mask using this mask. [Parameter(Mandatory = $true)] [String]$NewSubnetMask ) $null = $psboundparameters.Remove('NewSubnetMask') try { $network = ConvertToNetwork @psboundparameters $newNetwork = ConvertToNetwork 0 $NewSubnetMask } catch { $pscmdlet.ThrowTerminatingError($_) } if ($network.MaskLength -gt $newNetwork.MaskLength) { $errorRecord = [System.Management.Automation.ErrorRecord]::new( [ArgumentException]'The subnet mask of the new network is shorter (masks fewer addresses) than the subnet mask of the existing network.', 'NewSubnetMaskTooShort', 'InvalidArgument', $NewNetwork.MaskLength ) $pscmdlet.ThrowTerminatingError($errorRecord) } $numberOfNets = [Math]::Pow(2, ($newNetwork.MaskLength - $network.MaskLength)) $numberOfAddresses = [Math]::Pow(2, (32 - $newNetwork.MaskLength)) $decimalAddress = ConvertTo-DecimalIP (Get-NetworkAddress $network.ToString()) for ($i = 0; $i -lt $numberOfNets; $i++) { $networkAddress = ConvertTo-DottedDecimalIP $decimalAddress ConvertTo-Subnet -IPAddress $networkAddress -SubnetMask $newNetwork.MaskLength $decimalAddress += $numberOfAddresses } } function Resolve-IPAddress { <# .SYNOPSIS Resolves an IP address expression using wildcard expressions to individual IP addresses. .DESCRIPTION Resolves an IP address expression using wildcard expressions to individual IP addresses. Resolve-IPAddress expands groups and values in square brackets to generate a list of IP addresses or networks using CIDR-notation. Ranges of values may be specied using a start and end value using "-" to separate the values. Specific values may be listed as a comma separated list. .EXAMPLE Resolve-IPAddress "10.[1,2].[0-2].0/24" Returns the addresses 10.1.0.0/24, 10.1.1.0/24, 10.1.2.0/24, 10.2.0.0/24, and so on. #> [CmdletBinding()] param ( # The IPAddress expression to resolve. [Parameter(Mandatory, Position = 1, ValueFromPipeline)] [String]$IPAddress ) process { $groups = [Regex]::Matches($IPAddress, '\[(?:(?<Range>\d+(?:-\d+))|(?<Selected>(?:\d+, *)*\d+))\]|(?<All>\*)').Groups.Captures | Where-Object { $_ -and $_.Name -ne '0' } | ForEach-Object { $group = $_ $values = switch ($group.Name) { 'Range' { [int]$start, [int]$end = $group.Value -split '-' if ($start, $end -gt 255) { $errorRecord = [System.Management.Automation.ErrorRecord]::new( [ArgumentException]::new('Value ranges to resolve must use a start and end values between 0 and 255'), 'RangeExpressionOutOfRange', 'InvalidArgument', $group.Value ) $pscmdlet.ThrowTerminatingError($errorRecord) } $start..$end } 'Selected' { $values = [int[]]($group.Value -split ', *') if ($values -gt 255) { $errorRecord = [System.Management.Automation.ErrorRecord]::new( [ArgumentException]::new('All selected values must be between 0 and 255'), 'SelectionExpressionOutOfRange', 'InvalidArgument', $group.Value ) $pscmdlet.ThrowTerminatingError($errorRecord) } $values } 'All' { 0..255 } } [PSCustomObject]@{ Name = $_.Name Position = [Int32]$IPAddress.Substring(0, $_.Index).Split('.').Count - 1 ReplaceWith = $values PSTypeName = 'ExpansionGroupInfo' } } if ($groups) { GetPermutation $groups -BaseAddress $IPAddress } elseif (-not [IPAddress]::TryParse(($IPAddress -replace '/\d+$'), [Ref]$null)) { Write-Warning 'The IPAddress argument is not a valid IP address and cannot be resolved' } else { Write-Debug 'No groups found to resolve' } } } function Test-SubnetMember { <# .SYNOPSIS Tests an IP address to determine if it falls within IP address range. .DESCRIPTION Test-SubnetMember attempts to determine whether or not an address or range falls within another range. The network and broadcast address are calculated the converted to decimal then compared to the decimal form of the submitted address. .EXAMPLE Test-SubnetMember -SubjectIPAddress 10.0.0.0/24 -ObjectIPAddress 10.0.0.0/16 Returns true as the subject network can be contained within the object network. .EXAMPLE Test-SubnetMember -SubjectIPAddress 192.168.0.0/16 -ObjectIPAddress 192.168.0.0/24 Returns false as the subject network is larger the object network. .EXAMPLE Test-SubnetMember -SubjectIPAddress 10.2.3.4/32 -ObjectIPAddress 10.0.0.0/8 Returns true as the subject IP address is within the object network. .EXAMPLE Test-SubnetMember -SubjectIPAddress 255.255.255.255 -ObjectIPAddress 0/0 Returns true as the subject IP address is the last in the object network range. #> [CmdletBinding()] [OutputType([Boolean])] param ( # A representation of the subject, the network to be tested. Either a literal IP address, a network range expressed as CIDR notation, or an IP address and subnet mask in a string. [Parameter(Mandatory, Position = 1)] [String]$SubjectIPAddress, # A representation of the object, the network to test against. Either a literal IP address, a network range expressed as CIDR notation, or an IP address and subnet mask in a string. [Parameter(Mandatory, Position = 2)] [String]$ObjectIPAddress, # A subnet mask as an IP address. [String]$SubjectSubnetMask, # A subnet mask as an IP address. [String]$ObjectSubnetMask ) try { $subjectNetwork = ConvertToNetwork $SubjectIPAddress $SubjectSubnetMask $objectNetwork = ConvertToNetwork $ObjectIPAddress $ObjectSubnetMask } catch { throw $_ } # A simple check, if the mask is shorter (larger network) then it won't be a subnet of the object anyway. if ($subjectNetwork.MaskLength -lt $objectNetwork.MaskLength) { return $false } $subjectDecimalIP = ConvertTo-DecimalIP $subjectNetwork.IPAddress $objectDecimalNetwork = ConvertTo-DecimalIP (Get-NetworkAddress $objectNetwork) $objectDecimalBroadcast = ConvertTo-DecimalIP (Get-BroadcastAddress $objectNetwork) # If the mask is longer (smaller network), then the decimal form of the address must be between the # network and broadcast address of the object (the network we test against). if ($subjectDecimalIP -ge $objectDecimalNetwork -and $subjectDecimalIP -le $objectDecimalBroadcast) { return $true } else { return $false } } |