
Remote Software Deployment on multiple Azure Linux and Windows VMs.
In this blog, we will discuss a real-time scenario of deploying software on multiple Linux and Windows Virtual machines simultaneously. Suppose you have 500 virtual machines both windows and Linux and you want to push the software to these virtual machines. Obviously, it is not a workable solution to perform the manual installation on these 500 VMs. But there is good news that Azure provides a custom script extension for remote command execution. Here is the YouTube Video to demonstrate this concept. In the subsequent section, I will explain this script.
In this blog, I will share the PowerShell code and walk-through to installing Adobe reader into multiple VM. You can customize this code for other software installations.
Approach for Software Installation
We will use this approach for software installations:
- Develop PowerShell script which installs the software on Windows Virtual Machine. We need to test this script on one of the Windows Virtual machines and make sure that the script works perfectly fine. you need to save this script with the .ps1 file extension (filename.ps1).
# Silent install Adobe Reader DC
# https://get.adobe.com/nl/reader/enterprise/
# Path for the workdir
$workdir = "c:\installer\"
# Check if work directory exists if not create it
If (Test-Path -Path $workdir -PathType Container)
{ Write-Host "$workdir already exists" -ForegroundColor Red}
else
{ New-Item -Path $workdir -ItemType directory }
# Download the installer
$source = "http://ardownload.adobe.com/pub/adobe/reader/win/AcrobatDC/1502320053/AcroRdrDC1502320053_en_US.exe"
$destination = "$workdir\adobeDC.exe"
# Check if Invoke-Webrequest exists otherwise execute WebClient
if (Get-Command 'Invoke-Webrequest')
{
Invoke-WebRequest $source -OutFile $destination
}
else
{
$WebClient = New-Object System.Net.WebClient
$webclient.DownloadFile($source, $destination)
}
# Start the installation
Start-Process -FilePath "$workdir\adobeDC.exe" -ArgumentList "/sPB /rs"
# Wait XX Seconds for the installation to finish
Start-Sleep -s 35
# Remove the installer
rm -Force $workdir\adobe*
2. Develop a shell script that installs the software on a Linux Virtual Machine and make sure to test this script on one of the Linux Virtual machines. Once your testing is over and you have finalized the script you will have to copy it into the Azure Storage account. So please note this important trick because even if you test the script it may not work as expected when you deploy it into UNIX Virtual machine with Azure custom script extension.
IMPORTANT TIP: While creating Shell script especially if you are working on Windows OS please use notepad++ and enable EOL conversion for UNIX as shown below:

Here is the shell script:
#!/bin/bash
sudo apt update
sudo apt install snapd
sudo snap install acrordrdc
Once you enable the EOL conversion for UNIX then start writing the script in Notepad++. UNIX EOL feature of Notepad++ will allow UNIX line feed.Save the script with the .sh extension (filename.sh).
3. Create an Azure Storage account and container and copy PowerShell and Shell scripts into the container.

4. Now copy the storage account access key so we can access the scripts from the storage account container and invoke them with Azure Custom Script extension.

