Test Azure resource name availability

Background

Most of the services in Azure such as Storage Accounts, Key Vaults or AppService Websites must have globally unique names, where the fully qualified domain name (aka FQDN) for the service uses the name you selected and the suffix for the specific service. For example, for Key Vaults its vault.azure.net and for WebApps its azurewebsites.net

The Azure portal can help you determine the name availability during the service creation, but there’s no built-in PowerShell cmdlet or azure cli command to do so for ARM services (in the old ASM days, we had the Test-AzureName PowerShell cmdlet we could use to check for a classic cloud services name availability).

For scenarios where you have an automated deployment and don’t want the deployment failing because of the name availability, you’d want to have a simple command that returns a true/false boolean value that determines if the name is already taken or not.

Proposed solution

Several of the Azure providers have an API that exposes a checkNameAvailability action that you can use the test the name’s availability. Each provider requires and accepts a different set of parameters, where the most important ones are obviously the name you want to check and the service type.

To get a list of the providers that support the checkNameAvailability action, you can use the following PowerShell command:

Get-AzResourceProvider | 
    Where-Object { $_.ResourceTypes.ResourceTypeName -eq 'checkNameAvailability' } | 
        Select-Object ProviderNamespace

That outputs the following:

ProviderNamespace      
-----------------      
Microsoft.Sql          
Microsoft.Web          
Microsoft.DBforMySQL   
Microsoft.Media        
Microsoft.Cdn          
Microsoft.ApiManagement
Microsoft.BotService   
Microsoft.Storage      
Microsoft.KeyVault     
Microsoft.Management   
microsoft.support 

Drilling down to one of the providers, we can see the list of API versions that support the action, and we can build the URI string to invoke

Get-AzResourceProvider -ProviderNamespace Microsoft.Web |
    Where-Object { $_.ResourceTypes.ResourceTypeName -eq 'checkNameAvailability' } |
        Select-Object -ExpandProperty ResourceTypes | 
            Select-Object -ExpandProperty ApiVersions

Invoking Azure APIs using PowerShell is simple enough, you just need the bearer token, the URI to the API action and the needed parameters for the action. For some of the APIs we need a subscription ID to work with.

The important and main function is Test-AzNameAvailability:

function Test-AzNameAvailability {
    param(
        [Parameter(Mandatory = $true)] [string] $AuthorizationToken,
        [Parameter(Mandatory = $true)] [string] $SubscriptionId,
        [Parameter(Mandatory = $true)] [string] $Name,
        [Parameter(Mandatory = $true)] [ValidateSet(
            'ApiManagement', 'KeyVault', 'ManagementGroup', 'Sql', 'StorageAccount', 'WebApp')]
        $ServiceType
    )

    $uriByServiceType = @{
        ApiManagement   = 'https://management.azure.com/subscriptions/{subscriptionId}/providers/Microsoft.ApiManagement/checkNameAvailability?api-version=2019-01-01'
        KeyVault        = 'https://management.azure.com/subscriptions/{subscriptionId}/providers/Microsoft.KeyVault/checkNameAvailability?api-version=2019-09-01'
        ManagementGroup = 'https://management.azure.com/providers/Microsoft.Management/checkNameAvailability?api-version=2018-03-01-preview'
        Sql             = 'https://management.azure.com/subscriptions/{subscriptionId}/providers/Microsoft.Sql/checkNameAvailability?api-version=2018-06-01-preview'
        StorageAccount  = 'https://management.azure.com/subscriptions/{subscriptionId}/providers/Microsoft.Storage/checkNameAvailability?api-version=2019-06-01'
        WebApp          = 'https://management.azure.com/subscriptions/{subscriptionId}/providers/Microsoft.Web/checkNameAvailability?api-version=2019-08-01'
    }

    $typeByServiceType = @{
        ApiManagement   = 'Microsoft.ApiManagement/service'
        KeyVault        = 'Microsoft.KeyVault/vaults'
        ManagementGroup = '/providers/Microsoft.Management/managementGroups'
        Sql             = 'Microsoft.Sql/servers'
        StorageAccount  = 'Microsoft.Storage/storageAccounts'
        WebApp          = 'Microsoft.Web/sites'
    }

    $uri = $uriByServiceType[$ServiceType] -replace ([regex]::Escape('{subscriptionId}')), $SubscriptionId
    $body = '"name": "{0}", "type": "{1}"' -f $Name, $typeByServiceType[$ServiceType]

    $response = (Invoke-WebRequest -Uri $uri -Method Post -Body "{$body}" -ContentType "application/json" -Headers @{Authorization = $AuthorizationToken }).content
    $response | ConvertFrom-Json |
        Select-Object @{N = 'Name'; E = { $Name } }, @{N = 'Type'; E = { $ServiceType } }, @{N = 'Available'; E = { $_ | Select-Object -ExpandProperty *available } }, Reason, Message
}

To use it, you first have to get a bearer token, for either the current logged on user or for a service principal using one of the two functions Get-AccesTokenFromServicePrincipal or Get-AccesTokenFromCurrentUser:

function Get-AccesTokenFromServicePrincipal {
    param(
        [string] $TenantID,
        [string] $ClientID,
        [string] $ClientSecret
    )

    $TokenEndpoint = 'https://login.windows.net/{0}/oauth2/token' -f $TenantID
    $ARMResource = 'https://management.core.windows.net/'

    $Body = @{
        'resource'      = $ARMResource
        'client_id'     = $ClientID
        'grant_type'    = 'client_credentials'
        'client_secret' = $ClientSecret
    }
    $params = @{
        ContentType = 'application/x-www-form-urlencoded'
        Headers     = @{'accept' = 'application/json' }
        Body        = $Body
        Method      = 'Post'
        URI         = $TokenEndpoint
    }
    $token = Invoke-RestMethod @params
    ('Bearer ' + ($token.access_token).ToString())
}


function Get-AccesTokenFromCurrentUser {
    $azContext = Get-AzContext
    $azProfile = [Microsoft.Azure.Commands.Common.Authentication.Abstractions.AzureRmProfileProvider]::Instance.Profile
    $profileClient = New-Object -TypeName Microsoft.Azure.Commands.ResourceManager.Common.RMProfileClient -ArgumentList $azProfile
    $token = $profileClient.AcquireAccessToken($azContext.Subscription.TenantId)
    ('Bearer ' + $token.AccessToken)
}

To get the current (already logged in) user’s bearer token, use:

$AuthorizationToken = Get-AccesTokenFromCurrentUser

Or to get a Service Principal (App Registration) bearer token, use:

$AuthorizationToken = Get-AccesTokenFromServicePrincipal `
    -TenantID '<Directory Tenant ID>' `
    -ClientID '<Application Client ID>' `
    -ClientSecret '<Application Client Secret>'

And then, to test for the name availability for some of the services you can use:

Test-AzNameAvailability -Name martin -ServiceType ApiManagement -AuthorizationToken $AuthorizationToken -SubscriptionId $subscriptionId 
Test-AzNameAvailability -Name kv -ServiceType KeyVault -AuthorizationToken $AuthorizationToken -SubscriptionId $subscriptionId 
Test-AzNameAvailability -Name root -ServiceType ManagementGroup -AuthorizationToken $AuthorizationToken -SubscriptionId $subscriptionId 
Test-AzNameAvailability -Name sqlsrv1 -ServiceType Sql -AuthorizationToken $AuthorizationToken -SubscriptionId $subscriptionId 
Test-AzNameAvailability -Name storage -ServiceType StorageAccount -AuthorizationToken $AuthorizationToken -SubscriptionId $subscriptionId 
Test-AzNameAvailability -Name www -ServiceType WebApp -AuthorizationToken $AuthorizationToken -SubscriptionId $subscriptionId 

This outputs:

Name      : martin
Type      : ApiManagement
Available : False
reason    : AlreadyExists
message   : martin is already in use. Please select a different name.

Name      : kv
Type      : KeyVault
Available : False
reason    : Invalid
message   : Vault name must be between 3-24 alphanumeric characters. The name must begin with a letter, end with a letter or digit, and not contain consecutive hyphens.

Name      : root
Type      : ManagementGroup
Available : False
reason    : AlreadyExists
message   : The group with the specified name already exists

Name      : martin
Type      : Sql
Available : False
reason    : AlreadyExists
message   : Specified server name is already used.

Name      : storage
Type      : StorageAccount
Available : False
reason    : AlreadyExists
message   : The storage account named storage is already taken.

Name      : www
Type      : WebApp
Available : False
reason    : AlreadyExists
message   : Hostname 'www' already exists. Please select a different name.

So in a full script, you could use something like:

$params = @{
    Name               = 'myCoolWebSite'
    ServiceType        = 'WebApp'
    AuthorizationToken = Get-AccesTokenFromCurrentUser
    SubscriptionId     = $subscriptionId
}
if((Test-AzNameAvailability @params).Available) {
    # Continue with the deployment
}

Closing notes

The checkNameAvailability API is available in several Azure providers, but because of time constraints I implemented the test only for a few of them (ApiManagement, KeyVault, ManagementGroup, Sql, StorageAccount and WebApp), so you are more than welcome to improve it.

The complete code with the functions and examples is published under my github Azure repository as: https://github.com/martin77s/Azure/blob/master/PS/Test-AzNameAvailability.ps1