Understanding Azure Custom Script Extension

At Build 2014 conference, Microsoft launched the Azure VM Custom Script Extension. This is a very useful and straightforward extension. However, understanding it in detail helps make better use of it. Before we get started, there are a few prerequisites we need to take care of. The Azure VM agent is the first requirement and if your VMs don’t have this already installed, you can follow the procedure on the Microsoft Azure team blog. We can enable VM extensions in an Azure VM using the Azure PowerShell cmdlets. In fact, you need to update the existing Azure PowerShell module to version 0.8.0 or later.

Once we have all these prerequisites met and we have authenticated to Azure, let us just run the Get-AzureVMAvailableExtension cmdlet to see what extensions are available to us.

Now that we know what extensions are available in general, we can use the Azure VM extension cmdlets to manage them.

The Get-AzureVMExtension cmdlet tells us what extensions are already installed in our Azure VM.

$Vm = Get-AzureVM -ServiceName "DSCDemo" -Name "DSCPull"
Get-AzureVMExtension -VM $Vm | Select ExtensionName, Publisher, Version

So, I have only the BGInfo Extension installed in my Azure VM. We can use the Set-AzureVMExtension cmdlet to install the Custom Script Extension.

Set-AzureVMExtension -ExtensionName CustomScriptExtension -VM $vm -Publisher Microsoft.Compute | Update-AzureVM -Verbose

Now that we have the Custom Script Extension enabled, let’s take a look at the Set-AzureVMCustomScriptExtension cmdlet. This cmdlet helps us run a script available in a Azure storage account or at an Internet URI. Both methods have distinct use cases. You can find the binaries and log files related to this extension at C:\Packages\Plugins and C:\WindowsAzure\Logs\Plugins. If you think the script execution did not work as you expected, you can look at these locations to find what could have gone wrong. There is also PowerShell way of doing that. We will see that in detail in this article.

Executing scripts from your storage account

The -ContainerName parameter of the Set-AzureVMCustomScriptExtension cmdlet can be used to specify the Azure storage container where the scripts are stored. By default, the Custom Script Extension tries to find this container in the default Azure storage account. So, if you have multiple storage accounts, you will have to specify the -StorageAccountName parameter with the storage account name too. If this is not your own storage account, you need to specify the -StorageAccountKey parameter.

$Vm = Get-AzureVM -ServiceName "DSCDemo" -Name "DSCPull"
Set-AzureVMCustomScriptExtension -ContainerName scripts -StorageAccountName psmag -FileName user.ps1 -Run user.ps1 -VM $vm | Update-AzureVM -Verbose

The -FileName parameter can be used to specify all script files that need to be downloaded from the storage container. The -Run parameter specifies the script that we want to execute once the script files are downloaded. In my example above, I am using only one script and that is user.ps1. Since the -FileName parameter has only one script file, there is no need to explicitly specify -Run parameter. In the absence of -Run parameter, the Custom Script Extension will execute the first script in the list of file names provided as an input to the -FileName parameter.

When I run the above command, the user.ps1 gets downloaded to C:\Packages\Plugins\Microsoft.Compute.CustomScriptExtension\1.0.1 folder in the Azure VM. Once the script file is downloaded, it gets executed by CustomScriptHandler.exe process using the following PowerShell.exe commandline.

powershell -ExecutionPolicy Unrestricted -file user.ps1

The contents of my user.ps1 script are simple. I wanted to find out what credentials does the Azure VM Custom Script Extension use when running the PowerShell scripts.

#Contents of user.ps1

Since I have already executed the script using the Custom Script Extension, I can look at the output generated by script by looking at the VM properties. We need to refresh the VM object by using the Get-AzureVM cmdlet.

$vm = Get-AzureVM -ServiceName DSCDemo -Name DSCPull

The ExtensionSettingStatus property under ResourceExtensionStatusList property of the Azure VM object contains the result of our script execution.

