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