Azure Resource Manager (ARM) - Automate Installation of VM Extensions using PowerShell and JSON

Hello all

 

Neil Bird here from the Cloud & Infrastructure team in the UK. I am a Premier Field Engineer (PFE) specialising in helping customers get the most out of Microsoft Azure and Windows Server technologies.

 

One of my customers recently asked me if there was an easy way to install the BGInfo Extension on all of the Azure Resource Manager (ARM) VMs running in their Azure subscription. They had found a previous Azure Blog on VM Extensions, but this was from back in 2014 which meant the PowerShell cmdlets and example code was for Azure Service Manager (ASM), aka - "Classic VMs" and therefore is NOT valid for ARM VMs.

 

I hope everyone is using ARM for their Azure workloads, or if not, that you have plans to migrate from ASM to ARM in the near future. If you require more information on the difference between ASM and ARM, the following article provides useful information: Azure Resource Manager vs. Classic Deployment: Understand deployment models and the state of your resources

 

Background:

Azure virtual machine extensions are small applications that provide post-deployment configuration and automation tasks on Azure virtual machines. For example, if a virtual machine requires software installation, anti-virus protection, or Docker configuration, a VM extension can be used to complete these tasks.

 

Solution:

The script framework I created and that I am sharing with you today is a PowerShell script that can be used to automate the installation of Any VM Extension. I have added lots of comments in green to make it easier to understand what actions the code is performing. I am aware that it is possible to install VM Extensions using a PowerShell "One-Liner". However, I prefer to include prerequisite checks (logic) and an output showing the overall results as part of the installation process, this is useful when processing a large number of VMs and/or multiple subscriptions. The pre-install checks ensure the VMs are in the "correct state" to install an Extension, for example: in the BGInfo Extension scenario the checks ensure the following conditions are true:

 

  1. The VM has a Windows based OS installed, (OS compatibility check)
  2. The VM is Running, required to Install Extensions
  3. The VM does NOT already have the Extension Installed

 

If you use a PowerShell One-Liner to install VM Extensions, the installation could throw an error for any VM(s) that fail these tests. For example: if a VM is NOT running, it is NOT possible to install an Extension.

 

The reason I have called the PowerShell script a "framework" is that it can be used to install any VM Extension. Although the original request was for BGInfo, I used Parameters for the "Extension Type" (i.e. Name) and "Publisher Name". This means you can easily install any other Extension using the same script, including Extensions that require additional "Settings". I will provide more information on how to specify Extension settings later in the post.

 

Script Prerequisites:

  1. You need access to an Azure Subscription that has one or more ARM VMs deployed in it.
  2. The script must be executed from a device that has Azure PowerShell Installed, i.e - the AzureRM.Compute v3.2 or above module.

 
Updating AzureRM Module: # Install the Azure Resource Manager modules from the PowerShell Gallery Install-Module AzureRM

 

Script Parameters:

To make the script simple to use and familiarise yourself with its use, I have left the Default Extension as BGInfo. This means if you would like to install the BGInfo Extension on the VMs running in your subscription, you only need to specify the "-SubscriptionName "<Name of your subscription>" parameter (or alternatively you could edit the parameter in the Params section of the code, Line 144).  This parameter is a string value that stores the name of the subscription you would like the script to process.

 

Optionally also include the "-ProcessAllVMs" switch when executing the script. This switch instructs the script to "process ALL of the VMs in the subscription". Note - If this is NOT specified the script will only process the first 3 x VMs in the subscription.

 

For full details of the Script Parameters, including Syntax and a few Examples, once you have downloaded the script (URL link below), open a PowerShell prompt, change directory to the location you have saved the script to and run:

get-help .\azure-install-vm-extension.ps1 -full

 

The "get-help -full" command will output the Script Synopsis, Description, Parameters, Examples, Links and Notes.

 

Download The Script:

Recommended: Download the script from The Microsoft TechNet Gallery .
 

Sample Script:

Without further ado, find below a sample PowerShell script that can be used to "Install an Extension on ALL Powered On, ARM VMs in a Subscription".

 