The Code property specifies the status code from the CustomScriptHandler.exe execution. The SubStatusList property contains the Standard Output (StdOut) and Standard Error (StdErr) streams from the script execution. So, in our script output, we should be able to see the output of ‘whoami‘ command.

$Vm.ResourceExtensionStatusList.ExtensionSettingStatus.SubStatusList | Select Name, @{"Label"="Message";Expression = {$_.FormattedMessage.Message }}

As we see here, the CustomScriptHandler.exe process runs as the System account. This means using the Azure VM Custom Script Extension we can run any sort of code even if it requires highest system privileges. This might sound scary but given the fact that using this extension requires access to your publish settings or Azure account, this can be assumed safe.

Output from a script execution

As you see in the above example, there are only two output streams available from the script execution – the standard output and the standard error. So, what happens if your script execution returns a PowerShell object or some type of an object other than string/text output? Let us see an example.

#Contents of process.ps1

To understand the script output behavior, I created another script called process.ps1 and copied it to the same Azure container as the above example. As you see, the script has just one command that is Get-Process which returns a collection of process objects. I will use the method we saw above to execute the script and collect the output.

As we see, the output from the Get-Process cmdlet gets converted to text.

Using the FileUri parameter

When using the -FileUri parameter, you need to specify the complete URI to the script file you want to execute inside the Azure VM. The user.ps1 and process.ps1 are available in a public Azure container and therefore if we know the storage account and container names, we can build the URI to access the script file. In my example, the storage account name is psmag and the container name is scripts. So, the URI for the user.ps1 script will be_ http://psmag.blob.core.windows.net/scripts/user.ps1_.

Set-AzureVMCustomScriptExtension -FileUri http://psmag.blob.core.windows.net/scripts/user.ps1 -VM $vm -Verbose | Update-AzureVM -Verbose

Using the -FileUri parameter, the process of executing the script inside the Azure VM isn’t different. The CustomScriptHandler.exe process still downloads the file and then uses PowerShell.exe to execute that.

Passing arguments to scripts

You may want to pass arguments to the scripts that you plan to execute in the Azure VM using the Custom Script Extension. This is where the -Argument parameter comes handy. An example will explain this better.

Set-AzureVMCustomScriptExtension -FileUri http://psmag.blob.core.windows.net/scripts/hello.ps1 -Run hello.ps1 -Argument "-fname Windows -lname PowerShell" -VM $Vm | Update-AzureVM -Verbose

One caveat with the -Argument parameter is that it takes only string values as arguments. So, for the above example, if I want to specify the arguments without the named parameters, we need to specify the arguments for those named parameters as a single string.

Set-AzureVMCustomScriptExtension -FileUri http://psmag.blob.core.windows.net/scripts/hello.ps1 -Run hello.ps1 -Argument "Windows PowerShell" -VM $Vm | Update-AzureVM -Verbose

This results is the same output as the other example using named parameters.

Updating scripts downloaded to the Azure VM

Like I’d mentioned earlier, the scripts that we execute using the Custom Script Extension get downloaded to C:\Packages\Plugins\Microsoft.Compute.CustomScriptExtension\1.0.1 folder in the Azure VM. These script files do not get deleted after the script execution. And, the scripts get downloaded every time we run the Set-AzureVMCustomScriptExtension cmdlet. IMHO, this is not a very good design.

However, here is a caveat. If you use -FileUri to specify the path to a script file and you repeat the same command for the second time, you can see in the log files that the script file does not get downloaded again. This is true even if the script is updated. Here is the excerpt from the log file.

