Howdy folks and a happy new year to all of you,
today I’m gonna show you a way to automatically deploy and customize virtual machines in Azure Resource Manager without using ARM templates. Custom Script Extensions can help you to achieve your goal by only leveraging PowerShell.
Azure Custom Script Extensions (CSE) are a great way to customize your VM’s operating system without having to log on or do it manually. You can use CSE on Linux and Windows VMs and write them for the OS’s native scripting environment, e.g. PowerShell or bash. Let me give you an example:
I’ve deployed a new ARM VM using the following PowerShell script:
Import-Module Azure Login-AzureRmAccount $location = 'NorthEurope' $ResourceGroupName= 'myRG' $LocalAdminUser ='myAdmin' $ResourceGroup = New-AzureRmResourceGroup -Name $ResourceGroupName -Location $Location $StorageAccountName = 'letstalkazure' $Test = Get-AzureRmStorageAccountNameAvailability $StorageAccountName If ($Test) { $StorageAccount =New-AzureRmStorageAccount -ResourceGroupName $ResourceGroupName -Name $StorageAccountName -SkuName 'Standard_GRS' -Kind 'Storage' -Location $location } $mySubnet = New-AzureRmVirtualNetworkSubnetConfig -Name 'mySubnetConfig' -AddressPrefix 10.0.0.0/24 $myVnet = New-AzureRmVirtualNetwork -Name 'myVnet' -ResourceGroupName $ResourceGroupName -Location $location -AddressPrefix 10.0.0.0/16 -Subnet $mySubnet $myPublicIp = New-AzureRmPublicIpAddress -Name 'myPIP' -ResourceGroupName $ResourceGroupName -Location $location -AllocationMethod Static $myNIC = New-AzureRmNetworkInterface -Name 'myNIC' -ResourceGroupName $ResourceGroupName -Location $location -SubnetId $myVnet.Subnets[0].Id -PublicIpAddressId $myPublicIp.Id ## Now get the secret from your Azure Key Vault that you've created before and create your local admin credentials $Secret = Get-AzureKeyVaultSecret -VaultName 'myKeyVault' -Name 'mySecret' $Cred = [PSCredential]::new($LocalAdminUser, $Secret.SecretValue) $myVm = New-AzureRmVMConfig -VMName 'myVM' -VMSize 'Standard_A2' $myVM = Set-AzureRmVMOperatingSystem -VM $myVM -Windows -ComputerName 'myVM' -Credential $cred -ProvisionVMAgent -EnableAutoUpdate $myVM = Set-AzureRmVMSourceImage -VM $myVM -PublisherName 'MicrosoftWindowsServer' -Offer 'WindowsServer' -Skus '2016-Datacenter' -Version 'latest' $myVM = Add-AzureRmVMNetworkInterface -VM $myVM -Id $myNIC.Id $blobPath0 = 'vhds/myOsDisk1.vhd' $blobPath1 = 'vhds/myDataDisk1.vhd' $blobPath2 = 'vhds/myDataDisk2.vhd' $osDiskUri = $StorageAccount.PrimaryEndpoints.Blob.ToString() + $blobPath0 $DataDiskVhdUri1 = $StorageAccount.PrimaryEndpoints.Blob.ToString() + $blobPath1 $DataDiskVhdUri2 = $StorageAccount.PrimaryEndpoints.Blob.ToString() + $blobPath2 $myVM = Set-AzureRmVMOSDisk -VM $myVM -Name 'myOsDisk1' -VhdUri $osDiskUri -CreateOption fromImage $myVM = Add-AzureRmVMDataDisk -VM $myVM -Name 'myDataDisk1' -VhdUri $DataDiskVhdUri1 -Caching 'ReadOnly' -DiskSizeInGB 10 -Lun 0 -CreateOption Empty $myVM = Add-AzureRmVMDataDisk -VM $myVM -Name 'myDataDisk2' -VhdUri $DataDiskVhdUri2 -Caching 'ReadOnly' -DiskSizeInGB 10 -Lun 1 -CreateOption Empty New-AzureRmVM -ResourceGroupName $ResourceGroupName -Location $location -VM $myVM
This PowerShell script creates a new ARM VM with an OS disk and two data disks. Be aware of the passage I use to create the local admin credentials from an Azure Key Vault secret. If you are not familiar with Azure Key Vault and secrets that are stored within read my blogpost from November 2016.
After VM creation those data disks are neither initialized nor formatted so if you’re up to use them later on you will have further tasks to do. This is when Custom Script Extensions come into play. I’ve created a PowerShell script that performs the following tasks:
- all raw disks are initialized using GPT partition style
- all of the initialized disks are formatted with NTFS file system
- all of the formatted disks are assigned a drive letter starting with F:\ and ending with Y:\
$Disks = Get-Disk | where {$_.PartitionStyle -like "Raw"} $i = 0 $accessPath = @() $accessPath += 102..121 | foreach {"$([char]$_):\" } ## add drive letters f:\ to y:\ to the array Foreach ($Disk in $Disks) { Initialize-Disk -PartitionStyle GPT -Number $Disk.Number New-Volume -Disk $Disk -FileSystem NTFS -FriendlyName Data -AccessPath $accessPath[$i] $i++ }
You can run the script manually on a Windows operating system after logging in or you use Azure CSE. All you need to do is log in into Azure Primary Portal, choose your new VM and select the Extensions setting.

Then you click “+ Add” and select Custom Script Extension and create. Now you can upload your CSE script.
Of course you can also manage those tasks using PowerShell 🙂
First of all you need to upload your script file to Azure storage.
### Upload .ps1 script to Azure storage account $context = New-AzureStorageContext -StorageAccountName "MySAName" -StorageAccountKey "MySAKey" Set-AzureRmCurrentStorageAccount -Context $Context New-AzureStorageContainer -name 'myContainerName' Set-AzureStorageBlobContent -File 'localpath\myFilename.ps1' -container 'myContainerName'
then you can configure your VM to run the CSE.
### Run CSE on VM Set-AzureRmVMCustomScriptExtension -Name 'myCSE' ` -ContainerName 'myContainer' ` -FileName Azure_CustomScriptExtension.ps1 ` -StorageAccountName 'mySA' ` -ResourceGroupName 'myRG' ` -VMName 'myVM' ` -Run Azure_CustomScriptExtension.ps1 ` -Location NorthEurope
After your CSE has finished you can remove it again using the following command.
### Remove CSE after successful run remove-AzureRmVMCustomScriptExtension -Name 'myCSE' -ResourceGroupName 'myRG' -VMName 'myVM' -Force
That’s all for now.
Stay tuned and kind regards,
Tom
i had to change line 6 of your script to:
Initialize-Disk -PartitionStyle GPT -Number $Disk.Number
in order to get it working.
Thanks for this blog!
LikeLiked by 1 person
Hey there,
thank you for your input – I’ve changed the script accordingly. Seems like there have been some changes in newer PowerShell versions.
LikeLike