Windows Azure PowerShell Updates for IaaS GA

With the release of Windows Azure Virtual Machines and Virtual Networks into general availability the Windows Azure PowerShell team has been working feverishly to provide an even more powerful automation experience for deploying virtual machines in the cloud.

Remote PowerShell on Windows Azure – Automating Virtual Machines

One of the key requests we have heard from customers is to go beyond the current capabilities of automated infrastructure provisioning and allow the user to bootstrap a virtual machine as part of a fully automated deployment.

With this release we are announcing that Remote PowerShell will be enabled by default on Windows based virtual machines created with the latest version of the Windows Azure PowerShell Cmdlets.

Enabling Remote PowerShell allows a user to create a virtual machine and on boot immediately launch a script to bootstrap whatever configuration is desired. This could be installing and configuring Windows Roles and Features all the way to downloading and deploying an application or website. Authentication is over SSL for security and you can use your own certificate or we can even generate one for you. In addition to the bootstrapping abilities Remote PowerShell allows you to write powerful scripts for remote management and automation that can be ran at any time after the virtual machine is booted. The same scripts you use to manage your on-premises servers will work with your servers in Windows Azure. Of course, we do provide a switch to disable this functionality on boot if Remote PowerShell is not desired.

Installing Windows Server Features Automatically

In the example below the new -WaitForBoot parameter is used with New-AzureVM. This switch tells the cmdlet to wait for the virtual machine to be in the RoleReady (booted) state before continuing execution. Once the virtual machine is ready the script calls the Get-AzureWinRMUri cmdlet to retrieve the connection string to execute a remote script against the virtual machine. The script block passed to Invoke-Command installs the Web-Server IIS and the related management tools.

A PowerShell scripter could easily extend this script to automatically deploy a custom web application or service with just a few additional lines of code.

Installing Windows Features using Remote PowerShell



# Using this script installs the generated cert into your local cert store which allows 
# PowerShell to verify it is communicating with the correct endpoint. 
# This REQUIRES PowerShell run Elevated
. "C:\Scripts\WAIaaSPS\RemotePS\InstallWinRMCert.ps1" 

$user = ""
$pwd = ""
$svcName = ""
$VMName = "webfe1" 
$location = "West US"

$credential = Get-Credential 

New-AzureVMConfig -Name $VMName -InstanceSize "Small" -ImageName $image |
                Add-AzureProvisioningConfig -Windows -AdminUsername $user -Password $pwd |
                Add-AzureEndpoint -Name "http" -Protocol tcp -LocalPort 80 -PublicPort 80 |
                New-AzureVM -ServiceName $svcName -Location $location -WaitForBoot 

# Get the RemotePS/WinRM Uri to connect to
$uri = Get-AzureWinRMUri -ServiceName $svcName -Name $VMName 

# Using generated certs – use helper function to download and install generated cert.
InstallWinRMCert $svcName $VMName 

# Use native PowerShell Cmdlet to execute a script block on the remote virtual machine
Invoke-Command -ConnectionUri $uri.ToString() -Credential $credential -ScriptBlock {
    $logLabel = $((get-date).ToString("yyyyMMddHHmmss"))
    $logPath = "$env:TEMP\init-webservervm_webserver_install_log_$logLabel.txt"
    Import-Module -Name ServerManager
    Install-WindowsFeature -Name Web-Server -IncludeManagementTools -LogPath $logPath
} 


Contents of InstallWinRMCert.ps1



function InstallWinRMCert($serviceName, $vmname)
{
    $winRMCert = (Get-AzureVM -ServiceName $serviceName -Name $vmname | select -ExpandProperty vm).DefaultWinRMCertificateThumbprint

    $AzureX509cert = Get-AzureCertificate -ServiceName $serviceName -Thumbprint $winRMCert -ThumbprintAlgorithm sha1

    $certTempFile = [IO.Path]::GetTempFileName()
    Write-Host $certTempFile
    $AzureX509cert.Data | Out-File $certTempFile

    # Target The Cert That Needs To Be Imported
    $CertToImport = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2 $certTempFile

    $store = New-Object System.Security.Cryptography.X509Certificates.X509Store "Root", "LocalMachine"
    $store.Certificates.Count
    $store.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::ReadWrite)
    $store.Add($CertToImport)
    $store.Close()

    Remove-Item $certTempFile
}


Image and Disk Mobility

Windows Azure is an open computing platform and allows for the movement of your virtual machine disks between on-premises and the cloud. There are two optimized cmdlets that enable either you to upload your VHD or download it.

Uploading a VHD

The first example shows how to upload a VHD to Windows Azure. This can be a bootable OS disk or simply a data disk (remove -OS Windows for data disks). After Add-AzureDisk is called you could use the New-AzureVMConfig cmdlet or the management portal to provision a virtual machine that boots off of the uploaded VHD.



$source = "C:\vmstorage\myosdisk.vhd"
$destination = "https://<yourstorage>.blob.core.windows.net/vhds/myosdisk.vhd"

Add-AzureVhd -LocalFilePath $source -Destination $destination -NumberOfUploaderThreads 5
Add-AzureDisk -DiskName 'myosdisk' -MediaLocation $destination -Label 'mydatadisk' -OS Windows 


Downloading a VHD

Not only can you upload a disk to Windows Azure but it is also easy to download a VHD as well! The example below shows how you can save a VHD to the local file system ready to run on a Hyper-V enabled system. (Note: a virtual machine should not write to the VHD at the same time you are trying to download it).



$source = "https://<yourstorage>.blob.core.windows.net/vhds/myosdisk.vhd"
$destination = "C:\vmstorage\myosdisk.vhd"
Save-AzureVhd -Source $source -LocalFilePath $destination -NumberOfThreads 5 


VMDK Conversion and Migration to Windows Azure

If you have VMWare based virtual machines that you would like to migrate you can use the Microsoft Virtual Machine Converter Solution Accelerator to convert the disks to VHDs and then use the Add-AzureVHD cmdlet to upload the VHD and create a virtual machine in Windows Azure from it.

Copying a VHD across Windows Azure Regions



# Source VHD (West US)
$srcUri = "http://<yourweststorage>.blob.core.windows.net/vhds/myosdisk.vhd"      

# Target Storage Account (East US)
$storageAccount = "<youreaststorage>"
$storageKey = "<youreaststoragekey>"

$destContext = New-AzureStorageContext  –StorageAccountName $storageAccount `
                                        -StorageAccountKey $storageKey  

# Container Name
$containerName = "vhds"

New-AzureStorageContainer -Name $containerName -Context $destContext

$blob = Start-AzureStorageBlobCopy -srcUri $srcUri `
                                   -DestContainer $containerName `
                                   -DestBlob "testcopy1.vhd" `
                                   -DestContext $destContext   
                                    
$blob | Get-AzureStorageBlobCopyState 


Enhanced Security -AdminUserName is required for Windows (Breaking Change)

In order to protect you from unwanted attacks from connections attempting to use the dictionary on your password, we have made it mandatory to supply a username.
This change affects the New-AzureQuickVM and the Add-AzureProvisioningConfig cmdlets used for VM creation. Each will have a new –AdminUserName parameter that is now required.
Make sure you can remember it but do not use obvious names like Administrator or Admin.

High Memory Virtual Machine Support

The latest version of the WA PowerShell Cmdlets now support the new higher memory SKU sizes of A6 and A7 for larger workloads. For more information about Windows Azure compute sizes see the following: http://www.windowsazure.com/en-us/pricing/details/virtual-machines/.

high-mem-skus

Managing Availability Sets on Deployed VMs

We have also added the ability to specify availability set configuration for groups of virtual machines for highly available configurations. Previously, this could only be set at deployment time or post deployment from the Windows Azure Management Portal. For more information on availability sets see the following article:
http://www.windowsazure.com/en-us/manage/windows/common-tasks/manage-vm-availability/



Get-AzureVM -ServiceName "mywebsite" | Where {$_.Name -like "*web*"} | 
    Set-AzureAvailabilitySet -AvailabilitySetName "wfe-av-set" |
    Update-AzureVM


Wrapping Up

I hope you are excited about the new features in the Windows Azure PowerShell Cmdlets. If you would like to try this yourself you will need a subscription, to download the WA PowerShell Cmdlets and a short read on getting started.

Thanks!
Michael Washam
Senior Program Manager – Windows Azure

How to provision a Linux VM in a VNET with PowerShell

Getting Started Links


# From Get-AzureVMImage 
$img = 'b39f27a8b8c64d52b05eac6a62ebad85__Ubuntu-12_04_1-LTS-amd64-server-20121218-en-us-30GB'
$user = 'somelinuxuser'
$pass = 'somelinuxpwd!'
$vnet = 'HybridVNET'
$ag = 'WestUSAG'
$svcname = 'mylinuxsvc1' 

New-AzureVMConfig -Name 'linuxfromps1' -ImageName $img -InstanceSize Small |
	Add-AzureProvisioningConfig -Linux -LinuxUser $user -Password $pass |
	Set-AzureSubnet -SubnetNames 'AppSubnet' | # Optional 
	New-AzureVM -ServiceName $svcname -VNetName $vnet -AffinityGroup $ag
	

Migrate a Virtual Machine to Windows Azure with PowerShell

In my previous post I show how you can use the Add-AzureVHD cmdlet to upload a VHD. I wanted to take it a bit further and show how you can use this new cmdlet in conjunction with the other PowerShell cmdlets to migrate and provision an entire virtual machine.

The script below uploads two VHDs; one for the Operating System Disk and one for an additional data disk. Once the VHDs are uploaded it then creates the disk entities using the Add-AzureDisk cmdlet and then proceeds to construct the virtual machine using the newly uploaded VHDs.

# Retrieve with Get-AzureSubscription 
$subscriptionName = '[MY SUBSCRIPTION]'  

# Retreive with Get-AzureStorageAccount 
$storageAccountName = '[MY STORAGE ACCOUNT]'   

# Specify the storage account location to store the newly created VHDs 
Set-AzureSubscription -SubscriptionName $subscriptionName -CurrentStorageAccount $storageAccountName 
 
# Select the correct subscription (allows multiple subscription support) 
Select-AzureSubscription -SubscriptionName $subscriptionName 

# Retreive with Get-AzureLocation 
$location = 'West US' 

# ExtraSmall, Small, Medium, Large, ExtraLarge
$instanceSize = 'Medium' 

# Has to be a unique name. Verify with Test-AzureService
$serviceName = '[UNIQUE SERVICE NAME]' 

# Server Name
$vmname1 = '[MY VM NAME]'

# Source VHDs
$sourceosvhd = 'C:\MyVHDs\AppServer1OSDisk.vhd'
$sourcedatavhd = 'C:\MyVHDs\AppServer1DataDisk.vhd'

# Target Upload Location 
$destosvhd = 'http://' + $storageAccountName + '.blob.core.windows.net/uploads/AppServer1OSDisk.vhd'
$destdatavhd = 'http://' + $storageAccountName + '.blob.core.windows.net/uploads/AppServer1DataDisk.vhd'

Add-AzureVhd -LocalFilePath $sourceosvhd -Destination $destosvhd 
Add-AzureVhd -LocalFilePath $sourcedatavhd -Destination $destdatavhd

Add-AzureDisk -OS Windows -MediaLocation $destosvhd -DiskName 'AppServer1OSDisk'
Add-AzureDisk -MediaLocation $destdatavhd -DiskName 'AppServer1DataDisk'

$migratedVM = New-AzureVMConfig -Name $vmname1 -DiskName 'AppServer1OSDisk' -InstanceSize 'Medium' |
					Add-AzureDataDisk -Import -DiskName 'AppServer1DataDisk' -LUN 0 |
					Add-AzureEndpoint -Name 'Remote Desktop' -LocalPort 3389 -Protocol tcp 
					
New-AzureVM -ServiceName $serviceName -Location $location -VMs $migratedVM 					

New Windows Azure PowerShell Update – December 2012

The Windows Azure PowerShell team has just put out an update. Currently, downloadable from: GitHub.

The first one I want to call out because it is close to my IaaS focused heart is Add-AzureVhd.

If you have had the pleasure of uploading VHDs for IaaS before using CSUpload you know the old tool was pretty cumbersome.

Now uploading VHDs for onboarding virtual machines is simple(r).

In it’s simplest form you simply specify the local path to the VHD and the destination storage account URL:

Select-AzureSubscription 'mysubscription' 
Add-AzureVhd -LocalFilePath 'D:\VMStorage\SP2013VM1.vhd' -Destination 'http://mystorageaccount.blob.core.windows.net/uploads/SP2013VM1.vhd'

Once the upload has completed you can add the VHD to the disk repository by using the following command:

Add-AzureDisk -DiskName 'SP2013VM1OS' -MediaLocation 'http://mystorageaccount.blob.core.windows.net/uploads/SP2013VM1.vhd' -OS Windows

If you wanted to upload only a data disk just omit -OS Windows.
This cmdlet also supports uploading differencing disks to patch VHDs in storage as well. You can specify -BaseImageUriToPatch as the target VHD to apply the differencing disk too.

Once the disk is loaded to boot the virtual machine from the disk simply specify the disk name when configuring the VM.

uploaddisk

If you prefer to provision from PowerShell:

