Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / containers / virtual-machine

How to create an Azure Virtual Machine with ARM Template

5.00/5 (1 vote)
9 Feb 2020CPOL3 min read 2K  
Instructions detailing creation of an Azure Virtual Machine using ARM templates in PowerShell, emphasizing steps from setting up the ARM template to deploying it
The guide provides a comprehensive walkthrough of Azure VM creation using ARM templates in PowerShell, focusing on template setup and deployment steps.

Today let's create the same Virtual Machine, but using ARM this time.

First, fire up a PowerShell console and login to your subscription:

PowerShell
Login-AzureRmAccount 

1. Create Your ARM Template File

Fire up your favourite editor, Visual Studio Code or Visual Studio itself are my two choices.

Create a new JSON file and name it new-vm.json

Copy and paste the following structure into the new file. This is the beginning of our ARM Template.

PowerShell
{
  "$schema" "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
  "contentVersion" "1.0.0.0",
  "parameters" {
  },
  "variables" {
  },
  "resources" [
  ],
  "outputs" {
  }
} 

2. Create PowerShell Script that Will Run Our ARM Template

This, of course, can be done just in the PowerShell command line, but it’s much faster and more repeatable to create the script file and be able to run it multiple times without having to type these things out.

Create a new file called deploy-new-vm.ps1

3. Create a Resource Group

Just like last time we need to create a Resource Group, this is the logical place to group all the elements that we’re going to create. This needs to be done in PowerShell, so let’s add the following to our new deploy-new-vm.ps1 file.

We also need to select the subscription that we want to put this new Resource Group into, you can get a list of all your subscriptions by using the Get-AzureRmSubscription command, choose which you want and copy the Id and enter into the $subscriptionId below:

PowerShell
$subscriptionId <span class="o">= "<YOUR_SUBSCRIPTION_ID>"
Select-AzureRmSubscription -SubscriptionId $subscriptionId

$resourceGroup <span class="o">= "test-infra"
$location <span class="o">= "North Europe"

New-AzureRmResourceGroup -Name $resourceGroup -Location $location 

4. Create Storage Account

Finally, something that we can add to our new ARM Template.

First, add the following parameter to the “parameters” part of the template:

PowerShell
"parameters" {
    "storageName" {
      "type" "string",
      "defaultValue" "teststorage",
      "metadata" {
        "description" "Name for our storage account"
      }
    }
  } 

Now add a new resource, which is our storage account.

PowerShell
"resources" [
   {
     "name" "[concat(parameters('storageName'), uniqueString(resourceGroup().id))]",
     "type" "Microsoft.Storage/storageAccounts",
     "location" "[resourceGroup().location]",
     "apiVersion" "2015-06-15",
     "properties" {
       "accountType" "Standard_LRS"
     }
   }
 ]

This is the same as our storage we created in the previous post.

We take the storageName which is a parameter and we add a unique string which is generated by uniqueString(resourceGroup().id) this means we can run this multiple times, and also that we won’t clash with anybody else’s storage accounts in the Azure cloud.

The location is defined from where the resourcegroup is located by using [resourceGroup().location].

The storage account type is still the Standard Locally Redundant Storage.

The full file will now look like so:

PowerShell
{
  "$schema" "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
  "contentVersion" "1.0.0.0",
  "parameters" {
    "storageName" {
      "type" "string",
      "defaultValue" "teststorage",
      "metadata" {
        "description" "Name for our storage account"
      }
    }
  },
  "variables" {
  },
  "resources" [
    {
      "name" "[concat(parameters('storageName'), uniqueString(resourceGroup().id))]",
      "type" "Microsoft.Storage/storageAccounts",
      "location" "[resourceGroup().location]",
      "apiVersion" "2015-06-15",
      "properties" {
        "accountType" "Standard_LRS"
      }
    }
  ],
  "outputs" {
  }
} 

5. Create Virtual Network

The Virtual Machine needs to live in a Virtual Network, so we create a new Resource in the ARM Template.

