Contents
#>
##########################################################################################################
###############################
## 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
nt"
[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 "
n"tExtension
$($ VMExtensionName ) - Installation Results
Write-Host "Installation Successful:
t
t $($Global : SuccessCount ) "
Write-Host "Already Installed:
t
tt
$($Global : AlreadyInstalledCount ) "
Write-Host "Installation Failed:
tt
t $($Global : FailedCount ) "
Write-Host "VMs Not Running:
t
tt
$($Global : VMsNotRunningCount ) "
Write-Host "Extension Not Compatible with OS:
t $($Global : OSNotCompatibleCount ) n"
Write-Host "Total VMs Processed:
tt
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 *
-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…
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%SoftwareDistributionDatastoreDataStore.edb ; %windir%SoftwareDistributionDatastoreLogsEdb.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:scriptsIaaSAntimalware-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.
"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
resources available in the gallery. These provide
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.