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 (ommited if SkipUsableAddressList parameter is used)
 
    The subnet can be specified using any of the following methods:
 
    * CIDR notation
    * An IPv4 address and a CIDR prefix length
    * An IPv4 address and 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 (-not (Test-IPv4CIDRNotation -CIDRNotation $CIDRNotation)) {
                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"
            }
        }
    }

    $NetworkAddress = Get-IPv4NetworkAddress -IP $IP -PrefixLength $PrefixLength

    $Subnet = [PSCustomObject]@{
        NetworkAddress = $NetworkAddress
        PrefixLength = $PrefixLength
        SubnetMask = $SubnetMask
        CIDRNotation = "$NetworkAddress/$PrefixLength"
        FirstAddress = Get-IPv4FirstAddress -IP $IP -PrefixLength $PrefixLength
        LastAddress = Get-IPv4LastAddress -IP $IP -PrefixLength $PrefixLength
        BroadcastAddress = Get-IPv4BroadcastAddress -IP $IP -PrefixLength $PrefixLength
        UsableAddressCount = $null
    }

    if ($SkipUsableAddressList) {
        $Subnet.UsableAddressCount = Get-IPv4UsableAddressCount -PrefixLength $PrefixLength
    }
    else {
        $Subnet | Add-Member -NotePropertyName UsableAddressList -NotePropertyValue $(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 specified subnet.
.DESCRIPTION
    Returns an IPAddress object containing the network address of the specified subnet. The subnet can be specified using any of the following methods:
 
    * CIDR notation
    * An IPv4 address and a CIDR prefix length
    * An IPv4 address and an IPv4 subnet mask
.PARAMETER CIDRNotation
    CIDR notation representation of the subnet.
.PARAMETER IP
    IP address in the subnet.
.PARAMETER PrefixLength
    CIDR prefix length of the subnet.
.PARAMETER SubnetMask
    Subnet mask of the subnet.
#>

    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 ($PSCmdlet.ParameterSetName) {
        "CIDRNotation" {
            if (-not (Test-IPv4CIDRNotation -CIDRNotation $CIDRNotation)) {
                throw "Invaid CIDR format: $CIDRNotation"
            }

            try {
                [System.Net.IPAddress]$IP,[byte]$PrefixLength = $CIDRNotation -split "/"
            }
            catch {
                throw $Error[0]
            }
        }
        "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 specified subnet.
.DESCRIPTION
    Returns an IPAddress object containing the broadcast address of the specified subnet. The subnet can be specified using any of the following methods:
 
    * CIDR notation
    * An IPv4 address and a CIDR prefix length
    * An IPv4 address and an IPv4 subnet mask
.PARAMETER CIDRNotation
    CIDR notation representation of the subnet.
.PARAMETER IP
    IP address in the subnet.
.PARAMETER PrefixLength
    CIDR prefix length of the subnet.
.PARAMETER SubnetMask
    Subnet mask of the subnet.
#>

    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 ($PSCmdlet.ParameterSetName) {
        "CIDRNotation" {
            if (-not (Test-IPv4CIDRNotation -CIDRNotation $CIDRNotation)) {
                throw "Invaid CIDR format: $CIDRNotation"
            }

            try {
                [System.Net.IPAddress]$IP,[byte]$PrefixLength = $CIDRNotation -split "/"
            }
            catch {
                throw $Error[0]
            }
        }
        "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 specified subnet.
.DESCRIPTION
    Returns an IPAddress object containing the first usable address of the specified subnet. The subnet can be specified using any of the following methods:
 
    * CIDR notation
    * An IPv4 address and a CIDR prefix length
    * An IPv4 address and an IPv4 subnet mask
.PARAMETER CIDRNotation
    CIDR notation representation of the subnet.
.PARAMETER IP
    IP address in the subnet.
.PARAMETER PrefixLength
    CIDR prefix length of the subnet.
.PARAMETER SubnetMask
    Subnet mask of the subnet.
#>

    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 ($PSCmdlet.ParameterSetName) {
        "CIDRNotation" {
            if (-not (Test-IPv4CIDRNotation -CIDRNotation $CIDRNotation)) {
                throw "Invaid CIDR format: $CIDRNotation"
            }

            try {
                [System.Net.IPAddress]$IP,[byte]$PrefixLength = $CIDRNotation -split "/"
            }
            catch {
                throw $Error[0]
            }
        }
        "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 specified subnet.
.DESCRIPTION
    Returns an IPAddress object containing the last usable address of the specified subnet. The subnet can be specified using any of the following methods:
 
    * CIDR notation
    * An IPv4 address and a CIDR prefix length
    * An IPv4 address and an IPv4 subnet mask
.PARAMETER CIDRNotation
    CIDR notation representation of the subnet.
.PARAMETER IP
    IP address in the subnet.
.PARAMETER PrefixLength
    CIDR prefix length of the subnet.
.PARAMETER SubnetMask
    Subnet mask of the subnet.
#>

    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 ($PSCmdlet.ParameterSetName) {
        "CIDRNotation" {
            if (-not (Test-IPv4CIDRNotation -CIDRNotation $CIDRNotation)) {
                throw "Invaid CIDR format: $CIDRNotation"
            }

            try {
                [System.Net.IPAddress]$IP,[byte]$PrefixLength = $CIDRNotation -split "/"
            }
            catch {
                throw $Error[0]
            }
        }
        "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 the specified 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 the specified 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 specified subnet.
.DESCRIPTION
    Returns an array of IPAddress objects containing the usable addresses of the specified subnet. The subnet can be specified using any of the following methods:
 
    * CIDR notation
    * An IPv4 address and a CIDR prefix length
    * An IPv4 address and an IPv4 subnet mask
.PARAMETER CIDRNotation
    CIDR notation representation of the subnet.
.PARAMETER IP
    IP address in the subnet.
.PARAMETER PrefixLength
    CIDR prefix length of the subnet.
.PARAMETER SubnetMask
    Subnet mask of the subnet.
#>

    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 ($PSCmdlet.ParameterSetName) {
        "CIDRNotation" {
            if (-not (Test-IPv4CIDRNotation -CIDRNotation $CIDRNotation)) {
                throw "Invaid CIDR format: $CIDRNotation"
            }

            try {
                [System.Net.IPAddress]$IP,[byte]$PrefixLength = $CIDRNotation -split "/"
            }
            catch {
                throw $Error[0]
            }
        }
        "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 Get-IPv4CIDRNotation {
<#
.SYNOPSIS
    Returns the CIDR notation of the specified subnet.
.DESCRIPTION
    Returns the CIDR notation of the specified subnet. The subnet can be specified using any of the following methods:
 
    * CIDR notation
    * An IPv4 address and a CIDR prefix length
    * An IPv4 address and an IPv4 subnet mask
 
    While being able to specify the subnet in CIDR notation may seem redundant, this is useful for reformatting arbitrary
    CIDR notation into <network address>/<<prefix length> format.
.PARAMETER CIDRNotation
    CIDR notation representation of the subnet.
.PARAMETER IP
    IP address in the subnet.
.PARAMETER PrefixLength
    CIDR prefix length of the subnet.
.PARAMETER SubnetMask
    Subnet mask of the subnet.
#>

    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 ($PSCmdlet.ParameterSetName) {
        "CIDRNotation" {
            if (-not (Test-IPv4CIDRNotation -CIDRNotation $CIDRNotation)) {
                throw "Invaid CIDR format: $CIDRNotation"
            }

            try {
                [System.Net.IPAddress]$IP,[byte]$PrefixLength = $CIDRNotation -split "/"
            }
            catch {
                throw $Error[0]
            }
        }
        "SubnetMask" {
            $PrefixLength = Get-IPv4PrefixLength -SubnetMask $SubnetMask
            if ($PrefixLength -eq 0) {
                throw "Invalid subnet mask specified: $SubnetMask"
            }
        }
    }

    $NetworkAddress = Get-IPv4NetworkAddress -IP $IP -PrefixLength $PrefixLength

    return "$NetworkAddress/$PrefixLength"
}

function IncrementAddressBytes {
<#
.SYNOPSIS
    Increments a four byte array representing an IPv4 address.
.DESCRIPTION
    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.
.DESCRIPTION
    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.
.DESCRIPTION
    Returns true if the specified IP address is in the specified subnet. The subnet can be specified using any of the following methods:
 
    * CIDR notation
    * An IPv4 address and a CIDR prefix length
    * An IPv4 address and an IPv4 subnet mask
.PARAMETER IP
    IP address to test against the subnet.
.PARAMETER SubnetCIDR
    Subnet in CIDR format.
.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='CIDRNotation')]
    param(
        [System.Net.IPAddress]$IP,
        [Parameter(ParameterSetName='CIDRNotation')][string]$SubnetCIDR,
        [Parameter(ParameterSetName='PrefixLength')][Parameter(ParameterSetName='SubnetMask')][System.Net.IPAddress]$SubnetIP,
        [Parameter(ParameterSetName='PrefixLength')][ValidateRange(1,32)][byte]$PrefixLength,
        [Parameter(ParameterSetName='SubnetMask')][System.Net.IPAddress]$SubnetMask
    )

    switch ($PSCmdlet.ParameterSetName) {
        "CIDRNotation" {
            if (-not (Test-IPv4CIDRNotation -CIDRNotation $SubnetCIDR)) {
                throw "Invaid CIDR format: $SubnetCIDR"
            }

            try {
                [System.Net.IPAddress]$SubnetIP,[byte]$PrefixLength = $SubnetCIDR -split "/"
            }
            catch {
                throw $Error[0]
            }
        }
        "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 in any of the subnets specified in the SubnetCIDRList parameter.
.DESCRIPTION
    Returns true if the specified IP address is in any of the subnets specified in the SubnetCIDRList parameter.
 
    Subnets in SubnetCIDRList must be strings in CIDR format.
.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 ($subnetCIDR in $SubnetCIDRList) {
        if (Test-IPv4inSubnet -IP $IP -SubnetCIDR $subnetCIDR) {
            $MatchesFound++
        }
    }

    return ($MatchesFound -ne 0)
}

function Test-IPv4SubnetOverlap {
<#
.SYNOPSIS
    Returns true if the any of the subnets specified in the SubnetCIDRList parameter overlap.
.DESCRIPTION
    Returns true if the any of the subnets specified in the SubnetCIDRList parameter overlap.
 
    Subnets in SubnetCIDRList must be strings in CIDR format.
.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)
}

function Test-IPv4CIDRNotation {
<#
.SYNOPSIS
    Returns true if the specified CIDR notation representation of a subnet is valid and properly formatted.
.PARAMETER CIDRNotation
    CIDR notation representation of the subnet.
#>

    [Alias("Test-CIDRNotation")]
    param(
        [string]$CIDRNotation
    )

    return ($CIDRNotation -match "^(([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]))$")
}

function Test-IPv4CIDRNotationList {
<#
.SYNOPSIS
    Returns true if all of the CIDR notation entries in a list are valid and properly formatted.
.PARAMETER SubnetCIDRList
    Array of strings specifying subnets in CIDR format to test.
#>

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

    $ProblemsFound = 0

    foreach ($subnetCIDR in $SubnetCIDRList) {
        if (-not (Test-IPv4CIDRNotation -SubnetCIDR $subnetCIDR)) {
            $ProblemsFound++
        }
    }

    return ($ProblemsFound -eq 0)
}