New-AzureVMConfig -DiskName 'SP2013VM1OS' -InstanceSize Medium -Name 'SP2013VM1' | 
	Set-AzureSubnet -SubnetNames 'AppSubnet' | 
	New-AzureVM -ServiceName 'sp2013svc1' -VNETName 'HybridVNET' -AffinityGroup 'WestUSAG'

One potential regression I do want to call out in the IaaS space is a change to Get-AzureVMImage

The below code formerly worked and now no longer returns a value..

# Previous functionality 
(Get-AzureVMImage)[1].ImageName # Returns a value
Get-AzureVMImage | Select ImageName # Returns value

If your scripts did something similar you will need to use

Get-AzureVMIMage | ft imagename 

Store the specific image in a variable for later use.

Another key set of additions to the Windows Azure PowerShell Cmdlets:

ServiceBus
We finally have the ability to directly manage ServiceBus Namespaces from the command line. From a dev-ops perspective this one is HUGE.

  • New-AzureSBNamespace – Create a new Windows Azure ServiceBus namespace
  • Get-AzureSBLocation – Get the Windows Azure regions that may be used to create new Windows Azure
  • Get-AzureSBNamespace – Get information about existing Windows Azure ServiceBus namespaces
  • Remove-AzureSBNamespace – Delete a WindowsAzure ServiceBus namespace and all associated objects

Complete list of the cmdlets in the December release:
(Note the Windows Azure SQL Database cmdlets made it back into the official release in November)

Add-AzureCacheWorkerRole
Add-AzureCertificate
Add-AzureDataDisk
Add-AzureDisk
Add-AzureDjangoWebRole
Add-AzureEndpoint
Add-AzureNodeWebRole
Add-AzureNodeWorkerRole
Add-AzurePHPWebRole
Add-AzurePHPWorkerRole
Add-AzureProvisioningConfig
Add-AzureVhd
Add-AzureVMImage
Disable-AzureServiceProjectRemoteDesktop
Enable-AzureMemcacheRole
Enable-AzureServiceProjectRemoteDesktop
Export-AzureVM
Get-AzureAffinityGroup
Get-AzureCertificate
Get-AzureDataDisk
Get-AzureDeployment
Get-AzureDisk
Get-AzureDns
Get-AzureEndpoint
Get-AzureLocation
Get-AzureOSDisk
Get-AzureOSVersion
Get-AzurePublishSettingsFile
Get-AzureRemoteDesktopFile
Get-AzureRole
Get-AzureSBLocation
Get-AzureSBNamespace
Get-AzureService
Get-AzureServiceProjectRoleRuntime
Get-AzureSqlDatabase
Get-AzureSqlDatabaseServer
Get-AzureSqlDatabaseServerFirewallRule
Get-AzureStorageAccount
Get-AzureStorageKey
Get-AzureSubnet
Get-AzureSubscription
Get-AzureVM
Get-AzureVMImage
Get-AzureVNetConfig
Get-AzureVNetConnection
Get-AzureVNetGateway
Get-AzureVNetGatewayKey
Get-AzureVNetSite
Get-AzureWebsite
Get-AzureWebsiteDeployment
Get-AzureWebsiteLocation
Import-AzurePublishSettingsFile
Import-AzureVM
Move-AzureDeployment
New-AzureAffinityGroup
New-AzureCertificateSetting
New-AzureDeployment
New-AzureDns
New-AzureQuickVM
New-AzureSBNamespace
New-AzureService
New-AzureServiceProject
New-AzureSqlDatabase
New-AzureSqlDatabaseServer
New-AzureSqlDatabaseServerContext
New-AzureSqlDatabaseServerFirewallRule
New-AzureSSHKey
New-AzureStorageAccount
New-AzureStorageKey
New-AzureVM
New-AzureVMConfig
New-AzureVNetGateway
New-AzureWebsite
Publish-AzureServiceProject
Remove-AzureAffinityGroup
Remove-AzureCertificate
Remove-AzureDataDisk
Remove-AzureDeployment
Remove-AzureDisk
Remove-AzureEndpoint
Remove-AzureSBNamespace
Remove-AzureService
Remove-AzureSqlDatabase
Remove-AzureSqlDatabaseServer
Remove-AzureSqlDatabaseServerFirewallRule
Remove-AzureStorageAccount
Remove-AzureSubscription
Remove-AzureVM
Remove-AzureVMImage
Remove-AzureVNetConfig
Remove-AzureVNetGateway
Remove-AzureWebsite
Reset-AzureRoleInstance
Restart-AzureVM
Restart-AzureWebsite
Restore-AzureWebsiteDeployment
Save-AzureVMImage
Save-AzureWebsiteLog
Select-AzureSubscription
Set-AzureAffinityGroup
Set-AzureDataDisk
Set-AzureDeployment
Set-AzureEndpoint
Set-AzureOSDisk
Set-AzureRole
Set-AzureService
Set-AzureServiceProject
Set-AzureServiceProjectRole
Set-AzureSqlDatabase
Set-AzureSqlDatabaseServer
Set-AzureSqlDatabaseServerFirewallRule
Set-AzureStorageAccount
Set-AzureSubnet
Set-AzureSubscription
Set-AzureVMSize
Set-AzureVNetConfig
Set-AzureVNetGateway
Set-AzureWalkUpgradeDomain
Set-AzureWebsite
Show-AzurePortal
Show-AzureWebsite
Start-AzureEmulator
Start-AzureService
Start-AzureVM
Start-AzureWebsite
Stop-AzureEmulator
Stop-AzureService
Stop-AzureVM
Stop-AzureWebsite
Test-AzureName
Update-AzureDisk
Update-AzureVM
Update-AzureVMImage

