IP Address Math

Is there a native way in PowerShell to do IP math? Take for example I want to use the network 10.0.0.0/22 and find all pingable devices on the network. It’s a simple example but I should be able to loop through all the address by giving it the CIDR notation or a network with subnet. How should I go about accomplishing this?

As luck would have it, this was part of the practice event in the scripting games (given CIDR notation of a subnet, identify computers on the subnet, collect inventory data, etc.) Here’s how I accomplished it:

function Get-UInt32FromIPAddress
{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory)]
        [ipaddress]
        $IPAddress
    )

    $bytes = $IPAddress.GetAddressBytes()

    if ([BitConverter]::IsLittleEndian)
    {
        [Array]::Reverse($bytes)
    }

    return [BitConverter]::ToUInt32($bytes, 0)
}

function Get-IPAddressFromUInt32
{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory)]
        [UInt32]
        $UInt32
    )

    $bytes = [BitConverter]::GetBytes($UInt32)
            
    if ([BitConverter]::IsLittleEndian)
    {
        [Array]::Reverse($bytes)
    }

    return New-Object ipaddress(,$bytes)
}

function Get-SubnetAddresses
{
    # Converts an IPv4 subnet address in CIDR notation (ie, 192.168.0.0/24) into a collection of [ipaddress] objects.

    [CmdletBinding()]
    param (
        [Parameter(Mandatory)]
        [string]
        $Subnet
    )

    $ipaddress = $null

    # Validating the string format here instead of in a ValidateScript block allows us to use the
    # $ipaddress and $matches variables without having to perform the parsing twice.

    if ($Subnet -notmatch '^(?<Address>\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})/(?<Mask>\d{1,2})$')
    {
        throw "Subnet address '$Subnet' does not match the expected CIDR format (example:  192.168.0.0/24)"
    }

    if (-not [ipaddress]::TryParse($matches['Address'], [ref]$ipaddress))
    {
        throw "Subnet address '$Subnet' contains an invalid IPv4 address."
    }

    $maskDecimal = [int]$matches['Mask']

    if ($maskDecimal -gt 30)
    {
        throw "Subnet address '$Subnet' contains an invalid subnet mask (must be less than or equal to 30)."
    }

    $hostBitCount = 32 - $maskDecimal
        
    $netMask = [UInt32]0xFFFFFFFFL -shl $hostBitCount
    $hostMask = -bnot $netMask

    $networkAddress = (Get-UInt32FromIPAddress -IPAddress $ipaddress) -band $netMask
    $broadcastAddress = $networkAddress -bor $hostMask

    for ($address = $networkAddress + 1; $address -lt $broadcastAddress; $address++)
    {
        Get-IPAddressFromUInt32 -UInt32 $address
    }
}

I could then call the Get-SubnetAddresses function and either pipe it to something, or just save the results as an array to be enumerated with a foreach loop.

Edit: Looking over this code again, I should probably note that it requires PowerShell 3.0 or later (for the -shl operator). It can be modified to work with PowerShell 2.0, but the code would be a little bit harder to read. “$netMask = [UInt32]0xFFFFFFFFL -shl $hostBitCount” would become something like “$netMask = ([UInt32]((0xFFFFFFFL * [math]::Pow(2, $hostBitCount)) -band 0xFFFFFFFFL))”

Sorry to go off topic a bit, but I like the way that you use <Address> and <Mask> in your regex pattern to name the keys in the $matches hash table. Is there any documentation on this feature? I’ve never seen it done before…

Those are called “named groups”. You can find all of the official documentation on .NET regular expressions on MSDN, and http://www.regular-expressions.info/ is also quite a good reference (covering more than just the .NET implementation, plus a lot of good tutorials in general.)