functions/Get-AzSRFreeIpAddress.ps1

<#
.SYNOPSIS
    Find free IP addresses in a Azure Virtual Network
 
.DESCRIPTION
    Returns a specified number or all free IP addresses from a specified Azure Virtual Network
    This allows easy IP address management if a static IP address should be used
 
    This function is different to Test-AzPrivateIPAddressAvailability since it returns all free IP addresses or a specified number.
    There is also no need to provide a IP address
 
.PARAMETER NetworkName
    Name of the Virtual Network
 
.PARAMETER ResourceGroupName
    Name of the Resource Group
 
.PARAMETER SubnetName
    Name of the subnet
 
.PARAMETER First
    Gets only the specified number of objects. Enter the number of objects to get.
 
.EXAMPLE
    Find all free IP addresses in a specific Virtual Network
    Get-AzSRFreeIpAddress -NetworkName "MyVirtualNetwork" -ResourceGroupName "Subnet01"
 
.EXAMPLE
    Find one free IP address in one of the available virtual networks
    Get-AzVirtualNetwork | Get-AzSRFreeIpAddress -First 1
 
.NOTES
    Copyright: (c) 2018 Fabian Bader
    License: MIT https://opensource.org/licenses/MIT
#>

function Get-AzSRFreeIpAddress {
    [CmdletBinding()]
    param (
        [Alias('Name')]
        [Parameter(Mandatory = $true,
            ValueFromPipelineByPropertyName = $true)]
        [String]$NetworkName,
        [Parameter(Mandatory = $true,
            ValueFromPipelineByPropertyName = $true)]
        [String]$ResourceGroupName,
        [Parameter(Mandatory = $false)]
        [String]$SubnetName,
        [Int32]$First
    )

    Begin {
        #region Check if logged in
        try {
            Get-AzCachedAccessToken | Out-Null
        } catch {
            throw $($_.Exception.Message)
        }
        #endregion
    }

    Process {
        $FoundIPAddress = 0
        if ($SubnetName) {
                $SubnetConfiguration = Get-AzVirtualNetwork -ExpandResource "subnets/ipConfigurations" -Name $NetworkName -ResourceGroupName $ResourceGroupName | Select-Object -ExpandProperty Subnets | Where-Object { $_.Name -eq $SubnetName}
        } else {
                $SubnetConfiguration = Get-AzVirtualNetwork -ExpandResource "subnets/ipConfigurations" -Name $NetworkName -ResourceGroupName $ResourceGroupName | Select-Object -ExpandProperty Subnets
        }
        foreach ($Subnet in $SubnetConfiguration) {
            Write-Verbose "Check for free IP address in subnet:`t$($Subnet.Name)"
            if ( ( $PSBoundParameters.ContainsKey('First') ) -and ($FoundIPAddress -ge $First)) {
                # We have enough IP addresses
                break
            }
            Write-Verbose "Subnet address prefix:`t`t`t$($Subnet.AddressPrefix)"
            $PossibleIpAddresses = Get-SubnetAddress -Subnet "$($Subnet.AddressPrefix)" | Select-Object -ExpandProperty IPAddressToString
            $UsedIpAddresses = $Subnet.ipConfigurations.privateIPAddress
            if ([string]::IsNullOrEmpty($UsedIpAddresses)) {
                Write-Verbose "No IP addresses used"
                $FreeIPAddresses = $PossibleIpAddresses
            } else {
                $FreeIPAddresses = Compare-Object -ReferenceObject $PossibleIpAddresses -DifferenceObject $UsedIpAddresses | Where-Object { $_.SideIndicator -eq "<="} | Select-Object -ExpandProperty InputObject
            }
            Write-Verbose "Found $($FreeIPAddresses.Count) free IP addresses"
            foreach ($IPAddress in $FreeIPAddresses) {
                # First three address are reserved addresses in each subnet according to MSFT
                # https://docs.microsoft.com/en-us/azure/virtual-network/virtual-networks-faq#are-there-any-restrictions-on-using-ip-addresses-within-these-subnets
                if ($IPAddress -notin ( $PossibleIpAddresses | Select-Object -First 3 ) ) {
                    Write-Verbose "$IPAddress is free"
                    New-Object psobject -Property @{
                        "IPAddress"          = $IPAddress
                        "Subnet"             = $Subnet.Name
                        "VirtualNetworkName" = $NetworkName
                        "ResourceGroupName"  = $ResourceGroupName
                    }
                    $FoundIPAddress++
                    if ( ( $PSBoundParameters.ContainsKey('First') ) -and ($FoundIPAddress -ge $First)) {
                        # We have enough IP addresses
                        break
                    }
                } else {
                    Write-Verbose "$IPAddress is reserved"
                }
            }
        }
    }
}