Today, the Windows Azure management portal does not provide an out-of-the-box capability to define a time schedule for startup and shutdown of virtual machines. Having an automated process for this is of great use, as by simply deprovisioning VMs during off-hours can save you a lot of money. This post will describe a lightweight approach for automated provisioning of VMs according to time schedule.

You might say: “Why don’t you use the Windows Azure Scheduler?”. Well, the Scheduler is great, but it lets you either invoke web services or post messages to a queue. Controlling VMs this way is pretty cumbersome. And with the scheduler I can’t use PowerShell, which is my preferred environment when automating stuff in Azure.
So given I want to use PowerShell I have to think about a way to execute PS cmdlets according to schedule. Well, the Windows Task Scheduler immediately comes to mind. It’s easy to use, built into the Windows OS, reliable, free and can execute pretty much everything you ask it for. But where to host it? I could do it on my laptop, but that’s not running 24/7, so it makes more sense to use an always-on server. As I personally do not have a datacenter, I decided to host an extra Small (A0) virtual machine running in Windows Azure.
So let’s see what are the steps required to build a Windows Server VM in Azure to host the scheduling logic:

Create the Scheduler VM

First, create a Windows Server 2012 R2 VM in the Azure management portal. Deploy it to the datacenter region that fits your needs and specify ‘Extra Small’ as size. Leave the default endpoints, as we will need to log into the VM via RDP.
Essentially it doesn’t really matter if you create a new cloud service or use an existing one for the VM, as we will access the Azure subscription via the PowerShell SDK.

Prepare the Environment

As soon as the scheduler VM has been provisioned, logon via RDP. The first thing we need to do is to install the PowerShell module for Windows Azure into the VM. You can find it here in the Command-line tools section. The installation will be done via the Web Platform Installer.
Install
In order to access our Windows Azure subscription we need to make it ‘known’ to the Azure PowerShell cmdlets. The easiest way to do that is to call a specific cmdlet. So, open up the Windows PowerShell ISE and execute the following statement:
Get-AzurePublishSettingsFile
This will open up a browser window with a Windows Azure sign-in page. Logging in with your Azure service administrator credentials will take you to the following page that lets you download the publishsettings file:
publishsettings
Next, we have to import the publishsettings into our logged-on user’s profile:
Import-AzurePublishSettingsFile -PublishSettingsFile "<downloadpath>\<filename>.publishsettings"
This will essentially import some management certificates into the local user’s cert store (for all subscriptions assigned to your Azure account). These certificates are used to authenticate PowerShell calls to your subscriptions.
You should now delete the publishsettings file. It is no longer required and presents a security risk as it can be used to gain access to your subscriptions.
If you have multiple subscriptions assigned to your Azure account you will have to define a default subscription for which you want the automated provisioning to work:
Select-AzureSubscription -Default "<subscription name>"

Define the Scheduled Tasks

Start VM

Now, as we have the basis to execute authenticated calls to our Windows Azure subscription using PowerShell, we can embed the logic into the Windows Task Scheduler. Let’s say we want to manage a single VM called myvm sitting in a cloud service myservice. In this example we will start the instance up in the morning at 7am and shut it down (i.e. deprovision it) in the evening at 7pm.
First we will define a task to start myvm in the morning. Open up the Task Scheduler management console. From the Action menu, select ‘Create Task…’. Specify a name for the task (e.g. ‘Start myvm’), and select the radio button for ‘Run whether user is logged on or not’. Otherwise, leave all defaults. Note that the task will run in the context of the currently logged-on user.
Task1
Switch to the Triggers tab. Create a new trigger and set it to Daily at 7am:
Task2
Note: machines in Azure are set to UTC time, so you might have to adapt the start time to your local region!
Open the Actions tab. Create a new ‘Start a program’ action, specify powershell.exe as program and the following argument:
Start-AzureVM -Name myvm -ServiceName myservice
Task3
If you want to start multiple machines in a cloud service, you can also do that. Let’s say you might want to start all VMs in the cloud service myservice in a single go, you can specify the following argument:
Get-AzureService -ServiceName myservice | Foreach-Object { Start-AzureVM -ServiceName $_.ServiceName -Name "*" }
On the Settings tab you should activate the checkbox for ‘Run task as soon as possible after a scheduled start is missed’.
Task4
This will prevent missing execution of the task in case the VM would be unavailable at the specified execution time (e.g. because the Hyper-V got patched and our VM would be rebooted just as it had to execute the task). This is sort of a ‘poor man’s HA’ but actually works quite reliably. If you want to test this behavior note that the scheduler would not immediately start the task after coming up, actually but wait a couple of minutes.
That’s it! Note that when saving the task definition you will have to enter your username & password.

