Start DevTest Lab machines in order using Azure Runbooks


The auto-start feature within DevTest Labs is great but what if machines need to be started in a specific order.  There are several scenarios where this type of automation would be useful.  One scenario is where a Jumpbox VM within the lab needs to be started first, before the other VMs, as the Jumpbox is used as the access point to the other VMs.  This post will walk through setting up an Azure Automation account with a PowerShell runbook that will execute a script.   The script uses tags on the individual VMs in the lab to allow individual Lab Owners / VM owners to control the startup order without having to change the script.

Setup

For this example the VMs in the DevTest lab will need to have the tag “StartupOrder” added with the appropriate value (0,1,2,etc) for startup.  Designate any machine that will not be started as -1.

Azure Automation

Automation account

You’ll need to have an Azure Subscription with a DevTest Lab where the VMs reside.  We’ll start with creating a new Azure Automation account, be sure to include the “Run As Accounts”.  Once the automation account is created, open the “Modules” blade and “Update Azure Modules” on the menu bar.  The default modules are several versions old and without the update the script may not function.

Runbook

Now to add a runbook to the automation account, select “Runbooks” on the left pane which will show the tutorial scripts.  Select “Add a runbook” in the menu and create a PowerShell runbook.

PowerShell script

The following script will take the subscription name, the DevTest Lab name.  The flow of the script is to get all the VMs in the lab, then parse out the tag information to create a list of the VM name and the startup order.  The script walks through the VMs in order and starts the VMs, if there are multiple VMs in a specific order number they will be started asynchronously using PowerShell jobs.  For those VMs that don’t have a tag, set startup value to be the last (10), those will be started last by default.  If the lab doesn’t want the VM to be auto-started setting the tag value to 11 and it will be ignored.

<#

The MIT License (MIT)

Copyright (c) Microsoft Corporation

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

.SYNOPSIS

This script starts the VMs in a DevTest Lab based on the “StartupOrder” tag

.PARAMETER SubscriptionId

The subscription ID that the lab is created in.

.PARAMETER LabName

The Lab Name.

.NOTES

The script assumes that the subscription exists, the DTL exists and the user has access

#>

#Requires -Version 3.0

#Requires -Module AzureRM.Resources

param

(

[Parameter(Mandatory=$false, HelpMessage="The Subscription Name containing the DevTest lab")]

[string] $SubscriptionName,

[Parameter(Mandatory=$false, HelpMessage="The DevTest lab name")]

[string] $LabName

)

# Connect and add the appropriate subscription

$Conn = Get-AutomationConnection -Name AzureRunAsConnection

Add-AzureRMAccount -ServicePrincipal -Tenant $Conn.TenantID -ApplicationID $Conn.ApplicationId -Subscription $SubscriptionName -CertificateThumbprint $Conn.CertificateThumbprint

# Find the lab

$dtLab = Find-AzureRmResource -ResourceType 'Microsoft.DevTestLab/labs' -ResourceNameEquals $LabName

# Get the VMs

$dtlAllVms = New-Object System.Collections.ArrayList

$AllVMs = Get-AzureRmResource -ResourceId "$($dtLab.ResourceId)/virtualmachines" -ApiVersion 2016-05-15

# Get the StartupOrder tag, if missing set to be run last (10)

ForEach ($vm in $AllVMs) {

if ($vm.Tags) {

if ($vm.Tags['StartupOrder']) {

$startupValue = $vm.Tags['StartupOrder']

} else {

$startupValue = 10

}

} else {

$startupValue = 10

}

$dtlAllVms.Add(@{$vm.Name = $startupValue}) > $null

}

# Setup for the async multiple vm start

# Save profile

$profilePath = Join-Path $env:Temp "profile.json"

If (Test-Path $profilePath){

Remove-Item $profilePath

}

Save-AzureRmContext -Path $profilePath

# Job to start VMs asynch

$startVMBlock = {

Param($devTestLab,$vmToStart,$profilePath)

Import-AzureRmContext -Path ($profilePath)

Invoke-AzureRmResourceAction `

-ResourceId "$($devTestLab.ResourceId)/virtualmachines/$vmToStart" `

-Action Start `

-Force

Write-Output "Started: $vmToStart"

}

$current = 0

# Start in order from 0 to 10

While ($current -le 10) {

# Get the VMs in the current stage

$tobeStarted = $null

$tobeStarted = $dtlAllVms | Where-Object { $_.Values -eq $current}

if ($tobeStarted.Count -eq 1) {

# Run sync – jobs not necessary for a single VM

$returnStatus = Invoke-AzureRmResourceAction `

-ResourceId "$($dtLab.ResourceId)/virtualmachines/$($tobeStarted.Keys)" `

-Action Start `

-Force

Write-Output "$($tobeStarted.Keys) status: $($returnStatus.status)"

} elseif ($tobeStarted.Count -gt 1) {

# Start multiple VMs async

$jobs = @()

Write-Output "Start Jobs start: $(Get-Date)"

# Jobs

$jobs += Start-Job -ScriptBlock $startVMBlock -ArgumentList $dtLab, $($singlevm.Keys), $profilePath

Write-Output "Start Jobs end: $(Get-Date)"

}

# Get results from all jobs

if($jobs.Count -ne 0)                                                                                                    {

Write-Output "Receive Jobs start: $(Get-Date)"

foreach ($job in $jobs){

$jobResult = Receive-Job -Job $job -Wait | Write-Output

}

Remove-Job -Job $jobs -Force

}

else

{

Write-Output "Information: No jobs available"

}

}

}

Setup schedule

To have this script execute daily, a schedule will need to be created in the automation account.  Once the schedule is setup link it to the runbook.

In a large-scale situation, where there may be multiple subscriptions with multiple labs, store the parameter information in a file for the different labs and pass the file to the script instead of the individual parameters.  The script would need to be modified but the core execution would be the same.

While this sample uses the Azure Automation to execute the PowerShell script, there are other options like as a task in a Build/Release pipeline or a service that this will function with.

If you have any questions about DevTest Labs, please check out the MSDN forum.

Hope this helps.

clip_image002[6] Roger Best, Senior Software Engineer

Roger is part of the Visual Studio and .NET engineering team focused on Visual Studio and Azure customers. He has been at Microsoft for 19 years, focusing on developer technologies for the past decade or so. In his spare time, he watches too many movies, and tries to survive triathlons.


Comments (0)

Skip to main content