Archive for category PowerShell

Review Of Windows Azure Virtual Machines Plus Some Scripts To Help You Get Started

I’ve been using Windows Azure Virtual Machines somewhat extensively over the past 6 weeks. I wanted to take a few minutes to give you my thoughts and impressions along with the scripts that I use to create and destroy VMs on demand.

The Good

When everything is setup and working properly, it’s a very powerful platform, and for the most part, the pricing seems very fair, considering what you get. It’s relatively user-friendly, and it doesn’t take too long to get up and running with a basic infrastructure. More complicated setups will of course require a larger time investment, but with the PowerShell scripting interface, a lot can be accomplished.

The Bad

It’s kind of flaky right now, unfortunately. Creating VMs is not always successful. Sometimes you end up creating a VM that simply won’t start. The only solution is to destroy it and try again. Also, sometimes when you restart a functioning VM from within Windows, the machine shuts down but never starts back up again. You then have to manually start it from the Windows Azure Management Console. Documentation is currently rather scattered and still changing rapidly. I expect that in the coming months and years, Microsoft will dramatically improve the offering, but as it stands today, Azure definitely has some problems quirks. Below I will give you all of the details of my setup, along with the scripts that I use to create and destroy VMs, as well as to start and stop persistent VMs. I will, of course, also tell you about the various issues that I have encountered in the past or that I continue to encounter during normal usage.

The Details

First let me say that I’m a big supporter of Microsoft products, in general. As a sysadmin, I am intimately acquainted with many Microsoft offerings, having designed and administered numerous systems. I believe that Microsoft is on the ball, at least when it comes to their software products. Does this mean that I agree with every decision that Microsoft has made over the years with all of their products, licensing, and privacy issues etc? No. Of course not. However, I do believe that the modern computing world owes to Microsoft much more than most people are willing to concede.

With regard to Azure, I have a relatively simple requirement for my virtual infrastructure. My goal was to setup a small Windows domain with a couple of core, persistent infrastructure servers, while giving myself the flexibility to scale up the number of member servers in the domain from anywhere between 5 and 100.

The first thing that I needed to do was upload a Windows image to my Azure account. This image would be the baseline image for all of my virtual machines. Seems harmless enough, but unfortunately the Azure user interface does not provide you with this capability. If you want to upload your own image, as many/most Azure customers will want to do, you have to do it via the PowerShell command line interface. I found myself annoyed that there was no direct support for this operation in the UI, but I was able to complete the process without too much trouble, following the steps outlined here.

I then needed to create my first VM. One key thing to keep in mind when you create VMs is the -ServiceName parameter in the New-AzureVM cmdlet. Essentially, the current implementation of Azure Virtual Machines assumes that every virtual machine that you want to create will need to be accessible on the public internet. You are not required to expose your VMs, but you are given the ability to do so if you desire. As such, each VM you create must have an associated DNS name and Cloud Service name, so that Microsoft can handle the public to private mappings. For my needs, public accessibility isn’t necessary. I really only need to be able to access a couple of VMs from my personal computer. The rest of the VMs simply need to be able to see one another. They do not need to be accessible from my personal computer, as long as they are accessible to the couple of VMs that *are* accessible from my personal computer. It’s fine that the current Azure implementation doesn’t really account for or anticipate this form of usage, but it’s also not clearly documented by Microsoft, which makes things confusing. Furthermore, if you do not specify a unique -ServiceName, the error you get when you execute New-AzureVM is misleading. So, for the sake of efficiency, just take my word for it and make sure to specify a unique -ServiceName. Since I will not have any two virtual machines with the same name, it is sufficient for me to simply use the $vmName as the -ServiceName in addition to the name for the actual virtual machine.

Create a Standalone VM that is Not Joined to a Domain (Script)

This is the script that I use to create virtual machines on Azure that are NOT joined to any domain. So, when I created my first domain controller, I used this script. And now, any time I need a new standalone VM, I use this script. Before using this script, you will want to setup a Network name and an Affinity Group name inside of the Azure management portal. Any virtual machines that are created using the same Network name will automatically be able to communicate with one another:

##Create a standalone VM that is not joined to any domain
 
