Azure VMs turn off & on by schedule

save-timeIn this post I’ll show how to setup Azure VMs turn off & on by schedule using Azure Automation which is really useful for cost saving.

We’ll use dozen of actions to manage this task.

However, not all actions can be managed with PowerShell, so we’ll use old Azure portal a few times.

Initially I used one big script, but for this demo I decided to split it to make things simple.

In this particular example I’ll use two VMs running in the same cloud service.

Create management certificate

We’ll use Windows-based machine for all actions in this example, so we’ll use makecert utility (if you don’t have it yet, just install Windows ADK).

<#	
	.NOTES
	===========================================================================
	 Created on:   	Apr 2016
	 Created by:   	Dmitriy Kagarlickij
	 Contact: 	    dmitriy@kagarlickij.com
	===========================================================================
	.DESCRIPTION
		This script should be executing with Administrator-level permissions
#>

# Set variables
$tmpFolder = "C:\Users\$env:username\Documents\azure_vm_schedule-public"
$certName = "automationCert"
$certPswdPlain = "Password"

# Test makecert presence
if ((Test-Path -Path "C:\Program Files (x86)\Windows Kits\10\bin\x86\makecert.exe") -eq 'True') {
    Write-Output "makecert is ok"
} else {
    Write-Output "makecert isn't ok. Script will be stopped"
    exit
}

# Delete old certificates
Get-ChildItem -Path Cert:\CurrentUser\My -DnsName *$certName* | Remove-Item
if ((Test-Path -Path "$tmpFolder\$certName.crt") -eq 'True') {
   Remove-Item -Path "$tmpFolder\$certName.crt"
}
if ((Test-Path -Path "$tmpFolder\$certName.pfx") -eq 'True') {
        Remove-Item -Path "$tmpFolder\$certName.pfx"
}    
    
# Create management certificate (Windows 10 ADK must be installed)
Set-Location "C:\Program Files (x86)\Windows Kits\10\bin\x86"
.\makecert.exe -sky exchange -r -n "CN=$certName" -pe -a sha256 -len 2048 -ss My "$tmpFolder\$certName.cer"

# Export certificate to PFX
$certPswd = $(ConvertTo-SecureString -String $certPswdPlain -Force -AsPlainText)
$automationCert = Get-ChildItem -Path Cert:\CurrentUser\My | where {$_.Subject -match $certName}
Export-PfxCertificate -FilePath "$tmpFolder\$certName.pfx" -Password $certPswd -Cert $automationCert

 

Upload management certificate to Azure

This step requires manual action, so let’s go to the Old portal > Setting > Management certificates and click Upload:

Screen Shot 2016-04-25 at 12.16.23

If you find how it can be done with PowerShell please let me know.

Create Automation account

We’ll start with Azure PowerShell now (if you don’t have it, you can use this link – https://azure.microsoft.com/en-us/documentation/articles/powershell-install-configure/)

Let’s perform login to Azure:

 

So far we’re ready to create Azure automation account and assign certificate:

<#	
	.NOTES
	===========================================================================
	 Created on:   	Apr 2016
	 Created by:   	Dmitriy Kagarlickij
	 Contact: 	    dmitriy@kagarlickij.com
	===========================================================================
	.DESCRIPTION
		This script should be executing with Administrator-level permissions
#>

# Set variables
$tmpFolder = "C:\Users\$env:username\Documents\azure_vm_schedule-public"
$certName = "automationCert"
$certPswdPlain = "Password"
$azureAutomationAccount = "automationAccount"

# Create Azure automation account
Get-AzureAutomationAccount -Name $azureAutomationAccount -Location "East US"
if (-not $?) {    
	New-AzureAutomationAccount -Name $azureAutomationAccount -Location "East US"
} 

# Get certificate password
$certPwd = $(ConvertTo-SecureString -String $certPswdPlain -Force -AsPlainText)
   
# Assign certificate to account
Get-AzureAutomationCertificate -AutomationAccountName $azureAutomationAccount -Name $certName
if (-not $?) {
	New-AzureAutomationCertificate -AutomationAccountName $azureAutomationAccount -Name $certName -Path "$tmpFolder\$certName.pfx" -Password $certPwd
} else {
	Remove-AzureAutomationCertificate -AutomationAccountName $azureAutomationAccount -Name $certName -Force
	New-AzureAutomationCertificate -AutomationAccountName $azureAutomationAccount -Name $certName -Path "$tmpFolder\$certName.pfx" -Password $certPwd
}

 

Create Runbooks and Schedule

We’ll use two scripts to start VM, strictly speaking, first one is Azure runbook and second is for pushing runbook to Azure. With stop VM it’s exactly the same.

I don’t like to keep stuff hardcoded, so some variables will be placed in Automation account Assets and some will be suited as parameters.

Here’s another problem with PowerShell – I wasn’t able to put variable content with cyrillic symbols. It’s possible to find workaround, but let’s just save some time and use the old portal again.

Go to the Automation > automationaccount > Assets and click Add setting > Add variable (string):

Screen Shot 2016-04-25 at 13.36.24

 

Let’s take a look at Azure Start VM Runbook:

workflow Start-VM-Runbook
{
    Param (  
        [parameter(Mandatory=$true)] 
        [String] 
        $vm1, 
        
        [parameter(Mandatory=$true)] 
        [String] 
        $vm2,    
        
        [parameter(Mandatory=$true)] 
        [String] 
        $ServiceName 
    )
    
    $SubscriptionName = Get-AutomationVariable -Name "SubscriptionName" 
    $SubscriptionId = Get-AutomationVariable -Name "SubscriptionID" 
    $CertificateName = Get-AutomationVariable -Name "CertificateName" 
    $Certificate = Get-AutomationCertificate -Name $CertificateName  
    
    Set-AzureSubscription -SubscriptionName $SubscriptionName -SubscriptionId $SubscriptionId -Certificate $Certificate 
    Select-AzureSubscription $SubscriptionName

    Start-AzureVM -Name $vm1,$vm2 -ServiceName $ServiceName
}

 

Azure Stop VM Runbook is very similar, but we don’t want to lose our IP addresses (StayProvisioned parameter is used for it):

workflow Stop-VM-Runbook
{
    Param (  
        [parameter(Mandatory=$true)] 
        [String] 
        $vm1, 
        
        [parameter(Mandatory=$true)] 
        [String] 
        $vm2,    
        
        [parameter(Mandatory=$true)] 
        [String] 
        $ServiceName 
    )
    
    $SubscriptionName = Get-AutomationVariable -Name "SubscriptionName" 
    $SubscriptionId = Get-AutomationVariable -Name "SubscriptionID" 
    $CertificateName = Get-AutomationVariable -Name "CertificateName" 
    $Certificate = Get-AutomationCertificate -Name $CertificateName  
    
    Set-AzureSubscription -SubscriptionName $SubscriptionName -SubscriptionId $SubscriptionId -Certificate $Certificate 
    Select-AzureSubscription $SubscriptionName

    Stop-AzureVM -Name $vm1,$vm2 -ServiceName $ServiceName -StayProvisioned
}

 

We’ll push Start VM Runbook & set execution schedule using createStartRunbook script and here it is:

<#	
	.NOTES
	===========================================================================
	 Created on:   	Apr 2016
	 Created by:   	Dmitriy Kagarlickij
	 Contact: 	    dmitriy@kagarlickij.com
	===========================================================================
	.DESCRIPTION
		This script should be executing with Administrator-level permissions
#>

# Set variables
$tmpFolder = "C:\Users\$env:username\Documents\azure_vm_schedule-public"
$certName = "automationCert"
$azureAutomationAccount = "automationAccount"
$startVmRunbookName = "Start-VM-Runbook"
$startVmScheduleName = "Start-VM-Schedule"
$startVmScheduleStartTime = "4/26/2016 8:00:00 AM +00:00"
$startVmScheduleExpiryTime = "12/31/9999 12:00:00 AM +00:00"
$startVmScheduleDayInterval = "1"
$azureSubscriptionID = "21e736fa-3b61-4681-ba13-db7a0e34c7dd"
$FirstVMName = "kag-vm01"
$SecondVMName = "kag-vm02"
$ServiceName = "kagarlickij-vms"

#Create new runbook:
Get-AzureAutomationRunbook -AutomationAccountName $azureAutomationAccount -Name $startVmRunbookName
if (-not $?) {
    New-AzureAutomationRunbook -AutomationAccountName $azureAutomationAccount -Name $startVmRunbookName
}

#Set variables in Assets:
#  New-AzureAutomationVariable -AutomationAccountName $azureAutomationAccount -Name "SubscriptionName" -Value "Платформы MSDN" -Encrypted $false

Get-AzureAutomationVariable -AutomationAccountName $azureAutomationAccount -Name "SubscriptionID"
if (-not $?) {
    New-AzureAutomationVariable -AutomationAccountName $azureAutomationAccount -Name "SubscriptionID" -Value $azureSubscriptionID -Encrypted $false
}

Get-AzureAutomationVariable -AutomationAccountName $azureAutomationAccount -Name "CertificateName"
if (-not $?) {
    New-AzureAutomationVariable -AutomationAccountName $azureAutomationAccount -Name "CertificateName" -Value $certName -Encrypted $false
}

#Set new runbook content:
Set-AzureAutomationRunbookDefinition -AutomationAccountName $azureAutomationAccount -Name $startVmRunbookName -Path "$tmpFolder\$startVmRunbookName.ps1" -Overwrite

#Publish created runbook:
Publish-AzureAutomationRunbook -AutomationAccountName $azureAutomationAccount -Name $startVmRunbookName
    
# Set schedule
Get-AzureAutomationSchedule -AutomationAccountName $azureAutomationAccount -Name $startVmScheduleName
if (-not $?) {
    New-AzureAutomationSchedule -AutomationAccountName $azureAutomationAccount -Name $startVmScheduleName `
    -StartTime $startVmScheduleStartTime -ExpiryTime $startVmScheduleExpiryTime -DayInterval $startVmScheduleDayInterval
} 

#Link Schedule to runbook:
Get-AzureAutomationScheduledRunbook -AutomationAccountName $azureAutomationAccount -Name $startVmRunbookName
if (-not $?) {
    Register-AzureAutomationScheduledRunbook -AutomationAccountName $azureAutomationAccount -Name $startVmRunbookName `
    -ScheduleName $startVmScheduleName -Parameters @{"vm1"=$FirstVMName;"vm2"=$SecondVMName;"ServiceName"=$ServiceName}
}

 

And almost the same for Stop VM:

<#	
	.NOTES
	===========================================================================
	 Created on:   	Apr 2016
	 Created by:   	Dmitriy Kagarlickij
	 Contact: 	    dmitriy@kagarlickij.com
	===========================================================================
	.DESCRIPTION
		This script should be executing with Administrator-level permissions
#>

# Set variables
$tmpFolder = "C:\Users\$env:username\Documents\azure_vm_schedule-public"
$certName = "automationCert"
$azureAutomationAccount = "automationAccount"
$startVmRunbookName = "Stop-VM-Runbook"
$startVmScheduleName = "Stop-VM-Schedule"
$startVmScheduleStartTime = "4/26/2016 8:00:00 PM +00:00"
$startVmScheduleExpiryTime = "12/31/9999 12:00:00 PM +00:00"
$startVmScheduleDayInterval = "1"
$azureSubscriptionID = "21e736fa-3b61-4681-ba13-db7a0e34c7dd"
$FirstVMName = "kag-vm01"
$SecondVMName = "kag-vm02"
$ServiceName = "kagarlickij-vms"

#Create new runbook:
Get-AzureAutomationRunbook -AutomationAccountName $azureAutomationAccount -Name $startVmRunbookName
if (-not $?) {
    New-AzureAutomationRunbook -AutomationAccountName $azureAutomationAccount -Name $startVmRunbookName
}

#Set variables in Assets:
#  New-AzureAutomationVariable -AutomationAccountName $azureAutomationAccount -Name "SubscriptionName" -Value "Платформы MSDN" -Encrypted $false

Get-AzureAutomationVariable -AutomationAccountName $azureAutomationAccount -Name "SubscriptionID"
if (-not $?) {
    New-AzureAutomationVariable -AutomationAccountName $azureAutomationAccount -Name "SubscriptionID" -Value $azureSubscriptionID -Encrypted $false
}

Get-AzureAutomationVariable -AutomationAccountName $azureAutomationAccount -Name "CertificateName"
if (-not $?) {
    New-AzureAutomationVariable -AutomationAccountName $azureAutomationAccount -Name "CertificateName" -Value $certName -Encrypted $false
}

#Set new runbook content:
Set-AzureAutomationRunbookDefinition -AutomationAccountName $azureAutomationAccount -Name $startVmRunbookName -Path "$tmpFolder\$startVmRunbookName.ps1" -Overwrite

#Publish created runbook:
Publish-AzureAutomationRunbook -AutomationAccountName $azureAutomationAccount -Name $startVmRunbookName
    
# Set schedule
Get-AzureAutomationSchedule -AutomationAccountName $azureAutomationAccount -Name $startVmScheduleName
if (-not $?) {
    New-AzureAutomationSchedule -AutomationAccountName $azureAutomationAccount -Name $startVmScheduleName `
    -StartTime $startVmScheduleStartTime -ExpiryTime $startVmScheduleExpiryTime -DayInterval $startVmScheduleDayInterval
} 

#Link Schedule to runbook:
Get-AzureAutomationScheduledRunbook -AutomationAccountName $azureAutomationAccount -Name $startVmRunbookName
if (-not $?) {
    Register-AzureAutomationScheduledRunbook -AutomationAccountName $azureAutomationAccount -Name $startVmRunbookName `
    -ScheduleName $startVmScheduleName -Parameters @{"vm1"=$FirstVMName;"vm2"=$SecondVMName;"ServiceName"=$ServiceName}
}

 

Check results

You can execute runbook manually and see that it works:

Screen Shot 2016-04-25 at 15.51.35

Screen Shot 2016-04-25 at 17.29.25

 

I hope this info will be useful for you, and if you need any help feel free to use contact from on the main page.