Objective

 In this article we will focus on automation and will deploy azure VM using ARM template. We will deploy an ARM template using PowerShell. Automation has always been major focus of Azure. Consider a scenario where a user need to deploy 50-100 VM's I am sure no body is going to deploy this using Azure GUI portal it is just too much time consuming. Hence comes automating the deployment.

Out of scope

The article is in no way authoritative study on ARM template. The complete discussion of ARM template and JSON is beyond scope of this article. This article only used in built ARM template from MS to create a VM.

What you need

  1. ARM template
  2. PowerShell version 5.1 or higher.
  3. .Net Framework 4.7.2 ( some bug fixes related to PowerShell are there in latest version)
  4. Storage Account in your azure subscription. How to create a storage account

How to generate ARM Template

We can use JSON language to write own ARM template but that would need prior JSON knowledge and would need time as ARM templates are big and verbose. Easy way is to generate it  from Azure portal. This article will show you how to do that

Log into Azure Portal, click on Create resource

On Next page type Virtual Machine and then click 

We would get below page

NOTE: Before you start make sure Vnet which you will use to create VM lies in Resource Group  where you are trying to create Azure VM and both RG and Vnet should be in same region. If not the deployment might face if using ARM template.

Fill the details accordingly and on last page click on review and create BUT DO NOT START THE DEPLOYMENT BY CLICKING CREATE.

Click on Download A template for Automation. 


Understanding ARM template

Below is how ARM template looks like. 

Notice the various tabs Template, Parameters, CLI etc.  The CLI and PowerShell tab gives us command which can be used while deploying completely with PowerShell or Azure CLI.

Below is how parameters tab looks like. We have explained some of the values

{
 "contentVersion": "1.0.0.0",
 "parameters": {
 "location": {
 "value": "eastus"---Look at location this is EastUS what we selected
 },
 "networkInterfaceName": {
 "value": "azuretestvm974"
 },
 "networkSecurityGroupName": {
 "value": "AzureTestVM-nsg"--This is the NSG name
 },
 "networkSecurityGroupRules": {
 "value": [
 {
 "name": "RDP",--This is about the port which we allowed
 "properties": {
 "priority": 300,
 "protocol": "TCP",
 "access": "Allow",
 "direction": "Inbound",
 "sourceAddressPrefix": "*",
 "sourcePortRange": "*",
 "destinationAddressPrefix": "*",
 "destinationPortRange": "3389"
 }
 }
 ]
 },
 "subnetName": {
 "value": "default"
 },
 "virtualNetworkId": {
 "value": "/subscriptions/6400b22f-788d-48da-a164-b915c9678bd1/resourceGroups/test/providers/Microsoft.Network/virtualNetworks/Test-vnet"
 },
 "publicIpAddressName": {
 "value": "AzureTestVM-ip"---This is name of public IP address
 },
 "publicIpAddressType": {
 "value": "Static"
 },
 "publicIpAddressSku": {
 "value": "Standard"
 },
 "virtualMachineName": {
 "value": "AzureTestVM"---This is the VM name
 },
 "virtualMachineRG": {
 "value": "Test"
 },
 "osDiskType": {
 "value": "Standard_LRS"
 },
 "virtualMachineSize": {
 "value": "Standard_DS1_v2"
 },
 "adminUsername": {
 "value": "Shashank"
 },
 "adminPassword": {
 "value": null
 },
 "diagnosticsStorageAccountName": {
 "value": "storageacc621"   ---This is storage account name
 },
 "diagnosticsStorageAccountId": {
 "value": "/subscriptions/6400b22f-788d-48da-a164-b915c9678bd1/resourceGroups/Test/providers/Microsoft.Storage/storageAccounts/storageacc621"
 },
 "zone": {
 "value": "3"
 }
 }
}

Look at the above code carefully. All the variables we defined and options we selected are there in ARM template parameters file. 

Download the template and save it on local drive. Extract it and you would see below files

The two ARM templates files which are of interest to us are 

1. Parameters.json

2. Template.json

Editing ARM Template

We will briefly look into ARM template and the 2 JSON files. You can open it in Notepad but it is very difficult to read it. Either open it in Visual Studio or you can download TextPad. In this article we will use TextPad to open the JSON files. (You are free to use any software which can cleanly open the JSON files)

We will open the two files in Textpad and use the template to deploy a VM 

Lets analyse the Parameter.json file 

We can see various values marked in yellow in above image.

The password would be NULL, before deploying you need to replace null with valid strong password.

Lets also analyze template.json file. The template file takes variables/parameters from parameters.json file and does deployment. if you look closely it is calling functions to get values for various options selected via deployment.

{
 "contentVersion": "1.0.0.0",
 "parameters": {
 "location": {
 "type": "string"
 },
 "networkInterfaceName": {
 "type": "string"

Above is small code from template.json. The location excepts "string" type parameter and it takes parameter as "EastUS" which is there in parameters file.

"variables": {
 "nsgId": "[resourceId(resourceGroup().name, 'Microsoft.Network/networkSecurityGroups', parameters('networkSecurityGroupName'))]",
 "vnetId": "[parameters('virtualNetworkId')]",
 "subnetRef": "[concat(variables('vnetId'), '/subnets/', parameters('subnetName'))]"

Also look at above code, basically it is going to get nsgid value and it will get the value from 'NetworkSecurityGroupName' which is there in parameters.json file. Similarly other values work here. The JSON template looks big but very simple to understand.

We will not edit parameters.json template and create NEW VM, different from what we were creating. You can edit it in Textpad and save it like you save notepad.

New Parameters file looks like below. For more understanding I have added comment after == , you do not need to add this in JSON file

{
 "contentVersion": "1.0.0.0",
 "parameters": {
 "location": {
 "value": "Eastus"===Kept location same
 },
 "networkInterfaceName": {
 "value": "azuretestvmnew974"== Provide new name
 },
 "networkSecurityGroupName": {
 "value": "AzureTestVM-nsg-New"==provide new NSG name
 },
 "networkSecurityGroupRules": {
 "value": [
 {
 "name": "RDP",
 "properties": {
 "priority": 300,
 "protocol": "TCP",
 "access": "Allow",
 "direction": "Inbound",
 "sourceAddressPrefix": "*",
 "sourcePortRange": "*",
 "destinationAddressPrefix": "*",
 "destinationPortRange": "3389"
 }
 }
 ]
 },
 "subnetName": {
 "value": "default"
 },
 "virtualNetworkId": {
 "value": "/subscriptions/6400b22f-788d-48da-a164-b915c9678bd1/resourceGroups/test/providers/Microsoft.Network/virtualNetworks/Test-vnet"===You can replace test-vnet with vnet which resides in resource group AzureRG( later we changed RG)
 },
 "publicIpAddressName": {
 "value": "AzureTestVM-ip-New"  == New IP address name
 },
 "publicIpAddressType": {
 "value": "Static"
 },
 "publicIpAddressSku": {
 "value": "Standard"
 },
 "virtualMachineName": {
 "value": "AzureTestVMNew"  ===New VM name
 },
 "virtualMachineRG": {
 "value": "AzureRG"===Resource group changed
 },
 "osDiskType": {
 "value": "Standard_LRS"
 },
 "virtualMachineSize": {
 "value": "Standard_DS1_v2"
 },
 "adminUsername": {
 "value": "Shashank"
 },
 "adminPassword": {
 "value": "Password@1234567" ==Entered password
 },
 "diagnosticsStorageAccountName": {
 "value": "storageacc621"
 },
 "diagnosticsStorageAccountId": {
 "value": "/subscriptions/6400b22f-788d-48da-a164-b915c9678bd1/resourceGroups/Test/providers/Microsoft.Storage/storageAccounts/storageacc621"
 },
 "zone": {
 "value": "3"
 }
 }
}

Deploying Azure VM using ARM template

Now we will use PowerShell to deploy this new ARM template to cerate our VM.

Launch PowerShell as administrator and connect using below code

Connect-AzAccount

Make sure there is NO GAP or else you would get error as Connect is not recognized as internal or external command.

You should see below screen. Copy the link and paste it in URL and run it. Please enter code give and you would be connected.

After you are connected you get your subscription name and other details as shown above.

Let us use below code and deploy VM

New-AzResourceGroupDeployment  -ResourceGroupName AzureRG -TemplateFile 'E:\ARM Template\template\Template.json' -TemplateParameterFile 'E:\ARM Template\template\Parameters.json'

Like mentioned above if we do not make sure that Vnet and RG are in same location we might end up with below error.

looking closely at error message

New-AzResourceGroupDeployment : 10:51:32 AM - Resource Microsoft.Network/networkInterfaces 'azuretestvmnew975' failed with message '{
  "error": {
    "code": "InvalidResourceReference",
    "message": "Resource /subscriptions/6400b22f-788d-48da-a164-b915c9678bd1/resourceGroups/AzureRG/providers/Microsoft.Network/virtualNetworks/Test-v
net/subnets/default referenced by resource /subscriptions/6400b22f-788d-48da-a164-b915c9678bd1/resourceGroups/AzureRG/providers/Microsoft.Network/netw
orkInterfaces/azuretestvmnew975 was not found. Please make sure that the referenced resource exists, and that both resources are in the same region.",
    "details": []
  }
}'

You get above error for 2 reasons

  1. 1. The Vnet you were trying to create was in different RG and it is still picking up that region
  2. 2. If you look at code we missed to change the resource group and it is still using TEST ((resourceGroups/Test/providers)  it should be changed to AzureRG. 

As a workaround create Vnet in same RG where you are deploying VM

PS C:\Users\Shashank_2> New-AzResourceGroupDeployment  -ResourceGroupName AzureRG -TemplateFile 'E:\ARM Template\template\Template.json' -TemplatePara
meterFile 'E:\ARM Template\template\Parameters.json'
 
DeploymentName  : Template
ResourceGroupName  : AzureRG
ProvisioningState  : Succeeded
Timestamp  : 4/29/2019 5:30:09 AM
Mode  : Incremental
TemplateLink  :
Parameters  :
 Name  Type  Value
 ===============================  =========================  ==========
 location  String  Eastus
 networkInterfaceName  String  azuretestvmnew975
 networkSecurityGroupName  String  AzureTestVM-nsg-New
 networkSecurityGroupRules  Array  [
 {
 "name": "RDP",
 "properties": {
 "priority": 300,
 "protocol": "TCP",
 "access": "Allow",
 "direction": "Inbound",
 "sourceAddressPrefix": "*",
 "sourcePortRange": "*",
 "destinationAddressPrefix": "*",
 "destinationPortRange": "3389"
 }
 }
 ]
 subnetName  String  default
 virtualNetworkId  String  /subscriptions/6400b22f-788d-48da-a164-b915c9678bd1/resourceGro
 ups/AzureRG/providers/Microsoft.Network/virtualNetworks/Test-vnet
 publicIpAddressName  String  AzureTestVM-ip-New
 publicIpAddressType  String  Static
 publicIpAddressSku  String  Standard
 virtualMachineName  String  AzureTestVMNew
 virtualMachineRG  String  AzureRG
 osDiskType  String  Standard_LRS
 virtualMachineSize  String  Standard_DS1_v2
 adminUsername  String  Shashank
 adminPassword  SecureString
 diagnosticsStorageAccountName  String  storageacc621
 diagnosticsStorageAccountId  String  /subscriptions/6400b22f-788d-48da-a164-b915c9678bd1/resourceGro
 ups/AzureRG/providers/Microsoft.Storage/storageAccounts/storageacc621
 zone  String  3
 
Outputs  :
 Name  Type  Value
 ===============  =========================  ==========
 adminUsername  String  Shashank
 
DeploymentDebugLogLevel :

You can go ahead and change VM name and other parameters. Please note do not change Vnet and RG name. If you do so make sure you use Vnet and RG such that Vnet lies in the RG where you are deploying the VM.

Summary

In this article we saw how to create ARM template, edit it and use it to deploy Azure VM using PowerShell.

References

See Also