IPv4Calc.psm1

function New-IPv4SubnetObject {
<#
.SYNOPSIS
    Returns a custom object representing an IPv4 subnet that contains useful information about the IP space it represents.
.DESCRIPTION
    The New-IPv4SubnetObject cmdlet returns a custom object representing an IPv4 subnet that contains the following information:
 
    * CIDR notation representation of the subnet
    * CIDR prefix length
    * Subnet mask
    * Network address of the subnet
    * Subnet broadcast address
    * First usable IPv4 address in the subnet
    * Last usable IPv4 addresse in the subnet
    * Number of usable IPv4 addresses in the subnet
    * An array of all usable IPv4 addresses in the subnet
 
    The subnet can be specified in CIDR notation or with an IPv4 address and either a CIDR prefix length or an IPv4 subnet mask.
 
    Generating all usable addresses in a large subnet can take some time. If this information is not needed you can use the
    SkipUsableAddressList parameter to skip that operation. The first usable address, last usable address, and usable address count
    properties will still be populated.
.PARAMETER CIDRNotation
    CIDR notation representation of the subnet.
.PARAMETER IP
    Network address of the subnet.
.PARAMETER PrefixLength
    CIDR prefix length of the subnet.
.PARAMETER SubnetMask
    Subnet mask of the subnet.
.PARAMETER SkipUsableAddressList
    If set the object is created without the UsableAddressList property.
#>

    [Alias("New-SubnetObject")]
    [CmdletBinding(DefaultParameterSetName='CIDRNotation')]
    param(
        [Parameter(ParameterSetName='CIDRNotation')][string]$CIDRNotation,
        [Parameter(ParameterSetName='PrefixLength')][Parameter(ParameterSetName='SubnetMask')][System.Net.IPAddress]$IP,
        [Parameter(ParameterSetName='PrefixLength')][ValidateRange(1,32)][byte]$PrefixLength,
        [Parameter(ParameterSetName='SubnetMask')][System.Net.IPAddress]$SubnetMask,
        [switch]$SkipUsableAddressList
    )

    switch ($PSCmdlet.ParameterSetName) {
        "CIDRNotation" {
            if ($CIDRNotation -notmatch "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/([0-9]|[1-2][0-9]|3[0-2]))$") {
                throw "Invaid CIDR format: $CIDRNotation"
            }

            try {
                [System.Net.IPAddress]$IP,[byte]$PrefixLength = $CIDRNotation -split "/"
            }
            catch {
                throw $Error[0]
            }

            $SubnetMask = Get-IPv4SubnetMask -PrefixLength $PrefixLength
            if ($null -eq $SubnetMask) {
                throw "Invalid prefix length specified in CIDR format: $PrefixLength"
            }
        }
        "SubnetMask" {
            $PrefixLength = Get-IPv4PrefixLength -SubnetMask $SubnetMask
            if ($PrefixLength -eq 0) {
                throw "Invalid subnet mask specified: $SubnetMask"
            }
        }
        "PrefixLength" {
            $SubnetMask = Get-IPv4SubnetMask -PrefixLength $PrefixLength
            if ($null -eq $SubnetMask) {
                throw "Invalid prefix length specified: $PrefixLength"
            }
        }
    }

    $Subnet = New-Object System.Object
    $Subnet | Add-Member -MemberType NoteProperty -Name NetworkAddress -Value $null
    $Subnet | Add-Member -MemberType NoteProperty -Name PrefixLength -Value $null
    $Subnet | Add-Member -MemberType NoteProperty -Name SubnetMask -Value $null
    $Subnet | Add-Member -MemberType NoteProperty -Name CIDRNotation -Value $null
    $Subnet | Add-Member -MemberType NoteProperty -Name FirstAddress -Value $null
    $Subnet | Add-Member -MemberType NoteProperty -Name LastAddress -Value $null
    $Subnet | Add-Member -MemberType NoteProperty -Name BroadcastAddress -Value $null
    $Subnet | Add-Member -MemberType NoteProperty -Name UsableAddressCount -Value $null

    $Subnet.NetworkAddress = Get-IPv4NetworkAddress -IP $IP -PrefixLength $PrefixLength
    $Subnet.PrefixLength = $PrefixLength
    $Subnet.SubnetMask = $SubnetMask
    $Subnet.CIDRNotation = "$($Subnet.NetworkAddress)/$($Subnet.PrefixLength)"
    $Subnet.FirstAddress = Get-IPv4FirstAddress -IP $IP -PrefixLength $PrefixLength
    $Subnet.LastAddress = Get-IPv4LastAddress -IP $IP -PrefixLength $PrefixLength
    $Subnet.BroadcastAddress = Get-IPv4BroadcastAddress -IP $IP -PrefixLength $PrefixLength

    if ($SkipUsableAddressList) {
        $Subnet.UsableAddressCount = Get-IPv4UsableAddressCount -PrefixLength $PrefixLength
    }
    else {
        $Subnet | Add-Member -MemberType NoteProperty -Name UsableAddressList -Value $null
        $Subnet.UsableAddressList =  Get-IPv4UsableAddressList -IP $IP -PrefixLength $PrefixLength
        $Subnet.UsableAddressCount = $Subnet.UsableAddressList.Count
    }

    return $Subnet
}