Important Note - The Warranty statement shown in the .NOTES section of the script is standard wording for "sample scripts". As with anything that makes changes to IT systems, it is always highly recommended to perform acceptance tests against a "non-production or test subscription" prior to implementing in a live / production subscription(s). In addition to following your organisation's Change Control / Release Management / Pipeline processes and procedures. (Of course, you already know this, but I wanted to call this out. )

 

Once you have downloaded the script, you can make changes or review the code in your favourite Script Editor, my "coding weapon of choice" is the awesome (and free) Visual Studio Code with the optional PowerShell Extension Installed :-)
 

The code from the script "azure-install-vm-extension.ps1" is shown below, so you can review it whilst reading this blog (if you wish to). To skip reviewing the code / continue reading the the blog, click here.

 

##########################################################################################################

<#

.SYNOPSIS

Automates the installation of Extensions on VMs in an Azure Subscription.

Performs checks to ensure VM is in the correct state to install an extension.

.DESCRIPTION

Script framework to install VM Extensions on All of the VMs in a subscription.

The Extension "Type" and "Publisher" are specified as parameters.

If these parameters are NOT specified at execution, the script defaults to installing the "BGInfo" Extension on Windows VMs.

The script performs the following checks to ensure a VM is in the "correct state" to install the extension:

1) VM is Running (NOT deallocated / stopped).

2) VM is using an Operating System that is compatible with the Extension (configured as parameters).

3) VM does not currently have the extension installed.

.PARAMETER SubscriptionName

Mandatory parameter. This can be configured in the script Params section or by passing as a pipeline parameter.

The Name of the Azure Subscription you wish to process.

.PARAMETER VMExtensionName

Mandatory parameter with default of "BGInfo".

The Name (Type) of the VM Extension you wish to install.

This parameter is Case Sensitive, due to the comparison used to test if the Extension is already installed

The PowerShell code below can be used to obtain a full list of the VM Extensions that are available in an Azure region.

# Edit the $location variable with your target region

[string]$location = "uksouth"

`` Get-AzureRmVmImagePublisher -Location $location | `

`` Get-AzureRmVMExtensionImageType | `

Get-AzureRmVMExtensionImage | Select Type, PublisherName | ft *

.PARAMETER VMExtensionPublisher

Mandatory parameter with default of "Microsoft.Compute".

The Publisher of the VM Extension you wish to install.

.PARAMETER ProcessAllVMs

Include this parameter if you would like to "Process ALL VMs in the subscription".

If this parameter is NOT included the script will only process the first 3 x VMs in the subscription.

This is a safety measure to prevent unintentionally installing the Extension on ALL VMs.

.LINK

https://blogs.technet.microsoft.com/ukplatforms/2017/07/31/azure-resource-manager-arm-automate-installation-of-vm-extensions-using-powershell-and-json

.EXAMPLE

.\azure-install-vm-extension.ps1 -SubscriptionName "Visual Studio Enterprise"

Installs the default VM Extension configured in the script parameters (BGInfo) on first 3 x Windows VMs in the "Visual Studio Enterprise" subscription.

.EXAMPLE

.\azure-install-vm-extension.ps1 -SubscriptionName "Visual Studio Enterprise" -ProcessAllVMs

Installs the default VM Extension configured in the script parameters (BGInfo) on ALL of the Windows VMs in the "Visual Studio Enterprise" subscription.

.EXAMPLE

`` .\azure-install-vm-extension.ps1 -SubscriptionName "Visual Studio Enterprise" `

`` -VMExtensionName "IaaSAntimalware" `

`` -VMExtensionPublisher "Microsoft.Azure.Security" `

`` -VMExtensionWindowsCompatible $true `

`` -VMExtensionLinuxCompatible $false `

`` -VMExtensionSettingsFilePath "C:\scripts\IaaSAntimalware-Config.json" `

-ProcessAllVMs

Installs the "IaaSAntimalware" VM Extension on ALL of the Windows VMs in the "Visual Studio Enterprise" subscription.

