Home NewsX Switch off Virtual Machines on a schedule using an Azure Tag

Switch off Virtual Machines on a schedule using an Azure Tag

by info.odysseyx@gmail.com
0 comment 14 views


1. Introduction

Managing the costs of running virtual machines (VMs) in Azure can be challenging, especially when VMs are running during non-business hours. One effective solution is to use Azure tags to schedule automatic shutdowns. In this blog, we will walk you through a PowerShell script that uses Azure tags to define and enforce a VM shutdown schedule.

2. Prerequisites

Before you begin, make sure you have the following:

  • An activated Azure subscription.
  • Appropriate permissions to manage VMs and read tag information.
  • Basic knowledge of PowerShell scripting.

3. Script Overview

The script manages the power state of Azure virtual machines based on the specified tags and their values, which define a schedule. For example, a tag called “AutoShutdownSchedule” with a value of “10PM -> 6AM” will shut down the VM at 10PM and start it at 6AM.

4. Detailed script analysis

Parameters and Initial Settings

The script accepts three parameters:

  • TagName: The name of the tag to find in the virtual machine.
  • ManagementGroupId: The ID of the Azure management group to work with.
  • Simulate: If set $trueThe script only simulates the action without making any changes.
param (
    [parameter(Mandatory = $true)]
    [string]$TagName,
 
    [parameter(Mandatory = $true)]
    [string]$ManagementGroupId,
 
    [parameter(Mandatory = $false)]
    [bool]$Simulate = $false
)
Function: Get-SubscriptionsUnderManagementGroup

This function retrieves all subscription IDs that belong to the specified Azure management group.

 

function Get-SubscriptionsUnderManagementGroup {
    param (
        [Parameter(Mandatory = $true)]
        [string]$ManagementGroupId
    )

    # Array to store subscription IDs
    $subscriptionIds = @()

    # Get the management group hierarchy
    $managementGroup = Get-AzManagementGroup -GroupId $ManagementGroupId -Expand

    if ($managementGroup -and $managementGroup.Children) {       
        # Loop through each child in the management group
        foreach ($child in $managementGroup.Children) {
            if ($child.Type -eq "Microsoft.Management/managementGroups") {
                # Recursively get subscriptions from child management groups
                $childManagementGroupId = $child.Name
                $subscriptionIds += Get-SubscriptionsUnderManagementGroup -ManagementGroupId $childManagementGroupId
            } elseif ($child.Type -match "/subscriptions") {
                # Extract subscription ID
                $subscriptionId = [regex]::Match($child.Name, "([a-f0-9-]{36})").Value
                if ($subscriptionId) {
                    $subscriptionIds += $subscriptionId
                }
            }
        }
    }

    return $subscriptionIds
}

Function: CheckScheduleEntry

This function checks whether the current time is within a specified time range.

function CheckScheduleEntry ([string]$TimeRange) {  
    $rangeStart, $rangeEnd, $parsedDay = $null
    $currentTime = (Get-Date).ToUniversalTime().AddHours(2)
    $midnight = $currentTime.AddDays(1).Date
 
    try {
        if ($TimeRange -like "*->*") {
            $timeRangeComponents = $TimeRange -split "->" | ForEach-Object { $_.Trim() }
            if ($timeRangeComponents.Count -eq 2) {
                $rangeStart = Get-Date $timeRangeComponents[0]
                $rangeEnd = Get-Date $timeRangeComponents[1]
 
                if ($rangeStart -gt $rangeEnd) {
                    if ($currentTime -ge $rangeStart -and $currentTime -lt $midnight) {
                        $rangeEnd = $rangeEnd.AddDays(1)
                    }
                    else {
                        $rangeStart = $rangeStart.AddDays(-1)
                    }
                }
            }
            else {
                Write-Output "`WARNING: Invalid time range format. Expects valid .Net DateTime-formatted start time and end time separated by '->'" 
            }
        }
        else {
            if ([System.DayOfWeek].GetEnumValues() -contains $TimeRange) {
                if ($TimeRange -eq (Get-Date).DayOfWeek) {
                    $parsedDay = Get-Date "00:00"
                }
            }
            else {
                $parsedDay = Get-Date $TimeRange
            }
 
            if ($parsedDay -ne $null) {
                $rangeStart = $parsedDay
                $rangeEnd = $parsedDay.AddHours(23).AddMinutes(59).AddSeconds(59)
            }
        }
    }
    catch {
        Write-Output "`WARNING: Exception encountered while parsing time range. Details: $($_.Exception.Message). Check the syntax of entry, e.g. ' -> ', or days/dates like 'Sunday' and 'December 25'"   
        return $false
    }
 
    if ($currentTime -ge $rangeStart -and $currentTime -le $rangeEnd) {
        return $true
    }
    else {
        return $false
    }
}