Set-AzureSubscription "subscription_name_goes_here" -CurrentStorageAccount "storage_account_name_goes_here"
$vmName = 'vm_name_goes_here'
$location = 'East US'
$imageName = 'image_name_goes_here'
$adminUsername = 'username_goes_here'
$adminPassword = 'password_goes_here'
$instanceSize = 'ExtraSmall'
$cloudServiceName = '' #Turns out that this must be unique for every VM, and so we're not using this variable and instead below you'll notice we are instead using the $vmName.  It actually gets used as both the Cloud Service name AND the DNS Name of the VM, which is counter intuitive.
$VNetName = 'network_name_goes_here'
$affinityGroup = 'affinitygroup_name_goes_here'
 
#we attempt to remove the service before creating the VM to avoid errors where the service already exists, which can happen if the script errors early on a previous run
remove-azureservice -servicename $vmName -Force
 
New-AzureVMConfig -Name $vmName -InstanceSize $InstanceSize -ImageName $imageName |
Add-AzureProvisioningConfig -Windows -Password $adminPassword -AdminUsername $adminUsername |
New-AzureVM -ServiceName $vmName -Location $location -VNetName $VNetName -AffinityGroup $affinityGroup

Create a VM that is Joined to a Domain (Script)

Next I needed to create some member VMs that would automatically be joined to my domain during the creation process. The ability to do this smoothly is key for my needs. Below is the script I use to do that. Unfortunately, this process is not always smooth. The important thing to remember here is that this script cannot be run asynchronously, unfortunately. I find it to be a key limitation for me. Ideally, I’d like to be able to run this script in a loop, so that I can create X number of virtual machines at the same time. However, it turns out that when you specify a Network or Affinity Group in the script (VMs must be on the same Network to be able to communicate with one another, so this is a requirement for me), there is an exclusive lock put on those resources, such that only one operation can occur at a time on any given Network or Affinity Group. If you try to loop this script too quickly, some of the iterations will complete while others will fail due to this locking problem. The best solution that I’ve come up with is to run this script about once every 90 seconds, which usually works without any contention issues. However, even when the scripts run smoothly on 90 second intervals, this doesn’t mean that Azure spins everything up problem-free. I don’t believe that this is due to any problems with the script so much as just the inconsistency/instability of the current Azure platform. Unfortunately, sometimes a virtual machine will simply fail to initialize properly. It will get created, but it will never start. When this happens there is nothing you can do other than destroy it and try again. When I spin up a large number of member VMs, sometimes almost all will be operational. But other times it’s closer to about 50% operational, with 50% stuck in the “stopped” state. I then have to destroy the “stuck” VMs and create new ones to take their place.

##Create a VM that is joined to the specified domain
 
Set-AzureSubscription "subscription_name_goes_here" -CurrentStorageAccount "storage_account_name_goes_here"
$vmName = 'vm_name_goes_here'
$location = 'East US'
$imageName = 'image_name_goes_here'
$adminUsername = 'admin_username_goes_here'
$adminPassword = 'admin_password_goes_here'
$instanceSize = 'ExtraSmall'
$cloudServiceName = '' #Turns out that this must be unique for every VM, and so we're not using this variable and instead below you'll notice we are instead using the $vmName.  It actually gets used as both the Cloud Service name AND the DNS Name of the VM, which is counter intuitive.
$VNetName = 'network_name_goes_here'
$affinityGroup = 'affinitygroup_name_goes_here'
 
#Domain join and DNS settings 
$domain = 'domain_name_goes_here' 
$domainjoin = 'fully_qualified_domain_name_goes_here' 
$domainUser = 'domain_username_goes_here' 
$domainPassword = 'domain_password_goes_here'
 
#we attempt to remove the service before creating the VM to avoid errors where the service already exists, which can happen if the script errors early on a previous run
remove-azureservice -servicename $vmName -Force
 
New-AzureVMConfig -Name $vmName -InstanceSize $InstanceSize -ImageName $imageName |
Add-AzureProvisioningConfig -Password $adminPassword -AdminUsername $adminUsername -WindowsDomain -JoinDomain $domainjoin -Domain $domain -DomainPassword $domainPassword -DomainUserName $domainUser|
New-AzureVM -ServiceName $vmName -Location $location -VNetName $VNetName -AffinityGroup $affinityGroup

Destroy a VM and Remove the Associated VHD on Disk (Script)

