Measure an address pool without double counting
Combine exclusions, reservations, and active lease exports into a consistent pool-capacity report.
The pool is an explicit allocation range
pool.json declares an inclusive Start and End, excluded addresses, and reserved addresses for one ScopeId. Treat the range as already selected by its administrator. Do not subtract two addresses for network/broadcast: this task uses an explicit pool, not a generic subnet host-count formula.
Convert endpoints to IPv4 numbers. Capacity is last - first + 1, minus distinct exclusions inside the range. An exclusion listed twice still removes one address. Ignore valid excluded or reserved addresses outside the declared range.
Count occupied addresses as a union
Occupied means a reserved address or an Active lease for this scope, within the usable pool. A reserved address that also has an active lease occupies one address, not two. Repeated lease rows also count once. Ignore expired leases, other scopes, and excluded addresses.
'192.0.2.11','192.0.2.12','192.0.2.12' | Sort-Object -Unique
Return Usable, Occupied, Available, and UtilisationPercent. A pool with no usable addresses has null utilisation, because the percentage cannot be calculated. A non-empty pool with no occupied addresses has a real zero.
Use arithmetic and sets rather than enumerating the whole range. No lease is issued or reclaimed, and lease State is the supplied snapshot's observation rather than a claim about today's server state.
Get-Content '/home/user/pool.json' -Raw | ConvertFrom-Json
Put it into practice.
Dot-source AddressMath.ps1 and define Get-PoolCapacity -Pool -Leases. Apply the inclusive-range, exclusion, and occupied-union rules above. Return ScopeId and numeric Usable, Occupied, Available; round UtilisationPercent to two decimals, or null when Usable is zero. Reject a reversed range and malformed IPv4 input. Handle empty lease arrays without enumerating the range.
See an explained solution
. '/home/user/AddressMath.ps1'
function Get-PoolCapacity {
param($Pool,[AllowEmptyCollection()][object[]]$Leases)
$first = ConvertTo-IPv4Number $Pool.Start
$last = ConvertTo-IPv4Number $Pool.End
if ($last -lt $first) { throw 'Pool end precedes start.' }
$excluded = @($Pool.Excluded | ForEach-Object { ConvertTo-IPv4Number $_ } | Where-Object { $_ -ge $first -and $_ -le $last } | Sort-Object -Unique)
$candidates = @($Pool.Reservations) + @($Leases | Where-Object { $_.ScopeId -eq $Pool.ScopeId -and $_.State -eq 'Active' } | ForEach-Object { $_.Address })
$occupied = @($candidates | ForEach-Object { ConvertTo-IPv4Number $_ } | Where-Object { $_ -ge $first -and $_ -le $last -and $_ -notin $excluded } | Sort-Object -Unique)
$usable = $last - $first + 1 - $excluded.Count
[pscustomobject]@{ ScopeId=$Pool.ScopeId; Usable=$usable; Occupied=$occupied.Count; Available=($usable-$occupied.Count); UtilisationPercent=$(if ($usable -gt 0) { [math]::Round(100 * $occupied.Count / $usable,2) } else { $null }) }
}
The pool size is calculated without enumeration. Sets prevent reservations and leases from double counting, and ScopeId prevents another pool’s active lease from consuming this pool’s capacity.