Configures the "IaaSAntimalware" Extension settings using the configuration in the "C:\scripts\IaaSAntimalware-Config.json" file.

Example JSON Schema for "Microsoft Antimalware" Extension, "C:\scripts\IaaSAntimalware-Config.json" file:

{

"AntimalwareEnabled": true,

"RealtimeProtectionEnabled": true,

"ScheduledScanSettings": {

"isEnabled": true,

"day": "7",

"time": "120",

"scanType": "Quick"

},

"Exclusions": {

"Extensions": "",

"Paths": "%windir%\\SoftwareDistribution\\Datastore\\DataStore.edb;%windir%\\SoftwareDistribution\\Datastore\\Logs\\Edb.chk",

"Processes": ""

}

}

For full details on how to configure the "Microsoft Antimalware Extension" settings including file exclusions, see the following article:

"Microsoft Antimalware for Azure Cloud Services and Virtual Machines"

(https://docs.microsoft.com/enus/azure/security/azure-security-antimalware)

.NOTES

THIS CODE-SAMPLE IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED

OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR

FITNESS FOR A PARTICULAR PURPOSE.

This sample is not supported under any Microsoft standard support program or service.

The script is provided AS IS without warranty of any kind. Microsoft further disclaims all

implied warranties including, without limitation, any implied warranties of merchantability

or of fitness for a particular purpose. The entire risk arising out of the use or performance

of the sample and documentation remains with you. In no event shall Microsoft, its authors,

or anyone else involved in the creation, production, or delivery of the script be liable for

any damages whatsoever (including, without limitation, damages for loss of business profits,

business interruption, loss of business information, or other pecuniary loss) arising out of

the use of or inability to use the sample or documentation, even if Microsoft has been advised

of the possibility of such damages, rising out of the use of or inability to use the sample script,

even if Microsoft has been advised of the possibility of such damages.

#>

##########################################################################################################

###############################

## SCRIPT OPTIONS & PARAMETERS

###############################

#Requires -Version 3

#Requires -Modules AzureRM

# Version: 1.1

<# - 28/07/2017

* added progress bar and confirmation prompt.

* added "-ProcessAllVMs" switch, without this script only processes 3 x VMs by default.

* added parameter to specify a "SettingString" configuration file for Extension settings.

* added counters to provide an "installation results" report when the script completes.

- 14/07/2017

* initial script creation.

#>

# Define and validate mandatory parameters

[CmdletBinding()]

Param(

# Azure Subscription Name

[parameter(Position=1)]

[string]$SubscriptionName = "SUBSCRIPTION NAME",

# ***** EDIT ABOVE WITH YOUR SUBSCRIPTION NAME, OR PASS AS SCRIPT PARAMETER *****

# VM Extension Name (Case sensitive for "Extensions.id.Contains" comparison)

[parameter(Position=2)]

[string]$VMExtensionName = "BGInfo",

# VM Extension Publisher

[parameter(Position=3)]

[string]$VMExtensionPublisher = "Microsoft.Compute",

# VM Extension Windows OS Compatible

[parameter(Position=4)]

[bool]$VMExtensionWindowsCompatible = $true,

# VM Extension Linux OS Compatible

[parameter(Position=5)]

[bool]$VMExtensionLinuxCompatible = $false,

# VM Extension JSON Settings File Path

[parameter(Position=6)]

[string]$VMExtensionSettingsFilePath = "",

# Process All VMs in Subscription Switch, if not present script only processes first 3 VMs

[parameter(Position=7)]

[switch]$ProcessAllVMs

)

# Set strict mode to identify typographical errors

Set-StrictMode -Version Latest

# Make the script verbose by default

$VerbosePreference = "Continue"

##########################################################################################################

#####################################

## FUNCTION 1 - Install-VMExtension

#####################################

Function Install-VMExtension {

# Connect to Azure

Write-Host "`nPrompting for Azure Credentials and Authenticating..."

# Login to Azure Resource Manager (ARM), if this fails, stop script.

Login-AzureRmAccount -SubscriptionName $SubscriptionName -ErrorAction Stop

# Get all ARM VMs in Subscription

[array]$VMs = Get-AzureRMVM -Status -ErrorAction Stop

# Counter for Progress bar and $ProcessAllVMs switch

$VMsProcessed = 0

# Loop through all VMs in the Subscription

ForEach ($VM in $VMs) {

# Check if the ProcessAllVMs switch has NOT been set

if(!$ProcessAllVMs.IsPresent) {

# We are NOT Processing All VMs (switch NOT present), stop after first 3 x VMs

if($VMsProcessed -eq 3) {

# Write informational message about use of the -ProcessAllVMs switch

Write-Host "`nINFO: Script Stopping."

Write-Host 'INFO: To process more than the first 3 x VMs in a subscription, Set the -ProcessAllVMs parameter when executing the script.'

# Break out of the ForEach Loop to stop processing

Break

}

}

# Show the Progress bar for number of VMs Processed...

$VMsProcessed++

`` Write-Progress -Activity "Processing VMs in ""$($SubscriptionName)>""..." `

`` -Status "Processed: $VMsProcessed of $($VMs.count)" `

-PercentComplete (($VMsProcessed / $VMs.Count)*100)

# Ensure the VM OS is Compatible with Extension

`` if(($VM.OSProfile.WindowsConfiguration -and $VMExtensionWindowsCompatible) `

-or ($VM.OSProfile.LinuxConfiguration -and $VMExtensionLinuxCompatible)) {

# Ensure the Extension is NOT already installed

if(($VM.Extensions.count -eq 0) -or (!(Split-Path -Leaf $VM.Extensions.id).Contains($VMExtensionName))) {

# If VM is Running

if($VM.PowerState -eq 'VM running') {

# Output the VM Name

Write-Host "$($VM.Name): requires $($VMExtensionName), installing..."

# Get the latest version of the Extension in the VM's Location:

`` [version]$ExtensionVersion = (Get-AzureRmVMExtensionImage -Location $VM.Location `

`` -PublisherName $VMExtensionPublisher -Type $VMExtensionName).Version `

`` | ForEach-Object { New-Object System.Version ($PSItem)} | `

Sort-Object -Descending | Select-Object -First 1

[string]$ExtensionVersionMajorMinor = "{0}.{1}" -F $ExtensionVersion.Major,$ExtensionVersion.Minor

# If the $VMExtensionSettingFilePath parameter has been specified and the file exists

if(($VMExtensionSettingsFilePath -ne "") -and (Test-Path $VMExtensionSettingsFilePath)) {

# Import Extension Config File

$VMExtensionConfigfile = Get-Content $VMExtensionSettingsFilePath -Raw

# Install the Extension with SettingString parameter

`` $ExtensionInstallResult = Set-AzureRmVMExtension -ExtensionName $VMExtensionName `

`` -Publisher $VMExtensionPublisher -TypeHandlerVersion $ExtensionVersionMajorMinor -ExtensionType $VMExtensionName `

`` -Location $VM.Location -ResourceGroupName $VM.ResourceGroupName `

-SettingString $VMExtensionConfigfile -VMName $VM.Name

} else { # $VMExtensionSettingFilePath does NOT exist

# Install the Extension WITHOUT SettingString parameter

`` $ExtensionInstallResult = Set-AzureRmVMExtension -ExtensionName $VMExtensionName `

`` -Publisher $VMExtensionPublisher -TypeHandlerVersion $ExtensionVersionMajorMinor -ExtensionType $VMExtensionName `

`` -Location $VM.Location -ResourceGroupName $VM.ResourceGroupName `

-VMName $VM.Name

} # Install Extension with SettingString parameter if file specified and exists

# Installation finished, check the return status code

if($ExtensionInstallResult.IsSuccessStatusCode -eq $true) {

# Installation Succeeded

`` Write-Host "SUCCESS: " -ForegroundColor Green -nonewline; `

Write-Host "$($VM.Name): Extension installed successfully"

$Global:SuccessCount++

} else {

# Installation Failed

`` Write-Host "ERROR: " -ForegroundColor Red -nonewline; `

Write-Host "$($VM.Name): Failed - Status Code: $($ExtensionInstallResult.StatusCode)"

$Global:FailedCount++

}

} else {

# VM is NOT Running

`` Write-Host "WARN: " -ForegroundColor Yellow -nonewline; `

Write-Host "$($VM.Name): Unable to install $($VMExtensionName) - VM is NOT Running"

$Global:VMsNotRunningCount++

# Could use "Start-AzureRmVM -ResourceGroupName $vm.ResourceGroupName -Name $VM.Name",

# wait for VM to start and Install extension, possible improvement for future version.

}

} else {

# VM already has the Extension installed.

Write-Host "INFO: $($VM.Name): Already has the $($VMExtensionName) Extension Installed"

$Global:AlreadyInstalledCount++

}

# Extension NOT Compatible with VM OS, as defined in Script Parameters boolean values

} else {

# Linux

if($VM.OSProfile.LinuxConfiguration -and (!$VMExtensionLinuxCompatible)) {

# VM is running Linux distro and $VMExtensionLinuxCompatible = $false

Write-Host "INFO: $($VM.Name): Is running a Linux OS, extension $($VMExtensionName) is not compatible, skipping..."

$Global:OSNotCompatibleCount++

# Windows   

} elseif ($VM.OSProfile.WindowsConfiguration -and (!$VMExtensionWindowsCompatible)) {

# VM is running Windows $VMExtensionWindowsCompatible = $false

Write-Host "INFO: $($VM.Name): Is running a Windows OS, extension $($VMExtensionName) is not compatible, skipping..."

$Global:OSNotCompatibleCount++

# Error VM does NOT have a Windows or Linux Configuration

} else {

# Unexpected condition, VM does not have a Windows or Linux Configuration

`` Write-Host "ERROR: " -ForegroundColor Red -nonewline; `

Write-Host "$($VM.Name): Does NOT have a Windows or Linux OSProfile!?"

} # Extension OS Compatibility

} # ForEach VM Loop

} # end of Function Install-VMExtension

} # end of Function Install-VMExtension

