Version: 1.0.0

Series Introduction

Whether you’re new to ARM/Bicep or a pro, I’m sure you’ve run into the issue of needing conditional properties. Unfortunately, ARM and Bicep do not officially have functions built in to handle this natively. At least not as of yet. I was surprised when writing this, to not see a whole lot of articles showing how work around this.

In this series, I’m going to show you a few ways you can work a round this. All of them are convoluted, but they do work. Each one will have pros and cons, and it will be up to you determine which one you like best. Of course, you don’t have to stick to one or other. Like most things in technology, use what works best in your situation.

Series Index

Series Demo Setup

In this series demo, I’ll show you how I solved the issue with conditional properties in the virtual network “subnet” properties. This was a particularly challenging one as it’s an array of objects. I chose this for our series example to show you how to handle conditional properties in more complex scenarios.

Throughout this series, we’ll use a common set of files to demonstrate both working and non-working examples. The contents of the files will change throughout each post, so I would suggest creating a folder structure that looks something like this.

  • Root Directory
    • 1
    • 2
    • 3
    • etc.

You can store a copy of the files used in the demo in each folder. This will allow you to walk through each option, and go back to previous posts if you need to review.

Article Introduction

Before solving the problem, let’s first demonstrate a real world example. That’s what this post will focus on. Understanding how we run into the problem, and what symptoms you may encounter.

Article Demo Setup

For this article, you will need the following files.

  • parameters.ps1: This file is used to define / generate our deployment parameters.
  • template.bicep: This file is the main bicep template that consumes the deployment parameters and ultimately executes the deployment.
  • deploy.ps1: This file is used to execute the deployment.

You can find the snippets of each file below.

parameters.ps1

#############################
#START: Parameters
[CmdletBinding()]
param (
    [Parameter(Mandatory = $true)]
    [string]$TeamName
    )

#END: Parameters
#############################

#############################
#START: Variables
$networkPrefix = "10.0"

$googleDNSServer = @{
    dhcpOptions = @{
        dnsServers = @(
            '8.8.8.8'
            )
        }
    }

#END: Variables
#############################

#########################################
#START: allSubnets
$allSubnets = @(
    #########################################
    #START: GatewaySubnet
    @{
    name = "GatewaySubnet"
    properties = @{
        addressPrefix = "$($networkPrefix).0.0/24"
        applicationGatewayIpConfigurations = @()
        delegations = @()
        natGateway = @()
        networkSecurityGroup = @{}
        privateEndpointNetworkPolicies = "Disabled"
        privateLinkServiceNetworkPolicies = "Disabled"
        routeTable= @{}
        serviceEndpointPolicies = @()
        serviceEndpoints = @()
        }
    }
    #END: GatewaySubnet
    #########################################

    #########################################
    #START: webFarmSubnet
    @{
    name = "webFarm1"
    properties = @{
        addressPrefix = "$($networkPrefixSpoke00).10.0/24"
        applicationGatewayIpConfigurations = @()
        delegations = @(
            @{
            name = "Microsoft.Web/serverFarms"
            properties = @{
                serviceName = "Microsoft.Web/serverFarms"
                }
            }
            )
        natGateway = @{}
        networkSecurityGroup = @{}
        privateEndpointNetworkPolicies = "Disabled"
        privateLinkServiceNetworkPolicies = "Disabled"
        routeTable = @{}
        serviceEndpointPolicies = @()
        serviceEndpoints = @()
        }
    
    }
    #END: webFarmSubnet
    #########################################
    )

#############################
#START: Base Template
$template = @{
    name = "vnet$($TeamName)" #notice we now have a dynamic name vnet based on the team name
    location = 'eastUS'
    tags = @{environment = 'sandbox'}
    properties = @{
        addressSpace = @{
            addressPrefixes = @(
                "$($networkPrefix).0.0/16"
                )
            }
        #Notice we have no dhcpOption property specified
        enableDdosProtection = $false
        enableVmProtection = $false
        subnets = $allSubnets
        virtualNetworkPeerings = @()
        }   
    }
#END: Base Template
#############################

#############################
#START: Conditional update

If ($TeamName -eq "One")
    {
    $template.properties.add('dhcpOptions', $googleDNSServer.dhcpOptions)
    }

#END: Conditional update
#############################

#############################
#START: Output Object
$template
#END: Output Object
#############################

template.bicep

////////////////////////////////////////
//START: res_networkVirtualNetwork
param deploymentObject object
//END: res_networkVirtualNetwork
////////////////////////////////////////

////////////////////////////////////////
//START: res_networkVirtualNetwork
resource res_networkVirtualNetwork 'Microsoft.Network/virtualNetworks@2021-03-01' = {
  name: deploymentObject.name
  location: deploymentObject.location
  tags: deploymentObject.tags
  properties: deploymentObject.properties
  }
//END: res_networkVirtualNetwork
////////////////////////////////////////

deploy.ps1

$deploymentObject = & .\parameters.ps1 -TeamName 'One'

$resourceGroupDeploymentSplat = @{
    Name = "ericsDemoDeployment"
    ResourceGroupName = "eric-sandbox"
    deploymentObject = $deploymentObject
    TemplateFile = '.\template.bicep'
    }
New-AzResourceGroupDeployment @resourceGroupDeploymentSplat

Demo Execution

To run the demo, all we need to do is execute the deploy.ps1 file. This can be accomplished by the following command.\

. .\deploy.ps1

Demo Result

First thing you should notice is that the deployment fails, with errors that look a lot like this.

Error (PowerShell)

############################################
ExceptionMessage:
11:33:10 AM - The deployment 'ericsDemoDeployment' failed with error(s). Showing 1 out of 1 error(s).
Status Message: Cannot parse the request. (Code: InvalidRequestFormat)
 - Value for reference id is missing. Path properties.subnets[0].properties.routeTable. (Code:MissingJsonReferenceId)
 - Value for reference id is missing. Path properties.subnets[0].properties.natGateway. (Code:MissingJsonReferenceId)
 - Value for reference id is missing. Path properties.subnets[0].properties.networkSecurityGroup. (Code:MissingJsonReferenceId)
 - Value for reference id is missing. Path properties.subnets[1].properties.routeTable. (Code:MissingJsonReferenceId)
 - Value for reference id is missing. Path properties.subnets[1].properties.natGateway. (Code:MissingJsonReferenceId)
 - Value for reference id is missing. Path properties.subnets[1].properties.networkSecurityGroup. (Code:MissingJsonReferenceId)

Error (Azure Portal)

{
    "status": "Failed",
    "error": {
        "code": "InvalidRequestFormat",
        "message": "Cannot parse the request.",
        "details": [
            {
                "code": "MissingJsonReferenceId",
                "message": "Value for reference id is missing. Path properties.subnets[0].properties.routeTable."
            },
            {
                "code": "MissingJsonReferenceId",
                "message": "Value for reference id is missing. Path properties.subnets[0].properties.natGateway."
            },
            {
                "code": "MissingJsonReferenceId",
                "message": "Value for reference id is missing. Path properties.subnets[0].properties.networkSecurityGroup."
            },
            {
                "code": "MissingJsonReferenceId",
                "message": "Value for reference id is missing. Path properties.subnets[1].properties.routeTable."
            },
            {
                "code": "MissingJsonReferenceId",
                "message": "Value for reference id is missing. Path properties.subnets[1].properties.natGateway."
            },
            {
                "code": "MissingJsonReferenceId",
                "message": "Value for reference id is missing. Path properties.subnets[1].properties.networkSecurityGroup."
            }
        ]
    }
}

Demo Result Explanation

For the subnet property, each array item is an object which defines a subnet. The API for Azure, is expecting when a subnet property such as networkSecurityGroup is specified, that it will contain a value. We specified the property, but left it empty. Some Azure resource API, will handle this gracefully and ignore it. In this case however, it’s expect that if you’ve defined it, it must have a valid value.

In order to see this, let’s inspect the object being passed to Azure.

Generate JSON artifact

$deploymentObject | ConvertTo-Json -Depth 100
{
  "properties": {
    "addressSpace": {
      "addressPrefixes": [
        "10.0.0.0/16"
      ]
    },
    "subnets": [
      {
        "name": "GatewaySubnet",
        "properties": {
          "routeTable": {},
          "privateLinkServiceNetworkPolicies": "Disabled",
          "privateEndpointNetworkPolicies": "Disabled",
          "serviceEndpoints": [],
          "serviceEndpointPolicies": [],
          "natGateway": [],
          "networkSecurityGroup": {},
          "delegations": [],
          "applicationGatewayIpConfigurations": [],
          "addressPrefix": "10.0.0.0/24"
        }
      },
      {
        "name": "webFarm1",
        "properties": {
          "routeTable": {},
          "privateLinkServiceNetworkPolicies": "Disabled",
          "privateEndpointNetworkPolicies": "Disabled",
          "serviceEndpoints": [],
          "serviceEndpointPolicies": [],
          "natGateway": {},
          "networkSecurityGroup": {},
          "delegations": [
            {
              "name": "Microsoft.Web/serverFarms",
              "properties": {
                "serviceName": "Microsoft.Web/serverFarms"
              }
            }
          ],
          "applicationGatewayIpConfigurations": [],
          "addressPrefix": ".10.0/24"
        }
      }
    ],
    "enableVmProtection": false,
    "virtualNetworkPeerings": [],
    "enableDdosProtection": false,
    "dhcpOptions": {
      "dnsServers": [
        "8.8.8.8"
      ]
    }
  },
  "location": "eastUS",
  "name": "vnetOne",
  "tags": {
    "environment": "sandbox"
  }
}

You’ll see that all of the errors correspond directly to empty properties with in the various subnet array of objects.

Article Conclusion

In this article, I showed how a non-working example. You should now understand why it’s happening, and some example error messages or symptom you might run into. We also know now that we need a way to only specify properties that we’re using.