Deploying Azure Marketplace VMs

The first step is to gather information about the Market Place Virtual Machine (VM) image that we want to deploy. For this example I decided to deploy a Tableau Server image.

Login-AzureRmAccount

$location = 'eastus'
  
Get-AzureRmVMImagePublisher -Location $location `
    | Where-Object -Property PublisherName -Like Tableau*
 
$publisherName = 'tableau'
  
Get-AzureRmVMImageOffer -Location $location `
                        -PublisherName $publisherName
 
$offer = 'tableau-server'
  
Get-AzureRmVMImageSku -Location $location `
                      -PublisherName $publisherName `
                      -Offer $offer `
      | Select-Object -Property 'Skus'

Skus                  
----                  
bring-your-own-license

Now that we have the image information, it’s time to create an Azure Resource Manager (ARM) Template.

Azure Resource Manager (ARM) Template

Using Visual Studio we can build the following ARM Template. First we start by creating the Virtual Network. Then we add two Network Security Groups, the first is for RDP and the second is for HTTP traffic. Finally, we add a Storage Account, a Network Interface Card (NIC), a public IP (PIP) and a Virtual Machine (VM) to the Template.

{
  "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "msalexhackType": {
      "type": "string",
      "defaultValue": "Standard_LRS",
      "allowedValues": [
        "Standard_LRS",
        "Premium_LRS"
      ]
    },
    "msalexhackName": {
      "type": "string",
      "minLength": 1,
      "defaultValue": "msalexhack"
    },
    "msalexhackAdminUserName": {
      "type": "string",
      "minLength": 1
    },
    "msalexhackAdminPassword": {
      "type": "securestring"
    },
    "msalexhackPipDnsName": {
      "type": "string",
      "minLength": 1,
      "defaultValue": "msalexhackpip"
    }
  },
  "variables": {
    "alexmsNsgName": "alexmsfensg",
    "alexmsvnetPrefix": "10.0.0.0/16",
    "alexmsvnetSubnet1Name": "Subnet",
    "alexmsvnetSubnet1Prefix": "10.0.0.0/24",
    "msalexhackName": "[concat('msalexhack', uniqueString(resourceGroup().id))]",
    "msalexhackOSDiskName": "msalexhackOSDisk",
    "msalexhackVmSize": "Standard_D1",
    "msalexhackVnetID": "[resourceId('Microsoft.Network/virtualNetworks', 'alexmsvnet')]",
    "msalexhackSubnetRef": "[concat(variables('msalexhackVnetID'), '/subnets/', variables('alexmsvnetSubnet1Name'))]",
    "msalexhackStorageAccountContainerName": "vhds",
    "msalexhackNicName": "[concat(parameters('msalexhackName'), 'NetworkInterface')]",
    "msalexhackPipName": "msalexhackPip"
  },
  "resources": [
    {
      "apiVersion": "2015-05-01-preview",
      "type": "Microsoft.Network/networkSecurityGroups",
      "name": "[variables('alexmsNsgName')]",
      "location": "[resourceGroup().location]",
      "properties": {
        "securityRules": [
          {
            "name": "rdp_rule",
            "properties": {
              "description": "Allow RDP",
              "protocol": "Tcp",
              "sourcePortRange": "*",
              "destinationPortRange": "3389",
              "sourceAddressPrefix": "Internet",
              "destinationAddressPrefix": "*",
              "access": "Allow",
              "priority": 100,
              "direction": "Inbound"
            }
          },
          {
            "name": "web_rule",
            "properties": {
              "description": "Allow WEB",
              "protocol": "Tcp",
              "sourcePortRange": "*",
              "destinationPortRange": "80",
              "sourceAddressPrefix": "Internet",
              "destinationAddressPrefix": "*",
              "access": "Allow",
              "priority": 101,
              "direction": "Inbound"
            }
          }
        ]
      }
    },
    {
      "name": "alexmsvnet",
      "type": "Microsoft.Network/virtualNetworks",
      "location": "[resourceGroup().location]",
      "apiVersion": "2015-05-01-preview",
      "dependsOn": [
        "[concat('Microsoft.Network/networkSecurityGroups/', variables('alexmsNsgName'))]"
      ],
  
      "tags": {
        "displayName": "alexmsvnet"
      },
      "properties": {
        "addressSpace": {
          "addressPrefixes": [
            "[variables('alexmsvnetPrefix')]"
          ]
        },
        "subnets": [
          {
            "name": "[variables('alexmsvnetSubnet1Name')]",
            "properties": {
              "addressPrefix": "[variables('alexmsvnetSubnet1Prefix')]"
            }
          }
        ]
      }
    },
    {
      "name": "[variables('msalexhackName')]",
      "type": "Microsoft.Storage/storageAccounts",
      "location": "[resourceGroup().location]",
      "apiVersion": "2015-05-01-preview",
      "dependsOn": [ ],
      "tags": {
        "displayName": "msalexhack"
      },
      "properties": {
        "accountType": "[parameters('msalexhackType')]"
      }
    },
    {
      "name": "[variables('msalexhackNicName')]",
      "type": "Microsoft.Network/networkInterfaces",
      "location": "[resourceGroup().location]",
      "apiVersion": "2015-05-01-preview",
      "dependsOn": [
        "[concat('Microsoft.Network/virtualNetworks/', 'alexmsvnet')]",
        "[concat('Microsoft.Network/publicIPAddresses/', variables('msalexhackPipName'))]"
      ],
      "tags": {
        "displayName": "msalexhackNic"
      },
      "properties": {
        "ipConfigurations": [
          {
            "name": "ipconfig1",
            "properties": {
              "privateIPAllocationMethod": "Dynamic",
              "subnet": {
                "id": "[variables('msalexhackSubnetRef')]"
              },
              "publicIPAddress": {
                "id": "[resourceId('Microsoft.Network/publicIPAddresses', variables('msalexhackPipName'))]"
              }
            }
          }
        ]
      }
    },
    {
      "name": "[parameters('msalexhackName')]",
      "type": "Microsoft.Compute/virtualMachines",
      "location": "[resourceGroup().location]",
      "apiVersion": "2015-05-01-preview",
      "dependsOn": [
        "[concat('Microsoft.Storage/storageAccounts/', variables('msalexhackName'))]",
        "[concat('Microsoft.Network/networkInterfaces/', variables('msalexhackNicName'))]"
      ],
      "tags": {
        "displayName": "msalexhack"
      },
      "plan": {
                "name": "bring-your-own-license",
                "product": "tableau-server",
                "publisher": "tableau"
      },
      "properties": {
        "hardwareProfile": {
          "vmSize": "[variables('msalexhackVmSize')]"
        },
        "osProfile": {
          "computerName": "[parameters('msalexhackName')]",
          "adminUsername": "[parameters('msalexhackAdminUsername')]",
          "adminPassword": "[parameters('msalexhackAdminPassword')]"
        },        
        "storageProfile": {
          "imageReference": {
            "publisher": "tableau",
            "offer": "tableau-server",
            "sku": "bring-your-own-license",
            "version": "latest"
          },
          "osDisk": {
            "name": "msalexhackOSDisk",
            "vhd": {
              "uri": "[concat('http://', variables('msalexhackName'), '.blob.core.windows.net/', variables('msalexhackStorageAccountContainerName'), '/', variables('msalexhackOSDiskName'), '.vhd')]"
            },
            "caching": "ReadWrite",
            "createOption": "FromImage"
          }
        },
        "networkProfile": {
          "networkInterfaces": [
            {
              "id": "[resourceId('Microsoft.Network/networkInterfaces', variables('msalexhackNicName'))]"
            }
          ]
        }
      }
    },
    {
      "name": "[variables('msalexhackPipName')]",
      "type": "Microsoft.Network/publicIPAddresses",
      "location": "[resourceGroup().location]",
      "apiVersion": "2015-05-01-preview",
      "dependsOn": [ ],
      "tags": {
        "displayName": "msalexhackPip"
      },
      "properties": {
        "publicIPAllocationMethod": "Dynamic",
        "dnsSettings": {
          "domainNameLabel": "[parameters('msalexhackPipDnsName')]"
        }
      }
    }
  ],
  "outputs": {
  }
}

Plugging in the values

Under the storageProfile node, we use the imageReference to configure the Markertplace VM that we want to deploy.

"storageProfile": {
          "imageReference": {
            "publisher": "tableau",
            "offer": "tableau-server",
            "sku": "bring-your-own-license",
            "version": "latest"

          }
}

For Marketplace Virtual Machines, we also need to provide a plan property. This is typically placed next to the properties property of the Virtual Machine (VM) Resource.

"plan": {
           "name": "bring-your-own-license",
           "product": "tableau-server",
           "publisher": "tableau"
},

Failing to provide this property, will result in the following error message.

New-AzureRmResourceGroupDeployment : 4:03:06 PM - Resource Microsoft.Compute/virtualMachines 'msalexhack' failed with message '{
  "status": "Failed",
  "error": {
    "code": "ResourceDeploymentFailure",
    "message": "The resource operation completed with terminal provisioning state 'Failed'.",
    "details": [
      {
        "code": "VMMarketplaceInvalidInput",
        "message": "Creating a virtual machine from Marketplace image requires Plan information in the request. OS disk name is msalexhackOSDisk."
      }
    ]
  }
}'

If you haven’t had any luck finding the values for the plan property, try provisioning the VM through the azure portal. Then export the Resource Group as an ARM Template. This will give you a reference for your own template.

Deploying the ARM Template

Now it’s time to deploy our template to Azure.

# Login to your Azure Account
Login-AzureRmAccount
 
# Check that you are on the right Subscription.
Get-AzureSubscription -Current
 
# Switch to my MSDN Account
Select-AzureSubscription -SubscriptionName 'Visual Studio Ultimate with MSDN' -Current
 
# Create a Resource Group
New-AzureRmResourceGroup -Name 'mshack' -Location 'eastus'
 
# Deploy the Template to the Resource Group
New-AzureRmResourceGroupDeployment -ResourceGroupName 'mshack' `
                                   -TemplateFile 'DeploymentTemplate.json' `
                                   -TemplateParameterObject @{
                                                                msalexhackAdminUserName = '########'
                                                                msalexhackAdminPassword = '########'
                                                             }`
                                   -Verbose


  
# Launch a Remote Desktop Session 
Get-AzureRmRemoteDesktopFile -ResourceGroupName 'mshack' -Name 'msalexhack' -Launch

Share your thoughts in the comments below

One response to Deploying Azure Marketplace VMs via ARM Templates

  1. 

    awesome post. It worked like a cool boy :)

    Like

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.