Create New Azure VM with PowerShell

Every so often I set out to create new resources in my Azure subscription using the command line tools (PowerShell or CLI) just to keep up to date on the changes and improvements in the different methods. While the Azure portal is a great GUI for creating virtual machines, it definitely leaves something to be desired from a customization standpoint. I’m fairly OCD about my environment and you should be too. An organized cloud environment is a happy cloud environment.

One thing I try to embed in the mind of my customers each and every day is the need for standards. If you don’t have standards for your SSIS packages, Data Factory Pipelines, SQL Server names, database objects, and in today’s case, cloud resources, then you are asking for a maintenance nightmare in the future. Define your standards early and be sure to enforce them with very few exceptions (some Azure resources just can’t be renamed).

That brings us to today’s adventure: Virtual Machine Resource Naming.

Creating a virtual machine in the Azure portal is quick and easy. Fill in a few fields, click Create, and you have a VM ready to go in a few minutes!

Unfortunately, a few minutes later is when the problems arise. My OCD goes bonkers when I see what has been created. My beautiful, new VM has the right name but everything else is a garbage heap of nonsense!

Some of those names are…ok…but some of them are just plain bad. What is going on with the name of that managed disk? VMBlog_OsDisk_1_533318a6b0b84fdb98a00c67d1bdd0ff. The first part of that makes sense but the last part definitely doesn’t. Same with the network interface. What is that 418 at the end good for?

A year and a half ago this is how my process to fix all this would have gone.

  1. Create the NIC (with the name I want)
  2. Create the IP address (with the name I want)
  3. Create the VM
  4. Copy the VHD to be a name that’s not stupid
  5. Destroy the VM (this and the next steps is no longer necessary, the ability to swap managed disks was added in April 2018)
  6. Recreate the VM from the VHD image with the correct name
  7. Associate the new VM with the NIC that has the proper naming conventions
  8. Remove the VHD with the dumb name

That’s not exactly the easiest path to proper naming standards. Thankfully, today the process is much easier. After finding the right combination of PowerShell cmdlets I was able to get a pretty streamlined script down that I will share below.

To use this script you will need to already have a resource group and virtual network created or extend the script to add those items as well. The script is pretty basic, but feel free to use it and expand it as you see fit. I’ve taken pieces of code from several different Azure documentation pages and added them to my own code to get to this point, it’s by no means 100% my brainchild. A few good extensions to this script would be additional data disk and an NSG. For my test environment though, this gets me where I need to be. Maybe in the future I’ll expand the script some more and update it here.

#Install the Az module if you haven't done so already.
#Install-Module Az

#Login to your Azure account.

#Define the following parameters for the virtual machine.
$vmAdminUsername = "LocalAdminUserNameHere"
$vmAdminPassword = ConvertTo-SecureString "LocalAdminP@sswordHere" -AsPlainText -Force
$vmComputerName = "BS-SRV-SQL02"

#Define the following parameters for the Azure resources.
$azureLocation              = "EastUS2"
$azureResourceGroup         = "BSDomain-RG"
$azureVmName                = "BS-SRV-SQL02"
$azureVmOsDiskName          = "BS-SRV-SQL02-OS"
$azureVmSize                = "Standard_E4s_v3"

#Define the networking information.
$azureNicName               = "BS-SRV-SQL02-NIC"
$azurePublicIpName          = "BS-SRV-SQL02-IP"

#Define the existing VNet information.
$azureVnetName              = "BSDomain-Vnet"
$azureVnetSubnetName        = "default"

#Define the VM marketplace image details.
$azureVmPublisherName = "MicrosoftWindowsServer"
$azureVmOffer = "WindowsServer"
$azureVmSkus = "2019-Datacenter"

#Get the subnet details for the specified virtual network + subnet combination.
$azureVnetSubnet = (Get-AzVirtualNetwork -Name $azureVnetName -ResourceGroupName $azureResourceGroup).Subnets | Where-Object {$_.Name -eq $azureVnetSubnetName}

#Create the public IP address.
$azurePublicIp = New-AzPublicIpAddress -Name $azurePublicIpName -ResourceGroupName $azureResourceGroup -Location $azureLocation -AllocationMethod Dynamic

#Create the NIC and associate the public IpAddress.
$azureNIC = New-AzNetworkInterface -Name $azureNicName -ResourceGroupName $azureResourceGroup -Location $azureLocation -SubnetId $azureVnetSubnet.Id -PublicIpAddressId $azurePublicIp.Id

#Store the credentials for the local admin account.
$vmCredential = New-Object System.Management.Automation.PSCredential ($vmAdminUsername, $vmAdminPassword)