Stop VM

Now let’s do the same for shutting down the VM in the evening. Just create a second task called ‘Stop myvm’  and set the trigger to Daily at 7pm (or the corresponding time for your region). The argument for the PowerShell statement is this:
Stop-AzureVM -Name myvm -ServiceName myservice -Force
This statement will not only shutdown the VM, but also deprovision it, i.e. stop accruing cost on your bill. If you wish so, you could add the –StayProvisioned parameter, which will shutdown your VM but keep it deployed. No benefit cost-wise, but this way you could keep the cloud service’s public virtual IP address, in case this VM is the last one running in your cloud service.
Note, that if you deprovision the VM the –Force parameter will be required, in case the VM is the last one in the cloud service myservice. This will allow releasing the public virtual IP address of the cloud service without waiting for confirmation.
Again, you could also stop multiple machines in a cloud service like this:
Get-AzureService -ServiceName myservice | Foreach-Object { Stop-AzureVM -ServiceName $_.ServiceName -Name "*" –Force }
In your Task Scheduler console you should now see the following two task definitions:
Task5

Testing

Now you are ready to test the tasks. Before we start you should enable the task history in order to see what’s going on (it’s off by default). You can do that in the Task Scheduler Library on the right hand side in the management console:
Task6
Make sure the VM you want to manage (myvm in the example above) is running. Right-click the ‘Stop myvm’ task and select Run. If you select the task you can navigate to the History tab in the task details and follow the execution which takes a couple of seconds. As soon as task execution is completed (you can refresh the console using F5) you will see a couple of entries in the history event log. Select the Action completed event and have a look at the Details, especially the return code.
Task7
If the return code is 0, life is good and the VM should be in the Stopped (Deallocated) state in the portal. If an error occurred (i.e. result is not 0), you should take the PowerShell statement and execute it in the PowerShell ISE directly in order to see what the error statement is.
You should also test the second task ‘Start myvm’ interactively, in order to make sure both are configured correctly.

Making it Work

Now, things work fine as long as you are logged on to the machine with the user account that is also the account being used for task execution. If you log off the VM and let a task execute via schedule, you will see that it’s going to fail. Why is that? Well, unattended task execution in the scheduler is a tricky thing, and here’s how you can fix that:

Certificates

First thing to note is that the certificate to authenticate calls to Windows Azure has to be located in the local machine certificate store in case the user is not logged on. I assume the user profile is not loaded properly, and hence access to the local user cert store is not working.
Unfortunately we can’t re-use the certificates imported into the local user store before (by using the publishsettings import), as these don’t let you export the private key. So, we have to create a new certificate and add it to the local machine store. The easiest way to do this is to use the makecert utility from the Windows SDK. If you don’t have a machine handy with the SDK installed, you can get it from here. For makecert you just have to install the SDK itself, without any of the additional components.
Now, create a certificate like this in a command prompt (you can call the certificate whatever you like):
makecert -sky exchange -r -n "CN=azuremgmt" -pe -a sha1 -len 2048 -sr localmachine -ss My "azuremgmt.cer"
This adds a new certificate to the local machine store and creates a .cer file containing the public key of the certificate. Next, we have to upload the public key into our Windows Azure subscription. In order to do this log in to the Azure Management Portal and upload the .cer file in the Settings – Management Certificates page (for details go here).

Windows Azure Profile

What we need to do now is reference the new certificate in the Windows Azure PowerShell profile. This is contained in the WindowsAzureProfile.xml file that is stored in the user’s profile path under C:\Users\<user>\AppData\Roaming\Windows Azure Powershell. Open that file, identify your default subscription and change the <ManagementCertificate> tag to the thumbprint of the cert you created above.
<AzureSubscriptionData>

..

  <IsDefault>true</IsDefault>

  <ManagementCertificate>171183FB..</ManagementCertificate>

  <Name>your subscription name</Name>

..
You can get the thumbprint from the Azure Management Portal or from the .cer file.
What you also need to do is to copy the Windows Azure PowerShell profile to the default Windows profile of the VM. In order to do that copy the whole ‘Windows Azure Powershell’ folder to the path C:\Users\Default\AppData\Roaming.
Task8
Now, we’re good to go. Your scheduled tasks will be executed, no matter if you are signed into the VM or not.

Final Thoughts

You might argue that using the approach described above will cost you $15 a month, just for running the extra small VM hosting the scheduling logic. Well, you have different options here: you can either run the tasks on a server you own on-premises or use an existing VM in Windows Azure that is running anyway. Also, if you consider the cost savings you will achieve with deprovisioning your VMs it should pay off easily. If you shutdown only one single core VM in Azure during the night, it would already save you way more than you spend for the scheduler instance.