Function: AssertVirtualMachinePowerState

This feature ensures that VMs are in the desired power state (running or stopped) on a scheduled basis.

function AssertVirtualMachinePowerState {
    param (
        [Object]$VirtualMachine,
        [string]$DesiredState,
        [bool]$Simulate
    )
 
    $resourceManagerVM = Get-AzVM -ResourceGroupName $VirtualMachine.ResourceGroupName -Name $VirtualMachine.Name -Status
    $currentStatus = $resourceManagerVM.Statuses | Where-Object { $_.Code -like "PowerState*" }
    $currentStatus = $currentStatus.Code -replace "PowerState/", ""
 
    if ($DesiredState -eq "Started" -and $currentStatus -notmatch "running") {
        if ($Simulate) {
            Write-Output "[$($VirtualMachine.Name)]: SIMULATION -- Would have started VM. (No action taken)"
        }
        else {
            Write-Output "[$($VirtualMachine.Name)]: Starting VM"
            $resourceManagerVM | Start-AzVM
        }
    }
    elseif ($DesiredState -eq "StoppedDeallocated" -and $currentStatus -ne "deallocated") {
        if ($Simulate) {
            Write-Output "[$($VirtualMachine.Name)]: SIMULATION -- Would have stopped VM. (No action taken)"
        }
        else {
            Write-Output "[$($VirtualMachine.Name)]: Stopping VM"
            $resourceManagerVM | Stop-AzVM -Force
        }
    }
    else {
        Write-Output "[$($VirtualMachine.Name)]: Current power state [$currentStatus] is correct."
    }
}

The script iterates through all VMs in a given subscription, checks tags, and applies power states according to a schedule. Here's an overview of what the script does:

try {
    # Main script logic
}
catch {
    $errorMessage = $_.Exception.Message
    throw "Unexpected exception: $errorMessage"
}
finally {
    Write-Output "Script finished (Duration: $(("{0:hh\:mm\:ss}" -f ((Get-Date).ToUniversalTime() - $currentTime))))"
}

5. Usage examples

To run the script, use the following command:

.\StartStopVMsBasedOnTag.ps1 -TagName "AutoShutdownSchedule" -ManagementGroupId "MngEnv" -Simulate $true

We run the examples in local PowerShell, but PowerShell can run anywhere, including within your Automation account.

5.1 We tag the virtual machines accordingly.

Bernal_0-1722513240849.png

5.2 After you log in to Azure, examine the PowerShell code and run the commands.

Bernal_1-1722513686491.png

5.3 We used the "-Simulate $true" flag to show what would have happened. If you want to run this in production, you can first simulate and then, when you are satisfied with the test, switch "-Simulate $false" to stop the simulation.

Berneral_2-1722513896428.png

6. Conclusion

Automating VM shutdown schedules using Azure tags can help you optimize resource usage and reduce costs. Following this guide will help you implement a similar solution in your Azure environment. If you have any questions or feedback, please leave a comment below. A copy of this code can be found in my GitHub Repo --> RallTheory/StartStopVMs/StartStopVMsBasedOnTag.ps1 at main · WernerRall147/RallTheory (github.com)

disclaimer

The sample script is not supported by any Microsoft standard support program or service. The sample script or Power BI dashboard is provided AS IS without warranty of any kind. Microsoft further disclaims all implied warranties, including but not limited to the implied warranties of merchantability or fitness for a particular purpose. The entire risk arising out of the use or performance of the sample script and documentation remains with you. In no event shall Microsoft, the authors, or anyone else involved in the creation, production, or delivery of the script or Power BI dashboard be liable for any damages whatsoever (including but not limited to damages for loss of business profits, business interruption, loss of business information, or other pecuniary loss) arising out of the use of or inability to use the sample script or documentation, even if Microsoft has been advised of the possibility of such damages. This blog post was created with the assistance of Generation AI.





Source link

You may also like

Leave a Comment

Our Company

Welcome to OdysseyX, your one-stop destination for the latest news and opportunities across various domains.

Newsletter

Subscribe my Newsletter for new blog posts, tips & new photos. Let's stay updated!

Laest News

@2024 – All Right Reserved. Designed and Developed by OdysseyX