How to create a flexible VM Bicep Template 

Creating a single Bicep template for both Linux and Windows is fun. In this post, I share the important parts of the template for creating a flexible and conditional VM template.

If you are wondering about how to use a single template for creating both Windows and Linux virtual machines, I will share some tips. The virtual machine resource properties that separate those two operating systems are imageReference (Inside storageProfile) and osProfile.  

ImageReference 

The imageReference has four sub-properties to set. Those sub-properties are publisher, offer, sku and version. There are different ways to find the values for these properties.  

  • If you have existing virtual machines, go to their overview page in Azure portal. Under Essentials, on the right side, click on JSON View link. Check out those properties for this current VM. 
  • Alternatively, you can try to create a new VM from Azure portal, in the last stage of the wizard, you will have the chance to download a template for automation (link is in the bottom of the wizard page in the last stage) 
  • Use third-party websites, such as: https://az-vm-image.info/ 
  • Use Azure CLI. Example:  
az vm image list --all --publisher MicrosoftWindowsDesktop --offer Windows-10 --output table 

Once you find desired flavors of operation systems, use those values to create a list of choices in your template.  

@description('The OS version (SKU):') 
@allowed([ 
'win10' 
'win11' 
'ubuntu2004' 
'ubuntu2004gen2' 
]) 
param operatingSystemSKU string 

Here we define one parameter to ask the user about their choice of operating system SKU. The value that the user provides is primarily used in osImageReference variable to set the right values for imageReference properties.  

var osImageReference = {
  win10: {
    publisher: 'MicrosoftWindowsDesktop'
    offer: 'Windows-10'
    sku: '21h1-pron-g2'
    version: 'latest'
  }
  win11: {
    publisher: 'MicrosoftWindowsDesktop'
    offer: 'Windows-11'
    sku: 'win11-21h2-pron'
    version: 'latest'
  }
  ubuntu2004: {
    publisher: 'canonical'
    offer: '0001-com-ubuntu-server-focal'
    sku: '20_04-lts'
    version: 'latest'
  }
  ubuntu2004gen2: {
    publisher: 'canonical'
    offer: '0001-com-ubuntu-server-focal'
    sku: '20_04-lts-gen2'
    version: 'latest'
  }
}

To make it clear, here is an example of how it works. When the user is prompted to operatingSystemSKU parameter, a list of options (here we have 4 options) are provided for selection. If the user selects, ‘ubuntu2004’, the properties of imageReference are set based on osImageReference[‘ubuntu2004’].  

imageReference: {
  publisher: osImageReference[operatingSystemSKU].publisher
  offer: osImageReference[operatingSystemSKU].offer
  sku: osImageReference[operatingSystemSKU].sku
  version: osImageReference[operatingSystemSKU].version
}

osProfile 

osProfile specifies the operating system settings used while creating the virtual machine. Depending on the choice of the operating system, we need to whether set an admin password for Windows or configure the SSH key for Linux.  

osProfile: {
  computerName: virtualMachineName
  adminUsername: adminUsername
  adminPassword: adminPasswordOrPublicKey
  linuxConfiguration: ((authenticationType == 'password') ? json('null') : linuxConfiguration)
}

In this template, we use the parameter below to distinguish between password or public SSH key authentication.  

@description('Select the authentication type: (Password for Windows and SSH Public Key for Linux)')
@allowed([
  'password'
  'sshPublicKey'
])
param authenticationType string

If the authentication Type is ‘password’, we set the adminPassword property based on the password we ask from the user in adminPasswordOrPublicKey parameter. In addition, linuxConfiguration property will be set to null

However, if the authenticationType is ‘sshPublicKey‘, the linuxConfiguration variable will be used to disable the adminPassword and configure the use of SSH public key.  

var linuxConfiguration = {
  disablePasswordAuthentication: true
  ssh: {
    publicKeys: [
      {
        path: '/home/${adminUsername}/.ssh/authorized_keys'
        keyData: adminPasswordOrPublicKey
      }
    ]
  }
}

Please note that we use a single parameter to ask the user for admin password or SSH public key. It is up to the user to provide the right option based on their choice of operating system. 

We use another parameter to decide which AD (Active Directory) Login extension to install on the virtual machine. The user is prompted to select the OS:  

@description('Select the OS type to deploy:')
@allowed([
  'Windows'
  'Linux'
])
param operatingSystem string

Based on this selection, we will decide which extension to install on virtual machine after provisioning.  

var aadLoginExtensionName = (operatingSystem == 'Linux') ? 'AADSSHLoginForLinux' : 'AADLoginForWindows'

This variable is used in resource definition of virtual machine extensions. 

References