function Get-IPv4NetworkAddress {
<#
.SYNOPSIS
    Returns an IPAddress object containing the network address of the subnet defined by the supplied IP and either a CIDR prefix length or subnet mask.
.PARAMETER IP
    Network address of the subnet.
.PARAMETER PrefixLength
    CIDR prefix length of the subnet.
.PARAMETER SubnetMask
    Subnet mask of the subnet.
#>

    param(
        [Parameter(Mandatory=$True)][System.Net.IPAddress]$IP,
        [Parameter(ParameterSetName='PrefixLength')][ValidateRange(1,32)][byte]$PrefixLength,
        [Parameter(ParameterSetName='SubnetMask')][System.Net.IPAddress]$SubnetMask
    )

    switch ($PSCmdlet.ParameterSetName) {
        "SubnetMask" {
            $PrefixLength = Get-IPv4PrefixLength -SubnetMask $SubnetMask
            if ($PrefixLength -eq 0) {
                throw "Invalid subnet mask specified: $SubnetMask"
            }
        }
    }

    [byte[]]$NetworkAddressBytes = 0,0,0,0

    $IPAddressBytes = $IP.GetAddressBytes()
    [System.Net.IPAddress]$SubnetMask = Get-IPv4SubnetMask -PrefixLength $PrefixLength
    if ($null -eq $SubnetMask) {
        throw "Invalid prefix length specified: $PrefixLength"
    }
    $SubnetMaskAddressBytes = $SubnetMask.GetAddressBytes()

    for ($x = 0;$x -lt 4;$x++) {
        $NetworkAddressBytes[$x] = $SubnetMaskAddressBytes[$x] -band $IPAddressBytes[$x]
    }

    [System.Net.IPAddress]$NetworkAddress = $NetworkAddressBytes

    return $NetworkAddress
}

function Get-IPv4BroadcastAddress {
<#
.SYNOPSIS
    Returns an IPAddress object containing the broadcast address of the subnet defined by the supplied IP and either a CIDR prefix length or subnet mask.
.PARAMETER IP
    Network address of the subnet.
.PARAMETER PrefixLength
    CIDR prefix length of the subnet.
.PARAMETER SubnetMask
    Subnet mask of the subnet.
#>

    param(
        [Parameter(Mandatory=$True)][System.Net.IPAddress]$IP,
        [Parameter(ParameterSetName='PrefixLength')][ValidateRange(1,32)][byte]$PrefixLength,
        [Parameter(ParameterSetName='SubnetMask')][System.Net.IPAddress]$SubnetMask
    )

    switch ($PSCmdlet.ParameterSetName) {
        "SubnetMask" {
            $PrefixLength = Get-IPv4PrefixLength -SubnetMask $SubnetMask
            if ($PrefixLength -eq 0) {
                throw "Invalid subnet mask specified: $SubnetMask"
            }
        }
    }

    [byte[]]$BroadcastAddressBytes = 0,0,0,0

    $IPAddressBytes = $IP.GetAddressBytes()
    [System.Net.IPAddress]$SubnetMask = Get-IPv4SubnetMask -PrefixLength $PrefixLength
    if ($null -eq $SubnetMask) {
        throw "Invalid prefix length specified: $PrefixLength"
    }
    $SubnetMaskAddressBytes = $SubnetMask.GetAddressBytes()

    for ($x = 0;$x -lt 4;$x++) {
        #PowerShell bitwise operators return signed integers. For a NOT operation this will be a negative number. Have to add it to 256 to get a positive value that can be cast into a byte.
        $BroadcastAddressBytes[$x] = ((256 + -bnot $SubnetMaskAddressBytes[$x]) -bor $IPAddressBytes[$x])
    }

    [System.Net.IPAddress]$BroadcastAddress = $BroadcastAddressBytes

    return $BroadcastAddress
}

function Get-IPv4FirstAddress {
<#
.SYNOPSIS
    Returns an IPAddress object containing the first usable address of the subnet defined by the supplied IP and either a CIDR prefix length or subnet mask.
.PARAMETER IP
    Network address of the subnet.
.PARAMETER PrefixLength
    CIDR prefix length of the subnet.
.PARAMETER SubnetMask
    Subnet mask of the subnet.
#>

    param(
        [Parameter(Mandatory=$True)][System.Net.IPAddress]$IP,
        [Parameter(ParameterSetName='PrefixLength')][ValidateRange(1,32)][byte]$PrefixLength,
        [Parameter(ParameterSetName='SubnetMask')][System.Net.IPAddress]$SubnetMask
    )

    switch ($PSCmdlet.ParameterSetName) {
        "SubnetMask" {
            $PrefixLength = Get-IPv4PrefixLength -SubnetMask $SubnetMask
            if ($PrefixLength -eq 0) {
                throw "Invalid subnet mask specified: $SubnetMask"
            }
        }
    }

    [System.Net.IPAddress]$NetworkAddress = Get-IPv4NetworkAddress -IP $IP -PrefixLength $PrefixLength

    switch ($PrefixLength) {
        32 {
            [System.Net.IPAddress]$FirstAddress = $NetworkAddress
        }
        31 {
            [System.Net.IPAddress]$FirstAddress = $NetworkAddress
        }
        default {
            [System.Net.IPAddress]$FirstAddress = [byte[]](IncrementAddressBytes -AddressBytes $NetworkAddress.GetAddressBytes())
        }
    }

    return $FirstAddress
}

function Get-IPv4LastAddress {
<#
.SYNOPSIS
    Returns an IPAddress object containing the last usable address of the subnet defined by the supplied IP and either a CIDR prefix length or subnet mask.
.PARAMETER IP
    Network address of the subnet.
.PARAMETER PrefixLength
    CIDR prefix length of the subnet.
.PARAMETER SubnetMask
    Subnet mask of the subnet.
#>

    param(
        [Parameter(Mandatory=$True)][System.Net.IPAddress]$IP,
        [Parameter(ParameterSetName='PrefixLength')][ValidateRange(1,32)][byte]$PrefixLength,
        [Parameter(ParameterSetName='SubnetMask')][System.Net.IPAddress]$SubnetMask
    )

    switch ($PSCmdlet.ParameterSetName) {
        "SubnetMask" {
            $PrefixLength = Get-IPv4PrefixLength -SubnetMask $SubnetMask
            if ($PrefixLength -eq 0) {
                throw "Invalid subnet mask specified: $SubnetMask"
            }
        }
    }

    [System.Net.IPAddress]$BroadcastAddress = Get-IPv4BroadcastAddress -IP $IP -PrefixLength $PrefixLength

    switch ($PrefixLength) {
        32 {
            [System.Net.IPAddress]$LastAddress = $BroadcastAddress
        }
        31 {
            [System.Net.IPAddress]$LastAddress = $BroadcastAddress
        }
        default {
            [System.Net.IPAddress]$LastAddress = [byte[]](DecrementAddressBytes -AddressBytes $BroadcastAddress.GetAddressBytes())
        }
    }

    return $LastAddress
}

