Managing VM Scale Sets (VMSS) with Powershell and ARM templates

Azure VMSS (Virtual Machine Scale Sets)


A VMSS is an ARM based resource, and can be thought of as a container for building a scalable set of similar machines from a common image. In this post I’m going to show how to build an ARM template to reference and deploy your own custom VM image into a new VMSS.

The benefits of VMSS over Cloud Services are that
1. you do not have to pre-provision any of the VM instances. (With cloud services, if you wanted to scale up to 10 instances you would need to build and configure 10 seperate VMS, then turn off the 8 or 9 you didnt want running all the time.)
2. scaling is faster
3. the deployment can be templated with an ARM template.
4. Linux VMs are supported in VMSS.

Many of the popular Azure PaaS services are built on top of VMSS – for example, Service Fabric, which then in turn underpins services like Azure SQL, DocumentDB, and other highly available, highly scalable products. Scale Sets are the way forward to wrap IaaS VMs and the management of them – if you’re contemplating whether to use Cloud Services or VMSS, choose VMSS for future compatibility, a richer feature set and better performance. Having said that, Cloud Services do have one advantage right now over VMSS, and that’s the maintenance of the OS image. Although there are plans in the roadmap for automatic updates to be supported, they’re not right now, you have to manage that yourself.


The easiest way to get going with a VMSS is using the Azure Portal, but since I like to build systems in a repeatable way that can be tweaked and redeployed time and time again, I encourage the use of ARM templates, and the Visual Studio ARM Resource Group Deployment Project. This project type comes with many starter templates to get you going, two of which relate to VM Scale Sets – one for Windows based machines and one for Linux. The project generates a template and parameters file (see below), as well as a PowerShell deployment script. Both templates contain a storage account (for logging and Vhds), a virtual network, a Load balancer, a public IP address and the VMSS itself. The Windows template contains a parameter for specifying the Windows OS version, size, instance count and so on. When using a custom image, instead of the Windows OS version, I’ve added a parameter for sourceImageVhdUri – which needs to point to a VHD file stored in a storage account in your subscription:

    "sourceImageVhdUri": {
      "type": "string",
      "metadata": {
      "description": "The source of the blob containing the custom image",

Create a new VMSS

Before you can go much further though, you’re going to need a “golden image” – the sysprepped VM VHD that contains your base image which will be used when the VMSS creates its instances.

  • Use the Portal to create a new RM VM; RDP to it and install software/configure as required (remember to configure your Windows Firewall)
  • Use SYSPREP to generalize the image using “out of box experience” oobe
  • Use the following Powershell to capture the VM image to a VHD file
    Login-AzureRmAccount -subscriptionId **xxx**
    Stop-AzureRmVM -ResourceGroupName "RG-xxx" -Name "DemoVM" -Force
    Set-AzureRmVM -ResourceGroupName "RG-xxx" -Name "DemoVM" -Generalized 
    Save-AzureRmVMImage -ResourceGroupName "RG-xxx" -Name "DemoVM" `
        -DestinationContainerName "vhds" `
        -VHDNamePrefix "template" -Path "c:\temp\CaptTemplate.json" 
  • the VHD will be saved to your subscription’s default storage account in the vhd’s container – you will find the URI for it in c:\temp\CaptTemplate.json
  • Modify the VHD Uri in the template parameters file
  • deploy the ARM template – refer to my previous post to read about deploying and automating deployments of ARM templates using VSTS and the Build/Release manager.

Note that I’ve copied a complete sample template and parameters pair to GitHub at this url: – you will need to modify the parameters to use your own storage accounts and VHD files etc, but otherwise it’s ready to go.

The parameters file contains

        "instanceCount": {
            "value": 2
        "sourceImageVhdUri": {
            "value": ""

The key change to the out of the box template is to the virtualMachineProfile section, where you specify the custom image instead of trying to source one from the Azure Marketplace Gallery:

"virtualMachineProfile": {
          "storageProfile": {
            "osDisk": {
              "name": "[variables('osDiskName')]",
              "caching": "ReadOnly",
              "createOption": "FromImage",
              "osType": "Windows",
              "image": {
                "uri": "[parameters('sourceImageVhdUri')]"
          "osProfile": {
            "computerNamePrefix": "[variables('namingInfix')]",
            "adminUsername": "[parameters('adminUsername')]",
            "adminPassword": "[parameters('adminPassword')]"

Starting and Stopping

Currently starting and stopping can only be done via Powershell – the Portal UI is due to be enhanced to support VMSS features in the near future.

# Start all instances together
PS C:\> Start-AzureRmVmss -ResourceGroupName "my-dev-rg" -VMScaleSetName "mydevvmss"

# to start specific instances only
PS C:\> Start-AzureRmVmss -ResourceGroupName "my-dev-rg" -VMScaleSetName "mydevvmss" -InstanceId "0", "1"
# stop the scale set (all instances)
PS C:\> Stop-AzureRmVmss -ResourceGroupName "my-dev-rg" -VMScaleSetName "mydevvmss"

Connecting to instances

By default, the VMSS will configure each instance within its scale set with an internal NAT port for RDP. The VMSS itself has a public IP address which hits the load balancer.
Check the load balancer > Settings > Inbound NAT Rules to see the port mapping that has been applied to the VM instances for you. It should look something like:

Name Destination Target Service
mydevnatpool.1 mydevvmss (instance 1) Dynamic ports (TCP/50001)
mydevnatpool.2 mydevvmss (instance 2) Dynamic ports (TCP/50002)

Therefore, to RDP to instance 1, you would connect to, and port 50002 will connect to instance 2.
Note – instances are not always created consecutively, you may end up with instances numbered 0, 3, 4, 7 for example when asking for 4 VMs in the set!

Manual Scaling

It is thought that the need for dynamic scaling in this scenario is not necessary, so scalaing has been set to manual. Change the total number of instances in the scale set by modifying the template and redeploying it. Scale up or down within the scaleset using PowerShell Start-AzureRMVMSS and Stop-AzureRMVMSS as shown below. The VHD referred to in the template is a generalised, sysprepped image which contains Windows Server and our custom software pre-installed.

Other management commands

The following commands relate to managing VMSS’s:

get-command *vmss* | ft name

# common


# Start all instances together
PS C:\> Start-AzureRmVmss -ResourceGroupName "my-dev-rg" -VMScaleSetName "mydevvmss"

# to start specific instances only
PS C:\> Start-AzureRmVmss -ResourceGroupName "my-dev-rg" -VMScaleSetName "mydevvmss" -InstanceId "0", "1"
# stop the scale set (all instances)
PS C:\> Stop-AzureRmVmss -ResourceGroupName "my-dev-rg" -VMScaleSetName "mydevvmss"

# others