# Setup counters for Extension installation results

[double]$Global:SuccessCount = 0

[double]$Global:FailedCount = 0

[double]$Global:AlreadyInstalledCount = 0

[double]$Global:VMsNotRunningCount = 0

[double]$Global:OSNotCompatibleCount = 0

[string]$DateTimeNow = get-date -Format "dd/MM/yyyy - HH:mm:ss"

Write-Host "`n========================================================================`n"

Write-Host "$($DateTimeNow) - Install VM Extension Script Starting...`n"

Write-Host "========================================================================`n"

# Prompt for confirmation...

if($ProcessAllVMs.IsPresent) {

[string]$VMTargetCount = "ALL of the"

} else {

[string]$VMTargetCount = "the first 3 x"

}

# User prompt confirmation before processing

[string]$UserPromptMessage = "Do you want to install the ""$($VMExtensionName)"" Extension on $($VMTargetCount) VMs in the ""$($SubscriptionName)"" Subscription?"

if(!$ProcessAllVMs.IsPresent) {

$UserPromptMessage = $UserPromptMessage + "`n`nNote: use the ""-ProcessAllVMs"" switch to install the Extension on ALL VMs."

}

$UserPromptMessage = $UserPromptMessage + "`n`nType ""yes"" to confirm....`n`n`t"

