Reusable Powershell script and how to install things with Custom Script Extensions in an IaaS VM

This blog post will be a fairly short one focused on illustrating how you can efficiently provision IaaS VMs and add stuff to them without remoting in using a small amount of reusable Powershell code. If you find yourself in the situation of writing a new Powershell script for every new provisioning task you do and also with a combination of remoting into the machine and tweak it, this might be something for you. It is based on the Service Management API and not the Resource Manager. It will also show the use of Custom Script Extensions and how you can execute script code inside the VM during creation time.

The idea behind the reusable script

If you are like me, who spin up VMs to help you in a conversation or test things, you quickly realize that having a Powershell script saves you time because it is repeatable. However, over time you find your self in a position where you have almost a 1:1 mapping of a tiny little ps1 file and a demo scenario. You copy the latest when it’s time to create the next one and the folder you save them in starts multiplying like rabbits.

The idea came to me, why not build one script and have different configurations stored in a config xml file? All the Powershell scripts do anyway is pretty similar and the set of cmdlets you call, like Add-AzureEndpoint, are quite limited.

A sample run of the script looks like below


On the command line, the argument is passed for a configuration name called csdemo. That name identifies a deployment element in the xml config file.

The configuration file

The configuration file contains as many deployments descriptions as I like. When I need to setup a new demo, I do the copy-&-pase in the xml file and not in the Powershell. The format of this file is just made up by me, so it’s not anything standard.


The Deployment element just identifies what I want to run and towards what subscription. Then there is a bunch of elements with names you probably are familiar with if you know Azure.

CloudService describes the Cloud Service at can just be a name and Location, but if you plan to have anything in a VNet, you specify the VNet name here.

VM describes the settings that is needed to create a single VM with computer name, image family of what OS to use, what instance size you like and also if the machine should be deployed in a subnet.

As child elements to a VM you can specify multiple Endpoints and DataDisks that the VM requires.

As the blog post topic suggests, you can also specify a CustomScriptExtension and more on how that works later.

The Powershell deployment script

The script is pretty basic since all it does is lookup the specified Deployment element and invoke the correct Powershell cmdlets. If you have multiple VMs in a CloudService, it will fire one provisioning request once it reaches the end of the CloudService. Depending on what elements you have in the config file, different Powershell cmdlets will be called. So lets look at the script.


The lookup is done via an XPath query on the config name. Since the Get-AzureVMImage is an expensive and time consuming call, we do it once and save the results.

Since each CloudService could possibly be located in it’s own Azure datacenter, we need to update the current storage account for the Azure subscription, because the CustomScriptExtension are expected to be found there. It is also a convenient way to tell the Azure Fabric where to put the VMs VHD files.


If we are specifying InternalLoadBalancer in the config file, we can create that object before creating the CloudService. The loop over how many VMs there should be in the CloudService ends with adding the VM to the array of VMs. At the end we call New-AzureVM once and we need do it differently depending on if it’s a ILB set of machines or not.

The loop over VMs in a Cloud Service does a whole lot more. It checks what you have specified for configuration on the VM and invokes the appropriate Powershell cmdlets. Things you can have multiple of on a VM, such as Endpoints and DataDisks are wrapped in loops so you can add everything that was in the config file. Other cmdlets, like Add-AzureProvisioningConfig have if/else wrappings since you must invoke then differently depending on of you specified a Windows or Linux OS type. At the end you can see the CustomScriptExtension added.


In the source code, attached at the end, you will find that the config file has settings for machines of the following types

  • Standalone IIS webserver in a VNet with a DataDisk
  • Standalone IIS webserver in a VNet with two NICs, each in different subnet
  • SUSE server in a VNet with a DataDisk
  • Two IIS webservers in two different datacenters. I use this to demo Traffic Manager on top of them
  • Two IIS webservers in the same CloudService that are using the InternalLoadBalancer and static ip addresses
  • Two IIS webservers in the same CloudService a High Availability Set.

This is not rocket science, but I find my self more productive nowadays since I don’t spend time lookup up MSDN on the exact syntax of the different cmdlets. You might find my Powershell script useful if you start from scratch building a provisioning engine. However, you will find more sofisticated stuff on the internet.

Custom Script Extension

Most of the demo machines I mentioned above uses IIS, except the Linux server. The IIS role is not installed by default, so I need to turn that and other Windows features on. I also need to open the firewall to let http traffic through on port 80. Finally, I always modify the iisstart.htm to include the webservers computer name since that simplifies demoing load balancing since you can easily see that different servers actually responds.


When demoing internal ILB over VPN, it’s also quite handy to prove we reach the webserver over the VPN, so I let the CustomScriptExtension install a tiny ASP.Net page too.


To achieve this, I have a little Powershell script that looks like below that I named install-web-server-whoareyou.ps1.


It installs the Windows features required, opens the firewall, hacks the iisstart.htm page and creates the whoareyou.aspx andwhoareyou.aspx.cs files required to serve as the ASP.Net page.

The deployment script contains a few lines that calls Set-AzureVMCustomScriptExtension specifying in what Storage Container and with what name the script file can be found. It assumes that it is the subscriptions current storage account


You can pass an array of script files in the -FileName argument if you need to use more than one.

What happens then is that the Azure CustomScriptHandler on the VM downloads the script to C:\Packages\Plugins and runs them


If you are really interested on how it’s done, you will find a JSON file on the VM giving away how it actually invokes your script. Be patient when you test, because the script hasn’t run yet when the VM reaches status Running in the portal. Leave some time for it to complete.

In the sources I have included a second script that uses the same config file and where you can Start, Stop, Restart and Delete the deployment. This is handy for cleaning up and if you want to reduce cost and stop the deployment but keep it available for a quick start.

Source code available here