Programmatically adding elements to an Azure ARM template using PowerShell

One of the great features of Azure ARM templates is that you can do template linking so that you can split your deployment into smaller components. This promotes better reuse as well as less unwieldy template files. However, one limitation today is that it is not easily possible to do conditional logic within your ARM template (see this blob post for one possible method using arrays: https://jodygblog.wordpress.com/2016/05/02/conditional-parameters-for-arm-templates/). However, this technique does not work in all situations. For example, if you wanted a base Windows/Linux template and wanted to have one base template that utilizes an availability set and one that did not, you would need to create two versions of the base template. This is definitely not ideal because now you have to make changes to two base templates instead of one.

A solution to this is to use PowerShell’s ability to load a JSON file, make changes as required in memory and then emit the JSON file back to disk. You still have two base template files but only one of them will need to be edited manually and the other will be a generated file.

Using our example of having two base Windows templates with and without an availability set, this section demonstrates how the additional JSON can be added using PowerShell. We would first need to have variables for the additional JSON for elements like parameters, resources, and properties:

param
(
[string]$InputJsonFilePath,
[string]$OutputJsonFilePath,
[bool]$IsManagedAvailabilitySet #if this is a managed availability set, set this to true otherwise false

)

$availabilitySetParam =@"
{
"type":"string",
"metadata":
{
"description": "Name of the availability set"
}
}
"@
$faultDomainParam = @"
{
"type": "int",
"defaultValue": 3,
"metadata": {
"description": "Fault Domain Count"
}
}
"@
$updateDomainParam = @"
{
"type": "int",
"defaultValue": 5,
"metadata": {
"description": "Update Domain Count"
}
}
"@

$availabilitySetResourceJson = @"
{
"type": "Microsoft.Compute/availabilitySets",
"name": "[parameters('availabilitySetName')]",
"apiVersion": "2016-04-30-preview",
"location": "[resourceGroup().location]",
"tags": {
"displayName": "Availability Set"
},
"properties": {
"platformFaultDomainCount": "[parameters('faultDomainCount')]",
"platformUpdateDomainCount": "[parameters('updateDomainCount')]",
"managed": false
}
}
"@

$availabilitySetVMPropertiesJson = @"
{
"id": "[resourceId('Microsoft.Compute/availabilitySets' , parameters('availabilitySetName'))]"
}
"@

 

Next we would load our JSON file into memory and convert each of the JSON variables into JSON objects:

 

$JsonFile = Get-Content $InputJsonFilePath |Out-String
$JsonFile = ConvertFrom-Json $JsonFile

$availabilitySet = ConvertFrom-Json -InputObject $availabilitySetParam
$faultDomain = ConvertFrom-Json -InputObject $faultDomainParam
$updateDomain = ConvertFrom-Json -InputObject $updateDomainParam
$availabilitySetResource = ConvertFrom-Json -InputObject $availabilitySetResourceJson
$availabilitySetProperty = ConvertFrom-Json -InputObject $availabilitySetVMPropertiesJson

 

Now this is where the real magic happens. Smile We use Add-Member to add the new parameters:

 

$JsonFile.parameters |Add-Member -Name "availabilitySetName" -MemberType NoteProperty -Value $availabilitySet
$JsonFile.parameters |Add-Member -Name "faultDomainCount" -MemberType NoteProperty -Value $faultDomain
$JsonFile.parameters |Add-Member -Name "updateDomainCount" -MemberType NoteProperty -Value $updateDomain

 

For elements in the JSON file that are arrays, we use ‘+’ to add an element to the array:

$JsonFile.resources = $JsonFile.Resources + $availabilitySetResource

 

We now add a “DependsOn” as well as an AvailabilitySet property:

foreach ($vm in $JsonFile.Resources)
{
if ($vm.type -eq 'Microsoft.Compute/virtualMachines')
{
$vm.dependsOn = $vm.dependsOn + "[resourceId('Microsoft.Compute/availabilitySets', parameters('availabilitySetName'))]"
$vm.properties |Add-Member -Name "availabilitySet" -MemberType NoteProperty -Value $availabilitySetProperty
}

    if ($vm.type -eq 'Microsoft.Compute/availabilitySets')
{
if ($IsManagedAvailabilitySet)
{
$vm.properties.managed = $true
}
else
{
$vm.properties.managed = $false
}
}
}

Lastly we write the JSON file back to disk. Note that by default ConvertTo-JSON has a depth of 2 which will likely not be sufficient. Also, it will escape characters by default.
ConvertTo-Json -Depth 10 -InputObject $JsonFile |Out-File $OutputJsonFilePath –Force 

To remove the escape characters (which is purely for readability), you can reload the file and do a string replacement:

 

$jsonFile = $jsonFile.Replace('\u0027', "'")
$jsonFile |Out-File -FilePath $OutputJsonFilePath -force

 

Well, that’s all there is to it. You could use this same technique in other ways such as having base template with or without managed disks, copying parameters to multiple files, etc.

 

Updated: Corrected API version in availability set resource JSON to 2016-04-30-preview.

 

 

Here is the complete PowerShell for your convenience:

param
(
[string]$InputJsonFilePath,
[string]$OutputJsonFilePath,
[bool]$IsManagedAvailabilitySet #if this is a managed availability set, set this to true otherwise false

)
$availabilitySetParam =@"
{
"type":"string",
"metadata":
{
"description": "Name of the availability set"
}
}
"@
$faultDomainParam = @"
{
"type": "int",
"defaultValue": 3,
"metadata": {
"description": "Fault Domain Count"
}
}
"@
$updateDomainParam = @"
{
"type": "int",
"defaultValue": 5,
"metadata": {
"description": "Update Domain Count"
}
}
"@

$availabilitySetResourceJson = @"
{
"type": "Microsoft.Compute/availabilitySets",
"name": "[parameters('availabilitySetName')]",
"apiVersion": "2016-04-30-preview",
"location": "[resourceGroup().location]",
"tags": {
"displayName": "Availability Set"
},
"properties": {
"platformFaultDomainCount": "[parameters('faultDomainCount')]",
"platformUpdateDomainCount": "[parameters('updateDomainCount')]",
"managed": false
}
}
"@

$availabilitySetVMPropertiesJson = @"
{
"id": "[resourceId('Microsoft.Compute/availabilitySets' , parameters('availabilitySetName'))]"
}
"@

$JsonFile = Get-Content $InputJsonFilePath |Out-String
$JsonFile = ConvertFrom-Json $JsonFile

$availabilitySet = ConvertFrom-Json -InputObject $availabilitySetParam
$faultDomain = ConvertFrom-Json -InputObject $faultDomainParam
$updateDomain = ConvertFrom-Json -InputObject $updateDomainParam
$availabilitySetResource = ConvertFrom-Json -InputObject $availabilitySetResourceJson
$availabilitySetProperty = ConvertFrom-Json -InputObject $availabilitySetVMPropertiesJson

$JsonFile.parameters |Add-Member -Name "availabilitySetName" -MemberType NoteProperty -Value $availabilitySet
$JsonFile.parameters |Add-Member -Name "faultDomainCount" -MemberType NoteProperty -Value $faultDomain
$JsonFile.parameters |Add-Member -Name "updateDomainCount" -MemberType NoteProperty -Value $updateDomain
$JsonFile.resources = $JsonFile.Resources + $availabilitySetResource

foreach ($vm in $JsonFile.Resources)
{
if ($vm.type -eq 'Microsoft.Compute/virtualMachines')
{
$vm.dependsOn = $vm.dependsOn + "[resourceId('Microsoft.Compute/availabilitySets', parameters('availabilitySetName'))]"
$vm.properties |Add-Member -Name "availabilitySet" -MemberType NoteProperty -Value $availabilitySetProperty
}

    if ($vm.type -eq 'Microsoft.Compute/availabilitySets')
{
if ($IsManagedAvailabilitySet)
{
$vm.properties.managed = true
}
else
{
$vm.properties.managed = false
}
}
}

ConvertTo-Json -Depth 10 -InputObject $JsonFile |Out-File $OutputJsonFilePath -Force 

$JsonFile = Get-Content $OutputJsonFilePath |Out-String
$jsonFile = $jsonFile.Replace('\u0027', "'")
$jsonFile |Out-File -FilePath $OutputJsonFilePath -force