New-IPSubnet.ps1

<#PSScriptInfo
 
    .VERSION 1.1.0
 
    .GUID 3bb10ee7-38c1-41b9-88ea-16899164fc19
 
    .AUTHOR Anthony J. Raymond
 
    .COMPANYNAME
 
    .COPYRIGHT (c) 2022 Anthony J. Raymond
 
    .TAGS
 
    .LICENSEURI https://github.com/PoshAJ/PoshToolbox/blob/main/LICENSE
 
    .PROJECTURI https://github.com/PoshAJ/PoshToolbox
 
    .ICONURI
 
    .EXTERNALMODULEDEPENDENCIES
 
    .REQUIREDSCRIPTS
 
    .EXTERNALSCRIPTDEPENDENCIES
 
    .RELEASENOTES
        **DEPRECATED** The New-IPSubnet cmdlet have been integrated into the PoshToolbox module. This cmdlets will no longer be supported or updated as a seperate script.
 
    .PRIVATEDATA
 
#>


<#
 
    .DESCRIPTION
        **DEPRECATED** Moved to PoshToolbox Module.
 
    .PARAMETER InputObject
        Specifies an IP subnet in CIDR notation.
 
    .PARAMETER IPAddress
        Specifies an IP address in dotted-quad notation for IPv4 and in colon-hexadecimal notation for IPv6.
 
    .PARAMETER IPPrefix
        Specifies an IP prefix as the number of bits within the host section of the IP address.
 
#>


# Copyright (c) 2023 Anthony J. Raymond, MIT License (see manifest for details)

[CmdletBinding()]
[OutputType([object])]

## PARAMETERS #############################################################
param (
    [Parameter(
        Position = 0,
        Mandatory,
        ValueFromPipeline,
        ParameterSetName = "InputObject"
    )]
    [string[]]
    $InputObject,

    [Alias("Address")]
    [Parameter(
        Mandatory,
        ParameterSetName = "IPAddress"
    )]
    [string]
    $IPAddress,

    [Alias("Prefix")]
    [Parameter(
        Mandatory,
        ParameterSetName = "IPAddress"
    )]
    [int]
    $IPPrefix
)

## BEGIN ##################################################################
begin {
    class IPSubnet : System.Object {
        ## PROPERTIES #############################################################
        [System.Net.IPAddress] $Network
        [System.Net.Sockets.AddressFamily] $AddressFamily
        [int] $Prefix
        [System.Net.IPAddress] $SubnetMask
        [System.Net.IPAddress] $LastAddress

        [bigint] hidden $PrefixInt
        [bigint] hidden $StartInt
        [bigint] hidden $EndInt

        ## METHODS ################################################################
        static [IPSubnet] Parse([System.Net.IPAddress] $IPAddress, [int] $Prefix) {
            return ([IPSubnet]::new($IPAddress, $Prefix))
        }

        static [bool] TryParse([object] $IPAddress, [object] $Prefix, [ref] $Variable) {
            try {
                $Variable.Value = [IPSubnet]::new($IPAddress, $Prefix)

                return $true
            } catch {
                return $false
            }
        }

        [string] ToString() {
            return ("{0}/{1}" -f $this.Network, $this.Prefix)
        }

        [bool] Contains([System.Net.IPAddress] $IPAddress) {
            $ContainsAddress = $this.ToAddress($IPAddress, $false) -band $this.PrefixInt
            $NetworkAddress = $this.ToAddress($this.Network, $false)

            return ($NetworkAddress -eq $ContainsAddress)
        }

        [IPSubnet[]] Subnet([int] $Prefix) {
            $Power = switch ($this.AddressFamily) {
                "InterNetwork" { [bigint]::Pow(2, (32 - $Prefix)) }
                "InterNetworkV6" { [bigint]::Pow(2, (128 - $Prefix)) }
            }

            $Subnets = for ($AddressInt = $this.StartInt; $AddressInt -le $this.EndInt; $AddressInt += $Power) {
                [IPSubnet]::new(($this.FromAddress($AddressInt, $true)), $Prefix)
            }

            return $Subnets
        }

        [bigint] hidden ToAddress([System.Net.IPAddress] $IPAddress, [bool] $Reverse) {
            [byte[]] $Bytes = $IPAddress.GetAddressBytes()

            if ($Reverse) {
                [array]::Reverse($Bytes)
            }

            return ([bigint]::new($Bytes + 0))
        }

        [System.Net.IPAddress] hidden FromAddress([bigint] $BigInt, [bool] $Reverse) {
            [byte[]] $Bytes = $BigInt.ToByteArray()

            switch ($this.AddressFamily) {
                "InterNetwork" { [array]::Resize([ref] $Bytes, 4) }
                "InterNetworkV6" { [array]::Resize([ref] $Bytes, 16) }
            }

            if ($Reverse) {
                [array]::Reverse($Bytes)
            }

            return ([System.Net.IPAddress] $Bytes)
        }

        [bigint] hidden GetPrefixInt([int] $Int) {
            $Binary = switch ($this.AddressFamily) {
                "InterNetwork" { ('1' * $Int).PadRight(32, '0') }
                "InterNetworkV6" { ('1' * $Int).PadRight(128, '0') }
            }

            $Bytes = $Binary -isplit "(?<byte>[01]{8})", 0, "ExplicitCapture" |
                Where-Object { $_ } | ForEach-Object { [System.Convert]::ToUInt32($_, 2) }

            # append zero byte for unsigned
            return ([bigint]::new($Bytes + 0))
        }

        ## CONSTRUCTORS ###########################################################
        IPSubnet([System.Net.IPAddress] $IPAddress, [int] $Prefix) {
            $this.Prefix = $Prefix
            $this.AddressFamily = $IPAddress.AddressFamily

            switch ($true) {
                { $this.AddressFamily -eq "InterNetwork" -and $this.Prefix -iin 0..32 } { continue }
                { $this.AddressFamily -eq "InterNetworkV6" -and $this.Prefix -iin 0..128 } { continue }
                default { throw ([System.InvalidCastException] "An invalid prefix for the given address family was specified.") }
            }

            $this.PrefixInt = $this.GetPrefixInt($this.Prefix)
            $this.SubnetMask = $this.FromAddress($this.PrefixInt, $false)

            $StartAddress = $this.ToAddress($IPAddress, $false) -band $this.PrefixInt
            $this.Network = $this.FromAddress($StartAddress, $false)

            $EndAddress = switch ($this.AddressFamily) {
                "InterNetwork" { $StartAddress -bor (-bnot $this.PrefixInt -band ([bigint]::Pow(2, 32) - 1)) }
                "InterNetworkV6" { $StartAddress -bor (-bnot $this.PrefixInt -band ([bigint]::Pow(2, 128) - 1)) }
            }
            $this.LastAddress = $this.FromAddress($EndAddress, $false)

            $this.StartInt = $this.ToAddress($this.Network, $true)
            $this.EndInt = $this.ToAddress($this.LastAddress, $true)
        }
    }
}

## PROCESS ################################################################
process {
    foreach ($Object in $PSBoundParameters[$PSCmdlet.ParameterSetName]) {
        try {
            if ($PSCmdlet.ParameterSetName -eq "InputObject") {
                $IPAddress = ($Object -isplit "\\|\/")[0]
                $IPPrefix = ($Object -isplit "\\|\/")[-1]
            }

            Write-Output ([ipsubnet]::Parse($IPAddress, $IPPrefix))

            ## EXCEPTIONS #################################################
        } catch {
            $PSCmdlet.WriteError($_)
        }
    }
}

## END ####################################################################
end {}