I wrote a blog post on how to run scripts in your Azure VM by using Run Command, and explained how handy this feature is to manage Azure virtual machines (VMs). In this blog post, we are going to have a look at how you can run scripts against multiple Azure virtual machines (VMs) by using PowerShell and the Invoke-AzVMRunCommand feature.
Usually, you can access your Azure virtual machine (VM) in multiple ways, like SSH or RDP. However, if you have issues with the RDP or SSH network configuration, or don’t have any network access at all, the Run Command feature is another option. Run Command can run a PowerShell or shell script within an Azure VM remotely by using the Azure Virtual Machine Agent. This scenario is especially useful when you need to run scripts against Azure VMs where you do not have network access.
You use Run Command for Azure VMs through the Azure portal, REST API, Azure CLI, or PowerShell. Like I showed you in my blog post on Microsoft Tech Community.
Using Azure PowerShell
You can also use Azure PowerShell to use the run command capabilities to run PowerShell scripts against the guest agent inside the Azure VM. For that, you can simply use the Invoke-AzVMRunCommand
cmdlet from the Az PowerShell module. You can also run this command directly from Azure Cloud Shell as well.
How to run PowerShell scripts against multiple Azure VMs by using Run Command in Parallel
Now here is how you can use PowerShell 7 and the Azure PowerShell module, to run scripts against multiple Azure VMs in parallel. For that, I am using a simple Foreach-Object to run the script in “script.ps1” against all my Azure VMs in a specific resource group. By default, this would take some time because it would run through all the virtual machines in sequential order. However, with PowerShell 7 we can use the -Parallel
parameter to run the commands in parallel.
#Azure Subscription I want to use
$subscriptionId = "XXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
#Resource Group my VMs are in
$resourceGroup = "test-azurevms-rg"
#Select the right Azure subscription
Set-AzContext -Subscription $subscriptionId
#Get all Azure VMs which are in running state and are running Windows
$myAzureVMs = Get-AzVM -ResourceGroupName $resourceGroup -status | Where-Object {$_.PowerState -eq "VM running" -and $_.StorageProfile.OSDisk.OSType -eq "Windows"}
#Run the scirpt again all VMs in parallel
$myAzureVMs | ForEach-Object -Parallel {
$out = Invoke-AzVMRunCommand `
-ResourceGroupName $_.ResourceGroupName `
-Name $_.Name `
-CommandId 'RunPowerShellScript' `
-ScriptPath .\script.ps1
#Formating the Output with the VM name
$output = $_.Name + " " + $out.Value[0].Message
$output
}
I also modified the output, so it shows the VM name I have run the script against, and I selected only the Message output of Invoke-AzVMRunCommand.
You can also check out my video on YouTube:
Conclusion
I hope this blog post helps you to run PowerShell scripts against multiple Azure virtual machines (VM) in parallel using the VM run command. If you have any questions feel free to leave a comment.
Tags: automation, AzRunCommand, Azure, Azure VM, Azure VMs, Microsoft, PowerShell, Run, script, shell, Virtual Machine, VM Last modified: March 18, 2021
Hallo,
das war sehr hilfreich für mich. Habe da aber mal eine Frage:
Kann man über den Guest Agent der Azure VM auch (File) Ressourcen kopieren ohne Netzwerk?
Bzw. gibt es eine Möglichkeit im Nachhinein ohne Netzwerkzugriff Files zu kopieren?
vG Olaf
Hi Thomas, this is great for Azure VMs. Can I do this for arc enabled servers that are on-prem? If not, what would be the best option to run a script for on-prem?
This feature is currently only available for Azure VMs. However, if you want to run scripts against on-prem VMs, you have a couple of options. With Azure Arc, you can use the script extension: https://www.thomasmaurer.ch/2020/07/how-to-run-custom-scripts-on-azure-arc-enabled-servers/
or you can use Azure Automation Hybrid Workers: https://docs.microsoft.com/en-us/azure/automation/automation-hybrid-runbook-worker?WT.mc_id=modinfra-0000-thmaure
Depends a little bit on your scenario. :)
Do you have a solution or run into cases where passing multiple parameters/variables across using the command Invoke-AzVMRunCommand, the ordering gets mixed up? If I pass one parameter across it works, use two and it will take the last parameter in the list. Doesn’t seem to be matching up the name of the parameter or ordering.
func app ps file
*****************************************
$paramscmd = [ordered]@{
HostName = $HostName;
HostNameDescription = $HostNameDescription;
}
$out = Invoke-AzVMRunCommand -ResourceGroupName $RemoteCom_ResourceGroupName `
-VMName $RemoteCom_Name `
-CommandId ‘RunPowerShellScript’ `
-ScriptPath ./TimerTrigger1/ActiveDirectoryDescTagUpdate.ps1 `
-Parameter $paramscmd
$out.Value.Message
ActiveDirectoryDescTagUpdate.ps1 File
*****************************************
Param (
[string] $HostName,
[string] $HostNameDescription
)
(Get-ADComputer $HostName -properties description).description
Hi Thomas,
Thanks for the your valuable post.
I need to check and change to (GMT Standard Time) zone of all the Windows VM across 6 subscriptions (under one tenant). Is that can be done using power shell?
I would recommend using Azure Policy for that, there is even a built-in one!
I have an example here: https://www.thomasmaurer.ch/2021/03/audit-server-settings-with-azure-policy-guest-configuration/
https://portal.azure.com/#blade/Microsoft_Azure_Policy/PolicyDetailBlade/definitionId/%2Fproviders%2FMicrosoft.Authorization%2FpolicyDefinitions%2Fc633f6a2-7f8b-4d9e-9456-02f0f04f5505
This is fantastic and exactly what I was looking for. Thank you!
Is there any way to run a script across all Windows VMs in the directory? I presume we could query Azure for a listing of all subscriptions and all RGs under it to then execute the script across all of the VMs in that directory/tenant?
Thank you.
Yes, correct, just make sure you set the context to the right subscription when your done with all the vms in one subscription.
Hi Thomas,
Thakk you for your script. This has saved alot of time! One thing though, the -Parallel switch doesn’t seem to be working for me, unless I’ve misunderstood? At first I thought “great, this will run on all the VMs at the same time” however it did one at a time. I see now that the -Parallel switch is PS 7 and above. So I upgraded my PS to version 7. Still the same. Appears to only be running on one VM at a time?
Luke
Yes it needs PowerShell 7 an above.
Do you get an error?
My co-worker actually did a quick modify today and set it up to run on all RGs in a subscription which still helped speed up the process considerably.
#Azure Subscription I want to use
$subscriptionId = “XXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX”
#Select the right Azure subscription
Set-AzContext -Subscription $subscriptionId
Get-AzSubscription | ForEach-Object {
$subscriptionName = $_.Name
Set-AzContext -SubscriptionId $_.SubscriptionId
(Get-AzResourceGroup).ResourceGroupName | ForEach-Object {
Write-Host “Resource group $_”
$myAzureVMs = Get-AzVM -ResourceGroupName $_ -status | Where-Object {$_.PowerState -eq “VM running” -and $_.StorageProfile.OSDisk.OSType -eq “Windows”}
$myAzureVMs | ForEach-Object -Parallel {
Write-Host ” VM:” $_.Name
$out = Invoke-AzVMRunCommand `
-ResourceGroupName $_.ResourceGroupName `
-Name $_.Name `
-CommandId ‘RunPowerShellScript’ `
-ScriptPath .\Spooler-Service-Stop-Disable-Verify.ps1
#Formating the Output with the VM name
$output = $_.Name + ” ” + $out.Value[0].Message
Write-Host ” Output:” $output
}
}
}
Hi thomas,
Is there a way to start the service in services.msc through script without logging in to Azure VM ?
Using InvokeAzVMRunCommand or something ? thanks in advance
Yes, you can for exmaple run the following command:
Invoke-AzVMRunCommand -ResourceGroupName ‘myrg’ -VMName ‘myvmname’ -CommandId ‘RunPowerShellScript’ -ScriptPath ‘myscript.ps1’
In myscript.ps1 you write:
Start-Service -Name “theservicename”
Or run it in the Azure Portal, see here how: https://techcommunity.microsoft.com/t5/itops-talk-blog/how-to-run-scripts-in-your-azure-vm-by-using-run-command/ba-p/1362360?WT.mc_id=modinfra-18451-thmaure
Hi
Is it possible can we link this script via azure runbook to all vms in the subscription on a specific day ?
Hi Thomas,
How this can be achieved for Linux VM in azure.
Hi Thomas,
For longer scripts that I want to run often, is it possible to add a custom default script to the eleven that Microsoft provides, or do I have to use ‘RunPowerShell Script’ every time and paste in my code?
Thanks
Hello
I got a case for run a PowerShell script to install some software on VM. I plan to use Runbook with managed identity. In that runbook I will gather all VMs from the RG, and than run the script which would be downloaded to the VM from Blob. All those actions needs to be done from runbook. I do not want to use Set-AzureVMCustomScriptExtension for that as it needs to be scheduled action per RG. Can I use Invoke-AzVMRunCommand with the localpath as a value for -script paramter ?
Hi Thomas,
I have multiple subscription and at one go I need to start it parallelly but it is not working for me. below is my code
$subs | ForEach-Object -parallel {
Select-AzSubscription -Subscription “$($_.name)” -ErrorAction Stop -ErrorVariable errorvar
$vmdetails+=Get-AzVM -Status | Where-Object {$_.StorageProfile.OsDisk.OsType -eq ‘Windows’ -and $_.PowerState -eq ‘VM running’}
$vmdetails
}
$vmdetails | ForEach-Object -Parallel {
$Extensions = Get-AzVMExtension -VMName $_.Name -ResourceGroupName $_.ResourceGroupName
if($Extensions.ProvisioningState -notcontains ‘Failed’)
{
$out2=Invoke-AzVMRunCommand -ResourceGroupName $_.ResourceGroupName -Name $_.Name -CommandId ‘RunPowerShellScript’ -ScriptPath .\RunScript.ps1
}
}
$ subs contains all my subscription details
If I remove parallel for subs then code works like a charm
Hi Thomas,
How this can be achieved for Linux VM in azure.
Hi,
I’m getting an error that says:
ForEach-Object : Parameter set cannot be resolved using the specified named parameters.
At line:1 char:15
+ $myAzureVMs | ForEach-Object -Parallel {
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : MetadataError: (:) [ForEach-Object], ParameterBindingException
+ FullyQualifiedErrorId : AmbiguousParameterSet,Microsoft.PowerShell.Commands.ForEachObjectCommand
Hi Are you PowerShell version 7? https://www.thomasmaurer.ch/2019/07/how-to-install-and-update-powershell-7/
Thank you that was the problem I was using powershell v5.
Thomas, if I wanted to use this script to copy a .exe from repository to the server, can it be done?
How might you scope this to the subscription level. I’m struggling with doing so. I found one of the folks that commented but it seems like his script loops through each subscription and then through each resource and then the next subscription and so on….
Any help would be greatly appreciated.
Hey Thomas, thank for the excellent Video! hey can you help me solve this –
Invoke-AzAksRunCommand `
-ResourceGroupName $resourceGroup `
-Name gwae-util-01 `
-CommandID ‘RunPowerShellScript’ `
-ScriptPath ./testscript.ps1
—————————————————— Error Response
Invoke-AzAksRunCommand:
Line |
4 | -CommandID ‘RunPowerShellScript’ `
| ~~~~~~~~~~
| A parameter cannot be found that matches parameter name ‘CommandID’.
scriptstring instead of scriptpath will let you run it in line