Sunday, 5 January 2020

Schedule start and stop of Azure Virtual Machine (Az)

Case
There is an option to automatically shutdown your Azure Virtual Machine at a certain time, but where is the option to automatically start it at a certain time?
Auto-shutdown Auzre VM















Solution
In 2017 we showed you how to solve this with a scheduled PowerShell Runbook in Azure Automation. That post used the old Azure_RM PowerShell modules which will no longer be supported by the end of 2020. Therefor we have rewritten the code to use the Az PowerShell modules and made a few tweaks and extra checks. It can also shutdown the VM if you want to do everything in one place.

1) Create Automation Account
First we need to create an Automation Account to host our PowerShell code. If you already have one with the Run As Account enabled then you can skip this step.
  • Go to the Azure portal and create a new resource
  • Search for automation
  • Select Automation Account
  • Choose a useful name for the Automation Account
  • Select your Subscription, Resource Group and the Region
  • For this example we will use the Azure Run As account. So make sure to enable it and then click on the Create button.
Create Azure Automation Account















2) Add Module Az.Compute
Before we start writing code we need to add a PowerShell module called Az.Compute. This module contains Virtual Machine methods we need in our code. But first we need to add Az.Accounts because Az.Compute depends on it.

If you forget this step you will get error messages while running your code that state that some of your commands are not recognized:
Get-AzVM : The term 'Get-AzVM' is not recognized as the name of a cmdlet, function, script 
file, or operable program. Check the spelling of the name, or if a path was included, verify that the path is correct 
and try again.
  • Go to the newly created Azure Automation Account
  • Go to Modules in the left menu
  • Click on the Browse Gallery button
  • Search for Az.Accounts
  • Click on Az.Accounts in the result and import this module
  • Also search for Az.Compute (but wait until Az.Accounts is actually imported)
  • Click on Az.Compute in the result and import this module
Adding a new module to your Automation Account















Note: if you are using an existing Automation Account then you probably already added Az.Accounts.

3) Runbook
Now we are ready to create a runbook in the Azure Automation Account and start writing some PowerShell code.
  • Go back to the overview page of your newly created Azure Automation Account
  • Click on Runbooks in the left menu
  • Click on the + Create a runbook button to create a new Runbook
  • Enter a descriptive name for the Runbook like StartStopVM
  • Select PowerShell as Runbook type
  • Optionally add a description and click on the Create button
Create a Runbook














4) Edit Script
Next edit the new Runbook if it wasn't already opened by the previous step. Copy the code below and paste it in the editor. Then study the code and its comments to understand the code that can both start and stop your Azure Virtual Machine (VM). It exists of five parts:
  1. Parameters
  2. Log in to Azure
  3. Get current state
  4. Pause or Resume
  5. Logging
Parameters
To pause or resume the script needs three parameters. The first parameter 'VirtualMachineAction' is a string that indicates whether you want to stop or start the VM. The second parameter 'ResourceGroupName' indicates the location (resourcegroup) of your VM and the last parameter 'VirtualMachineName' is the name of your VM. There are a couple of validations which you could extend to make your script even more monkey proof.

Log in to Azure
This is a standard piece of code that you will see in all of our examples. Please read our blog post about the Azure Run as Account for more detailed information.

Get current state
This piece of code tests whether it can find the VM and gets its current state. It stores the current state and uses it later on for an extra check when stopping or starting the VM.

Stop or Start
This is the actual code for stopping or starting the VM. There is an extra check to compare the current state with the new desired state. It now throws an error when you want to stop a VM that is already stopped. You could change that to write an warning instead of an error.
Error






Note: you could also send emails to notify you of any errors

Logging
The last piece of code is for logging purposes. It shows you that it successfully changed the state of the VM and how long it took to accomplish that.

# PowerShell code

########################################################
# Parameters
########################################################
[CmdletBinding()]
param(
    [Parameter(Mandatory=$True,Position=0)]
    [ValidateSet('Start','Stop')]
    [string]$VirtualMachineAction,
    
    [Parameter(Mandatory=$True,Position=1)]
    [ValidateLength(1,100)]
    [string]$ResourceGroupName,

    [Parameter(Mandatory=$True,Position=2)]
    [ValidateLength(1,100)]
    [string]$VirtualMachineName
)

# Keep track of time
$StartDate=(GET-DATE)



########################################################
# Log in to Azure with AZ (standard code)
########################################################
Write-Verbose -Message 'Connecting to Azure'
 
# Name of the Azure Run As connection
$ConnectionName = 'AzureRunAsConnection'
try
{
    # Get the connection properties
    $ServicePrincipalConnection = Get-AutomationConnection -Name $ConnectionName       
  
    'Log in to Azure...'
    $null = Connect-AzAccount `
        -ServicePrincipal `
        -TenantId $ServicePrincipalConnection.TenantId `
        -ApplicationId $ServicePrincipalConnection.ApplicationId `
        -CertificateThumbprint $ServicePrincipalConnection.CertificateThumbprint 
}
catch 
{
    if (!$ServicePrincipalConnection)
    {
        # You forgot to turn on 'Create Azure Run As account' 
        $ErrorMessage = "Connection $ConnectionName not found."
        throw $ErrorMessage
    }
    else
    {
        # Something else went wrong
        Write-Error -Message $_.Exception.Message
        throw $_.Exception
    }
}
########################################################
 



########################################################
# Getting the VM for testing and logging purposes
########################################################
$myVirtualMachine = Get-AzVM -ResourceGroupName $ResourceGroupName -Name $VirtualMachineName
if (!$myVirtualMachine)
{
    Write-Error "$($VirtualMachineName) not found in $($ResourceGroupName)"
    return
}
else
{
    # Get status of VM when user provides data for existing VM
    $myVirtualMachineState = ((Get-AzVM -ResourceGroupName $ResourceGroupName -Name $VirtualMachineName -Status).Statuses | Where {$_.code -like 'PowerState/*'}).Code
    Write-Output "Current status of $($VirtualMachineName): $($myVirtualMachineState)"
}



########################################################
# Start or Stop VM
########################################################
# Check for incompatible actions
if (($VirtualMachineAction -eq "Start" -And $myVirtualMachineState -eq "PowerState/running") -Or ($VirtualMachineAction -eq "Stop" -And $myVirtualMachineState -eq "PowerState/deallocated"))
{
    Write-Error "Cannot $($VirtualMachineAction) $($VirtualMachineName) while the status is $($myVirtualMachineState)"
    return
}
# Resume Azure Analysis Services
elseif ($VirtualMachineAction -eq "Start")
{
    Write-Output "Now starting $($VirtualMachineName)"
    Start-AzVM -ResourceGroupName $ResourceGroupName -Name $VirtualMachineName -Confirm:$false
}
# Pause Azure Analysis Services
else
{
    Write-Output "Now stopping $($VirtualMachineName)"
    $null = Stop-AzVM -ResourceGroupName $ResourceGroupName -Name $VirtualMachineName -Confirm:$false -Force
}



########################################################
# Show when finished
########################################################
$Duration = NEW-TIMESPAN –Start $StartDate –End (GET-DATE)
Write-Output "Done in $([int]$Duration.TotalMinutes) minute(s) and $([int]$Duration.Seconds) second(s)"

5) Testing
Testing the functionality of your code can be done in the runbook editor. Click on the Test pane button above your script. After that you need to fill in the parameters and hit the Start button to execute the script.
Testing your script















6) Scheduling Runbook
To schedule your runbook in Azure Automation you first need to publish it via the Runbook editor. After it has been published you can add a schedule to this runbook.
  • Edit the script in the runbook editor
  • Click on publish (the editor will close and you will be redirected to the overview page)
  • In the overview page click on Link to schedule
  • In the Schedule menu you can select an existing schedule or create a new one
  • In the Parameter menu you can provide the value for the parameters
Add schedule a runbook















Note: If you have multiple Azure Virtual Machines that you all want to start on the same time then you have a slight problem because you cannot reuse a schedule for the same runbook multiple times with different parameters (please upvote or add a comment). Workarounds:
  1. create multiple identical schedules (ugly but works)
  2. do everything in one big script (less flexible but works)
Log of runbook executions (start VM on weekdays)














Summary
In this post you saw how you can start an Azure Virtual Machine with PowerShell because the menu only supports auto-shutdown. With just a few lines of code and a schedule you can accomplish an auto-startup.
If you also add a webhook to this runbook then you could call this runbook with PowerShell code from other applications. For example Microsoft Automate (Flow) or a Power App on your mobile. In a future blog post we will show that.