function Get-IPv4SubnetMask {
<#
.SYNOPSIS
    Returns an IPAddress object of the subnet mask corresponding to a CIDR prefix length
.PARAMETER PrefixLength
    CIDR prefix length of the subnet.
#>

    param(
        [ValidateRange(1,32)][byte]$PrefixLength
    )

    switch ($PrefixLength) {
        1 { $SubnetMaskText = "128.0.0.0" }
        2 { $SubnetMaskText = "192.0.0.0" }
        3 { $SubnetMaskText = "224.0.0.0" }
        4 { $SubnetMaskText = "240.0.0.0" }
        5 { $SubnetMaskText = "248.0.0.0" }
        6 { $SubnetMaskText = "252.0.0.0" }
        7 { $SubnetMaskText = "254.0.0.0" }
        8 { $SubnetMaskText = "255.0.0.0" }
        9 { $SubnetMaskText = "255.128.0.0" }
        10 { $SubnetMaskText = "255.192.0.0" }
        11 { $SubnetMaskText = "255.224.0.0" }
        12 { $SubnetMaskText = "255.240.0.0" }
        13 { $SubnetMaskText = "255.248.0.0" }
        14 { $SubnetMaskText = "255.252.0.0" }
        15 { $SubnetMaskText = "255.254.0.0" }
        16 { $SubnetMaskText = "255.255.0.0" }
        17 { $SubnetMaskText = "255.255.128.0" }
        18 { $SubnetMaskText = "255.255.192.0" }
        19 { $SubnetMaskText = "255.255.224.0" }
        20 { $SubnetMaskText = "255.255.240.0" }
        21 { $SubnetMaskText = "255.255.248.0" }
        22 { $SubnetMaskText = "255.255.252.0" }
        23 { $SubnetMaskText = "255.255.254.0" }
        24 { $SubnetMaskText = "255.255.255.0" }
        25 { $SubnetMaskText = "255.255.255.128" }
        26 { $SubnetMaskText = "255.255.255.192" }
        27 { $SubnetMaskText = "255.255.255.224" }
        28 { $SubnetMaskText = "255.255.255.240" }
        29 { $SubnetMaskText = "255.255.255.248" }
        30 { $SubnetMaskText = "255.255.255.252" }
        31 { $SubnetMaskText = "255.255.255.254" }
        32 { $SubnetMaskText = "255.255.255.255" }
        default { $SubnetMaskText = $null }
    }

    [System.Net.IPAddress]$SubnetMask = $SubnetMaskText
    return $SubnetMask
}

function Get-IPv4PrefixLength {
<#
.SYNOPSIS
    Returns the CIDR prefix length corresponding to a subnet mask.
.PARAMETER SubnetMask
    Subnet mask to get the prefix length of.
#>

    param(
        [ValidateSet(
            "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"
        )][string]$SubnetMask
    )

    switch ($SubnetMask) {
        "128.0.0.0" { $PrefixLength = 1 }
        "192.0.0.0" { $PrefixLength = 2 }
        "224.0.0.0" { $PrefixLength = 3 }
        "240.0.0.0" { $PrefixLength = 4 }
        "248.0.0.0" { $PrefixLength = 5 }
        "252.0.0.0" { $PrefixLength = 6 }
        "254.0.0.0" { $PrefixLength = 7 }
        "255.0.0.0" { $PrefixLength = 8 }
        "255.128.0.0" { $PrefixLength = 9 }
        "255.192.0.0" { $PrefixLength = 10 }
        "255.224.0.0" { $PrefixLength = 11 }
        "255.240.0.0" { $PrefixLength = 12 }
        "255.248.0.0" { $PrefixLength = 13 }
        "255.252.0.0" { $PrefixLength = 14 }
        "255.254.0.0" { $PrefixLength = 15 }
        "255.255.0.0" { $PrefixLength = 16 }
        "255.255.128.0" { $PrefixLength = 17 }
        "255.255.192.0" { $PrefixLength = 18 }
        "255.255.224.0" { $PrefixLength = 19 }
        "255.255.240.0" { $PrefixLength = 20 }
        "255.255.248.0" { $PrefixLength = 21 }
        "255.255.252.0" { $PrefixLength = 22 }
        "255.255.254.0" { $PrefixLength = 23 }
        "255.255.255.0" { $PrefixLength = 24 }
        "255.255.255.128" { $PrefixLength = 25 }
        "255.255.255.192" { $PrefixLength = 26 }
        "255.255.255.224" { $PrefixLength = 27 }
        "255.255.255.240" { $PrefixLength = 28 }
        "255.255.255.248" { $PrefixLength = 29 }
        "255.255.255.252" { $PrefixLength = 30 }
        "255.255.255.254" { $PrefixLength = 31 }
        "255.255.255.255" { $PrefixLength = 32 }
        default {$PrefixLength = 0}
    }

    return $PrefixLength
}

