Updated 08/01/2017
Due to the lack of articles regarding this topic i decided to do a quick post on how to get the Custom Script extension to work correctly on both Linux and Windows ARM (Resource Manager) virtual machines.
Some very important notes and key differences before we get started
-
For Windows machines
- The extension details are:
- $ExtensionType = ‘CustomScriptExtension’
- $Publisher = ‘Microsoft.Compute’
- $Version = ‘1.8’
- When entering the commandToExecute note that in Windows the command is executed from the root of the container. This means that if your script in located in “\scripts\version1\my.ps1” on the blob storage container to run the powershell script you need to reference the full path like shown below. This is because when the agent downloads the files it creates the folder structure the same way as the blob (Do not put the container name in the path!):
1"commandToExecute" = "powershell.exe -ExecutionPolicy Unrestricted -File '\scripts\version1\my.ps1'" - If you get the directory path wrong this will be indicated by an error in the logs located at “C:\Packages\Plugins\Microsoft.Compute.CustomScriptExtension\1.8\Status”. The error will be something like:
1The term '.\\scripts\\myscript.ps1' is not \\nrecognized as the name of a cmdlet, function, script file, or operable \\nprogram. Check the spelling of the name, or if a path was included, verify \\nthat the path is correct and try again. - The download directory is located in “C:\Packages\Plugins\Microsoft.Compute.CustomScriptExtension\1.8\Downloads”
- Log file are in “C:\WindowsAzure\Logs\Plugins\Microsoft.Compute.CustomScriptExtension\1.8”. Be aware they are a bit generic for more details errors including errors generated by your script inside the machine by not using throw please check: “C:\Packages\Plugins\Microsoft.Compute.CustomScriptExtension\1.8\Status”
- If you are using named parameters rather than arguments in your script “eg. -MemoryinGB 100” you need to use the -Command parameter rather than -File, like:
1"commandToExecute" = "powershell.exe -ExecutionPolicy Unrestricted -Command '\scripts\version1\my.ps1 -MemoryinGB 100 - Name Archi'" - If you want your script to tell Azure automation that an error has occurred that’s meaningful make sure you use “Throw” in your script (the one that runs on the machine) as the exit, if you want to catch all messages regardless if they use throw make sure you use try,catch and finally as shown below, this will give you the error messages from “C:\Packages\Plugins\Microsoft.Compute.CustomScriptExtension\1.8\Status”
- To get the status you can use the command below. Just specify the virtual machine name, it’s resource group and the name you gave to your custom extension:
1((Get-AzureRmVM -Name $VMName -ResourceGroupName $RGName -Status).Extensions | Where-Object {$_.Name -eq $ExtensionName}).Substatuses
- The extension details are:
-
For all Machines
- Any files downlaoded are not removed after script has finished executing
- You can use the “Get-AzureServiceAvailableExtension” to get all the extensions and there current version
- If you are using Azure Automation scripts and you would like them to fail correctly make sure you add the line below at the top of your code :
1$ErrorActionPreference = "Stop" - You can only run a script with the same parameters on a virtual machine once. If yo want to run it multiple times on the same VM you can specify a time stamp in the Setting or SettingString of Set-AzureRmVMExtension
12$timestamp = (Get-Date).Ticks"timestamp" = "$timestamp"; - If you are using hashtables (and i recommend them) note that a certain format is expected for the fileUris in the Setting of Set-AzureRmVMExtension. Since we can get the extension to download multiple files for us we need to follow the following format:
- For a Single File
1"fileUris" = [Object[]]"http://example.com/showmemore.sh"; - For Multiple Files
1"fileUris" = [Object]"http://example.com/showmemore.sh","http://example.com/otherthing.sh";
- For a Single File
- If you want to execute commands with sensitive variables like passwords you can move the commandToExecute to ProtectedSettings or ProtectedSettingString in Set-AzureRmVMExtension. Make sure you only have it in one place (Setting or ProtectedSetting).
-
For Linux Machines
- The extension details are:
- $ExtensionType = ‘CustomScriptForLinux’
- $Publisher = ‘Microsoft.OSTCExtensions’
- $Version = ‘1.5’
- When entering the commandToExecute note that in Linux the command is executed from the same folder where the script is located. This is because all files are downloaded to the same folder and blob folder structure is ignored. This means that if your script in located in “\scripts\version1\” on the blob storage container to run the sh script you need to ignore the structure and only specify the file name like:
1"commandToExecute" = "sh showmemore.sh" - The download directory is located in “/var/lib/waagent/Microsoft.OSTCExtensions.CustomScriptForLinux-1.5.2.0/download/”
- Log file are in “/var/log/azure/Microsoft.OSTCExtensions.CustomScriptForLinux/1.5.2.0”
- If you have your own DNS servers and you haven’t set it up to forward for Azure dns queries you might get an error. If you run “hostname -f” and you get errors, you can tell the custom script to skip dns check with the code below in the Setting or SettingString of Set-AzureRmVMExtension. Note that at this stage the script wants a bool variable, looking at the code future versions will take a string.
1"enableInternalDNSCheck" = $False - To get the status you can use the command below. Just specify the virtual machine name, it’s resource group and the name you gave to your custom extension:
1((Get-AzureRmVM -Name $VMName -ResourceGroupName $RGName -Status).Extensions | Where-Object {$_.Name -eq $ExtensionName}).Statuses
- The extension details are:
-
Two important Notes:
- You can only have one custom extension at a time so you can remove it after you run your script or before with a code similar to the one below:
- Currently, if the extension is installed it will be re-run every time the VM is Delocated and Started again, to avoid this remove the extension with a code similar to the one below:
1
2
3
4
5
|
$TheVM = Get-AzureRmVM -ResourceGroupName $RGName -Name $VMName
if (($TheVM.Extensions | Where-Object {$_.VirtualMachineExtensionType -eq "CustomScriptExtension"}).Count -gt 0){
$ExtName = ($TheVM.Extensions | Where-Object {$_.VirtualMachineExtensionType -eq "CustomScriptExtension"}).Name
Remove-AzurermVMCustomScriptExtension -ResourceGroupName $RGName -VMName $VMname –Name $ExtName -Force
}
|
So let’s see some scripts the first is a Windows script which create a dns entry on a Domain Controller:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
|
$ScriptBlobAccount = "MyStorageAccount"
$ScriptBlobKey = "dsfsd43sdgfsd43wfsdfsdfdsfsd4334534jfg"
$ScriptBlobURL = "https://MyStorageAccount.blob.core.windows.net/mycontainer/"
$ScriptName = "DNSCreation.ps1"
$ExtensionName = 'MyExtentionName'
$ExtensionType = 'CustomScriptExtension'
$Publisher = 'Microsoft.Compute'
$Version = '1.8'
$timestamp = (Get-Date).Ticks
$VMName = "MyTestVM123"
$RGName = "MyResourceGroup"
$VMLocation = "West Europe"
$DNSName = "test123"
$RecordType = "A-Record"
$ScriptLocation = $ScriptBlobURL + $ScriptName
$ScriptExe = ".\myscripts\$ScriptName -DNSName '$DNSName' -Type '$RecordType'"
$PrivateConfiguration = @{"storageAccountName" = "$ScriptBlobAccount";"storageAccountKey" = "$ScriptBlobKey"}
$PublicConfiguration = @{"fileUris" = [Object[]]"$ScriptLocation";"timestamp" = "$timestamp";"commandToExecute" = "powershell.exe -ExecutionPolicy Unrestricted -Command $ScriptExe"}
Try
{
Set-AzureRmVMExtension -ResourceGroupName $RGName -VMName $VMName -Location $VMLocation `
-Name $ExtensionName -Publisher $Publisher -ExtensionType $ExtensionType -TypeHandlerVersion $Version `
-Settings $PublicConfiguration -ProtectedSettings $PrivateConfiguration
}
Catch
{
Throw $_
exit 1
}
Finally
{
((Get-AzureRmVM -Name $VMName -ResourceGroupName $RGName -Status).Extensions | Where-Object {$_.Name -eq $ExtensionName}).Substatuses
}
|
And here is a Linux script which join a Linux machine to a domain, however this time we don’t want the execution of the script to log the variables we pass to it so we move it to the ProtectedSettings to be encrypted:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
$ScriptBlobAccount = "MyStorageAccount"
$ScriptBlobKey = "dsfsd43sdgfsd43wfsdfsdfdsfsd4334534jfg"
$ScriptBlobURL = "https://MyStorageAccount.blob.core.windows.net/mycontainer/"
$ScriptName = "LinuxPrepare.sh"
$ExtensionName = 'COnfigureLinuxMachine'
$ExtensionType = 'CustomScriptForLinux'
$Publisher = 'Microsoft.OSTCExtensions'
$Version = '1.5'
$timestamp = (Get-Date).Ticks
$VMName = "MyTestVM123"
$RGName = "MyResourceGroup"
$VMLocation = "West Europe"
$ADUser = "User1"
$ADPass = "Welcome123"
$AdminGroup = "%SirArchi"
$ScriptLocation = $ScriptBlobURL + $ScriptName
$ScriptExe = "$ScriptName -adu $ADUser -adp $ADPass -agr $AdminGroup"
$PrivateConfiguration = @{"storageAccountName" = "$ScriptBlobAccount";"storageAccountKey" = "$ScriptBlobKey";"commandToExecute" = "sh $ScriptExe"}
$PublicConfiguration = @{"fileUris" = [Object[]]"$ScriptLocation";"timestamp" = "$timestamp";"enableInternalDNSCheck" = $False}
Try
{
Set-AzureRmVMExtension -ResourceGroupName $RGName -VMName $VMName -Location $VMLocation `
-Name $ExtensionName -Publisher $Publisher -ExtensionType $ExtensionType -TypeHandlerVersion $Version `
-Settings $PublicConfiguration -ProtectedSettings $PrivateConfiguration
}
Catch
{
Throw $_
exit 1
}
Finally
{
((Get-AzureRmVM -Name $VMName -ResourceGroupName $RGName -Status).Extensions | Where-Object {$_.Name -eq $ExtensionName}).Substatuses
}
|
Is there a way to stop Custom Script Execution when starting VM from the status stop(deallocated)?
Good summary of some dark details that azure custom script extensions have. MS documentation does not really cover many of them and we have to learn them the hard way.
Very useful. thanks for writing this blog.
Likewise from me, thanks for taking the time to write the blog and the update. It was very help full..