Testing Visual Studio on Azure

A few months ago I wrote about my Dev and Test adventure, where I used the Azure Portal to create my Virtual Machine. Since then, Microsoft has released significant additions to the Azure Resource Manager (ARM). This post is all about provisioning a Visual Studio Virtual Machine to your MSDN Azure Subscription using ARM.

Finding a Virtual Machine Image

Using PowerShell, switch to the Azure Resource Manager Mode.

Switch-AzureMode -Name AzureResourceManager

Then, using the Get-AzureVMImagePublisher, find out wheter the MicrosoftVisualStudio publisher is available in your Azure Region.

$location = 'eastus2'

Get-AzureVMImagePublisher -Location $location `
    | Where-Object -Property PublisherName -Like MicrosoftVisualStudio*'

# result

PublisherName : MicrosoftVisualStudio
Id            : /Subscriptions/64668628-d5e6-4c5b-9be4-9e577d5b8fd0/Providers/Microsoft.Compute/Locations/eastus2/Publishers/MicrosoftVisualStudio
Location      : eastus2
RequestId     : 4a42e94f-9d97-428f-8ebd-b97a20c17262
StatusCode    : OK

Using the Get-AzureVMImageOffer cmdlet, verify that the VisualStudio offer is available in your Azure Region.

$publisherName = 'MicrosoftVisualStudio'

Get-AzureVMImageOffer -Location $location `
                      -PublisherName $publisherName

# result

Offer         : TeamFoundationServer
PublisherName : MicrosoftVisualStudio
Id            : /Subscriptions/64668628-d5e6-4c5b-9be4-9e577d5b8fd0/Providers/Microsoft.Compute/Locations/eastus2/Publishers/MicrosoftVisualStudio/ArtifactTypes/VMImage/Offers/TeamFoundationServer
Location      : eastus2
RequestId     : 311cf331-50db-442b-a2a4-8bd30e514024
StatusCode    : OK

Offer         : VisualStudio
PublisherName : MicrosoftVisualStudio
Id            : /Subscriptions/64668628-d5e6-4c5b-9be4-9e577d5b8fd0/Providers/Microsoft.Compute/Locations/eastus2/Publishers/MicrosoftVisualStudio/ArtifactTypes/VMImage/Offers/VisualStudio
Location      : eastus2
RequestId     : 311cf331-50db-442b-a2a4-8bd30e514024
StatusCode    : OK

Offer         : Windows
PublisherName : MicrosoftVisualStudio
Id            : /Subscriptions/64668628-d5e6-4c5b-9be4-9e577d5b8fd0/Providers/Microsoft.Compute/Locations/eastus2/Publishers/MicrosoftVisualStudio/ArtifactTypes/VMImage/Offers/Windows
Location      : eastus2
RequestId     : 311cf331-50db-442b-a2a4-8bd30e514024
StatusCode    : OK

Now, we need to find a Visual Studio SKU.

$offer = 'VisualStudio'

Get-AzureVMImageSku -Location $location `
                    -PublisherName $publisherName `
                    -Offer $offer `
    | Select-Object -Property 'Skus'

# result

Skus
----
2013-Community-Update-4-ws2012-az25-ntvs10
2013-Community-Update-4-ws2012-az26
2013-Community-Update-4-ws2012-az26-cor31
2013-Premium-Update-4-win81
2013-Premium-Update-4-win81n-az26
2013-Premium-Update-4-ws2012-az26
2013-Professional-Update-4-ws2012-az26
2013-Ultimate-Update-4-win81
2013-Ultimate-Update-4-win81n-az26
2013-Ultimate-Update-4-ws2012-az26
2015-Community-RC
2015-Enterprise-RC
2015-Enterprise-Win10Tools
2015-Professional-RC
CoreCLR

With the SKU ‘2015-Enterprise-RC‘, Publisher Name, Location and Offer we can list the details of the Virtual Machine Image.

$sku = '2015-Enterprise-RC'

Get-AzureVMImage -Location $location `
                 -PublisherName $publisherName `
                 -Offer $offer `
                 -Skus $sku

# result

Version          : 14.0.22823
FilterExpression :
Skus             : 2015-Enterprise-RC
Offer            : VisualStudio
PublisherName    : MicrosoftVisualStudio
Id               : /Subscriptions/64668628-d5e6-4c5b-9be4-9e577d5b8fd0/Providers/Microsoft.Compute/Locations/eastus2/Publishers/MicrosoftVisualStudio/ArtifactTypes/VMImage/Offers/VisualStudio/Skus/2015-Enterprise-RC/Versions/14.0.22823
Location         : eastus2
RequestId        : 1a6ee45e-ce67-4b94-be56-94be768e3815
StatusCode       : OK

We are now ready to build our Azure Resource Manager Template.

Building the Azure Resource Manager Template

We can create a template by using the Windows Server Virtual Machine as a base, and the values from the previous section.

The sample template below, demonstrates how to change the imageReference values so that we can deploy a Visual Studio 2015 Enterprise RC Virtual Machine in our MSDN Subscription on Microsoft Azure.