function Get-IPv4UsableAddressList {
<#
.SYNOPSIS
    Returns an array of IPAddress objects containing the usable addresses of the subnet defined by the supplied IP and either a CIDR prefix length or subnet mask.
.PARAMETER IP
    Network address of the subnet.
.PARAMETER PrefixLength
    CIDR prefix length of the subnet.
.PARAMETER SubnetMask
    Subnet mask of the subnet.
#>

    param(
        [Parameter(Mandatory=$True)][System.Net.IPAddress]$IP,
        [Parameter(ParameterSetName='PrefixLength')][ValidateRange(1,32)][byte]$PrefixLength,
        [Parameter(ParameterSetName='SubnetMask')][System.Net.IPAddress]$SubnetMask
    )

    switch ($PSCmdlet.ParameterSetName) {
        "SubnetMask" {
            $PrefixLength = Get-IPv4PrefixLength -SubnetMask $SubnetMask
            if ($PrefixLength -eq 0) {
                throw "Invalid subnet mask specified: $SubnetMask"
            }
        }
    }

    $UsableAddressCount = Get-IPv4UsableAddressCount -PrefixLength $PrefixLength

    try {
        [System.Net.IPAddress[]]$UsableAddressList = @($null) * $UsableAddressCount
    }
    catch {
        throw $Error[0]
    }

    switch ($PrefixLength) {
        32 {
            $UsableAddressList[0] = Get-IPv4NetworkAddress -IP $IP -PrefixLength $PrefixLength
        }
        31 {
            $UsableAddressList[0] = Get-IPv4NetworkAddress -IP $IP -PrefixLength $PrefixLength
            $UsableAddressList[1] = Get-IPv4BroadcastAddress -IP $IP -PrefixLength $PrefixLength
        }
        default {
            $NetworkAddress = Get-IPv4NetworkAddress -IP $IP -PrefixLength $PrefixLength
            $AddressBytes = $NetworkAddress.GetAddressBytes()

            for ($x = 0; $x -lt $UsableAddressCount; $x++) {
                [byte[]]$AddressBytes = IncrementAddressBytes -AddressBytes $AddressBytes
                [System.Net.IPAddress]$UsableAddressList[$x] = $AddressBytes
            }
        }
    }

    return $UsableAddressList
}

function Get-IPv4UsableAddressCount {
<#
.SYNOPSIS
    Returns the number of usable addresses in a subnet with the specified CIDR prefix length or subnet mask.
.PARAMETER PrefixLength
    CIDR prefix length of the subnet.
.PARAMETER SubnetMask
    Subnet mask of the subnet.
#>

    param(
        [Parameter(ParameterSetName='PrefixLength')][ValidateRange(1,32)][byte]$PrefixLength,
        [Parameter(ParameterSetName='SubnetMask')][System.Net.IPAddress]$SubnetMask
    )

    switch ($PSCmdlet.ParameterSetName) {
        "SubnetMask" {
            $PrefixLength = Get-IPv4PrefixLength -SubnetMask $SubnetMask
            if ($PrefixLength -eq 0) {
                throw "Invalid subnet mask specified: $SubnetMask"
            }
        }
    }

    switch ($PrefixLength) {
        32 { $UsableAddressCount = 1 }
        31 { $UsableAddressCount = 2 }
        default { $UsableAddressCount = [Math]::Pow(2,(32 - $PrefixLength)) - 2 }
    }

    return [int]$UsableAddressCount
}

function IncrementAddressBytes {
<#
.SYNOPSIS
    Increments a four byte array representing an IPv4 address. Does not wrap around, so if called on an array representing 255.255.255.255 it will return 255.255.255.255.
.PARAMETER AddressBytes
    Array of bytes representing an IPv4 address.
.PARAMETER PositionToDecrement
    Array index to increment. Defaults to 3, which represents the least signigicant byte.
#>

    param(
        [byte[]]$AddressBytes,
        [int]$PositionToIncrement = 3
    )

    if ($PositionToIncrement -lt 0) {
        #do nothing
    }
    elseif ($AddressBytes[$PositionToIncrement] -eq 255) {
        $AddressBytes[$PositionToIncrement] = 0
        $AddressBytes = IncrementAddressBytes -AddressBytes $AddressBytes -PositionToIncrement ($PositionToIncrement - 1)
    }
    else {
        $AddressBytes[$PositionToIncrement] += 1
    }

    return $AddressBytes
}

function DecrementAddressBytes {
<#
.SYNOPSIS
    Decrements a four byte array representing an IPv4 address. Does not wrap around, so if called on an array representing 0.0.0.0 it will return 0.0.0.0.
.PARAMETER AddressBytes
    Array of bytes representing an IPv4 address.
.PARAMETER PositionToDecrement
    Array index to decrement. Defaults to 3, which represents the least signigicant byte.
#>

    param(
        [byte[]]$AddressBytes,
        [int]$PositionToDecrement = 3
    )

    if ($PositionToDecrement -lt 0) {
        #do nothing
    }
    elseif ($AddressBytes[$PositionToDecrement] -eq 0) {
        $AddressBytes[$PositionToDecrement] = 255
        $AddressBytes = DecrementAddressBytes -AddressBytes $AddressBytes -PositionToDecrement ($PositionToDecrement - 1)
    }
    else {
        $AddressBytes[$PositionToDecrement] -= 1
    }

    return $AddressBytes
}

function Test-IPv4inSubnet {
<#
.SYNOPSIS
    Returns true if the specified IP address is in the specified subnet.
.PARAMETER IP
    IP address to test against the subnet.
.PARAMETER SubnetIP
    Network address of the subnet.
.PARAMETER PrefixLength
    CIDR prefix length of the subnet.
.PARAMETER SubnetMask
    Subnet mask of the subnet.
#>

    [Alias("Test-IPinSubnet")]
    [CmdletBinding(DefaultParameterSetName='PrefixLength')]
    param(
        [System.Net.IPAddress]$IP,
        [System.Net.IPAddress]$SubnetIP,
        [Parameter(ParameterSetName='PrefixLength')][ValidateRange(1,32)][byte]$PrefixLength,
        [Parameter(ParameterSetName='SubnetMask')][System.Net.IPAddress]$SubnetMask
    )

    switch ($PSCmdlet.ParameterSetName) {
        "SubnetMask" {
            $PrefixLength = Get-IPv4PrefixLength -SubnetMask $SubnetMask
            if ($PrefixLength -eq 0) {
                throw "Invalid subnet mask specified: $SubnetMask"
            }
        }
    }

    return (Get-IPv4NetworkAddress -IP $IP -PrefixLength $PrefixLength).Equals($(Get-IPv4NetworkAddress -IP $SubnetIP -PrefixLength $PrefixLength))
}

function Test-IPv4inSubnetList {
<#
.SYNOPSIS
    Returns true if the specified IP address is any of the subnets specified with the SubnetCIDRList parameter.
.PARAMETER IP
    IP address to test against each subnet.
.PARAMETER SubnetCIDRList
    Array of strings specifying the subnets to test against in CIDR format.
#>

    [Alias("Test-IPinSubnetList")]
    param(
        [System.Net.IPAddress]$IP,
        [string[]]$SubnetCIDRList
    )

    $MatchesFound = 0
    foreach ($subnetText in $SubnetCIDRList) {
        if ($subnetText -notmatch "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/([0-9]|[1-2][0-9]|3[0-2]))$") {
            throw "Invaid CIDR format: $subnetText"
        }

        [System.Net.IPAddress]$SubnetIP,[byte]$PrefixLength = $subnetText -split "/"
        if (Test-IPv4inSubnet -IP $IP -SubnetIP $SubnetIP -PrefixLength $PrefixLength) {
            $MatchesFound++
        }
    }

    return ($MatchesFound -gt 0)
}

function Test-IPv4SubnetOverlap {
<#
.SYNOPSIS
    Returns true if the any of the subnets specified in the SubnetCIDRList parameter overlap.
.PARAMETER SubnetCIDRList
    Array of strings specifying subnets in CIDR format to test for overlap.
#>

    [Alias("Test-SubnetOverlap")]
    param(
        [string[]]$SubnetCIDRList
    )

    $SubnetList = @()
    foreach ($CIDR in $SubnetCIDRList) {
        $SubnetList += New-IPv4SubnetObject -CIDRNotation $CIDR -SkipUsableAddressList
    }

    $OverlapCount = 0
    for ($x = 0; $x -lt $SubnetList.Count; $x++) {
        for ($y = $x + 1; $y -lt $SubnetList.Count; $y++) {
            if (Test-IPv4inSubnet -IP $SubnetList[$x].NetworkAddress -SubnetIP $SubnetList[$y].NetworkAddress -PrefixLength $SubnetList[$y].PrefixLength) {
                $OverlapCount++
            }

            if (Test-IPv4inSubnet -IP $SubnetList[$x].BroadcastAddress -SubnetIP $SubnetList[$y].NetworkAddress -PrefixLength $SubnetList[$y].PrefixLength) {
                $OverlapCount++
            }

            if (Test-IPv4inSubnet -IP $SubnetList[$y].NetworkAddress -SubnetIP $SubnetList[$x].NetworkAddress -PrefixLength $SubnetList[$x].PrefixLength) {
                $OverlapCount++
            }

            if (Test-IPv4inSubnet -IP $SubnetList[$y].BroadcastAddress -SubnetIP $SubnetList[$x].NetworkAddress -PrefixLength $SubnetList[$x].PrefixLength) {
                $OverlapCount++
            }
        }
    }

    return $OverlapCount -ne 0
}