New-IPSubnet.ps1

<#PSScriptInfo
 
    .VERSION 1.0.2
 
    .GUID 3bb10ee7-38c1-41b9-88ea-16899164fc19
 
    .AUTHOR Anthony J. Raymond
 
    .COMPANYNAME
 
    .COPYRIGHT (c) 2022 Anthony J. Raymond
 
    .TAGS IP Subnet Network IPv4 IPv6
 
    .LICENSEURI https://github.com/CodeAJGit/posh/blob/main/LICENSE
 
    .PROJECTURI https://github.com/CodeAJGit/posh
 
    .ICONURI
 
    .EXTERNALMODULEDEPENDENCIES
 
    .REQUIREDSCRIPTS
 
    .EXTERNALSCRIPTDEPENDENCIES
 
    .RELEASENOTES
        20220302-AJR: v1.0.0 - Initial Release
        20220302-AJR: v1.0.1 - Fix Clerical Errors and Added Tags
        20220305-AJR: v1.0.2 - Updated Metadata
 
    .PRIVATEDATA
 
#>


<#
 
    .DESCRIPTION
        Creates an [IPSubnet] object for use to manipulate IPv4 & IPv6 subnets.
 
    .EXAMPLE
        .\New-IPSubnet.ps1 192.168.1.0/30
 
    .EXAMPLE
        .\New-IPSubnet.ps1 -Subnet 2001:db8::1 -Prefix 32
 
    .PARAMETER Subnet
        Specifies an IPv4 or IPv6 address.
 
    .PARAMETER Prefix
        <Optional> Specifies the prefix (or network portion) of the address.
 
#>

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

## PARAMETERS #############################################################
param (
    [Parameter(
        Position = 0,
        Mandatory
    )]
    [string]
    $Subnet,

    [Parameter()]
    [ValidateRange(0, 128)]
    [int]
    $Prefix = ($Subnet -split '\\|\/')[-1]
)

## FUNCTIONS AND SCRIPT VARIABLES #########################################
class IPSubnet : System.Object {
    # class properties ####################################################
    [System.Net.IPAddress] $Network
    [System.Net.Sockets.AddressFamily] $AddressFamily
    [ValidateRange(0, 128)] [int] $Prefix
    [System.Net.IPAddress] $SubnetMask
    [System.Net.IPAddress] $LastAddress

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

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

    [bool] Contains([System.Net.IPAddress]$InputIPAddress) {
        # compute and compare network address
        $InputAddress = $this.ToAddress($InputIPAddress, $false) -band $this.PrefixInt
        $NetworkAddress = $this.ToAddress($this.Network, $false)

        return ($NetworkAddress -eq $InputAddress)
    }

    [IPSubnet[]] Subnet([int]$InputPrefix) {
        $Power = switch ($this.AddressFamily) {
            "InterNetwork" { [bigint]::Pow(2, (32 - $InputPrefix)) }
            "InterNetworkV6" { [bigint]::Pow(2, (128 - $InputPrefix)) }
        }
        $Subnets = for ($AddressInt = $this.StartInt; $AddressInt -le $this.EndInt; $AddressInt += $Power) {
            [IPSubnet]::new(($this.FromAddress($AddressInt, $true)), $InputPrefix)
        }
        return $Subnets
    }

    [bigint] hidden ToAddress([System.Net.IPAddress]$InputIPAddress, [bool]$Reverse) {
        # here we go address -> bytes -> reverse -> bigint
        [byte[]] $Bytes = $InputIPAddress.GetAddressBytes()

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

    [System.Net.IPAddress] hidden FromAddress([bigint]$InputInt, [bool]$Reverse) {
        # here we go backward bigint -> bytes -> reverse -> address
        [byte[]] $Bytes = $InputInt.ToByteArray()

        # we're going to pad the array for the size that the IPAddress constructor expects
        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]$InputInt) {
        # turn prefix into binary string so we can work with it
        $Binary = switch ($this.AddressFamily) {
            "InterNetwork" { ('1' * $InputInt).PadRight(32, '0') }
            "InterNetworkV6" { ('1' * $InputInt).PadRight(128, '0') }
        }
        # the last element of this match is null, so skip it
        $Octet = $Binary -split '(?<=\G[01]{8})' | Select-Object -SkipLast 1
        $Bytes = $Octet | ForEach-Object { [System.Convert]::ToUInt32($_, 2) }

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

    [void] hidden __init__([System.Net.IPAddress]$InputIPAddress, [int]$InputPrefix) {
        $this.Prefix = $InputPrefix
        $this.AddressFamily = $InputIPAddress.AddressFamily

        # quick prefix validation
        switch ($true) {
            {$this.AddressFamily -eq "InterNetwork" -and $this.Prefix -in 0..32} { break }
            {$this.AddressFamily -eq "InterNetworkV6" -and $this.Prefix -in 0..128} { break }
            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)

        # using the prefix we can find the network address
        $StartAddress = $this.ToAddress($InputIPAddress, $false) -band $this.PrefixInt
        $this.Network = $this.FromAddress($StartAddress, $false)

        # using the prefix, we can find the last address
        $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)

        Update-TypeData -TypeName IPSubnet -MemberType AliasProperty -MemberName Broadcast -Value LastAddress -Force
        Update-TypeData -TypeName IPSubnet -MemberType AliasProperty -MemberName Mask -Value SubnetMask -Force
        Update-TypeData -TypeName IPSubnet -DefaultDisplayPropertySet Network, AddressFamily, Prefix, LastAddress -Force
    }

    # class constructors ##################################################
    IPSubnet([System.Net.IPAddress]$InputIPAddress, [int]$InputPrefix) {
        $this.__init__($InputIPAddress, $InputPrefix)
    }

    IPSubnet([string]$InputString) {
        try {
            $SplitString = $InputString -split '\\|\/'
            [System.Net.IPAddress] $InputIPAddress = $SplitString[0]
            [int] $InputPrefix = $SplitString[-1]
        }
        catch {
            throw [System.InvalidCastException] "An invalid IP address or prefix format was specified."
        }
        $this.__init__($InputIPAddress, $InputPrefix)
    }
}

## EXECUTION ##############################################################
[IPSubnet]::new(($Subnet -split '\\|\/')[0], $Prefix)

## CLEAN UP ###############################################################
[void]([System.GC]::GetTotalMemory($true))