To download the latest bits:
Latest PowerShell Bits

To file an issue:
File a bug

Deploying certificates with Windows Azure Virtual Machines and PowerShell

A common question around using the Windows Azure PowerShell cmdlets is how to deploy certificates with VMs? In this post I’ve put together two samples on how to do this on Windows and Linux VMs.

Windows VM Example

Select-AzureSubscription mysub 

$service = 'yourservicename1'
$location = 'West US'

## Cloud Service must already exist 
New-AzureService -ServiceName $service -Location $location

## Add Certificate to the store on the cloud service (.cer or .pfx with -Password)
Add-AzureCertificate -CertToDeploy 'D:\User-Data\development\Azure Samples\mlwdevcert.cer' -ServiceName $service

## Create a certificate setting for deploying the VM 'My' is the only supported store (goes into computer account)
$cert1 = New-AzureCertificateSetting -Thumbprint D7BECD4D63EBAF86023BB4F1A5FBF5C2C924902A -StoreName 'My'

## Create the VM passing the certificate setting in the provisioning config 
New-AzureVMConfig -ImageName 'MSFT__Windows-Server-2012-Datacenter-201208.01-en.us-30GB.vhd' -InstanceSize 'Small' -Name 'win2012cert' |
      Add-AzureProvisioningConfig -Windows -Password 'somepass@1' -Certificates $cert1 |
      New-AzureVM -ServiceName $service 

Linux VM Example

Select-AzureSubscription mysub 

$service = 'yourservicename1'
$location = 'West US'

## Cloud Service must already exist 
New-AzureService -ServiceName $service -Location $location

## Add Certificate to the store on the cloud service (.cer or .pfx with -Password)
Add-AzureCertificate -CertToDeploy 'D:\User-Data\development\Azure Samples\mlwdevcert.cer' -ServiceName $service

## Create a certificate in the users home directory
$sshkey = New-AzureSSHKey -PublicKey -Fingerprint D7BECD4D63EBAF86023BB4F1A5FBF5C2C924902A -Path '/home/mwasham/.ssh/authorized_keys'

New-AzureVMConfig -ImageName 'CANONICAL__Canonical-Ubuntu-12-04-amd64-server-20120528.1.3-en-us-30GB.vhd' -InstanceSize 'Small' -Name 'linuxwithcert' |
	Add-AzureProvisioningConfig -Linux -LinuxUser 'mwasham' -Password 'somepass@1' -SSHPublicKeys $sshKey |
	New-AzureVM -ServiceName $service 

Note: The -Certificates and -SSHPublicKeys parameters are arrays so they can accept multiple certificates.
-SSHPublicKeys $sshKey1,$sshKey2

For Linux there is also the -SSHKeyPairs parameter for passing a key pair instead of just the public key. -Certificates can handle both types on Windows.

Publishing and Synchronizing Web Farms using Windows Azure Virtual Machines

Deploying new web applications is pretty painless with Windows Azure Web Sites and “fairly” painless using Windows Azure PaaS style cloud services. However, for existing web apps that are being migrated to the cloud both solutions can require significant rewriting/re-architecture. That is where Windows Azure Infrastructure as a Service comes in. Running Virtual Machines allows you to have the economies of scale of using a cloud based solution and have full access to cloud services such as storage, service bus etc.. while not requiring you to re-architect your application to take advantage of these services.

