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:

AzureVMBuild.ps1

# Input parameters
param (
[Parameter(Mandatory=$false,Position=1)]
[string]$csvFile=”.\AzureVMBuildInputs.csv”,
[Parameter(Mandatory=$false,Position=3)]
[string]$subscriptionId = ‘8d549018-802b-4924-a1bc-e274d6666644’,
[Parameter(Mandatory=$false,Position=4)]
[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”

AzureVMBuildInputs.csv

vmName,location,initVmSize,resourceGroupName,vNetName,vNetRGName,vmSubnet,storageAccountName,storageContainer,disksConfig,computerName,osVersion,adminUserName,adminPassword,asRGName,asName
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

3 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

  2. hhasanmd@hotmail.com

    HI
    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
    expression.
    + CategoryInfo : InvalidOperation: (:) [AzureVMBuild1.ps1], Runti
    meException
    + FullyQualifiedErrorId : InvokeMethodOnNull,AzureVMBuild1.ps1

    PS C:\Windows\system32> $vmName
    TESTVM

    Please help me in getting this fixed.

Leave a Reply

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