PowerShell
{
      "name" "test-net",
      "type" "Microsoft.Network/virtualNetworks",
      "location" "[resourceGroup().location]",
      "apiVersion" "2015-06-15",
      "properties" {
        "addressSpace" {
          "addressPrefixes" [
            "10.0.0.0/16"
          ]
        },
        "subnets" [
          {
            "name" "frontendSubnet",
            "properties" {
              "addressPrefix" "10.0.1.0/24"
            }
          }
        ]
      }
    } 

6. Setup IP Address and Network Interface

We need to give our new Virtual Machine a Network Interface and an IP address. This can be static, but we’re going to leave it as dynamic as per the last post.

Create a new resource for our Public IP address that we will use to RDP into the VM.

PowerShell
{
     "apiVersion" "2016-03-30",
     "type" "Microsoft.Network/publicIPAddresses",
     "name" "test-publicip",
     "location" "[resourceGroup().location]",
     "properties" {
       "publicIPAllocationMethod" "Dynamic"
     }
   }

Let's create a parameter for our VM Name.

PowerShell
"vmName" {
      "type" "string",
      "defaultValue" "testvm1",
      "metadata" {
        "description" "Name for our Virtual Machine"
      }
    } 

Now let’s create a new resource for our new Network Interface.

PowerShell
{
      "name" "[concat(parameters('vmName'),'-nic0')]",
      "type" "Microsoft.Network/networkInterfaces",
      "location" "[resourceGroup().location]",
      "apiVersion" "2017-06-01",
      "properties" {
        "ipConfigurations" [
          {
            "name" "ipconfig",
            "properties" {
              "privateIPAllocationMethod" "Dynamic",
              "publicIPAddress" {
                "id" "[resourceId('Microsoft.Network/publicIPAddresses',
                                  'test-publicip')]"
              },
              "subnet" {
                "id" "[concat(resourceId('Microsoft.Network/virtualNetworks', 
                                         'test-net'),'/subnets/','frontendSubnet')]"
              }
            }
          }
        ]
      }
    }

You can see here, we are using the concat keyword to create a name for our Network Interface prefix of the VM Name and then adding -nic0.

7. Create Our Virtual Machine

PowerShell
{
      "name" "[concat(parameters('vmName'))]",
      "type" "Microsoft.Compute/virtualMachines",
      "location" "[resourceGroup().location]",
      "apiVersion" "2017-03-30",
      "dependsOn" [
        "[concat('Microsoft.Network/networkInterfaces/',parameters('vmName'),'-nic0')]"
      ],
      "properties" {
        "hardwareProfile" {
          "vmSize" "Basic_A1"
        },

        "osProfile" {
          "computerName" "[parameters('vmName')]",
          "adminUsername" "YOURUSERNAME",
          "adminPassword" "YOUR_PASSWORD12345678"
        },
        "storageProfile" {
          "imageReference" {
            "publisher" "MicrosoftWindowsServer",
            "offer" "WindowsServer",
            "sku" "2012-R2-Datacenter",
            "version" "latest"
          },
          "osDisk" {
            "osType" "Windows",
            "name" "[concat(parameters('vmName'),'-','osdisk')]",
            "createOption" "FromImage",
            "caching" "ReadWrite"
          }
        },
        "networkProfile" {
          "networkInterfaces" [
            {
              "id" "[concat(resourceId('Microsoft.Network/networkInterfaces',
                     concat(parameters('vmName'))),'-nic0')]"
            }
          ]
        }
      }
    }

The dependsOn value in this resource means that it will wait until the provisioning of the specified resource before beginning to create this current resource. You can depend on multiple resources. In this case, we are depending on the Network Interface being setup before we continue to create the Virtual Machine.

8. Deploy the ARM Template

Let’s add the deployment of the ARM Template to our deploy-new-vm.ps1 script.

PowerShell
New-AzureRmResourceGroupDeployment `
    -Name test-infra-deployment `
    -ResourceGroupName $resourceGroup `
    -TemplateFile new-vm.json `
    -Verbose -Force 

The Full Solution

Our final ARM Template json file (new-vm.json) looks like this:

PowerShell
{
  "$schema" "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
  "contentVersion" "1.0.0.0",
  "parameters" {
    "storageName" {
      "type" "string",
      "defaultValue" "teststorage",
      "metadata" {
        "description" "Name for our storage account"
      }
    },
    "vmName" {
      "type" "string",
      "defaultValue" "testvm1",
      "metadata" {
        "description" "Name for our Virtual Machine"
      }
    }
  },
  "variables" {
  },
  "resources" [
    {
      "name" "[concat(parameters('storageName'), uniqueString(resourceGroup().id))]",
      "type" "Microsoft.Storage/storageAccounts",
      "location" "[resourceGroup().location]",
      "apiVersion" "2015-06-15",
      "properties" {
        "accountType" "Standard_LRS"
      }
    },
    {
      "name" "test-net",
      "type" "Microsoft.Network/virtualNetworks",
      "location" "[resourceGroup().location]",
      "apiVersion" "2015-06-15",
      "properties" {
        "addressSpace" {
          "addressPrefixes" [
            "10.0.0.0/16"
          ]
        },
        "subnets" [
          {
            "name" "frontendSubnet",
            "properties" {
              "addressPrefix" "10.0.1.0/24"
            }
          }
        ]
      }
    },
    {
      "apiVersion" "2016-03-30",
      "type" "Microsoft.Network/publicIPAddresses",
      "name" "test-publicip",
      "location" "[resourceGroup().location]",
      "properties" {
        "publicIPAllocationMethod" "Dynamic"
      }
    },
    {
      "name" "[concat(parameters('vmName'),'-nic0')]",
      "type" "Microsoft.Network/networkInterfaces",
      "location" "[resourceGroup().location]",
      "apiVersion" "2017-06-01",
      "properties" {
        "ipConfigurations" [
          {
            "name" "ipconfig",
            "properties" {
              "privateIPAllocationMethod" "Dynamic",
              "publicIPAddress" {
                "id" "[resourceId('Microsoft.Network/publicIPAddresses','test-publicip')]"
              },
              "subnet" {
                "id" "[concat(resourceId('Microsoft.Network/virtualNetworks', 'test-net'),'/subnets/','frontendSubnet')]"
              }
            }
          }
        ]
      }
    },
    {
      "name" "[concat(parameters('vmName'))]",
      "type" "Microsoft.Compute/virtualMachines",
      "location" "[resourceGroup().location]",
      "apiVersion" "2017-03-30",
      "dependsOn" [
        "[concat('Microsoft.Network/networkInterfaces/',parameters('vmName'),'-nic0')]"
      ],
      "properties" {
        "hardwareProfile" {
          "vmSize" "Basic_A1"
        },

        "osProfile" {
          "computerName" "[parameters('vmName')]",
          "adminUsername" "YOURUSERNAME",
          "adminPassword" "Your_PASSWORD12345678"
        },
        "storageProfile" {
          "imageReference" {
            "publisher" "MicrosoftWindowsServer",
            "offer" "WindowsServer",
            "sku" "2012-R2-Datacenter",
            "version" "latest"
          },
          "osDisk" {
            "osType" "Windows",
            "name" "[concat(parameters('vmName'),'-','osdisk')]",
            "createOption" "FromImage",
            "caching" "ReadWrite"
          }
        },
        "networkProfile" {
          "networkInterfaces" [
            {
              "id" "[concat(resourceId('Microsoft.Network/networkInterfaces',
                                        concat(parameters('vmName'))),'-nic0')]"
            }
          ]
        }
      }
    }
  ],
  "outputs" {
  }
} 

And our Powershell file (deploy-new-vm.ps1):

PowerShell
$subscriptionId = "<YOUR_SUBSCRIPTION_ID>"
Select-AzureRmSubscription -SubscriptionId $subscriptionId

$resourceGroup = "test-infra"
$location = "North Europe"

New-AzureRmResourceGroup -Name $resourceGroup -Location $location

New-AzureRmResourceGroupDeployment `
    -Name test-infra-deployment `
    -ResourceGroupName $resourceGroup `
    -TemplateFile new-vm.json `
    -Verbose -Force 

Deploy It!

Pull up a PowerShell prompt and make sure you have already run Login-AzureRmAccount, browse to your folder where our two new files are located and run them.

PowerShell
cd c:\dev\Create-VM-with-ARM\
.\deploy-new-vm.ps1

Github

Source files are available on Github.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)