Usually when you think of cloud computing with Infrastructure as a Service you think of a lot of manual work and management pain. While it is certainly a bit more work than a pure PaaS operation it is possible to lower that management burden using automation tools and techniques.

In this post I will walk through how to use Windows Azure Virtual Machines to create a web farm that you can directly publish to using Visual Studio Web Deploy. In addition to simple publishing I will also show how you can automatically synchronize web content across multiple virtual machines in your service to make web farm content synchronization simple and painless.

Step #1 – Image Preparation

Create a new virtual machine using either Windows Server 2008 R2 or Server 2012. On this machine install the Application Server and Web Server roles and enable ASP.NET).

TIP: Don’t forget to install the .NET Framework 4.0 if you are using Server 2008 R2.

For this solution you will also need the Windows Azure PowerShell Cmdlets on the web server. See this article for configuring your publish settings with the PS cmdlets.
I will use the cmdlets to discover the VM names in my web farm without having to manually keep track of them. This helps if you need the ability to grow and shrink your web farm at will without updating your synchronization scripts.

The tool I will use for content sync is Web Deploy 3.0. Download but do not install Web Deploy 3.0.

Web Deploy works by a starting a remote agent that listens for commands from either Visual Studio or the MSDeploy.exe client. By default it will listen on port 80. This default port configuration will not work in a load balanced environment.

To install on an alternate external port such as 8080:
C:\WebDeployInstall>msiexec /I webdeploy_amd64_en-us.msi /passive ADDLOCAL=ALL LISTENURL=http://+:8080/

Once installed you will need to configure a firewall rule to allow traffic in on port 8080 for publishing and synchronization.

Now that the image is configured you will sysprep the vm to remove any unique characteristics like machine names etc. Ensure you have Enter System-Out-Of-Box Experience, Generalize and Shutdown all selected.

Once the VM status is shown as shut down in the Windows Azure Management portal highlight the VM and click capture. This will be the customized image you can use to quickly provision new VMs for your web farm using the management portal or powershell.

Ensure you check I have sysprepped this VM and name the image WebAppImg and click the check mark button to capture the image.

Step #2 – Virtual Machine Deployment

Once the image has been created you can use the portal or the Windows Azure PowerShell cmdlets to provision the web farm.

Here is a PowerShell example of using the new image as the basis for a three VM web farm.

A few things to note: I have created a load balanced endpoint for port 80 but for 8080 I’m only selecting a single server.
This server will be the target server for publishing from Visual Studio that will then be used as the source server for publishing to the other nodes in the web farm.

$imgname = 'WebAppImg'
$cloudsvc = 'MyWebFarm123'
$pass = 'your password'

$iisvm1 = New-AzureVMConfig -Name 'iis1' -InstanceSize Small -ImageName $imgname |
	Add-AzureEndpoint -Name web -LocalPort 80 -PublicPort 80 -Protocol tcp -LBSetName web -ProbePath '/' -ProbeProtocol http -ProbePort 80 |
	Add-AzureEndpoint -Name webdeploy -LocalPort 8080 -PublicPort 8080 -Protocol tcp | 
	Add-AzureProvisioningConfig -Windows -Password $pass
	
$iisvm2 = New-AzureVMConfig -Name 'iis2' -InstanceSize Small -ImageName $imgname |
	Add-AzureEndpoint -Name web -LocalPort 80 -PublicPort 80 -Protocol tcp -LBSetName web -ProbePath '/' -ProbeProtocol http -ProbePort 80 |
	Add-AzureProvisioningConfig -Windows -Password $pass
	
$iisvm3 = New-AzureVMConfig -Name 'iis3' -InstanceSize Small -ImageName $imgname |
	Add-AzureEndpoint -Name web -LocalPort 80 -PublicPort 80 -Protocol tcp -LBSetName web -ProbePath '/' -ProbeProtocol http -ProbePort 80 |
	Add-AzureProvisioningConfig -Windows -Password $pass	
	
New-AzureVM -ServiceName $cloudsvc -VMs $iisvm1,$iisvm2,$iisvm3 -Location 'West US'

Once the VMs are provisioned RDP into iis1 by clicking connect in the management portal. This is where you will configure a PowerShell script that will run MSDeploy to synchronize content across the other servers.

Inside of the iis1 virtual machine create a new text file named sync.ps1 in a directory off of your root such as C:\SynchScript and paste the following in (ensuring that you update $serviceName with your cloud service name).

