Use Azure Automation accounts, Run Books and Schedules to start stop VMs automatically running in Azure

By using this article, we can start/stop VMs during off-business houses.This greatly benefits the customers especially in cost optimization and manual task overhead of performing this action manually. But we need to make sure that the VMs that we are selecting is present in the same subscription where the automation account and this schedule is created by selecting only the required VMs and excluding the other VMs.

Login to Azure portal

Go to ALL Services and Type Automation Account and Create Automation Account.

Under Process Automation, click Runbooks to open the list of runbooks.

Click on + Create a runbook button to create a new runbook

Provide and Name and Runbook Type as PowerShell for the new runbook and then click Create button.

The Run Book is Empty, must deploy the Code  then Save and Publish

Note: You can Find lot of powershell scripts for this task in the github and technet gallery. You can use the below one as well taken from github.

#PowerShell Script to Start and Stop VM's in Azure on Scheduled Intervals using Azure Automation Account#
Param(
[Parameter(Mandatory=$true,HelpMessage="Enter the value for Action. Values can be either stop or start")][String]$Action,
[Parameter(Mandatory=$false,HelpMessage="Enter the value for WhatIf. Values can be either true or false")][bool]$WhatIf = $false,
[Parameter(Mandatory=$false,HelpMessage="Enter the VMs separated by comma(,)")][string]$VMList
)

function ValidateVMList ($FilterVMList)
{
[boolean] $ISexists = $false
[string[]] $invalidvm=@()
$ExAzureVMList=@()

foreach($filtervm in $FilterVMList) 
{
    $currentVM = Get-AzureRmVM | where Name -Like $filtervm.Trim()  -ErrorAction SilentlyContinue

    if ($currentVM.Count -ge 1)
    {
        $ExAzureVMList+= @{Name = $currentVM.Name; Location = $currentVM.Location; ResourceGroupName = $currentVM.ResourceGroupName; Type = "ResourceManager"}
        $ISexists = $true
    }
    elseif($ISexists -eq $false)
    {
        $invalidvm = $invalidvm+$filtervm                
    }
}

if($invalidvm -ne $null)
{
    Write-Output "Runbook Execution Stopped! Invalid VM Name(s) in the VM list: $($invalidvm) "
    Write-Warning "Runbook Execution Stopped! Invalid VM Name(s) in the VM list: $($invalidvm) "
    exit
}
else
{
    return $ExAzureVMList
}
}

function CheckExcludeVM ($FilterVMList)
{
[boolean] $ISexists = $false
[string[]] $invalidvm=@()
$ExAzureVMList=@()
<pre><code>foreach($filtervm in $FilterVMList) 
{
    $currentVM = Get-AzureRmVM | where Name -Like $filtervm.Trim()  -ErrorAction SilentlyContinue
    if ($currentVM.Count -ge 1)
    {
        $ExAzureVMList+=$currentVM.Name
        $ISexists = $true
    }
    elseif($ISexists -eq $false)
    {
        $invalidvm = $invalidvm+$filtervm
    }

}
if($invalidvm -ne $null)
{
    Write-Output "Runbook Execution Stopped! Invalid VM Name(s) in the exclude list: $($invalidvm) "
    Write-Warning "Runbook Execution Stopped! Invalid VM Name(s) in the exclude list: $($invalidvm) "
    exit
}
else
{
    Write-Output "Exclude VM's validation completed..."
}    </code></pre>
}
<h1>-----L O G I N - A U T H E N T I C A T I O N-----</h1>
$connectionName = "AzureRunAsConnection"
try
{
# Get the connection "AzureRunAsConnection "
$servicePrincipalConnection=Get-AutomationConnection -Name $connectionName
<pre><code>"Logging in to Azure..."
Add-AzureRmAccount `
    -ServicePrincipal `
    -TenantId $servicePrincipalConnection.TenantId `
    -ApplicationId $servicePrincipalConnection.ApplicationId `
    -CertificateThumbprint $servicePrincipalConnection.CertificateThumbprint </code></pre>
}
catch
{
if (!$servicePrincipalConnection)
{
$ErrorMessage = "Connection $connectionName not found."
throw $ErrorMessage
} else{
Write-Error -Message $_.Exception
throw $_.Exception
}
}
<h1>---------Read all the input variables---------------</h1>
$StartResourceGroupNames = Get-AutomationVariable -Name 'RG-Of-VMs-To-Start'
$StopResourceGroupNames = Get-AutomationVariable -Name 'RG-Of-VMs-To-Stop'
$ExcludeVMNames = Get-AutomationVariable -Name 'Excluded-List-Of-VMs'

