PowerShell How-To

How To Modify Azure ARM Templates with PowerShell

It's widely known that PowerShell can deploy Microsoft Azure ARM templates using the New-AzResourceGroupDeployment cmdlet, but it's not so common to use PowerShell to create and modify existing ARM templates.

Since PowerShell is so flexible, a savvy developer can build a script to make building and modifying these templates an easy task.

At its most basic, an ARM template is a JSON file adhering to all of the rules that make JSON, well, JSON. It just so happens that PowerShell can natively read JSON using the ConvertFrom-Json cmdlet. This cmdlet understands JSON's structure and returns a pscustomobject object. Likewise, PowerShell can also convert a pscustomobject file back to JSON using the ConvertTo-Json cmdlet. With these two cmdlets, we can make anything happen.

I've created an ARM template that deployed a Web application with some other resources. I'd like to ensure this template defines a storage account that currently doesn't exist. To deploy this storage account, we'd need to define a JSON node that looks like this:

{
    "type": "Microsoft.Storage/storageAccounts",
    "name": "adbpluralsightstorage",
    "apiVersion": "2019-04-01",
    "location": "[resourceGroup().location]",
    "sku": {
        "name": "Standard_LRS"
    },
    "properties": {}
}

Once we've got the JSON node, we could open up the ARM template in a code editor and insert it. It seems easy enough, but we're human and we make mistakes. We could forget a comma, accidentally insert it in the wrong place or a lot of other potential snafus. Instead, we can use PowerShell to ensure it's right every time.

First, we need to read the existing template from disk and convert it to a pscustomobject object.

$templatePath = 'C:\testarmtemplate.json'
$armTemplate = Get-Content -Path $templatePath -Raw | ConvertFrom-Json

Next, we'll need to define our storage account resource as a JSON string. Optionally, we could define this string as a hashtable and convert it to JSON, as well.

$snippetJson = @'
{
    "type": "Microsoft.Storage/storageAccounts",
    "name": "adbpluralsightstorage",
    "apiVersion": "2019-04-01",
    "location": "[resourceGroup().location]",
    "sku": {
        "name": "Standard_LRS"
    },
    "properties": {}
}
'@

Next, we'll convert our JSON snippet to a pscustomobject object. Now we're able to add objects to our "ARM template object." We'll do that by appending the storage account resource to the resources array.

$resource = $snippetJson | ConvertFrom-Json
$armTemplate.resources += $resource

At this point, the "ARM template object" is in the state we need. We need to commit it back to disk. Since it's still in a pscustomobject, we'll convert it back to a JSON string via ConvertTo-Json using the Depth parameter to ensure we catch all of the nested nodes. Normally, this would be enough, but since ARM templates will typically have single quotes in them, the PowerShell creates unicode escape characters in place of single quotes.

We have to unescape all of these characters using [System.Text.RegularExpressions.Regex]::Unescape() on each line by using a foreach object, as shown below. Once that's done, we then write the JSON back to an ARM template on disk.

$armTemplate | ConvertTo-Json -Depth 100 | foreach { [System.Text.RegularExpressions.Regex]::Unescape($_) } | Set-Content -Path 'C:\modfied-armtemplate.json'

At this point, your ARM template will have a new resource inserted exactly where it needs to be!

Building a PowerShell Tool
Since we're using PowerShell, it's always important to notice times when a tool can be made. In this case, we can build a couple of functions called New-AzArmTemplateResource and Set-AzArmTemplate to do most of this work for us. Below is an example. You can see we're executing the same basic code, but this time all of that code is encapsulated into two handy functions:

function New-AzArmTemplateResource {
    [OutputType('pscustomobject')]
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [string]$Json
    )

    $Json | ConvertFrom-Json
}

function Set-AzArmTemplate {
    [OutputType('void')]
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [string]$TemplatePath,

        [Parameter(Mandatory, ValueFromPipeline)]
        [ValidateNotNullOrEmpty()]
        [pscustomobject]$Resource
    )

    ## Read the template and convert to an object
    $armTemplate = Get-Content -Path $TemplatePath -Raw | ConvertFrom-Json
    
    ## Append the resource object to the end of the resource section
    $armTemplate.resources += $Resource

    ## Remove escaped unicode characters
    $template = $armTemplate | ConvertTo-Json -Depth 100 | foreach { [System.Text.RegularExpressions.Regex]::Unescape($_) }

    ## Commit to disk
    $template | Set-Content -Path $TemplatePath
}

I've saved these functions in a file called ARMTemplateMods.ps1. I can use the functions inside of my file by dot-sourcing them into my PowerShell session. Once I do that, I can then create a resource by simply running New-AzArmTemplateResource -Json $snippetJson, assuming that I've still got the JSON string stored in the $snippetJson variable.

I can then use the pipeline to pass that resource to Set-AzArmTemplate providing the path to the ARM template that would add it in.

PS> .\ARMTemplateMods.ps1
PS> New-AzArmTemplateResource -Json $snippetJson
PS> New-AzArmTemplateResource -Json $snippetJson | Set-AzArmTemplate -TemplatePath $templatePath

This tool is just a start. You could potentially build out a PowerShell module capable of automating nearly all facets of building and modifying ARM templates.

About the Author

Adam Bertram is a 20-year veteran of IT. He's an automation engineer, blogger, consultant, freelance writer, Pluralsight course author and content marketing advisor to multiple technology companies. Adam also founded the popular TechSnips e-learning platform. He mainly focuses on DevOps, system management and automation technologies, as well as various cloud platforms mostly in the Microsoft space. He is a Microsoft Cloud and Datacenter Management MVP who absorbs knowledge from the IT field and explains it in an easy-to-understand fashion. Catch up on Adam's articles at adamtheautomator.com, connect on LinkedIn or follow him on Twitter at @adbertram or the TechSnips Twitter account @techsnips_io.


comments powered by Disqus
Most   Popular