5. Now we need to write a script that can access the storage account and invoke the scripts based on the condition of whether the machine is Windows or Linux. Let’s understand the part of the script here:
- The script will initialize variables so they can be used in the following steps. Please note that the script takes the server names from a CSV file and you need to define the path of the CSV file. It also stores the PowerShell and Shell script file names along with the Storage account container and access keys. Since we have different extension names for UNIX and windows it stores them in a separate variable.
### Installing AdobeReader on Multiple virtual machines #####
#Login to Azure via PowerShell and Azure CLI
connect-azAccount
az login
###################Script Variables###################################
#csv file path where servernames are fetched. use your own path here
$serverFilePath="C:\script\servers.csv"
#Name of the PowerShell Script
$Powershell_Script_Name="AdobeInstaller.ps1"
# container name where the custom script is stored
$Container_Name="software"
# Name of the UNIX script
$Linux_Script_Name="AdobeInstallationOnLinux.sh"
#Storage Account Name where Script is stored, Replace it with your storage account name
$storage_account_name = "demosoftware123"
# storage account key of where the custom script is stored. Please replace the storage account key from your storage account,
$storage_account_key = "RJ2A7rn1Hx325cj0zHqfE1L6ldX5/uurupRHhcrm3KvyYNkHGE40D9OCvSgtEj0EbEA9ohWfJ0cNpWn43gQVAg=="
# Assuming the state of the virtual machine is not de-allocated
$is_dellocated = $false
#Name of the Resource group where your servers are kept
$resource_group = "Demo"
$ExtensionTypeForWindows="CustomScriptExtension"
$ExtensionTypeForLinux="customScript"
#############################Script Variables End Here##########################
# this part of the script uses variables declared above.
$fileUri = @("https://"+$storage_account_name+".blob.core.windows.net/software/"+$Powershell_Script_Name)
$settings = @{"fileUris" = $fileUri};
$extensionName="Adobe_Reader_install_extension"
$protectedSettings = @{"storageAccountName" = $storage_account_name; "storageAccountKey" = $storage_account_key; "commandToExecute" = "powershell -ExecutionPolicy Unrestricted -File "+$Powershell_Script_Name};
$protectedSettingsLinux='{\"storageAccountName\":\"'+$storage_account_name+'\",\"storageAccountKey\":\"'+$storage_account_key+"\"+'"}'
#Replace it with your path
$serverFilePath="C:\Scripts\servers.csv"
$serverList = Get-Content $serverFilePath
$uriParameterForLinux='{\"fileUris\": [\"https://'+$storage_account_name+".blob.core.windows.net/software/$Linux_Script_Name\""],"+" "+'\"commandToExecute\":'+"\""./$Linux_Script_Name\"""+'}'
2. Script fetches the server names from the CSV file and then verifies if the Machine is stopped or not. If the machine is stopped it starts the machine and checks if the machine is windows or Linux. In case the machine is a windows machine it uses a custom script extension for windows otherwise it uses the CLI command for UNIX. Script also verifies if the machine is in a Generalized state then it does not takes any action. If an extension is already installed script uninstalls the extension and then starts installing it again. It repeats all the steps for each VM. Here is how you need to specify all the server names in the CSV file.

Here is the script:
#LIST OF VM comign from CSV file
foreach ($server in $serverList)
{
$vm_name=$server
# Checking if the Resource Group is valid .
if($resource_group -eq $null -or $vm_name -eq $null){
"Either Resource Group or Virtual Machine name, not present. This could be because the input variables could be misspelled. Make sure the input names are - 'ResourceGroup' and 'VirtualMachine'. " | write-output
exit
}
#### Checking if the Virtual Machine is a Windows machine or Linux Machine ########
# Obtaining the Virtual Machine object
$vm = get-AzVM -ResourceGroupName $resource_group -Name $vm_name
# Obtaining the Virtual Machine status object
$vm_status = get-AzVM -ResourceGroupName $resource_group -Name $vm_name -Status
"Displaying the status of Virtual machine...." | write-output
$vm_status.Statuses[1].DisplayStatus | write-output
"" | write-output
"" | write-output
"Checking if the VM:$vm_name is Windows or Linux." | write-output
$vm.OSProfile.WindowsConfiguration | write-output
$VmOS=""
if($vm.OSProfile.WindowsConfiguration -eq $null){
$VmOS="Linux"
}else
{
$VmOS="Windows"
}
<# IF THE VIRTUAL MACHINE IS STOPPED-DEALLOCATED, THIS SCRIPT WILL START THE VIRTUAL MACHINE, INSTALL AGENTS AND WILL DE-ALLOCATE IT
######## Checking the status of the Virtual Machine ########
IF VM is Generalized --> Do not take any action. Exit Execution
IF VM is Deallocated --> Start the Virtual Machine
IF VM is Running --> Do not take any action, Proceed with Execution
#>
if($vm_status.Statuses[1].DisplayStatus -eq "VM Generalized"){
"Virtual Machine:$vm_name is in the GENERALIZED state. Do not proceed further... " | write-output
"" | write-output
"" | write-output
exit
}
if($vm_status.Statuses[1].DisplayStatus -eq "VM deallocated"){
"Virtual Machine:$vm_name is STOPPED. Starting the virtual machine... " | write-output
$is_dellocated = $true
$vm | Start-AzVM
"Successfully started Virtual Machine:$vm_name.." | write-output
""| write-output
"" | write-output
}
if($vm_status.Statuses[1].DisplayStatus -eq "VM running"){
"Virtual Machine:$vm_name is already RUNNING. Proceeding with agents installation" | write-output
"" | write-output
"" | write-output
}
# Checking if the virtual machine already has a Custom Script Extension
$vm = get-AzVM -ResourceGroupName $resource_group -Name $vm_name
$vm_status = get-AzVM -ResourceGroupName $resource_group -Name $vm_name -Status
$vm_extensions = $vm.Extensions
foreach($vm_extensions_iterator in $vm_extensions){
if($vm_extensions_iterator.VirtualMachineExtensionType -eq $ExtensionTypeForWindows -or $vm_extensions_iterator.VirtualMachineExtensionType -eq $ExtensionTypeForLinux ){
"Extension Type for the VM:$vm_name is $vm_extensions_iterator.VirtualMachineExtensionType" | write-output
"Removing the CSE from VM:$vm_name..." | write-output
if($VmOS -eq"Windows"){
#For windows Machine
Remove-AzVMCustomScriptExtension -Name $vm_extensions_iterator.Name -ResourceGroupName $resource_group -VMName $vm_name -force
}else
{
#For Linux Machine
az vm extension delete `
--resource-group $resource_group `
--vm-name $vm_name --name $ExtensionTypeForLinux
}
"Removed the CSE from VM:$vm_name " | write-output
"" | write-output
"" | write-output
}
}
# Re-creating the Virtual Machine object, since one of the above condition - starts the virtual machine
$vm = get-AzVM -ResourceGroupName $resource_group -Name $vm_name
$vm_status = get-AzVM -ResourceGroupName $resource_group -Name $vm_name -Status
########### Installing Adobe Reader client via Azure Custom Script Extension ###########
if($vm_status.Statuses[1].DisplayStatus -eq "VM running"){
"Installing AdobeReader extension on VM:$vm_name..." | write-output
if($VmOS -eq "Windows"){
# azure powershell cmdlet to add the custom script extension to execute the powershell file
#For Windows
Set-AzVMExtension -ResourceGroupName $resource_group `
-Location $vm.Location`
-VMName $vm_name `
-Name $extensionName `
-Publisher "Microsoft.Compute" `
-ExtensionType $ExtensionTypeForWindows `
-TypeHandlerVersion "1.10" `
-Settings $settings `
-ProtectedSettings $protectedSettings `
}else
{
#For Linux
az vm extension set `
--resource-group $resource_group `
--vm-name linuxvm --name $ExtensionTypeForLinux `
--publisher Microsoft.Azure.Extensions `
--settings $uriParameterForLinux `
--protected-settings $protectedSettingsLinux
}
}
"waiting for 10 seconds..." | write-output
"" | write-output
"" | write-output
Start-Sleep -s 10
######## Stopping the Virtual machine that we had started ########
if($is_dellocated -eq $true){
"We had started the virtual machine:$vm_name before installing the Adobe Reader agent. STOPPING the virtual machine:$vm_name to preserve the initial state..." | write-output
$vm | Stop-AzVM -force
"Successfully stopped the virtual machine:$vm_name" | write-output
"" | write-output
"" | write-output
}
}
You can customize this script for any other software. Here is the end-to-end Script.
### Installing AdobeReader on Multiple virtual machines #####
#Login to Azure via PowerShell and Azure CLI
connect-azAccount
az login
###################Script Variables###################################
#csv file path where servernames are fetched. use your own path here
$serverFilePath="C:\script\servers.csv"
#Name of the PowerShell Script
$Powershell_Script_Name="AdobeInstaller.ps1"
# container name where the custom script is stored
$Container_Name="software"
# Name of the UNIX script
$Linux_Script_Name="AdobeInstallationOnLinux.sh"
#Storage Account Name where Script is stored, Replace it with your storage account name
$storage_account_name = "demosoftware123"
# storage account key of where the custom script is stored. Please replace the storage account key from your storage account,
$storage_account_key = "RJ2A7rn1Hx325cj0zHqfE1L6ldX5/uurupRHhcrm3KvyYNkHGE40D9OCvSgtEj0EbEA9ohWfJ0cNpWn43gQVAg=="
# Assuming the state of the virtual machine is not de-allocated
$is_dellocated = $false
#Name of the Resource group where your servers are kept
$resource_group = "Demo"
$ExtensionTypeForWindows="CustomScriptExtension"
$ExtensionTypeForLinux="customScript"
#############################Script Variables End Here##########################
# this part of the script uses variables declared above.
$fileUri = @("https://"+$storage_account_name+".blob.core.windows.net/software/"+$Powershell_Script_Name)
$settings = @{"fileUris" = $fileUri};
$extensionName="Adobe_Reader_install_extension"
$protectedSettings = @{"storageAccountName" = $storage_account_name; "storageAccountKey" = $storage_account_key; "commandToExecute" = "powershell -ExecutionPolicy Unrestricted -File "+$Powershell_Script_Name};
$protectedSettingsLinux='{\"storageAccountName\":\"'+$storage_account_name+'\",\"storageAccountKey\":\"'+$storage_account_key+"\"+'"}'
#Replace it with your path
$serverFilePath="C:\Scripts\servers.csv"
$serverList = Get-Content $serverFilePath
$uriParameterForLinux='{\"fileUris\": [\"https://'+$storage_account_name+".blob.core.windows.net/software/$Linux_Script_Name\""],"+" "+'\"commandToExecute\":'+"\""./$Linux_Script_Name\"""+'}'
#LIST OF VM comign from CSV file
foreach ($server in $serverList)
{
$vm_name=$server
# Checking if the Resource Group is valid .
if($resource_group -eq $null -or $vm_name -eq $null){
"Either Resource Group or Virtual Machine name, not present. This could be because the input variables could be misspelled. Make sure the input names are - 'ResourceGroup' and 'VirtualMachine'. " | write-output
exit
}
#### Checking if the Virtual Machine is a Windows machine or Linux Machine ########
# Obtaining the Virtual Machine object
$vm = get-AzVM -ResourceGroupName $resource_group -Name $vm_name
# Obtaining the Virtual Machine status object
$vm_status = get-AzVM -ResourceGroupName $resource_group -Name $vm_name -Status
"Displaying the status of Virtual machine...." | write-output
$vm_status.Statuses[1].DisplayStatus | write-output
"" | write-output
"" | write-output
"Checking if the VM:$vm_name is Windows or Linux." | write-output
$vm.OSProfile.WindowsConfiguration | write-output
$VmOS=""
if($vm.OSProfile.WindowsConfiguration -eq $null){
$VmOS="Linux"
}else
{
$VmOS="Windows"
}
<# IF THE VIRTUAL MACHINE IS STOPPED-DEALLOCATED, THIS SCRIPT WILL START THE VIRTUAL MACHINE, INSTALL AGENTS AND WILL DE-ALLOCATE IT
######## Checking the status of the Virtual Machine ########
IF VM is Generalized --> Do not take any action. Exit Execution
IF VM is Deallocated --> Start the Virtual Machine
IF VM is Running --> Do not take any action, Proceed with Execution
#>
if($vm_status.Statuses[1].DisplayStatus -eq "VM Generalized"){
"Virtual Machine:$vm_name is in the GENERALIZED state. Do not proceed further... " | write-output
"" | write-output
"" | write-output
exit
}
if($vm_status.Statuses[1].DisplayStatus -eq "VM deallocated"){
"Virtual Machine:$vm_name is STOPPED. Starting the virtual machine... " | write-output
$is_dellocated = $true
$vm | Start-AzVM
"Successfully started Virtual Machine:$vm_name.." | write-output
""| write-output
"" | write-output
}
if($vm_status.Statuses[1].DisplayStatus -eq "VM running"){
"Virtual Machine:$vm_name is already RUNNING. Proceeding with agents installation" | write-output
"" | write-output
"" | write-output
}
# Checking if the virtual machine already has a Custom Script Extension
$vm = get-AzVM -ResourceGroupName $resource_group -Name $vm_name
$vm_status = get-AzVM -ResourceGroupName $resource_group -Name $vm_name -Status
$vm_extensions = $vm.Extensions
foreach($vm_extensions_iterator in $vm_extensions){
if($vm_extensions_iterator.VirtualMachineExtensionType -eq $ExtensionTypeForWindows -or $vm_extensions_iterator.VirtualMachineExtensionType -eq $ExtensionTypeForLinux ){
"Extension Type for the VM:$vm_name is $vm_extensions_iterator.VirtualMachineExtensionType" | write-output
"Removing the CSE from VM:$vm_name..." | write-output
if($VmOS -eq"Windows"){
#For windows Machine
Remove-AzVMCustomScriptExtension -Name $vm_extensions_iterator.Name -ResourceGroupName $resource_group -VMName $vm_name -force
}else
{
#For Linux Machine
az vm extension delete `
--resource-group $resource_group `
--vm-name $vm_name --name $ExtensionTypeForLinux
}
"Removed the CSE from VM:$vm_name " | write-output
"" | write-output
"" | write-output
}
}
# Re-creating the Virtual Machine object, since one of the above condition - starts the virtual machine
$vm = get-AzVM -ResourceGroupName $resource_group -Name $vm_name
$vm_status = get-AzVM -ResourceGroupName $resource_group -Name $vm_name -Status
########### Installing Adobe Reader client via Azure Custom Script Extension ###########
if($vm_status.Statuses[1].DisplayStatus -eq "VM running"){
"Installing AdobeReader extension on VM:$vm_name..." | write-output
if($VmOS -eq "Windows"){
# azure powershell cmdlet to add the custom script extension to execute the powershell file
#For Windows
Set-AzVMExtension -ResourceGroupName $resource_group `
-Location $vm.Location`
-VMName $vm_name `
-Name $extensionName `
-Publisher "Microsoft.Compute" `
-ExtensionType $ExtensionTypeForWindows `
-TypeHandlerVersion "1.10" `
-Settings $settings `
-ProtectedSettings $protectedSettings `
}else
{
#For Linux
az vm extension set `
--resource-group $resource_group `
--vm-name linuxvm --name $ExtensionTypeForLinux `
--publisher Microsoft.Azure.Extensions `
--settings $uriParameterForLinux `
--protected-settings $protectedSettingsLinux
}
}
"waiting for 10 seconds..." | write-output
"" | write-output
"" | write-output
Start-Sleep -s 10
######## Stopping the Virtual machine that we had started ########
if($is_dellocated -eq $true){
"We had started the virtual machine:$vm_name before installing the Adobe Reader agent. STOPPING the virtual machine:$vm_name to preserve the initial state..." | write-output
$vm | Stop-AzVM -force
"Successfully stopped the virtual machine:$vm_name" | write-output
"" | write-output
"" | write-output
}
}
I hope this would be helpful!!
+ There are no comments
Add yours