WindowsVirtualMachine.json

{
    "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
    "contentVersion": "1.0.0.0",
    "parameters": {
        "newStorageAccountName": {
            "type": "string",
            "metadata": {
                "description": "Unique DNS Name for the Storage Account where the Virtual Machine's disks will be placed."
            }
        },
        "adminUsername": {
            "type": "string",
            "metadata": {
                "description": "Username for the Virtual Machine."
            }
        },
        "adminPassword": {
            "type": "securestring",
            "metadata": {
                "description": "Password for the Virtual Machine."
            }
        },
        "dnsNameForPublicIP": {
            "type": "string",
            "metadata": {
                "description": "Unique DNS Name for the Public IP used to access the Virtual Machine."
            }
        }
    },
    "variables": {
        "location": "East US 2",
        "imagePublisher": "MicrosoftVisualStudio",
        "imageOffer": "VisualStudio",
        "windowsOSVersion": "2015-Enterprise-RC",
        "OSDiskName": "osdisk",
        "nicName": "DevVMNic",
        "addressPrefix": "10.0.0.0/16",
        "subnetName": "Dev",
        "subnetPrefix": "10.0.0.0/24",
        "storageAccountType": "Standard_LRS",
        "publicIPAddressName": "DevPublicIP",
        "publicIPAddressType": "Dynamic",
        "vmStorageAccountContainerName": "vhds",
        "vmName": "BriseboisDev",
        "vmSize": "Standard_D1",
        "virtualNetworkName": "DevVNET",
        "vnetID": "[resourceId('Microsoft.Network/virtualNetworks',variables('virtualNetworkName'))]",
        "subnetRef": "[concat(variables('vnetID'),'/subnets/',variables('subnetName'))]"
    },
    "resources": [
        {
            "type": "Microsoft.Storage/storageAccounts",
            "name": "[parameters('newStorageAccountName')]",
            "apiVersion": "2015-05-01-preview",
            "location": "[variables('location')]",
            "tags": {
                "displayName": "StorageAccount"
            },
            "properties": {
                "accountType": "[variables('storageAccountType')]"
            }
        },
        {
            "apiVersion": "2015-05-01-preview",
            "type": "Microsoft.Network/publicIPAddresses",
            "name": "[variables('publicIPAddressName')]",
            "location": "[variables('location')]",
            "tags": {
                "displayName": "PublicIPAddress"
            },
            "properties": {
                "publicIPAllocationMethod": "[variables('publicIPAddressType')]",
                "dnsSettings": {
                    "domainNameLabel": "[parameters('dnsNameForPublicIP')]"
                }
            }
        },
        {
            "apiVersion": "2015-05-01-preview",
            "type": "Microsoft.Network/virtualNetworks",
            "name": "[variables('virtualNetworkName')]",
            "location": "[variables('location')]",
            "tags": {
                "displayName": "VirtualNetwork"
            },
            "properties": {
                "addressSpace": {
                    "addressPrefixes": [
                        "[variables('addressPrefix')]"
                    ]
                },
                "subnets": [
                    {
                        "name": "[variables('subnetName')]",
                        "properties": {
                            "addressPrefix": "[variables('subnetPrefix')]"
                        }
                    }
                ]
            }
        },
        {
            "apiVersion": "2015-05-01-preview",
            "type": "Microsoft.Network/networkInterfaces",
            "name": "[variables('nicName')]",
            "location": "[variables('location')]",
            "tags": {
                "displayName": "NetworkInterface"
            },
            "dependsOn": [
                "[concat('Microsoft.Network/publicIPAddresses/', variables('publicIPAddressName'))]",
                "[concat('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-05-01-preview",
            "type": "Microsoft.Compute/virtualMachines",
            "name": "[variables('vmName')]",
            "location": "[variables('location')]",
            "tags": {
                "displayName": "DevVirtualMachine"
            },
            "dependsOn": [
                "[concat('Microsoft.Storage/storageAccounts/', parameters('newStorageAccountName'))]",
                "[concat('Microsoft.Network/networkInterfaces/', variables('nicName'))]"
            ],
            "properties": {
                "hardwareProfile": {
                    "vmSize": "[variables('vmSize')]"
                },
                "osProfile": {
                    "computername": "[variables('vmName')]",
                    "adminUsername": "[parameters('adminUsername')]",
                    "adminPassword": "[parameters('adminPassword')]"
                },
                "storageProfile": {
                    "imageReference": {
                        "publisher": "[variables('imagePublisher')]",
                        "offer": "[variables('imageOffer')]",
                        "sku": "[variables('windowsOSVersion')]",
                        "version": "latest"
                    },
                    "osDisk": {
                        "name": "osdisk",
                        "vhd": {
                            "uri": "[concat('http://',parameters('newStorageAccountName'),'.blob.core.windows.net/',variables('vmStorageAccountContainerName'),'/',variables('OSDiskName'),'.vhd')]"
                        },
                        "caching": "ReadWrite",
                        "createOption": "FromImage"
                    }
                },
                "networkProfile": {
                    "networkInterfaces": [
                        {
                            "id": "[resourceId('Microsoft.Network/networkInterfaces',variables('nicName'))]"
                        }
                    ]
                }
            }
        }
    ]
}

Deploying the Azure Resource Manager Template

Prepare the Template Parameter File.

WindowsVirtualMachine.param.dev.json

{
    "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#",
    "contentVersion": "1.0.0.0",
    "parameters": {
        "newStorageAccountName": {
            "value": "devbrisebois"
        },
        "adminUsername": {
            "value": "devbrisebois"
        },
        "dnsNameForPublicIP": {
            "value": "devbriseboisms"
        }
    }
}

Then deploy the Azure Resource Manager Template and Parameters using PowerShell.

Select-AzureSubscription -SubscriptionName 'Visual Studio Ultimate with MSDN' -Current

New-AzureResourceGroup -Name 'devbriseboisms' `
                       -Location 'eastus2' `
                       -TemplateFile 'WindowsVirtualMachine.json' `
                       -TemplateParameterFile 'WindowsVirtualMachine.param.dev.json' `
                       -Force -Verbose

# result 

VERBOSE: 7:15:43 PM - Created resource group 'devbriseboisms' in location 'eastus2'
VERBOSE: 7:15:43 PM - Template is valid.
VERBOSE: 7:15:44 PM - Create template deployment 'WindowsVirtualMachine'.
VERBOSE: 7:15:49 PM - Resource Microsoft.Network/virtualNetworks 'DevVNET' provisioning status is running
VERBOSE: 7:15:54 PM - Resource Microsoft.Network/publicIPAddresses 'DevPublicIP' provisioning status is running
VERBOSE: 7:15:54 PM - Resource Microsoft.Storage/storageAccounts 'devbrisebois' provisioning status is running
VERBOSE: 7:16:03 PM - Resource Microsoft.Network/virtualNetworks 'DevVNET' provisioning status is succeeded
VERBOSE: 7:16:08 PM - Resource Microsoft.Network/publicIPAddresses 'DevPublicIP' provisioning status is succeeded
VERBOSE: 7:16:11 PM - Resource Microsoft.Network/networkInterfaces 'DevVMNic' provisioning status is succeeded
VERBOSE: 7:16:20 PM - Resource Microsoft.Storage/storageAccounts 'devbrisebois' provisioning status is succeeded
VERBOSE: 7:16:28 PM - Resource Microsoft.Compute/virtualMachines 'BriseboisDev' provisioning status is running
VERBOSE: 7:21:13 PM - Resource Microsoft.Compute/virtualMachines 'BriseboisDev' provisioning status is succeeded

Protect the Virtual Machine from Deletion

As mentioned in a previous post, it can be critical to protect important Virtual Machines from deletion. Personally, I consider my development environment to be critical, so I decided to provide the following example. It allows us to apply a lock on the Virtual Machine, and prevents us from accidentally deleting it.

New-AzureResourceLock -LockLevel CanNotDelete `
                      -LockNotes 'Dev Virtual Machine, do not delete.' `
                      -LockName 'briseboisdevlock' `
                      -ResourceName 'BriseboisDev' `
                      -ResourceType 'Microsoft.Compute/virtualMachines' `
                      -ResourceGroupName 'devbriseboisms' `
                      -Verbose

Remember, locking a Virtual Machine doesn’t protect you against corruption or data loss. For that type of protection, I recommend that we take a look at Azure Backup or at taking blob snapshots of the disks that matter the most. Think of these as restore points that you can leverage when things go wrong.

Connecting to the Virtual Machine

Once the Virtual Machine is provisioned and secured from accidents, it’s time to using Remote Desktop and to login. The following will ensure that the Virtual Machine is started. Then it will download the RDP file to the local disk and initiate the RDP session.

$resourceGroup = 'DevBriseboisms'
$vmName = 'BriseboisDev'

Start-AzureVM -ResourceGroupName $resourceGroup `
              -Name $vmName `
              -Verbose `
              -ErrorAction Stop

$rdpFile = 'C:\Users\albriseb\Downloads\briseboisDev.rdp'

Get-AzureRemoteDesktopFile -ResourceGroupName $resourceGroup `
                           -Name $vmName `
                           -LocalPath $rdpFile `
                           -Verbose

Invoke-Item $rdpFile

Stopping the Virtual Machine

Once we’re done working, it’s important to shutdown the Virtual Machine. Otherwise we end up paying for wasted cycles.

$resourceGroup = 'DevBriseboisms'
$vmName = 'BriseboisDev'

Stop-AzureVM -ResourceGroupName $resourceGroup `
             -Name $vmName `
             -Verbose `
             -ErrorAction Stop

Trackbacks and Pingbacks:

  1. Dew Drop – June 19, 2015 (#2038) | Morning Dew - June 19, 2015

    […] Creating a Visual Studio Enterprise 2015 RC Virtual Machine with ARM (Alexandre Brisebois) […]

    Like

Leave a comment

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