How to configure Azure VM extension in Terraform

Posted by Nikos Tsirmirakis on

In my previous post, I have described how to use Pester framework for infrastructure testing and how to incorporate it into Azure DevOps pipeline. Now I would like to describe how to use VM extensions in Terraform script. It will allow us to customize our VMs further. One of the extensions require a Domain Controller and we will create it in the first step. All scripts are available in the DBAinTheCloud GitHub repository.

VM extensions

There are many VM extensions available in Azure (doc), provided directly by Microsoft or third-party vendors. In this guide, we will configure the following extensions.

  • JsonADDomainExtension - Add VM to the AD domain
  • IaaSAntimalware - Install and configure AV agent
  • CustomScriptExtension - Run custom script

Adding VM to Active Directory (JsonADDomainExtension)

If we are creating our VMs in an Active Directory environment, this extension is very handy to add VM to the AD as part of the deployment process.

  resource "azurerm_virtual_machine_extension" "sql-jd" {
    count                = "${var.server_count}"
    name                 = "${local.prefix}${count.index + 1}-jd"
    location             = "${var.location}"
    resource_group_name  = "${}"
    virtual_machine_name = "${local.prefix}${count.index + 1}"
    depends_on           = ["azurerm_virtual_machine.vm1"]
    publisher            = "Microsoft.Compute"
    type                 = "JsonADDomainExtension"
    type_handler_version = "1.3"
    settings = <<SETTINGS
          "Name": "winopsdba-demo5.local",
          "User": "winopsdba-demo5.local\\devadmin",
          "Restart": "true",
          "Options": "3"
    protected_settings = <<PROTECTED_SETTINGS
          "Password": "${var.adminpassword}"

After the successful run, we can see a computer added to the domain in AD console.

Enabling and configuring Antimalware (IaaSAntimalware)

This extension installs Microsoft Antivirus agent, it is very useful if you have to have an AV agent on every VM as part of the policy requirements. There are other AV extensions provided by third-party vendors as well, however, for demonstration purposes, we will use a Microsoft solution. As part of the installation, we will also configure exceptions for SQL server.

  resource "azurerm_virtual_machine_extension" "sql-am" {
    count                      = "${var.server_count}"
    name                       = "${local.prefix}${count.index + 1}-am"
    location                   = "${var.location}"
    resource_group_name        = "${}"
    virtual_machine_name       = "${local.prefix}${count.index + 1}"
    depends_on                 = ["azurerm_virtual_machine_extension.sql-jd"]
    publisher                  = "Microsoft.Azure.Security"
    type                       = "IaaSAntimalware"
    type_handler_version       = "1.3"
    auto_upgrade_minor_version = "true"
    settings = <<SETTINGS
      "AntimalwareEnabled": true,
      "RealtimeProtectionEnabled": "true",
      "ScheduledScanSettings": {
      "isEnabled": "true",
      "day": "1",
      "time": "120",
      "scanType": "Quick"
      "Exclusions": {
      "Extensions": ".mdf;.ldf;.ndf;.bak;.trn;",
      "Paths": "C:\\Program Files\\Microsoft SQL Server\\MSSQL\\FTDATA",
      "Processes": "SQLServr.exe;ReportingServicesService.exe;MSMDSrv.exe"

To verify configuration on the VM open exclusion setting in Windows Defender.

Run custom script (CustomScriptExtension)

In my opinion, this is one of the most useful extensions. It allows running scripts after creating the VM. In our case, we will download and execute the script from blob storage.

Uploading script to blob storage

  resource "azurerm_storage_account" "sa1" {
    name                     = var.storage_name
    resource_group_name      =
    location                 = var.location
    account_tier             = "Standard"
    account_replication_type = "LRS"
    network_rules {
      default_action = "Deny"
      ip_rules                   = [var.allowed_ip]
      virtual_network_subnet_ids = []
    tags = merge(local.tags, map("FirstApply", timestamp(), "LastApply", timestamp()))
    lifecycle {
      ignore_changes = [tags.FirstApply]
  resource "azurerm_storage_container" "scripts" {
    name                  = "scripts"
    storage_account_name  =
    container_access_type = "blob"
  resource "azurerm_storage_blob" "script_ps1" {
    name = "sql-install.ps1"
    storage_account_name   =
    storage_container_name =
    type                   = "Block"
    source                 = "sql-install.ps1"

Running extension

  resource "azurerm_virtual_machine_extension" "SQL-localPS" {
    count                = "${var.server_count}"
    name                 = "${local.prefix}${count.index + 1}-localPS"
    location             = "${var.location}"
    resource_group_name  = "${}"
    virtual_machine_name = "${local.prefix}${count.index + 1}"
    depends_on           = ["azurerm_virtual_machine_extension.sql-am"]
    publisher            = "Microsoft.Compute"
    type                 = "CustomScriptExtension"
    type_handler_version = "1.9"
    settings             = <<SETTINGS
          "fileUris": ["https://${var.storage_name}"],
          "commandToExecute": "powershell -ExecutionPolicy Unrestricted -file sql-install.ps1"      


You have created a VM with an AV agent, added it to an AD domain and executed custom script.

Coming next …

We will install Azure DevOps build / deployment agent (former VSTS agent) in our environment.