try
{
$Action = $Action.Trim().ToLower()
<pre><code>    if(!($Action -eq "start" -or $Action -eq "stop"))
    {
        Write-Output "`$Action parameter value is : $($Action). Value should be either start or stop."
        Write-Output "Completed the runbook execution..."
        exit
    }            
    Write-Output "Runbook Execution Started..."
    [string[]] $VMfilterList = $ExcludeVMNames -split ","
    #If user gives the VM list with comma seperated....
    $AzurVMlist = Get-AutomationVariable -Name $VMList
    if(($AzurVMlist))
    {
        Write-Output "list of  all VMs = $($AzurVMlist)"
        [string[]] $AzVMList = $AzurVMlist -split ","        
    }
    if($Action -eq "stop")
    {
        Write-Output "List of  all ResourceGroups = $($StopResourceGroupNames)"
        [string[]] $VMRGList = $StopResourceGroupNames -split ","
    }

    if($Action -eq "start")
    {
        Write-Output "List of  all ResourceGroups = $($StopResourceGroupNames)"
        [string[]] $VMRGList = $StartResourceGroupNames -split ","
    }

    #Validate the Exclude List VM's and stop the execution if the list contains any invalid VM
    if (([string]::IsNullOrEmpty($ExcludeVMNames) -ne $true) -and ($ExcludeVMNames -ne "none"))
    {
        Write-Output "Values exist on the VM's Exclude list. Checking resources against this list..."
        CheckExcludeVM -FilterVMList $VMfilterList 
    } 
    $AzureVMListTemp = $null
    $AzureVMList=@()

    if ($AzVMList -ne $null)
    {
        ##Action to be taken based on VM List and not on Resource group.
        ##Validating the VM List.
        Write-Output "VM List is given to take action (Exclude list will be ignored)..."
        $AzureVMList = ValidateVMList -FilterVMList $AzVMList 
    } 
    else
    {

        ##Getting VM Details based on RG List or Subscription
        if (($VMRGList -ne $null) -and ($VMRGList -ne "*"))
        {
            foreach($Resource in $VMRGList)
            {
                Write-Output "Validating the resource group name ($($Resource))"  
                $checkRGname = Get-AzureRmResourceGroup -Name $Resource.Trim() -ev notPresent -ea 0  

                if ($checkRGname -eq $null)
                {
                    Write-Warning "$($Resource) is not a valid Resource Group Name. Please verify your input"
                }
                else
                {    
                    # Get resource manager VM resources in group and record target state for each in table
                    $taggedRMVMs =  Get-AzureRmVM | ? { $_.ResourceGroupName -eq $Resource} 
                     foreach($vmResource in $taggedRMVMs)
                    {
                        if ($vmResource.ResourceGroupName -Like $Resource)
                        {
                            $AzureVMList += @{Name = $vmResource.Name; ResourceGroupName = $vmResource.ResourceGroupName; Type = "ResourceManager"}
                        }
                    }
                }
            }
        } 
        else
        {
            Write-Output "Getting all the VM's from the subscription..."  
            $ResourceGroups = Get-AzureRmResourceGroup 

            foreach ($ResourceGroup in $ResourceGroups)
            {    

                # Get resource manager VM resources in group and record target state for each in table
                $taggedRMVMs = Get-AzureRmVM | ? { $_.ResourceGroupName -eq $ResourceGroup.ResourceGroupName}

                foreach($vmResource in $taggedRMVMs)
                {
                    Write-Output "RG : $($vmResource.ResourceGroupName) , ARM VM $($vmResource.Name)"
                    $AzureVMList += @{Name = $vmResource.Name; ResourceGroupName = $vmResource.ResourceGroupName; Type = "ResourceManager"}
                }
            }

        }
    }

    $ActualAzureVMList=@()

    if($AzVMList -ne $null)
    {
        $ActualAzureVMList = $AzureVMList
    }
    #Check if exclude vm list has wildcard       
    elseif(($VMfilterList -ne $null) -and ($VMfilterList -ne "none"))
    {
        $ExAzureVMList = ValidateVMList -FilterVMList $VMfilterList 

        foreach($VM in $AzureVMList)
        {  
            ##Checking Vm in excluded list                         
            if($ExAzureVMList.Name -notcontains ($($VM.Name)))
            {
                $ActualAzureVMList+=$VM
            }
        }
    }
    else
    {
        $ActualAzureVMList = $AzureVMList
    }

    Write-Output "The current action is $($Action)"

    $ActualVMListOutput=@()

    if($WhatIf -eq $false)
    {   
        $AzureVMListARM=@()


        # Store the ARM VM's
        $AzureVMListARM = $ActualAzureVMList | Where-Object {$_.Type -eq "ResourceManager"}


        # process the ARM VM's
        if($AzureVMListARM -ne $null)
        {
            foreach($VM in $AzureVMListARM)
            {  
                $ActualVMListOutput = $ActualVMListOutput + $VM.Name + " "
                #ScheduleSnoozeAction -VMObject $VM -Action $Action

                #------------------------
                try
                {          
                    Write-Output "VM action is : $($Action)"
                    Write-OutPut $VM.ResourceGroupName

                    $VMState = Get-AzureRmVM -ResourceGroupName $VM.ResourceGroupName -Name $VM.Name -status | Where-Object { $_.Name -eq  $VM.Name }
                    if ($Action.Trim().ToLower() -eq "stop")
                    {
                        Write-Output "Stopping the VM : $($VM.Name)"
                        Write-Output "Resource Group is : $($VM.ResourceGroupName)"


                        if($VMState.Statuses[1].DisplayStatus -ne "VM running")
                        {
                            Write-Output "VM is not in running state, No actions performed"
                        }
                        else
                        {
                            $Status = Stop-AzureRmVM -Name $VM.Name -ResourceGroupName $VM.ResourceGroupName -Force

                            if($Status -eq $null)
                            {
                                Write-Output "Error occured while stopping the Virtual Machine."
                            }
                            else
                            {
                                Write-Output "Successfully stopped the VM $($VM.Name)"
                            }            
                        }

                    }
                    elseif($Action.Trim().ToLower() -eq "start")
                    {
                        Write-Output "Starting the VM : $($VM.Name)"
                        Write-Output "Resource Group is : $($VM.ResourceGroupName)"

                        if($VMState.Statuses[1].DisplayStatus -eq "VM running")
                        {
                            Write-Output "VM already Running, No actions performed"
                        }
                        else
                        {
                            $Status = Start-AzureRmVM -Name $VM.Name -ResourceGroupName $VM.ResourceGroupName

                            if($Status -eq $null)
                            {
                                Write-Output "Error occured while starting the Virtual Machine $($VM.Name)"
                            }
                            else
                            {
                                Write-Output "Successfully started the VM $($VM.Name)"
                            }
                        }
                    }      

                }
                catch
                {
                    Write-Output "Error Occurred..."
                    Write-Output $_.Exception
                }
                #--------------------------
            }
            Write-Output "Completed the $($Action) action on the following ARM VMs: $($ActualVMListOutput)"
        }


    }
    elseif($WhatIf -eq $true)
    {
        Write-Output "WhatIf parameter is set to True..."
        Write-Output "When 'WhatIf' is set to TRUE, runbook provides a list of Azure Resources (e.g. VMs), that will be impacted if you choose to deploy this runbook."
        Write-Output "No action will be taken at this time..."
        Write-Output $($ActualAzureVMList) 
    }
    Write-Output "Runbook Execution Completed..."
}
catch
{
    $ex = $_.Exception
    Write-Output $_.Exception
}

Now in Automation Account, under Shared Resources click Variables and add these variables [Note: while creating the variables you have to provide some Value, later you can delete the value if required]


Explanation of these variables-

RG-Of-VMs-To-Start: ResourceGroup that contains VMs to be started. Must be in the same subscription that the Azure Automation Run-As account has permission to manage.

RG-Of-VMs-To-Stop: ResourceGroup that contains VMs to be stopped. Must be in the same subscription that the Azure Automation Run-As account has permission to manage.

Excluded-List-Of-VMs: VM names to be excluded from being started/Stopped

Note: These Variables are defined in the PowerShell Code and should not be changed if used by default.

VMstoStartStop: Provide the list of the VMs to be started or stopped.

Now we will create schedule for the start & stop of VMs. Under Shared Resources, click on Schedules and then click + Add a schedule and provide a Name and time and recurrence frequency for the schedule and Click Create

Similarly create schedule for Stop_VM


Now we will attach these schedules with the runbook. Go to Runbook and then open the runbook, click schedules and then + Add a schedule

Now link Start_VM and then provide parameter values. ACTION can have values like start or stop. In VMLIST provide the variable name “VMstoStartStop” which contains the VM names. Click create

Similarly attach the Stop_VM and provide the Action value Stop and VMList value VMstoStartstop.

So now we have two schedules start-vm and stop-vm which would be running on the defined schedules.

Now your runbook will execute the start of VM and stop of VM as per the schedule attached. The results of runbook execution can be seen under Process Automation –> Jobs.

This task greatly benefits the hassle of maintaining and performing this task manually from the admin side.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d bloggers like this: