Stop Microsoft Azure Virtual Machines in Parallel using PowerShell

Managing virtual machines (VMs) in Microsoft Azure can be a frustrating experience using the web-based management portal. In my article “Stop or Start VMs in a Microsoft Azure Subscription Using PowerShell Loop” on the Petri IT Knowledgebase, I showed you how to stop all VMs in a given subscription using a simple loop.

That method runs the Stop-AzureVM command in series against all VMs in a subscription, no matter whether they have started or not. Although the Stop-AzureVM command serves a purpose, it’s kind of dumb and slow. In this Ask the Admin, I’ll show you how to stop running Microsoft Azure virtual machines in parallel using PowerShell.

PowerShell vs. VBScript Code Execution

If you are used to automating admin tasks using VBScript, then you probably know that when a script runs, the parser doesn’t wait for each command to complete before moving to the next line of code. Although this can be handy in some situations, it’s hard to determine the status of a task once it’s been launched.

PowerShell solves this problem by moving to the next line of code only when the previous command has returned a result. That’s why in a simple Foreach-Object loop as shown below, we need to wait for each Stop-AzureVM command to return a successful result, meaning until the status of the VM changes to Stopped (Deallocated), before the next VM can be stopped. As you can imagine, if you have a lot of VMs in your subscription, it can take some time before the script completes.

​ Get-AzureService | Foreach-Object { Stop-AzureVM -ServiceName $_.ServiceName -Name "*" –force –verbose }

PowerShell Background Jobs

The above Foreach-Object loop is fine as a simple method for getting all the VMs in your subscription stopped, but it’s not particularly intelligent or fast. PowerShell Background Jobs lets us run more than one script-block simultaneously, and it’s the perfect solution for the problem outlined above. Not only can I run cmdlets in parallel, but I can have the results of each task displayed at the end of the script so I can be sure the VMs have stopped.

Stop Virtual Machines in Azure

Make sure that you have installed the PowerShell tools for Azure on your management workstation and set up a secure connection to your subscription using the instructions in the “Setup Windows Azure PowerShell Management” article on Petri.

  1. Start at least one VM on Azure.
  2. Log in to your management workstation, press WINDOWS to switch to the Start menu, type powershell and press CTRL+SHIFT+ENTER. Enter administrative credentials or give consent if prompted.
  3. In the PowerShell prompt, type get-azurevm and press ENTER.

You should now see that running VMs are shown with the status ReadyRole. In the first part of the script, we can filter out any VMs that don’t have the ReadyRole status and set the number of jobs to null:

​ $jobs = @()
$vms = get-azurevm | ?{ $_.Status -eq "ReadyRole"}

Next we need to create a foreach loop to launch a Background Job (Start-Job) to stop each running VM (Stop-AzureVM). The name ($vm.name) and cloud service name ($vm.servicename) are passed to the job as an argument list for each VM, as they are required by the Stop-AzureVM cmdlet. You should note that this script only works where each VM is assigned to a dedicated cloud service. Unless you are load balancing two or more VMs, it’s best practice to assign a unique cloud service to each VM.

​ foreach ($vm in $vms)
{
   $params = @($vm.name, $vm.servicename)
   $job = start-job -scriptblock {
     param($computername, $servicename)
     stop-azurevm -name $computername -servicename $servicename -force
     } -argumentlist $params
   $jobs = $jobs + $job
}

The next part of the script adds some basic if/else logic to do one of two things:

  • determine that no VMs were running and use the Get-AzureVM command to show to status of each VM
  • wait for the jobs to stop any VMs that were found to be running, and then display the results of each job (Get-Job/Receive-Job).

The –ne operator in the command below signifies not equal to, and @() means null.

​ If($jobs -ne @())
  {
  write-host "Waiting for jobs to complete..." -foregroundcolor yellow -backgroundcolor red
  wait-job -job $jobs
  get-job | receive-job
  }
Else
  {
  write-host "There were no running VMs" -foregroundcolor yellow -backgroundcolor red
  get-azurevm
  }

Stop Microsoft Azure Virtual Machines in Parallel using PowerShell

Stopping Azure VMs in parallel with PowerShell Jobs. (Image: Russell Smith)

And that’s the end of the script, which you can see in its complete form below. Don’t forget that it should be run with local administrator privileges.

​ $jobs = @()
$vms = get-azurevm | ?{ $_.Status -eq "ReadyRole"}
foreach ($vm in $vms)
{
   $params = @($vm.name, $vm.servicename)
   $job = start-job -scriptblock {
     param($computername, $servicename)
     stop-azurevm -name $computername -servicename $servicename -force
     } -argumentlist $params
   $jobs = $jobs + $job
}
If($jobs -ne @())
  {
  write-host "Waiting for jobs to complete..." -foregroundcolor yellow -backgroundcolor red
  wait-job -job $jobs
  get-job | receive-job
  }
Else
  {
  write-host "There were no running VMs" -foregroundcolor yellow -backgroundcolor red
  get-azurevm
  }