Create a VM from PowerShell

While there is nothing wrong in using the Azure Portal to create VM’s, scripting via PowerShell provides some flexibility on some options which we will cover shortly.

In the example, we will create a VM running Windows Server 2019 Datacenter UI using PowerShell, which requires the Az module locally or you can also use the Azure Cloud Shell instead.

Before getting to the crux of it, let’s go though some of the variables. Most are self-explanatory, so I'll provide context on the more interesting ones, including:

  • $vmDiskName
    • This takes the VM name and appends _OsDisk, which provides a neat naming convention. Creating a VM in the Azure Portal also uses the VM name, but appends a rather long set of random characters.
    • Whilst this is purely for aesthetics, this will appease those who like to have control over naming resources
  • $vmDiskSize
    • The default and minimum OS disk size is 127 GiB. In PowerShell, you can define a much smaller size, say 30 GiB; this is fine for testing or using a smaller image size
  • $vmDiskaccountType
    • While you can define this in the Azure Portal, the default is Premium SSD, which can easily be glossed over. While Standard HDD has low IOPS as its backed by spinning disk storage, it is suitable for most testing and development needs
  • $timezone
    • This is something that I personally find annoying, especially if you live outside the US. I can understand the rationale to maintain a single image for the entire Marketplace, it is a pain having to change this after; at least you can change this via PowerShell
  • $vmConfig
    • While there are many parameters listed with this variable, lets focus on a particular one in bold italics '2019-Datacenter-smalldisk'.
    • You can deploy a lighter build which is just over 10 GB; coupled with a smaller OS disk, this is helps reduce costs
    • For Server Core using the small disk image, use the following SKU ‘2019-Datacenter-Server-Core-smalldisk’, or change the version to 2016 to build a Windows Server 2016 Datacenter VM

Note: Please refer to the Manage Disk Pricing page for a complete list to help decide which disk size and type to use, and how much it will cost. 

Now that we have covered the key variables that you can benefit from using PowerShell over the Azure Portal, here is the script below. Ensure you change particular variables to suit your environment:

# To get all available regions, run 'Get-AzLocation | Format-Table'
# Declare variables
Write-Host "Azure VM creation script" -foreground yellow
$resourceGroup = 'Servers'
$location = 'eastus'
$vmName = Read-Host -Prompt 'Enter VM Name'
$cred = Get-Credential -Message "Enter VM's credentials"
$vmName = "$vmName"
$vnetSubnet = 'subnet1'
$vmDiskName = (''+$vmName.ToLower()+'_OsDisk')
$vmDiskSize = '30'
$vmDiskaccountType = 'Standard_LRS'
$timezone = 'AUS Eastern Standard Time'
#$vmAvailabilitySet = Get-AzAvailabilitySet -ResourceGroupName Servers -Name MyAvailabilitySet
$vmStorageDiag = Get-AzStorageAccount -ResourceGroupName Servers -Name MyStorageAccount
$vnet = Get-AzVirtualNetwork -Name magrin -ResourceGroupName Servers
$SubnetID = Get-AzVirtualNetworkSubnetConfig -Name $vnetSubnet -VirtualNetwork $vnet
$tags += @{Servers="Virtual Machine"}

# Create a public IP address and specify a DNS name
# If you don't need a public IP, comment out this line
#$pip = New-AzPublicIpAddress -ResourceGroupName $resourceGroup -Location $location  -Name "$vmName" -AllocationMethod Static -IdleTimeoutInMinutes 4

# Create an inbound network security group rule for port 3389
# If you don't need an NSG, comment out the 'nsg' variable line
#$nsgRuleRDP = New-AzNetworkSecurityRuleConfig -Name AllowRDP -Protocol Tcp -Direction Inbound -Priority 100 -SourceAddressPrefix * -SourcePortRange * -DestinationAddressPrefix * -DestinationPortRange 3389 -Access Allow

# Create a network security group
#$nsg = New-AzNetworkSecurityGroup -ResourceGroupName $resourceGroup -Location $location -Name $vmName -SecurityRules $nsgRuleRDP

# For a virtual network card and associate with public IP address and NSG, use the first line.
# If you don't require a public IP and a NSG, use the second line
#$nic = New-AzNetworkInterface -Name $vmName -ResourceGroupName $resourceGroup -Location $location -SubnetId $SubnetID.Id -PublicIpAddressId $pip.Id -NetworkSecurityGroupId $nsg.Id
$nic = New-AzNetworkInterface -Name $vmName -ResourceGroupName $resourceGroup -Location $location -SubnetId $SubnetID.Id

# Create a virtual machine configuration
$vmConfig = New-AzVMConfig -VMName $vmName -VMSize Standard_B1s | Set-AzVMOperatingSystem -Windows -ComputerName $vmName -Credential $cred -TimeZone $timezone | Set-AzVMSourceImage -PublisherName MicrosoftWindowsServer -Offer WindowsServer -Skus 2019-Datacenter-smalldisk -Version latest | Add-AzVMNetworkInterface -Id $nic.Id

# Define Storage Account for boot diagnostics
Set-AzVMBootDiagnostics -Enable -ResourceGroupName $vmStorageDiag.ResourceGroupName -VM $vmConfig -StorageAccountName $vmStorageDiag.StorageAccountName

# Specify VM OsDisk name
Set-AzVMOSDisk -CreateOption fromImage -VM $vmConfig -Name $vmDiskName -DiskSizeInGB $vmDiskSize -Caching ReadWrite -StorageAccountType $vmDiskaccountType -Windows

# Create the virtual machine
New-AzVM -ResourceGroupName $resourceGroup -Location $location -VM $vmConfig

# Set the tag
Set-AzResource -Name $vmName -ResourceGroupName $resourceGroup -ResourceType "Microsoft.Compute/VirtualMachines" -Tag $tags -Force

# Stop the VM
#Stop-AzVM -Name $vmName -ResourceGroupName $resourceGroup -Force

# Write completion message
Write-Host "Azure VM creation completed" -foreground green

# Clear variables
Remove-Variable * -ErrorAction SilentlyContinue