Deploy in parallel multiple Azure virtual machines – PowerShell workflow script

I have been working on various Azure  projects where it was necessary to deploy multiple virtual machines in parallel. Below is an example of using PowerShell workflow script to deploy group of virtual machine from CSV file into ARM:


# Input parameters
param (
[string]$subscriptionId = ‘8d549018-802b-4924-a1bc-e274d6666644’,
[string]$tenantId = ‘d9cd498f-ae5c-4ab5-9a69-fe74fc666655’

# Main PS workflow
# ——————–
workflow Provision-AzureVM{
param (
[string]$vmName, # $vmName = ‘TESTVM’
[string]$location, # $location = ‘North Europe’
[string]$initVmSize, # $initVmSize = ‘Standard_D3_v2’
[string]$resourceGroupName, # $resourceGroupName = ‘RG-EUN-Workloads’
[string]$vNetName, # $vNetName = ‘VN-EUN’
[string]$vNetRGName, # $vNetRGName = ‘RG-EUN-Networking’
[string]$vmSubnet, # $vmSubnet = ‘FrontEnd’
[string]$storageAccountName, # $storageAccountName = ‘saeunstgrsapp’
[string]$storageContainer, # $storageContainer = ‘vhds’
[string]$disksConfig, # $disksConfig = ‘1\C\OS\127\127\4096;2\D\DATA\120\120\4096;’
[string]$computerName, # $computerName = ‘TESTVM’
[string]$osVersion, # $osVersion = ‘Windows Server 2012 R2’
[string]$adminUserName, # $adminUserName = ‘adm’
[string]$adminPassword, # $adminPassword = ‘P@$$w0rd1234’
[string]$asRGName, # $asRGName = ‘RG-EUN-Workloads’
[string]$asName, # $asName = ‘ASName01’

[string]$azuresubscriptionId, # $azuresubscriptionId = ‘8d549018-802b-4924-a1bc-e274d6666644’
[string]$azuretenantId, # $azuretenantId = ‘d9cd498f-ae5c-4ab5-9a69-fe74fc666655’
[PSCredential]$azureCreds # $azureCreds

$vmName = $vmName.ToUpper()
$computerName = $computerName.ToUpper()

inlinescript {

$ErrorActionPreference = ‘Stop’

# Login to Azure
$Output = Login-AzureRmAccount -TenantId $Using:azuretenantId -SubscriptionId $Using:azuresubscriptionId -Credential $Using:azureCreds
$Output = Select-AzureRmSubscription -TenantId $Using:azuretenantId -SubscriptionId $Using:azuresubscriptionId

# Get Storage Configuration
$storageAccount = Get-AzureRmStorageAccount -ResourceGroupName $Using:resourceGroupName -StorageAccountName $Using:storageAccountName
write-output “$(Get-Date -Format “yyyy-MM-dd HH:mm:ss”) – $Using:vmName – Get storage account configuration”

# Get Network Configuration
$vNet = Get-AzureRmVirtualNetwork -Name $Using:vNetName -ResourceGroupName $Using:vNetRGName
$subnetConfig = Get-AzureRmVirtualNetworkSubnetConfig -Name $Using:vmSubnet -VirtualNetwork $vNet
write-output “$(Get-Date -Format “yyyy-MM-dd HH:mm:ss”) – $Using:vmName – Get network subnet configuration”

# Set OS Disk name and location
$osDiskName = $Using:vmName + “_disk_0”
$osDiskUri = $StorageAccount.PrimaryEndpoints.Blob.ToString() + $Using:storageContainer + “/” + $osDiskName + “.vhd”
write-output “$(Get-Date -Format “yyyy-MM-dd HH:mm:ss”) – $Using:vmName – Set OS Disk name and location”

# Set network adapter name and configuration
$nicname = “$($Using:vmName)_nic1”
$nic1 = New-AzureRmNetworkInterface -Name $nicname -ResourceGroupName $Using:resourceGroupName -Location $Using:location -SubnetId $subnetConfig.Id -Force
write-output “$(Get-Date -Format “yyyy-MM-dd HH:mm:ss”) – $Using:vmName – Set network adapter name and configuration”

# Set OS version
$Publisher = ‘MicrosoftWindowsServer’
$Offer = ‘WindowsServer’
$Version = ‘latest’
If ($Using:osVersion -eq “Windows Server 2012 R2”) {
$Sku = ‘2012-R2-Datacenter’
ElseIf ($Using:osVersion -eq “Windows Server 2012”) {
$Sku = ‘2012-Datacenter’
ElseIf ($Using:osVersion -eq “Windows Server 2008 R2”) {
$Sku = ‘2008-R2-SP1’
Else {
Throw “Found error in setting OS version.”

write-output “$(Get-Date -Format “yyyy-MM-dd HH:mm:ss”) – $Using:vmName – Set operating system version”

# Set user credentials
$secpasswd = ConvertTo-SecureString $Using:adminPassword -AsPlainText -Force
$adminCreds = New-Object System.Management.Automation.PSCredential ($Using:adminUserName, $secpasswd)
write-output “$(Get-Date -Format “yyyy-MM-dd HH:mm:ss”) – $Using:vmName – Set administrator credentials”

# Set virtual machine configuration
If ($Using:asName -ne “”) {
$availabilitySet = Get-AzureRmAvailabilitySet -ResourceGroupName $Using:asRGName -Name $Using:asName
$vm = New-AzureRmVMConfig -VMName $Using:vmName -VMSize $Using:initVmSize -AvailabilitySetId $availabilitySet.Id }
Else { $vm = New-AzureRmVMConfig -VMName $Using:vmName -VMSize $Using:initVmSize }

$vm = Set-AzureRmVMOperatingSystem -Windows -ComputerName $Using:computerName -Credential $adminCreds -ProvisionVMAgent -EnableAutoUpdate -VM $vm -TimeZone “GMT Standard Time”
$vm = Set-AzureRmVMSourceImage -PublisherName $Publisher -Offer $Offer -Skus $Sku -Version $Version -VM $vm
$vm = Set-AzureRmVMOSDisk -Name $osDiskName -CreateOption fromImage -Caching ReadWrite -VhdUri $osDiskUri -VM $vm
$vm = Add-AzureRmVMNetworkInterface -VM $vm -Id $nic1.Id
write-output “$(Get-Date -Format “yyyy-MM-dd HH:mm:ss”) – $Using:vmName – Set virtual machine configuration”

# Build VM in Azure
write-output “$(Get-Date -Format “yyyy-MM-dd HH:mm:ss”) – $Using:vmName – Build VM in Azure”
$Output = New-AzureRmVM -ResourceGroupName $Using:resourceGroupName -Location $Using:location -VM $vm -DisableBginfoExtension
write-output “$(Get-Date -Format “yyyy-MM-dd HH:mm:ss”) – $Using:vmName – Internal IP: $((Get-AzureRmNetworkInterface -Name $nicname -ResourceGroupName $Using:resourceGroupName).IpConfigurations[0].PrivateIpAddress)”

# Set static IP address
write-output “$(Get-Date -Format “yyyy-MM-dd HH:mm:ss”) – $Using:vmName – Set static IP Address”
$nicConfig = Get-AzureRmNetworkInterface -Name $nicname -ResourceGroupName $Using:resourceGroupName
$nicConfig.IpConfigurations[0].PrivateIpAllocationMethod = ‘Static’
$Output = Set-AzureRmNetworkInterface -NetworkInterface $nicConfig

# Adding disks to VM
write-output “$(Get-Date -Format “yyyy-MM-dd HH:mm:ss”) – $Using:vmName – Add DATA disks to VM”
$vm = Get-AzureRmVM -ResourceGroupName $Using:resourceGroupName -VMName $Using:vmName
$Disks = @() #LUN, diskNameSuffix, diskLabelSuffix, diskSize, caching (None/ReadOnly/ReadWrite) -> $Disks += ,@(0, ‘disk_1’, ‘disk_1’, 72, ‘ReadWrite’)

$disksCfgs = $($Using:DisksConfig).Split(“;”)

foreach ($diskCfgs in $disksCfgs) {
$diskCfg = $diskCfgs.Split(“/”)
$diskLabel = “disk_” + $($diskCfg[0] – 1).ToString()
If ([int]$diskCfg[0] -ge 2) {
$Disks += ,@($($diskCfg[0] – 1), $diskLabel, $diskLabel, $diskCfg[3], ‘None’)
foreach ($Disk in $Disks)
$LUN = $Disk[0]
$diskName = “$($Using:vmName)_$($Disk[1])”
$diskLabel = “$($Using:vmName)_$($Disk[2])”
$diskSize = $Disk[3]
$Caching = $Disk[4]
$DataDiskUri = $StorageAccount.PrimaryEndpoints.Blob.ToString() + “vhds/” + $diskName.ToLower() + “.vhd”
$output = Add-AzureRmVMDataDisk -VM $vm -Name $diskLabel -DiskSizeInGB $diskSize -VhdUri $DataDiskUri -Caching $Caching -Lun $LUN -CreateOption empty
$output = Update-AzureRmVM -VM $vm -ResourceGroupName $Using:resourceGroupName

$ErrorActionPreference = ‘Stop’

#region Main code
write-output “Reading CSV file…”
Start-Sleep -Seconds 3
# Read CSV File – Input parameters
if (!(Test-Path $(split-path $csvFile))){
Throw “CSV file doesn’t exist in specified location – $csvFile.”
else {
$CSVJobs = Import-CSV -Path $csvfile
Write-Verbose “Successfully imported CSV File.”

# Logging to Azure
write-output “Logging to Azure…”
Start-Sleep -Seconds 2

$AzureCreds = Get-Credential

# Start workflow
$PSJobs = @()
ForEach ($CSVJob in $CSVJobs) {
$PSJobs += Provision-AzureVM $($CSVJob.vmName).Trim() $($CSVJob.location).Trim() $($CSVJob.initVmSize).Trim() `
$($CSVJob.resourceGroupName).Trim() $($CSVJob.vNetName).Trim() $($CSVJob.vNetRGName).Trim() $($CSVJob.vmSubnet).Trim() `
$($CSVJob.storageAccountName).Trim() $($CSVJob.storageContainer).Trim() $($CSVJob.disksConfig).Trim() $($CSVJob.computerName).Trim() $($CSVJob.osVersion).Trim() $($CSVJob.adminUserName).Trim() `
$($CSVJob.adminPassword).Trim() $($CSVJob.asRGName).Trim() $($CSVJob.asName).Trim() $subscriptionId.Trim() $tenantId.Trim() $AzureCreds `
-JobName $($CSVJob.vmName) -AsJob

# Get output of all jobs
$PSJobs | Get-Job | Format-Table -AutoSize

# Instructions for user
write-output “IMPORTANT:`n
1. To check state of all job enter command: GET-JOB | FORMAT-TABLE -AUTOSIZE.
2. To get results of specific job enter command: RECEIVE-JOB – ID <ID_OF_JOB_TO_RECEIVE>
3. To suspend job enter command: SUSPEND-JOB -ID <ID_OF_JOB_TO_SUSPEND>
4. To resume suspended job enter command: RESUME-JOB -ID <ID_OF_JOB_TO_RESUME>
5. After all jobs complete enter command: GET-JOB | REMOVE-JOB”


TESTVM,North Europe,Standard_D1_v2,RG-EUN-Applications,VN-EUN,RG-EUN-Networking,FrontEnd,saeunstlrsapp01,vhds,1/C/OS/40/40/4096;2/E/SQLData/466/466/4096;3/L/SQLLogs/233/233/4096;,TESTVM,Windows Server 2008 R2,adm,P@$$w0rd,,

Enterprise Mobility Workshops - 24th November 2015 - London | 9:00am – 3:00pm

5 thoughts on “Deploy in parallel multiple Azure virtual machines – PowerShell workflow script

  1. Hello Bartosz,
    This is great. It is exactly what I am looking for. Is there a way to get the downloadable code and a sample CSV file.
    Also to use it, do I have to just import into Azure automation runbook?

    Thank you


    PS C:\Windows\system32> C:\hasan\AzureVMBuild1.ps1
    Reading CSV file…
    Logging to Azure…
    cmdlet Get-Credential at command pipeline position 1
    Supply values for the following parameters:
    C:\hasan\AzureVMBuild1.ps1 : You cannot call a method on a null-valued
    + CategoryInfo : InvalidOperation: (:) [AzureVMBuild1.ps1], Runti
    + FullyQualifiedErrorId : InvokeMethodOnNull,AzureVMBuild1.ps1

    PS C:\Windows\system32> $vmName

    Please help me in getting this fixed.

  3. I tried running the above script to build 4 Azure VM’s but, strange thing was three of them were built but I still get state failed as detailed below:

    Here is copy of CSV file:
    TESTVM1,North Europe,Standard_D1_v2,GS-RG-TEMP,GS-VNET1,GS-RG-VNET1,GS-VNET1-SUB3-DMZ,gssantemp,vhds,1\C\OS\127\127\4096,TESTVM1,Windows Server 2008 R2,Gurjit.Singh,P@$$w0rd1234,,
    TESTVM2,North Europe,Standard_D1_v2,GS-RG-TEMP,GS-VNET1,GS-RG-VNET1,GS-VNET1-SUB3-DMZ,gssantemp,vhds,1\C\OS\127\127\4096,TESTVM2,Windows Server 2008 R2,Gurjit.Singh,P@$$w0rd1234,,
    TESTVM3,North Europe,Standard_D1_v2,GS-RG-TEMP,GS-VNET1,GS-RG-VNET1,GS-VNET1-SUB3-DMZ,gssantemp,vhds,1\C\OS\127\127\4096,TESTVM3,Windows Server 2012 R2,Gurjit.Singh,P@$$w0rd1234,,
    TESTVM4,North Europe,Standard_D1_v2,GS-RG-PROD,GS-VNET1,GS-RG-VNET1,GS-VNET1-SUB1,gssanprod,vhds,1\C\OS\127\127\4096,2\D\DATA\10\10\4096,TESTVM4,Windows Server 2016,Gurjit.Singh,P@$$w0rd1234,,

    Also how many VM’s I can deploy with this script simultaneously ?
    Can I re-run the same script again or it will overwrite everything, I tried but nothing happened, I assume it will check if the VM exist and nothing with happen?

    Id Name PSJobTypeName State HasMoreData Location Command
    — —- ————- —– ———– ——– ——-
    1 TESTVM1 PSWorkflowJob Failed False localhost Provision-AzureVM
    3 TESTVM2 PSWorkflowJob Failed False localhost Provision-AzureVM
    5 TESTVM3 PSWorkflowJob Failed False localhost Provision-AzureVM
    7 TESTVM4 PSWorkflowJob Failed False localhost Provision-AzureVM

  4. I have noticed you don’t monetize your website, don’t waste your traffic,
    you can earn additional bucks every month because you’ve got high quality
    content. If you want to know how to make extra money, search for: Mrdalekjd methods for $$$

Leave a Reply

Your email address will not be published. Required fields are marked *