What about destroying member VMs? Here’s the script I use to do that. Note that this script seems to be the best way to completely destroy a VM and remove the associated VHD. However, it’s not the only way. Microsoft does provide a -DeleteVHD parameter for the Remove-AzureDisk cmdlet. However, I have found that this is not a safe option to use because it frequently leaves orphaned VHDs, which do not get deleted and cannot be deleted easily due to a lease on the blob never properly getting destroyed. When you have an orphaned VHD, you end up having to jump through a couple annoying hoops to break the lease on the blob in order to be able to delete the VHD properly. You can avoid this problem by NOT using the -DeleteVHD parameter. Instead, call Remove-AzureDisk without that parameter, and then call Remove-AzureStorageBlob afterward:

##Destroy VM and delete the VHD that's associated with it.
 
Set-AzureSubscription "subscription_name_goes_here" -CurrentStorageAccount "storage_account_name_goes_here"
 
$vmName = 'vm_name_goes_here'
 
remove-azurevm -Name $vmName -serviceName $vmName
 
Start-Sleep -s 10
 
remove-azureservice -servicename $vmName -Force
 
##we have to wait a little bit here for the vm to be removed before we will be allowed to remove the disk.  If we try to remove it took quickly, it will fail.
#sleep for 5 minutes before proceeding with disk deletion, to make sure there is enough time for the VM to be removed first
Start-Sleep -s 300
 
#Note that we use the wildcard * here when we're looking for disks to remove.  Be careful with what you pass to $vmName to make sure that you don't accidentally remove the wrong disk
#As long as you are using unique VM names, this shouldn't be a problem.  But if you have machines named VM1, VM2, VM3, and then you set $vmName to be "VM" all three of the disks will be removed.  So, make sure to pass just VM1 as $vmName if that's the only one you want to remove
get-azuredisk | where-object {$_.diskname -like "*" + $vmName + "*"} | remove-azuredisk
 
Start-Sleep -s 30
get-azurestorageblob -container vhds | where-object {$_.name -like "*" + $vmName + "*"} | remove-azurestorageblob

Export a VM Without Destroying the VHD, So That You Can Start It Again Later – Prevent accruing fees for a VM that you’re not using now but want to use again in the future (Script)

What if you don’t want to destroy a VM and you simply want to shut it down to use again later? Here’s how you remove/export a persistent VM without destroying it, so that you can then later re-add/import it. This lets you avoid being charged for the VM when you’re not using it. Note, if you shutdown a VM but do not export it, you will still be charged for it just as if it were running. You have to export the settings and then remove the VM but leave the VHD as-is to avoid getting charged for the VM. Once it has been exported and removed, you will only be charged for the blob storage. This is the script that I use for my persistent VMs, like my domain controllers. I first shut them down from inside the VM. Once they have been shutdown, I run this script:

##Export and remove VM but do NOT delete the VHD because we want to be able to use it later
 
Set-AzureSubscription "subscription_name_goes_here" -CurrentStorageAccount "storage_account_name_goes_here"
 
$vmName = 'vm_name_goes_here'
$path = "C:\users\myUsername\desktop\windowsazure\vm_settings_files\" + $vmName + "_Settings.xml"
 
# Exporting the settings to an XML file 
Export-AzureVM -ServiceName $vmName -Name $vmName -Path $path
 
#Remove the VM and service
remove-azurevm -Name $vmName -serviceName $vmName
 
Start-Sleep -s 10
 
remove-azureservice -servicename $vmName -Force

Import a VM That Had Been Previously Exported (Script)

Here’s how you import a VM that you previously exported:

##Import a VM that we previously exported
 
Set-AzureSubscription "subscription_name_goes_here" -CurrentStorageAccount "storage_account_name_goes_here"
 
$vmName = 'vm_name_goes_here'
$path = "C:\users\myUsername\desktop\windowsazure\vm_settings_files\" + $vmName + "_Settings.xml"
$VNetName = 'network_name_goes_here'
$affinityGroup = 'affinitygroup_name_goes_here'
$location = 'East US'
 
#we attempt to remove the service before creating the VM to avoid errors where the service already exists, which can happen if the script errors early on a previous run
remove-azureservice -servicename $vmName -Force
 
Import-AzureVM -Path $path | 
New-AzureVM -ServiceName $vmName -Location $location -VNetName $VNetName -AffinityGroup $affinityGroup

, ,

No Comments