#Define the parameters for the new virtual machine.
$VirtualMachine = New-AzVMConfig -VMName $azureVmName -VMSize $azureVmSize
$VirtualMachine = Set-AzVMOperatingSystem -VM $VirtualMachine -Windows -ComputerName $vmComputerName -Credential $vmCredential -ProvisionVMAgent -EnableAutoUpdate
$VirtualMachine = Add-AzVMNetworkInterface -VM $VirtualMachine -Id $azureNIC.Id
$VirtualMachine = Set-AzVMSourceImage -VM $VirtualMachine -PublisherName $azureVmPublisherName -Offer $azureVmOffer -Skus $azureVmSkus -Version "latest"
$VirtualMachine = Set-AzVMBootDiagnostic -VM $VirtualMachine -Disable
$VirtualMachine = Set-AzVMOSDisk -VM $VirtualMachine -StorageAccountType "Premium_LRS" -Caching ReadWrite -Name $azureVmOsDiskName -CreateOption FromImage

#Create the virtual machine.
New-AzVM -ResourceGroupName $azureResourceGroup -Location $azureLocation -VM $VirtualMachine -Verbose

After the script finishes I have a VM, NIC, IP address, and OS disk all with exactly the names I desire. My OCD is satisfied, and my Azure environment is organized and conforms to all my standards!

Check out the new resources in the screenshot below.

  • Virtual Machine: BS-SRV-SQL02
  • Virtual Machine OS Disk: BS-SRV-SQL02-OS
  • Network Interface: BS-SRV-SQL02-NIC
  • IP Address: BS-SRV-SQL02-IP

Update: If you want to add a data disk to the VM you can use the code snippet below.

#Define the following parameters for the Azure resources. Add this to the "#Define the following parameters for the Azure resources." code section.
$azureVmDataDisk01Name      = "BS-SRV-SQL01-Data01"

#Optionally, add an additional data disk. Add this to the "#Define the parameters for the new virtual machine." code section.
$vmDataDisk01Config = New-AzDiskConfig -SkuName Standard_LRS -Location $azureLocation -CreateOption Empty -DiskSizeGB 127
$vmDataDisk01 = New-AzDisk -DiskName $azureVmDataDisk01Name -Disk $vmDataDisk01Config -ResourceGroupName $azureResourceGroup 
$VirtualMachine = Add-AzVMDataDisk -VM $VirtualMachine -Name $azureVmDataDisk01Name -CreateOption Attach -ManagedDiskId $vmDataDisk01.Id -Lun 0

Bradley Schacht

Bradley Schacht is a Cloud Solution Architect on the state and local government team with Microsoft based in Jacksonville, FL. He has co-authored 3 SQL Server books including "SQL Server 2014 Professional Administration". As a former consultant and trainer, he uses his experience on many parts of the Microsoft BI and data platform to help customers deliver the best possible solutions. Bradley frequently presents at community events around the country. He is a contributor to sites such as and an active member of the Jacksonville SQL Server User Group (JSSUG).

You may also like...

4 Responses

  1. bdcoder says:

    So in the “real world”, we have a Windows server with a system disk (C) and two data disks: D and E. We have numerous scripts and scheduled tasks that run on this server; naturally, the scripts reference paths on D and E.

    So, if one wants to “lift and shift” a server as described above to the “cloud” (specifically to Azure) – how does one define and attach the data disks (D) and (E) so we can simply and easily copy all of our scripts and directory structures from the old server to the new? Do disk / driver letters even exist in the “cloud”?

    Thanks in advance.

    • In the IaaS world (Infrastructure as a Service aka, VMs) drive letters exist just as they do on your local computer, on a Hyper-V VM, or a VMWare VM. You can absolutely use that configuration you outlined as well. There is code to add data disks at the very bottom of this post.

      There are also some instructions on adding data disks to your VM outlined here for how to do this from the Azure portal. (or the PowerShell version here if you want more details than what I have in this post). Those can be assigned any drive letter you’d like.

      Something to note is that by default, the D: drive is gong to be temporary storage. Each time the VM is restarted the drive contents will be cleared. If your application needs to use D: and E: (i.e. can’t be changed to use M: and N: for instance) then there is a way to change the temporary drive to a different letter. Those instructions are outlined here.

      • bdcoder says:

        Thanks for the reply – probably one of the worst design decisions of Azure was to allocate D (“the first available drive letter”) as a temporary drive – from what I have read – this is still an issue when one re-sizes a VM – that is, if you you have scripts referencing paths on the D drive and then resize the VM, you could be in for a world of hurt when D is turned into a temp drive… Probably 98% of non-cloud based servers out there use D for data or other uses other than temp storage !!

        Thanks again.

  2. Randy Dover says:

    I was trying my own script and got an error, so I did a search and came across your script. I’m getting the same error. When I execute the “$azureVnetSubnet = (Get-AzVirtualNetwork -Name $azureVnetName -ResourceGroupName $azureResourceGroup).Subnets | Where-Object {$_.Name -eq $azureVnetSubnetName}” line I get: “Get-AzVirtualNetwork : The Resource ‘Microsoft.Network/virtualNetworks/vnet_preprod_eastus’ under resource group ‘rg_ovrapp_preprod_eastus’ was not found.”
    Any ideas how to fix that?
    I’ve run it in PowerShell 5.1 and Visual Studio Code 1.51.1

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.