Get-UniqueString: generate unique ID for Azure Deployments
When deploying resources to Azure, you sometimes need to generate a world-wide unique name. Examples of these are DNS names, storage account names, Azure Batch account names, etc. Some of these names have additional requirements. For instance, storage account names must be all lowercase with a length of 3 to 24 letters. How do you reliably generate such names?
With manual input this is not so hard. The user just needs to retry until he gets it right. With automation, the problem gets a little more difficult. Let's take an example. You want to generate VM, with a public IP address including a DNS name. The user should tell you the name of the resource group, the VM name, and nothing more. The configuration has more parameters than that, so you need to make up some parameters on the fly. With an existing resource group name of "rg-adtest" and a VM name of "dc1", the definition of the Public IP address could look like this:
[powershell]
$publicIp = New-AzureRmPublicIpAddress -Name "dc1-pub-ip" -ResourceGroupName "rg-adtest" `
-AllocationMethod Static -DomainNameLabel "<problem>" -Location "west europe"
[/powershell]
The problem is the -DomainNameLabel parameter. Whatever you put here, it must be worldwide unique or the deployment will fail. Just using the string "dc1" is not going to work, obviously (yes, I tried). One solution is to use a random generator to create a fixed-length lowercase random string, something like this:
[powershell]
"dc1-" + -join ((97..122) | Get-Random -Count 13 | % {[char]$_})
[/powershell]
But this has another problem; the code is not repeatable. Each time it runs, the name for the IP address is going to come out different. So how do we solve this? How do we get a string that is random enough to be worldwide unique, but is the same every time the code is executed?
When you use Azure ARM templates for deployment, this is easy. You use the UniqueString function, which generates a 13-character unique string based on some other input string. It's traditional to use the ID of the resource group, which is a string containing the world-wide unique Azure Subscription GUID, as well as the name of the resource group. Using this, you get the following results:
- Deploy to the same resource group, you get the same name.
- Deploy to a different resource group in the same subscription, you get a different name.
- Deploy to the same resource group name, but in a different subscription, you get a different name.
Useful stuff. There is no direct PowerShell equivalent for this, so I decided to put one together in the form of a new function called Get-UniqueString. It's short, but a little bit arcane if encryption technology is not your thing. Take a look:
[powershell]
function Get-UniqueString ([string]$id, $length=13)
{
$hashArray = (new-object System.Security.Cryptography.SHA512Managed).ComputeHash($id.ToCharArray())
-join ($hashArray[1..$length] | ForEach-Object { [char]($_ % 26 + [byte][char]'a') })
}
[/powershell]
It does something very simple: it takes a string as input, and generates something that looks random, but really isn't: for the same string, the same output is generated, meaning that the output is deterministic. Any different input string: different output. From the input string it calculates a 512 bit hash value (64 characters) using a cryptographically strong algorithm, which ensures that the result "looks" random. The result is translated back to a string with all lowercase characters, with a default string length of 13 just like the corresponding ARM function. Examples:
[powershell]
PS C:\> Get-UniqueString -id "123"
xjtclbzhdshll
PS C:\> Get-UniqueString -id "1234"
ehdsuphgcqoyk
PS C:\> Get-UniqueString -id $(Get-AzureRmResourceGroup "rg-adtest").ResourceID
dbewhnjvchmfk
[/powershell]
Putting it together, the generation of the IP Address could be:
[powershell]
$dnsname = "dc1-$(Get-UniqueString -id $(Get-AzureRmResourceGroup "rg-adtest").ResourceID)"
$publicIp = New-AzureRmPublicIpAddress -Name "dc1-pub-ip" -ResourceGroupName "rg-adtest" `
-AllocationMethod Static -DomainNameLabel $dnsname -Location "west europe"
# what DNS name did we get?
$dnsname
dc1-dbewhnjvchmfk
[/powershell]
Having a unique but deterministic random string is a powerful trick. It enables scenarios like these:
- Use the Resource Group ID to generate a name for a storage account. This way you can add resources without having to specify the name of the storage account. At the same time, the name will be different for another resource group or subscription.
- Use the Subscription ID for stable resources that should be shared by all. Key Vaults, VNETs, ...
A word of warning to wrap this up: don't fall into the temptation of using these functions to generate a password or connection string. You will find this trick on various blog posts discussing ARM templates. The output string looks random, but it really isn't, and these values can easily be regenerated by anyone having read access to your subscription, as discussed in my previous post.
For a practical application of Get-UniqueString, see these posts. The first post creates some Azure Resources such as a Batch Account and Storage Account which must be world-wide unique, and the second one derives the names of these accounts straight from its Resource Group.