[string]$UserConfirmation = Read-Host -Prompt $UserPromptMessage

if($UserConfirmation.ToLower() -ne 'yes') {

# Abort script, user reponse was NOT "yes"

Write-Host "`nUser typed ""$($UserConfirmation)"", Aborting script...`n`n" -ForegroundColor Red

Exit

} else {

# Continue, user responded "yes" to confirm

Write-Host "`nUser typed 'yes' to confirm...." -ForegroundColor Green

Write-Host "Processing...`n"

# Call Function to Install Extension on VMs

Install-VMExtension

}

# Add up all of the counters

`` [double]$TotalVMsProcessed = $Global:SuccessCount + $Global:FailedCount + $Global:AlreadyInstalledCount `

+ $Global:VMsNotRunningCount + $Global:OSNotCompatibleCount

# Output Extension Installation Results

Write-Host "`n"

Write-Host "========================================================================"

Write-Host "`tExtension $($VMExtensionName) - Installation Results`n"

Write-Host "Installation Successful:`t`t$($Global:SuccessCount)"

Write-Host "Already Installed:`t`t`t$($Global:AlreadyInstalledCount)"

Write-Host "Installation Failed:`t`t`t$($Global:FailedCount)"

Write-Host "VMs Not Running:`t`t`t$($Global:VMsNotRunningCount)"

Write-Host "Extension Not Compatible with OS:`t$($Global:OSNotCompatibleCount)`n"

Write-Host "Total VMs Processed:`t`t`t$($TotalVMsProcessed)"

Write-Host "========================================================================`n`n"

[string]$DateTimeNow = get-date -Format "dd/MM/yyyy - HH:mm:ss"

Write-Host "`n========================================================================`n"

Write-Host "$($DateTimeNow) - Install VM Extension Script Complete.`n"

Write-Host "========================================================================`n"

 

Executing the Script:

Download the script from the TechNet Gallery. Open a PowerShell prompt, change directory into the same location where you downloaded / saved the script to and type:

 

.\azure-install-vm-extension.ps1 -SubscriptionName "Name of your subscription"

 

If you wish to install the Extension (script defaults to BGInfo) on All VMs in the Subscription, add the parameter: -ProcessAllVMs

 

Script Output:

Example output from the PowerShell script is shown in the screenshot below. The script displays a "Progress Bar" that calculates the Percentage Complete based on number of VMs in the subscription. It also shows the Name of the VM that is currently being processed and finally once complete it provides Individual Counters and a Total for the number of Extension Installations:

 

What about installing VM Extensions other than BGInfo?

As described earlier, the script can be used to install any VM Extension, as the "Extension Name" (Type) and "PublisherName" are both parameters. To obtain a full list of the VM Extensions that are available in a particular region you can use the PowerShell code below.

This example returns the "Type" (Name) and "PublisherName" for All VM Extensions in the UK South region:

 

#  Edit below location variable with your target region

[string]$location = "uksouth"

`` Get-AzureRmVmImagePublisher -Location $location | `

`` Get-AzureRmVMExtensionImageType | `

Get-AzureRmVMExtensionImage | Select Type, PublisherName | ft *

 

Another common requirement and highly recommended practice is to install Anitmalware on all VMs, this could be Microsoft's Antimalware Extension or an alternative offered by one of our partners. To use the sample script above to install the Microsoft Antimalware Extension all you would need to do is set the two parameters below:

 

-VMExtensionName = "IaaSAntimalware"

-VMExtensionPublisher = "Microsoft.Azure.Security"

Additional Info: If you plan to install the Microsoft Antimalware Extension, you may also want to consider configuring "File Path, Process Name or File Extension Exclusions for the Real Time Scanner". As you know, file system exclusions vary depending on the specific workload deployed on a VM, however Microsoft provides a list of recommendations which are documented here: Virus scanning recommendations for Enterprise computers that are running currently supported versions of Windows

 

To configure Exclusions for the "Real Time Scanner" component of the Microsoft Antimalware VM Extension, you need to either create a PowerShell hash table storing the properties or alternatively use a simple ARM JSON Template, my preference is a JSON file and an example is shown below:

Save this file as "IaaSAntimalware-Config.json" after adding your required exclusions in the relevant sections.

 

{

"AntimalwareEnabled": true,

"RealtimeProtectionEnabled": true,

"ScheduledScanSettings": {

"isEnabled": true,

"day": "7",

"time": "120",

"scanType": "Quick"

},

"Exclusions": {

"Extensions": "",

"Paths": "",

"Processes": ""

}

}

 

To define Exclusions in the "Extensions", "Paths" and "Processes" sections of the JSON file, the syntax is a "List of Semi-Colon Delimited Values". "Paths" also require an escape character for the backslash (i.e - double backslashes). For example:

 

"Paths": "%windir%\\SoftwareDistribution\\Datastore\\DataStore.edb ; %windir%\\SoftwareDistribution\\Datastore\\Logs\\Edb.chk",

 

For full details on how to configure Antimalware Settings such as Exclusions, see Microsoft Antimalware for Azure Cloud Services and Virtual Machines.

 

Now that you have created your "Default File Exclusions Configuration File" saved in JSON format, we can use the -VMExtensionSettingsFilePath parameter to pass the File Path of the JSON file to the script. An example PowerShell command line to install the Microsoft Antimalware Extension is shown below:

 

.\azure-install-vm-extension.ps1 -SubscriptionName "Visual Studio Enterprise" -VMExtensionName "IaaSAntimalware" -VMExtensionPublisher "Microsoft.Azure.Security" -VMExtensionWindowsCompatible $true -VMExtensionLinuxCompatible $false -VMExtensionSettingsFilePath "C:\scripts\IaaSAntimalware-Config.json" -ProcessAllVMs

 

The example above could be used to "Automate the installation of the Microsoft Antimalware Extension on All Windows VMs running in a Subscription. "

 

I am sharing this sample script as I believe it will be useful to other customers who have a requirement to automate the installation of VM Extensions across their Azure IaaS environments. However.......

 

Important: As mentioned earlier regarding Warranty of sample scripts. Please ensure that you Test Automation Scripts against a "Test / Dev Subscription" or against a "Subset of VMs" prior to implementing against your Production Subscription(s). You are responsible for testing and validating the desired outcomes are achieved by the code you execute.

 

Possible Future Improvements:

  1. Make use of PowerShell Jobs to process multiple VM Extension installations concurrently.
  2. Add an option to turn on VM's that are not running, install the extension, then turn them off again.
  3. Update script to work with Azure Automation.
  4. Add code to allow a -Whatif parameter, to report on what the script "would do", useful for Change Control documentation.
  5. ...The list is endless, but I hope to find time to add some of these soon.... :-)

 

If you find this sample script useful, please add a comment or suggestion below.

 

This is useful for existing VMs, but what about new VM deployments?

Ok, yes yes, this is only half of the story! The sample script above is great for installing Extensions on existing VMs that have already been deployed into a subscription. However as we continue to transition into an "Infrastructure as Code" (JSON Templates) and "Configuration as Code" (DSC) world, the recommended approach is to provision (deploy) your VMs with the required Extensions (and Configuration) automatically installed. This can be achieved by leveraging the declarative programming model offered by ARM JSON Templates.

 

In its simplest form, adding the code below to an existing JSON would automatically install the BGInfo Extension on a VM when it is deployed:

{

"type": "Microsoft.Compute/virtualMachines/extensions","name": "BGInfo","apiVersion": "2016-04-30-preview","scale": null,"location": "[resourceGroup().location]","properties": {

"publisher": "Microsoft.Compute","type": "BGInfo","typeHandlerVersion": "2.1","autoUpgradeMinorVersion": true

}

}

 

The "Name" property can be customised if you want to include the VM Name, but this is optional. And you would also require a "dependsOn" property specifying "[resourceId('Microsoft.Compute/virtualMachines', parameters('virtualMachineName'))]" or similar, depending on the parameter name you use for the VMs Name.

 

There are lots of other useful examples on the Azure Windows VM Extension Configuration Samples page that provide details of how to automatically install Extensions as part of your VM deployments. Using JSON based templates allows you to make further use of parameters.json files to store all of the settings (such as Antimalware Exclusions for specific roles) along with the code that deploys and configures the VM.

 

And finally, there are hundreds of Azure Quickstart Templates resources available in the gallery. These provide JSON Template examples for tasks such as how to "Join a VM to an existing AD Domain", through to deploying "Storage Spaces Direct (S2D)" or even deploying an entire "RDS Farm" automatically.

 

I hope you found this blog post useful.

 

Cheers

Neil