Using the Azure ARM REST API – End to end Example Part 2

This blog post is a continuation of the third blog post in this Azure (ARM) REST API series. Please review the following blog posts first if you have not done yet:

  1. Using the Azure ARM REST API – Get Access Token
  2. Using the Azure ARM REST API – Get Subscription Information
  3. Using the Azure ARM REST API – End to end Example Part 1

In this blog post we will create a Virtual Machine using an ARM template and call an Azure Automation Runbook to stop the Virtual Machine.

Create a Virtual Machine using an ARM Template

The easiest way to find ARM templates to create a Virtual Machine is via the Azure Quickstart templates on Github. This repository contains all currently available Azure Resource Manager templates contributed by the community. A searchable template index is maintained at https://azure.microsoft.com/en-us/documentation/templates/.

If we search the repository we can find a very simple simple deployment of an Windows VM. This template allows you to deploy a simple Windows VM using a few different options for the Windows version, using the latest patched version. This will deploy a D1 size VM in the resource group location and return the fully qualified domain name of the VM.

First step is using the option to test the deployment before we are going to use the Azure ARM REST API to deploy the VM. When you click on the Deploy to Azure button and login to your Azure Subscription you are presented with the needed parameters for this ARM Template.

image

Configure the correct parameter values and check the successful deployment of the simple Windows VM and click on Purchase. Wait for the deployment to finish and check if this is the kind of VM you want to deploy in Azure.

You would see something like this if everything went ok.

image

If you want you can also connect to the VM to do a final check on the deployment. When you are happy with the result you can delete the Resource Group with VM in it.

We are going to download the Azuredeploy.json and azuredeploy.parameters.json from the Github repository as input for our REST API call body.

When we have the correct ARM Template for the Deployment the next step is use this ARM template according to the documentation on the Azure Reference website on how to deploy ARM Templates using the REST API.

According to the documentation you need the use the following information for the web request:

Method Request URI
PUT

https://management.azure.com/subscriptions/{subscription-id}/resourcegroups/{resource-group-name}/providers/microsoft.resources/deployments/{deployment-name}?api-version={api-version}

 

The request body will have the ARM Template JSON with the parameter values. You would normally enter the parameter values manually or store them in the azuredeploy.parameters.json file but now we are adding the parameter values within the request body of the web request.

image

Open both downloaded files in Visual Studio Code and combine both files into the following web request body:

  {
    "properties": {
        "template": {
            "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
            "contentVersion": "1.0.0.0",
            "parameters": {
                "adminUsername": {
                    "type": "string",
                    "metadata": {
                        "description": "Username for the Virtual Machine."
                    }
                },
                "adminPassword": {
                    "type": "securestring",
                    "metadata": {
                        "description": "Password for the Virtual Machine."
                    }
                },
                "dnsLabelPrefix": {
                    "type": "string",
                    "metadata": {
                        "description": "Unique DNS Name for the Public IP used to access the Virtual Machine."
                    }
                },
                "windowsOSVersion": {
                    "type": "string",
                    "defaultValue": "2016-Datacenter",
                    "allowedValues": [
                        "2008-R2-SP1",
                        "2012-Datacenter",
                        "2012-R2-Datacenter",
                        "2016-Nano-Server",
                        "2016-Datacenter-with-Containers",
                        "2016-Datacenter"
                    ],
                    "metadata": {
                        "description": "The Windows version for the VM. This will pick a fully patched image of this given Windows version."
                    }
                }
            },
            "variables": {
                "storageAccountName": "[concat(uniquestring(resourceGroup().id), 'sawinvm')]",
                "nicName": "myVMNic",
                "addressPrefix": "10.0.0.0/16",
                "subnetName": "Subnet",
                "subnetPrefix": "10.0.0.0/24",
                "publicIPAddressName": "myPublicIP",
                "vmName": "SimpleWindowsVM",
                "virtualNetworkName": "MyVNET",
                "subnetRef": "[resourceId('Microsoft.Network/virtualNetworks/subnets', variables('virtualNetworkName'), variables('subnetName'))]"
            },
            "resources": [
                {
                    "type": "Microsoft.Storage/storageAccounts",
                    "name": "[variables('storageAccountName')]",
                    "apiVersion": "2016-01-01",
                    "location": "[resourceGroup().location]",
                    "sku": {
                        "name": "Standard_LRS"
                    },
                    "kind": "Storage",
                    "properties": {}
                },
                {
                    "apiVersion": "2016-03-30",
                    "type": "Microsoft.Network/publicIPAddresses",
                    "name": "[variables('publicIPAddressName')]",
                    "location": "[resourceGroup().location]",
                    "properties": {
                        "publicIPAllocationMethod": "Dynamic",
                        "dnsSettings": {
                            "domainNameLabel": "[parameters('dnsLabelPrefix')]"
                        }
                    }
                },
                {
                    "apiVersion": "2016-03-30",
                    "type": "Microsoft.Network/virtualNetworks",
                    "name": "[variables('virtualNetworkName')]",
                    "location": "[resourceGroup().location]",
                    "properties": {
                        "addressSpace": {
                            "addressPrefixes": [
                                "[variables('addressPrefix')]"
                            ]
                        },
                        "subnets": [
                            {
                                "name": "[variables('subnetName')]",
                                "properties": {
                                    "addressPrefix": "[variables('subnetPrefix')]"
                                }
                            }
                        ]
                    }
                },
                {
                    "apiVersion": "2016-03-30",
                    "type": "Microsoft.Network/networkInterfaces",
                    "name": "[variables('nicName')]",
                    "location": "[resourceGroup().location]",
                    "dependsOn": [
                        "[resourceId('Microsoft.Network/publicIPAddresses/', variables('publicIPAddressName'))]",
                        "[resourceId('Microsoft.Network/virtualNetworks/', variables('virtualNetworkName'))]"
                    ],
                    "properties": {
                        "ipConfigurations": [
                            {
                                "name": "ipconfig1",
                                "properties": {
                                    "privateIPAllocationMethod": "Dynamic",
                                    "publicIPAddress": {
                                        "id": "[resourceId('Microsoft.Network/publicIPAddresses',variables('publicIPAddressName'))]"
                                    },
                                    "subnet": {
                                        "id": "[variables('subnetRef')]"
                                    }
                                }
                            }
                        ]
                    }
                },
                {
                    "apiVersion": "2015-06-15",
                    "type": "Microsoft.Compute/virtualMachines",
                    "name": "[variables('vmName')]",
                    "location": "[resourceGroup().location]",
                    "dependsOn": [
                        "[resourceId('Microsoft.Storage/storageAccounts/', variables('storageAccountName'))]",
                        "[resourceId('Microsoft.Network/networkInterfaces/', variables('nicName'))]"
                    ],
                    "properties": {
                        "hardwareProfile": {
                            "vmSize": "Standard_D1"
                        },
                        "osProfile": {
                            "computerName": "[variables('vmName')]",
                            "adminUsername": "[parameters('adminUsername')]",
                            "adminPassword": "[parameters('adminPassword')]"
                        },
                        "storageProfile": {
                            "imageReference": {
                                "publisher": "MicrosoftWindowsServer",
                                "offer": "WindowsServer",
                                "sku": "[parameters('windowsOSVersion')]",
                                "version": "latest"
                            },
                            "osDisk": {
                                "name": "osdisk",
                                "vhd": {
                                    "uri": "[concat(reference(resourceId('Microsoft.Storage/storageAccounts/', variables('storageAccountName'))).primaryEndpoints.blob, 'vhds/osdisk.vhd')]"
                                },
                                "caching": "ReadWrite",
                                "createOption": "FromImage"
                            },
                            "dataDisks": [
                                {
                                    "name": "datadisk1",
                                    "diskSizeGB": "100",
                                    "lun": 0,
                                    "vhd": {
                                        "uri": "[concat(reference(resourceId('Microsoft.Storage/storageAccounts/', variables('storageAccountName'))).primaryEndpoints.blob, 'vhds/datadisk1.vhd')]"
                                    },
                                    "createOption": "Empty"
                                }
                            ]
                        },
                        "networkProfile": {
                            "networkInterfaces": [
                                {
                                    "id": "[resourceId('Microsoft.Network/networkInterfaces',variables('nicName'))]"
                                }
                            ]
                        },
                        "diagnosticsProfile": {
                            "bootDiagnostics": {
                                "enabled": "true",
                                "storageUri": "[reference(resourceId('Microsoft.Storage/storageAccounts/', variables('storageAccountName'))).primaryEndpoints.blob]"
                            }
                        }
                    }
                }
            ],
            "outputs": {
                "hostname": {
                    "type": "string",
                    "value": "[reference(variables('publicIPAddressName')).dnsSettings.fqdn]"
                }
            }
        },
        "mode": "Incremental",
        "parameters": {
            "adminUsername": {
                "value": "localadmin"
            },
            "adminPassword": {
                "value": "VerySecretPassword!"
            },
            "dnsLabelPrefix": {
                "value": "stsarestapidemovm"
            }
        }
    }
}

 

Open HttpMaster or another tool you want to use for sending the web request and create the following request:

 URL: {SubscriptionId}/resourcegroups/{ResourceGroupName}/providers/microsoft.resources/deployments/ARMDeployment?api-version=2016-09-01e

* In HttpMaster we have stored the first part of the request (global url: https://management.azure.com/subscriptions) If you are using a different tool make sure you use the complete endpoint.

Remark:

Check previous blog post on how to use HttpMaster and store parameter values in your HttpMaster project.

image

When you execute the following requests from HttpMaster your simple VM should be deployed in Azure.

image

Pro tip:

With the professional edition of HttpMaster you can use chaining and execution groups to streamline the requests even better compared to the free version.
When using the free version of HttpMaster make sure you store the AccessToken in the Header request. See the Using the Azure ARM REST API – End to end Example Part 1 blog post for more information on how to store the Accesstoken.

If we now check in Azure, the simple VM should be deployed.

When the deployment is finished we can continue with calling the runbook to stop the VM we just created with the ARM Template.

Stop a VM using a Runbook in Azure Automation

If you have not created a Stop VM runbook in Azure Automation please do so before continuing. You can browse the Gallery for examples if you need some help.

I created the following PowerShell script Runbook which can be called from a webhook. More info on creating webhooks can be found here.

  # ---------------------------------------------------
# Script: C:\Scripts\StopAzureVM.ps1
# Version: 0.1
# Author: Stefan Stranger
# Date: 11/18/2016 16:06:47
# Description: Stop Azure VM PowerShell Script Runbook triggered by WebHook
# Comments:
# Changes:  
# Disclaimer: 
# This example is provided "AS IS" with no warranty expressed or implied. Run at your own risk. 
# **Always test in your lab first**  Do this at your own risk!! 
# The author will not be held responsible for any damage you incur when making these changes!
# ---------------------------------------------------

<# 
    The values ResourceGroup name and VM Name are sent in the body response from the webrequest.
    The body should look like this:
    @{ ResourcegroupName="ResourceGroupName";VMName="SimpleWindowsVM"}
#>
[CmdletBinding()]
param (
        [object]$WebhookData
    )

#Verify if Runbook is started from Webhook.

# If runbook was called from Webhook, WebhookData will not be null.
if ($WebhookData){

    # Collect properties of WebhookData
    $WebhookName     =     $WebhookData.WebhookName
    $WebhookHeaders =     $WebhookData.RequestHeader
    $WebhookBody     =     $WebhookData.RequestBody

    # Collect individual headers. VMList converted from JSON.
    $From = $WebhookHeaders.From
    $VMInfo = ConvertFrom-Json -InputObject $WebhookBody
    Write-Output -InputObject ('Runbook started from webhook {0} by {1}.' -f $WebhookName, $From)
    }
else
    {
        Write-Error -Message 'Runbook was not started from Webhook' -ErrorAction stop
    }



#region Variables
$connectionName = 'AzureRunAsConnection'
#endregion

#region Connect to Azure
try
{
       
    # Get the connection "AzureRunAsConnection "
    $servicePrincipalConnection=Get-AutomationConnection -Name $connectionName         

    'Logging in to Azure...'
    Add-AzureRmAccount `
        -ServicePrincipal `
        -TenantId $servicePrincipalConnection.TenantId `
        -ApplicationId $servicePrincipalConnection.ApplicationId `
        -CertificateThumbprint $servicePrincipalConnection.CertificateThumbprint 
}
catch 
{
    if (!$servicePrincipalConnection)
    {
        $ErrorMessage = ('Connection {0} not found.' -f $connectionName)
        throw $ErrorMessage
    } else{
        Write-Error -Message $_.Exception
        throw $_.Exception
    }
}
#endregion


#region shutdown vm
#Retrieve VM with Status information
$VM = Get-AzureRmVM -ResourceGroupName $($VMInfo.ResourceGroupName) -Name $($VMInfo.VMName) -Status 
if ($vm.statuses | where-object {$_.code -eq 'PowerState/deallocated' }) {
    Write-output -InputObject ('VM {0} is already in PowerState Deallocated' -f $VM.name)
}
else
{
    Write-output -InputObject ('Shutting down VM {0}' -f $VM.name)
    $VM | Stop-AzureRMVM -Force
}
#endregion

The final step is calling the runbook using the webhook you created earlier. We configure the following in HttpMaster

Url: url from the webhook you created for your runbook in the Azure Portal.

Body:

 { 
   "ResourcegroupName":"[ResourceGroupName]",
   "VMName":"[NameOfVMtoStop]"
}

 

image

In the header you can configure the following information:

 From:stefan@contoso.com";Date="11/19/2016 15:47:00"

 image

You should now be able to Stop the VM using the web request.

image

image

image

image

image

I hope you enjoyed these blog series about using the Azure REST API.

You can find the PowerShell scripts and HttpMaster files at the following Github Repository: https://github.com/stefanstranger/AzureARMRESTAPI

References: