Skip to main content

Getting VM Data Disk Ready During Azure DevOps Deployment (BICEP)

Hey Folks

Back again with a small blog, and I think this is a helpful solution too. Do you know when you provision data disks using the Bicep template they are provisioned as raw disk..? So many of you provision the disks and handover to the dev or app team and they format the disk manually. But have you ever wondered if you can get the disk ready during the initial deployment itself.? Yes, it is possible.

There are many different ways to do it when you are doing it during the implementation, today I'm focusing on how to do this using bicep templates. For this, I'm leveraging a resource module that was recently released by Microsoft.

Which is, The virtualMachines/runCommands resource.

you can use this to Run PowerShell commands once you provision the VM. I guess now you know what is about to come. :D

You can get more details from the below link

In my lab setup, I have a few sub-modules,

  1. Main Bicep File
  2. VM Provision Module
  3. Run Command Resource Module

My deployment structure is as follows

The main Bicep file will be calling the VM Deployment submodule and if that's is a successful Main file then again calls the VM run command module

Here is my Main File

//Deploying required Virtual Machines to the Environnment

targetScope = 'subscription'

@description('appliction shortname. Used for resource naming.')
param appShortName string = 'ss'

@description('Customer shortname. Used for resource naming.')
param customerShortName string = 'dc'

@description('Shortname of evironment. Used for resource naming.')
param envShortName string = 'test'

@description('Domain Join User Name')
param domainJoinUserName string

@description('Domain Join User Secret Name to retrive it from KV')
param domainJoinSecretName string

@description('Local User Name')
param localAdminUserName string

@description('Local User Secret Name to retrive it from KV')
param localAdminSecretName string

@description('Domain to Join')
param domainToJoin string

@description('Service Principal Name for the SQL to Access the Key vault')
param sqlAkvPrincipalName string

@description('Service Principal Name for the SQL to Access the Key vault')
param sqlAkvPrincipalSecret string

@description('Virtual Machine Size')
param virtualMachineSize string = 'Standard_E2s_v3'

//existing resources Name
var logWorkspaceName = 'log-${appShortName}-${envShortName}-${custShortName}'
var storageName = toLower('st${appShortName}${envShortName}${custShortName}log')
var vnetName = 'vnet-${appShortName}-${envShortName}-${custShortName}'
var appSubnetName = 'snet-${appShortName}-${envShortName}-${custShortName}-app'
var dataSubnetName = 'snet-${appShortName}-${envShortName}-${custShortName}-data'
var keyvaultName = 'kv-${appShortName}-${envShortName}-${custShortName}'
var rgName = 'rg-${appShortName}-${envShortName}-${custShortName}'

//New Declarations
var appSubnetID = '/subscriptions/${subscription().subscriptionId}/resourceGroups/${rgName}/providers/Microsoft.Network/virtualNetworks/${vnetName}/subnets/${appSubnetName}'
var dataSubnetID = '/subscriptions/${subscription().subscriptionId}/resourceGroups/${rgName}/providers/Microsoft.Network/virtualNetworks/${vnetName}/subnets/${dataSubnetName}'
var logAnalyticsId = '/subscriptions/${subscription().subscriptionId}/resourceGroups/${rgName}/providers/Microsoft.OperationalInsights/workspaces/${logWorkspaceName}'
var storageUri = 'https://${storageName}'
var virtualMachineNameSuffix = '${custShortName}${lzShortName}${envShortName}db'

//Reference Existing KeyVault
resource keyVault 'Microsoft.KeyVault/vaults@2019-09-01' existing = {
scope: resourceGroup(rgName)
name: keyvaultName

/New Declarations
var appSubnetID = '/subscriptions/${subscription().subscriptionId}/resourceGroups/${rgName}/providers/Microsoft.Network/virtualNetworks/${vnetName}/subnets/${appSubnetName}'
var dataSubnetID = '/subscriptions/${subscription().subscriptionId}/resourceGroups/${rgName}/providers/Microsoft.Network/virtualNetworks/${vnetName}/subnets/${dataSubnetName}'
var logAnalyticsId = '/subscriptions/${subscription().subscriptionId}/resourceGroups/${rgName}/providers/Microsoft.OperationalInsights/workspaces/${logWorkspaceName}'
var storageUri = 'https://${storageName}'

//Reference Existing KeyVault
resource keyVault 'Microsoft.KeyVault/vaults@2019-09-01' existing = {
scope: resourceGroup(rgName)
name: keyvaultName

//Deploying Application Server
module appServer '../../../modules/virtual-machine/virtual-machine-windows.bicep' = {
scope: resourceGroup(rgName)
name: 'deploy_app_server'
params: {
// location:location
adminPassword: keyVault.getSecret(localAdminSecretName)
adminUsername: localAdminUserName
domainToJoin: domainToJoin
domainJoinUser: domainJoinUserName
domainJoinPassword: keyVault.getSecret(domainJoinSecretName)
dataDisksDefinition: [
createOption: 'empty'
caching: 'ReadOnly'
writeAcceleratorEnabled: false
diskType: 'Premium_LRS'
diskSize: 128
enableDomainJoin: true
enableHybridBenefit: true
logAnalyticsId: logAnalyticsId
osDiskType: 'Premium_LRS'
subnetId: appSubnetID
useAvailabilityZones: false
virtualMachineNameSuffix: virtualMachineNameSuffix
virtualMachineSize: virtualMachineSize
storageId: storageUri

module initializeDisks '../../../modules/virtual-machine/virtual-machine-windows-runcommand.bicep' = {
scope: resourceGroup(rgName)
name: 'deploy_initialize_disks'
params: {
vmName: appServer.outputs.vmName
script:'Get-Disk | Where-Object partitionstyle -eq "raw" | Initialize-Disk -PartitionStyle GPT -PassThru | New-Partition -AssignDriveLetter -UseMaximumSize | Format-Volume -FileSystem NTFS -Confirm:$false'

output appServerName string = appServer.outputs.vmName
output rgName string = rgName

If you notice When I'm calling the run command module, I pass a powershell command to run which is going to initialze and format the disks
'Get-Disk | Where-Object partitionstyle -eq "raw" | Initialize-Disk -PartitionStyle GPT -PassThru | New-Partition -AssignDriveLetter -UseMaximumSize | Format-Volume -FileSystem NTFS -Confirm:$false'

The above command will get all the raw disks and format them to GPT NTFS file format. you can change the values as required

Below is my run command module file

@description('Name of the Virtual Machine')
param vmName string

@description('Script to Run')
param script string

@description('Script to Run')
param runAsPassword string = ''
@description('Script to Run')
param runAsUser string = ''

var name = '${vmName}/RunCommand'

resource runCommand 'Microsoft.Compute/virtualMachines/runCommands@2021-07-01' = {
name: name
location: resourceGroup().location
properties: {
asyncExecution: true
source: {
script: script
timeoutInSeconds: 300
runAsPassword: empty(runAsPassword) ? json('null') : runAsPassword
runAsUser: empty(runAsUser) ? json('null') : runAsUser


Looks this is a simple solution but, this method can come in very handy if you want to perform something inside the VM, by using this method we can avoid using DSC templates and make it very simple. All you need to do is pass the PowerShell command to run, and if you have a big script you can pass the Uri of the PowerShell file to Run :)

So hope this helps someone and comes in handy when you need it :D

As always please do reach out to me if you have any questions :)


Popular posts from this blog

Updating Azure VM Data Disk Sizes

Summary Hope you are doing great, this time I came up with a simple azure DevOps solution for updating VM disk sizes. The current project that I'm in has a bunch of virtual machines. So, we need a way to update the VM disk with minimal administrative effort and changes.  In this scenario, we have used Bicep as the IAC language Azure DevOps pipelines YAML variable files  Here is the high-level workflow for a particular VM in the solution YAML Pipeline file got two workflows, firstly the VM build pipeline, and the second is the disk update one. If you focus on the green arrow and the purple arrow, basically I'm modifying the same bicep module file and passing the same set of variables. You may wonder why we cannot use the same flow to build the VM and update the disk later, that's because for the disk updates VM needs to be in a shutdown state, and other components in the 1st flow need the VM up and running especially the extension modules YAML Variable Files I have decided t

Deploying an Automation Account with a Runbook and Schedule Using Bicep

Introduction Automation is a key component in many organizations' cloud strategy. Azure Automation allows you to automate the creation, deployment, and management of resources in your Azure environment. In this post, we will walk through the process of deploying an Automation Account with a Runbook and Schedule using Bicep, a new domain-specific language for deploying Azure resources. Intention My intention at the  end is to run a PowerShell  script to start and shutdown Azure VMs based on tag values. PowerShell  script that I have used is from below l ink.  And two  of me   collogue s ( Michael Turnley   and Saudh Mohomad helped to modify the  PowerShell  script. Prerequisites Before we begin, you will need the following: An Azure subscription The Azure CLI installed on your machine. The Azure Bicep extension for the Azure CLI Creating the Automation Account The first step in deploying an Automation Account with a Runbook and Schedule is to create the Aut

Securing Azure Services with Fetian FIDO

Hey Folks  Here again with another security topic with Fetian Fido. And once again Fetian devices proved their excellent quality and stability. For this I choose Fetian K33 -  AllinPass FIDO Security Key – FEITIAN ( and  K39 -  Single-button FIDO Security Keys | FEITIAN ( Use case  In an organization following changes needs to be implemented.  1. Update the password policy 2. Update the user session time out to 30 minutes Once these changes being implemented, the following issues need to be addressed 1. Users' complaint new passwords need to be so long 2. Users complain sessions time out makes them work so much slower with the longer passwords 3. Etc... Solution  One of my friends reached out to me to help solve this problem. All I could think of was using passwordless auth with FIDO devices. We have decided to use Fido2 keys for better security and flexibility for the users. The FIDO (Fast IDentity Online) Alliance helps to promote open authentication stand