Recently I was tasked with automating the power-down and power-up process for DEV/UAT servers in Azure in order to save on running costs. This was my first look into Azure automation. There is a lot of information out there on completing these tasks, but I struggled to find a complete implementation guide which also answered all my questions, so here we are…

1. The Script

I was skeptical to run any ‘ready’ made script against production infrastructure, without understanding exactly what it did, therefore I wrote the script below in stages using various sources. The script is a PowerShell Workflow, rather than just a PowerShell script, mainly for the benefit of being able to use the ‘parallel’ function within a foreach loop (allowing multiple VMs to be stopped/started simultaneously).

Parameters (all mandatory)
1. Resource Group Name
2. List of VMs (command separated list of VMs)
3. Action (stop or start)

The script performs the following tasks:
a) Parameter Verification
1. Resource Group Name – Cannot contain wildcard characters (% or *)
2. List of VMs – Cannot contain wildcard characters (% or *)
3. Action – Must be ‘stop’ or ‘start’, nothing else.

b) VM Separation
The VMs parameter is split into an array, using a comma (,) as the separator.

c) VM Processing
Based on the action specified (stop or start), the VMs are either stopped or started simultaneously.

Save the following, or whichever script you’re comfortable with, to your machine as a PowerShell (ps1) file.

workflow nzs-stop-start-vms
{
    Param (
        [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()]
        [String]
        $RGName,
        [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()]
        [String]
        $VMList,
        [Parameter(Mandatory=$true)][ValidateSet("start","stop")]
        [String]
        $Action
    )

    $connectionName = "AzureRunAsConnection"
    try
    {
        # Get the connection "AzureRunAsConnection "
        $servicePrincipalConnection=Get-AutomationConnection -Name $connectionName         

        "Logging in to Azure..."
        Add-AzureRmAccount `
            -ServicePrincipal `
            -TenantId $servicePrincipalConnection.TenantId `
            -ApplicationId $servicePrincipalConnection.ApplicationId `
            -CertificateThumbprint $servicePrincipalConnection.CertificateThumbprint
    }
    catch {
        if (!$servicePrincipalConnection)
        {
            $ErrorMessage = "Connection $connectionName not found."
            throw $ErrorMessage
        } else{
            Write-Error -Message $_.Exception
            throw $_.Exception
        }
    }

    if($RGName -match "\*" -OR $RGName -match "%") {
        write-output "You cannot use the wildcard characters for the Resource Group, exiting."
        exit
    }       

    if($VMList -match "\*" -OR $VMList -match "%") {
        write-output "You cannot use the wildcard characters in the VM List, exiting."
        exit
    }

    if($Action -ne "Start" -AND $Action -ne "Stop") {
        write-output "Action must be 'Stop' or 'Start', exiting. $action is invalid."
        exit
    }

    $VMs = $VMList.Split(",")
    [System.Collections.ArrayList]$ProcessVMs = $VMs
    
    if($Action -eq "Stop") {
        foreach -parallel ($VM in $ProcessVMs) {
            $now = Get-Date
            write-output "Stopping VM $VM at $now"
            Stop-AzureRmVM -ResourceGroupName $RGName -Name $VM -Force
        }
    }
    
    if($Action -eq "Start") {
        foreach -parallel ($VM in $ProcessVMs) {
            $now = Get-Date
            write-output "Starting VM $VM at $now"
            Start-AzureRmVM -ResourceGroupName $RGName -Name $VM
        }
    }
}

2. Azure Automation Account

In Azure, runbooks are used to house scripts, which are then scheduled to run as desired. Among other things, automation accounts contain the runbooks, schedules as well as run-as accounts, which have the necessary permissions to perform the scripted tasks.

Navigate to ‘Automation Accounts’ and click on ‘Add’. Enter the automation account name, the subscription, resource group and location. Make sure the ‘Create Azure Run As account’ option is set to ‘Yes’.

3. Run-As Account

The process above would have created a run-as account with the default permissions, which give it ‘Contributor’ rights on the whole subscription. This is somewhat dangerous. To remove these permissions, navigate to ‘Subscriptions > > Access Control (IAM)’. Select the run-as account and click on ‘Remove’.

Now we need to assign the run-as account more appropriate (granular) permissions. Go into the resource group (or groups) where your target VMs are located and give the run-as account ‘Contributor’ rights.

4. Schedules

Back in the automation account, select ‘Schedules’ under ‘Shared Resources’ and create the desired schedules. This is pretty self explanatory but I will mention that you will need one schedule per action per resource group. Therefore, if you have target VMs in two resource groups and you want to execute stop and start actions against VMs in both resource groups, you will need 4 schedules. See the limitations section below for more info on this.

5. Runbook Import and Publishing

Select ‘Runbooks’ in the automation account under ‘Process Automation’. You will see 5 tutorial runbooks already created and published, these can be deleted. Once ready, click on ‘Add a runbook > Import’ and select the ps1 file you saved to your computer previously. Make sure ‘PowerShell Workflow’ is selected under ‘Runbook type’ and click on ‘Create’.

At this point the runbook will have a ‘New’ status. In order to schedule a runbook, you must publish it. To do this, click on the runbook imported above, click the ‘Edit’ button and then the ‘Publish’ button.

6. Runbook Scheduling

Without leaving the runbook menu, click on ‘Schedules’ under ‘Resources’ and click on ‘Add a schedule’. Select one of the schedules you created above under ‘Schedules’. Then, on the ‘Parameters and run settings’ menu, enter the required parameter values and click on ‘Ok’. The schedules window on the runbook will now show you when the runbook is next scheduled to execute.

7. Logging

On the runbook itself, you can enable verbose logging via the ‘Logging and tracing’ menu under ‘Runbook settings’. The logs will be visible on the ‘Jobs’ menu once the runbook has executed at least once.

Limitations

1. A schedule cannot be reused. This means you need to create a schedule per Resource Group per Action.
2. Scheduled runbook parameters cannot be edited once created. This is rather annoying, as to remove a single VM from a runbook, you need to delete the schedule and re-create it. This feature should be available soon, as per https://feedback.azure.com/forums/246290-automation/suggestions/7655052-update-the-scheduler-after-creation.

Further Resources

https://azure.microsoft.com/en-gb/blog/azure-automation-runbook-management/
https://docs.microsoft.com/en-us/azure/automation/automation-offering-get-started
https://azure.microsoft.com/en-gb/blog/azure-automation-in-depth-runbook-authoring/
https://docs.microsoft.com/en-us/azure/automation/automation-creating-importing-runbook
https://docs.microsoft.com/en-us/azure/automation/automation-runbook-execution