Import-Module 'C:\Program Files (x86)\Microsoft SDKs\Windows Azure\PowerShell\Azure\Azure.psd1'

$publishingServer = (gc env:computername).toLower()

$serviceName = 'REPLACE WITH YOUR CLOUD SERVICE' 

Get-AzureVM -ServiceName $serviceName | foreach { 
    if ($_.Name.toLower() -ne $publishingServer) {
       $target = $_.Name + ":8080"
       $source = $publishingServer + ":8080"

       $exe = "C:\Program Files\IIS\Microsoft Web Deploy V3\msdeploy.exe"
       [Array]$params = "-verb:sync", "-source:contentPath=C:\Inetpub\wwwroot,computerName=$source", "-dest:contentPath=C:\Inetpub\wwwroot,computerName=$target";

        & $exe $params;
    }   
}

This script enumerates all of the virtual machines in your cloud service and attempts to run a web deploy sync job on them. If you have other servers in your cloud service like database etc.. you could exclude them by filtering on the VM name. Note: Web Deploy supports MANY more operations other than just synchronizing directories. Click here to find more information.

To enable content synchronization you will need to create a new scheduled task by going into Control Panel -> Administrative Tools -> Scheduled Tasks -> Create a new Task.

Accept the defaults for everything except when it gets to the action screen.

Program/Script: powershell.exe
Parameters: -File C:\WebDeployInstall\sync.ps1

Open the properties of the new task and you’ll need to modify the schedule to synchronize content fairly often so content isn’t out of sync during a publish.

Ensure you select Run Whether User Is Logged on or Not. You will need to provide an account for the task to run as. I’m choosing the administrator account because I am lazy. However, you could create new duplicate accounts on each of the VMs to use for synchronization.

Step #3 – Publishing with Visual Studio

Finally, to test the configuration create a new MVC app and tweak the code slightly to show the computer name.

Now right click on the project and select publish. In the drop down select new profile.

In the settings page add your cloud app url and append :8080 to it for the service URL.
Set the site/app name to Default Web Site
Set the Destination URL to your cloud app url (without :8080)

Finish the wizard and let Visual Studio publish.

When the web app first launches you may or may not see the new content. It may show the default IIS8 content. As soon as the scheduled task runs the content should sync across all of the servers.

Once it has synchronized press CTRL F5 a few times and you should see the content with the individual machine names to verify the load balancing is working.


In this post you have seen how you can configure a custom OS image that can be used to provision virtual machines for a web farm. You have then seen how you can use Web Deploy along with PowerShell to synchronize content published from Visual Studio across all of the servers in your farm.

Automation is great :)

HTTP Error Message: The location or affinity group East US specified for source image

If you run into the following error trying to create VMs with PowerShell:

New-AzureVM : HTTP Status Code: BadRequest – HTTP Error Message: The location or affinity group East US specified for source image MSFT__Windows-Server-2012-RC-June2012-v1-en-us-30GB.vhd is invalid. The source image must reside in same affinity group or location as specified for hosted service West US.

The problem is your storage account is in a different location/affinity group then the VNET you are deploying to. There may be other causes but I hit this one today and thought it was a strange enough message to blog it so I would remember :)

Connecting Windows Azure Virtual Machines with PowerShell

In my post on automating virtual machines I showed the basics for getting around and managing aspects of Windows Azure VMs. In this post I want to cover a few of the more complex scenarios such as connectivity between VMs, deploying into a virtual network and finally deploying a virtual machine automatically domain joined into an Active Directory Domain.

Connecting Virtual Machines

So far we have seen details on provisioning a single virtual machine. What happens if your application requires more than one VM? How to connect them? There are two options for connecting virtual machines. The first is to add multiple virtual machines to the same cloud service and the second is to provision each VM into the same virtual network. When machines are added to the same cloud service and are not within a virtual network they receive the benefits of being on the same network and receive built in name resolution with each other through Windows Azure provided DNS.


So how do you add two virtual machines to the same cloud service? When you create the first virtual machine using New-AzureVM or New-AzureQuickVM you are required to specify the -Location or -AffinityGroup parameter. When you specify either parameter it tells the cmdlets that you wish to create the cloud service at that time because the data center location can only be set on initial creation. To tell the cmdlets to create the VM in an existing cloud service you just omit the -Location/-AffinityGroup parameter.

Create a VM and a New Cloud Service (specify -Location/-AffinityGroup)

New-AzureVMConfig -ImageName $img -Name $vmn -InstanceSize Small | 
	Add-AzureProvisioningConfig -Windows -Password $PWD |
	New-AzureVM -ServiceName $svc -Location $loc

Create a VM and Adds to an Existing Cloud Service by (omit -Location/-AffinityGroup)

New-AzureVMConfig -ImageName $img -Name $vmn -InstanceSize Small | 
	Add-AzureProvisioningConfig -Windows -Password $PWD |
	New-AzureVM -ServiceName $svc 

Connecting Virtual Machines with Windows Azure Virtual Networks

The second way of provisioning connected virtual machines is by using a Windows Azure Virtual Network. With a Windows Azure Virtual Network the network can span cloud services. This enables scenarios such as virtual machines (or web and worker roles) in different cloud services to be fully connected in the cloud.

How do you provision VMs into a VNET with PowerShell?

Just like the -Location parameter a VNET can only be specified when creating the first VM in a cloud service (note that the subnet for each VM can be set per VM on provisioning). Additionally, VNETs require that the cloud service be deployed into the same affinity group as the VNET was created in. The New-AzureVM cmdlet requires -AffinityGroup instead of -Location when deploying to a VNET.

Joining a Virtual Network at Provision Time

New-AzureVMConfig -ImageName $img -Name $vmn -InstanceSize Small | 
	Add-AzureProvisioningConfig -Windows -Password $PWD |
       Set-AzureSubnet 'subnet' |
	New-AzureVM -ServiceName $svc -AffinityGroup 'myag' -VNetName 'VNET' 

Specifying DNS

One of the significant differences between deploying a virtual machine outside of a VNET and one within is inside of a VNET there is no Windows Azure Provided DNS for VM to VM name resolution. To provide for this you are allowed to specify DNS servers inside of the Virtual Network configuration. When deploying with PowerShell you also have the ability to specify DNS settings when you create the first VM. This is a very flexible approach because it allows you the ability to specify DNS at deployment time without the need to modify the underlying virtual network configuration.

Specifying DNS Server on Provisioning

In this example I am creating a DNS object that references a DNS server (10.1.1.4) and I specify it with New-AzureVM. All VMs created in this cloud service will inherit this DNS setting on boot.

$dns = New-AzureDns -Name 'onprem-dns' -IPAddress '10.1.1.4'
New-AzureVMConfig -ImageName $img -Name $vmn -InstanceSize Small | 
    Add-AzureProvisioningConfig -Windows -Password $PWD |
    Set-AzureSubnet 'subnet' |
    New-AzureVM -ServiceName $svc -AffinityGroup 'myag' -VNetName 'VNET' -DnsSettings $dns

Deploying a Virtual Machine into an Active Directory Domain

With Windows Azure Virtual Machines it is entirely possible to have a full Active Directory environment in the cloud. AD can either be hosted on-premises with connectivity provided by a site-to-site VPN tunnel using Windows Azure Virtual Networks OR you can host an AD domain directly in the cloud.

Once AD connectivity is in place you can use the PowerShell cmdlets to automatically join a Windows Virtual Machine directly to an Active Directory domain at provision time. For AD domain join to work you must specify the DNS server IP address for your Active Directory domain.
In this example New-AzureDNS is used to specify the DNS for the VM to point to an AD DNS Server in the cloud (10.2.0.4) which itself has been configured to point to an on-premise AD server (192.168.1.6) in a previous deployment. Setting DNS at this level is also useful because any future VMs added to this cloud service will inherit the DNS setting.

$subnet = 'APPSubnet'
$ou = 'OU=AzureVMs,DC=fabrikam,DC=com'
$dom = 'fabrikam'
$domjoin = 'fabrikam.com'
$domuser = 'administrator'

$domVM = New-AzureVMConfig -Name 'advm1' -InstanceSize Small -ImageName $image |      
	Add-AzureProvisioningConfig -WindowsDomain -JoinDomain $domjoin -Domain $dom -DomainPassword $pass -Password $pass -DomainUserName $domuser -MachineObjectOU $ou | 	
	Set-AzureSubnet -SubnetNames $subnet

$dns = New-AzureDns -Name 'clouddc-ad' -IPAddress '10.2.0.4' 

New-AzureVM -ServiceName 'app-cloudservice' -AffinityGroup 'ADAG' -VNetName 'HybridVNET' -DnsSettings $dns -VMs $domVM

If you would like to try some of this out on your own I highly suggest the Windows Azure Training Kit as a starting point. There are many hands on labs including deploying Active Directory and connecting multiple virtual machines.