2014-04-29T17:59:33.9920457Z [Info]: Starting IaaS ScriptHandler Extension v1
2014-04-29T17:59:33.9920457Z [Info]: HandlerEnvironment = Version: 1, HandlerEnvironment: [LogFolder: "C:\WindowsAzure\Logs\Plugins\Microsoft.Compute.CustomScriptExtension\1.0.1", ConfigFolder: "C:\Packages\Plugins\Microsoft.Compute.CustomScriptExtension\1.0.1\RuntimeSettings", StatusFolder: "C:\Packages\Plugins\Microsoft.Compute.CustomScriptExtension\1.0.1\Status", HeartbeatFile: "C:\Packages\Plugins\Microsoft.Compute.CustomScriptExtension\1.0.1\Status\HeartBeat.Json"]
2014-04-29T17:59:33.9920457Z [Info]: Enabling Handler
2014-04-29T17:59:33.9920457Z [Info]: Handler successfully enabled
2014-04-29T17:59:34.0076663Z [Info]: Loading configuration for sequence number 24
2014-04-29T17:59:34.0545913Z [Info]: HandlerSettings = ProtectedSettingsCertThumbprint: , ProtectedSettings: {}, PublicSettings: {FileUris: [https://psmag.blob.core.windows.net/scripts/process.ps1], CommandToExecute: powershell -ExecutionPolicy Unrestricted -file process.ps1 }
2014-04-29T17:59:34.0545913Z [Info]: Downloading files specified in configuration...
2014-04-29T17:59:34.1014232Z [Info]: DownloadFiles: fileUri = "https://psmag.blob.core.windows.net/scripts/process.ps1", baseUri = "https://psmag.blob.core.windows.net/"
2014-04-29T17:59:35.6763364Z [Info]: Files downloaded. Asynchronously executing command: 'powershell -ExecutionPolicy Unrestricted -file process.ps1 '
2014-04-29T17:59:35.6953042Z [Info]: Command execution task started. Awaiting completion...
2014-04-29T17:59:36.5209451Z [Info]: Command execution finished. Command exited with code: 0
2014-04-29T18:01:04.4843341Z [Info]: Starting IaaS ScriptHandler Extension v1
2014-04-29T18:01:04.4843341Z [Info]: HandlerEnvironment = Version: 1, HandlerEnvironment: [LogFolder: "C:\WindowsAzure\Logs\Plugins\Microsoft.Compute.CustomScriptExtension\1.0.1", ConfigFolder: "C:\Packages\Plugins\Microsoft.Compute.CustomScriptExtension\1.0.1\RuntimeSettings", StatusFolder: "C:\Packages\Plugins\Microsoft.Compute.CustomScriptExtension\1.0.1\Status", HeartbeatFile: "C:\Packages\Plugins\Microsoft.Compute.CustomScriptExtension\1.0.1\Status\HeartBeat.Json"]
2014-04-29T18:01:04.4843341Z [Info]: Enabling Handler
2014-04-29T18:01:04.4843341Z [Info]: Handler successfully enabled
2014-04-29T18:01:04.4982194Z [Info]: Loading configuration for sequence number 24
2014-04-29T18:01:04.5294705Z [Info]: HandlerSettings = ProtectedSettingsCertThumbprint: , ProtectedSettings: {}, PublicSettings: {FileUris: [https://psmag.blob.core.windows.net/scripts/process.ps1], CommandToExecute: powershell -ExecutionPolicy Unrestricted -file process.ps1 }
2014-04-29T18:01:04.5450936Z [Warn]: Current sequence number, 24, is not greater than the sequence number of the most recently executed configuration. Exiting...

As you see towards the end of the above log, it finds that the sequence number generated for the execution is not greater than the previous run. Looking at the code for CustomScriptHandler.exe, I feel that this is a bug. The only workaround is to execute some other script and then try the Uri method. This downloads the script from storage container again.

So, this brings us to the end of our exploration of the new Azure VM Custom Script Extension. There are a couple of other cmdlets that can help manage this extension.

The Get-AzureVMCustomScriptExtension cmdlet gives you the details about the last script that was executed and the Remove-AzureVMCustomScriptExtension cmdlet removes the Custom Script Extension from the Azure VM.

Share on: