3 min read

Azure Automation RunBook Virtual Machine

Azure Automation RunBook Virtual Machine

Azure VM Tags to use Runbook

Automation Powershell script (powershell version 7.1)

# Ensures you do not inherit an AzContext in your runbook
Disable-AzContextAutosave -Scope Process
# Connect to Azure with system-assigned managed identity
$AzureContext = (Connect-AzAccount -Identity -Subscription "XXXXXXXX").context
$subscriptions = Get-AzSubscription | select-object Id -ExpandProperty Id
foreach ($subscription in $subscriptions) {
	# Set and store context
	Remove-AzContext -InputObject (Get-AzContext) -Force | Out-Null;
	$AzureContext = (Connect-AzAccount -Identity -Subscription $subscription).context
	#Get all VMs that should be part of the Schedule:
	$VMs = Get-AzResource -ResourceType "Microsoft.Compute/VirtualMachines" -TagName "Operational-Schedule" -TagValue "Yes"
	Write-Output "Processing Subscription $($subscription) and number of VMs $($VMs.length)..."
	foreach ($VM in $VMs) {
		Write-Output "Processing VM $($VM.Name)..."
		$TimeZone = $($VM.Tags)."Operational-Timezone"
		Write-Output "VM defined timezone is ($TimeZone), see for available timzones: https://docs.microsoft.com/en-us/windows-hardware/manufacture/desktop/default-time-zones?view=windows-11"
		try{
			$TimeZoneAdjusted = [System.TimeZoneInfo]::ConvertTimeBySystemTimeZoneId([DateTime]::Now,$TimeZone)
		}catch{
			Write-Output "Failed to set VM timezone due to wrong timezone configured, Norway time defaults,, see for correct timzone: https://docs.microsoft.com/en-us/windows-hardware/manufacture/desktop/default-time-zones?view=windows-11"
			$TimeZoneAdjusted = [System.TimeZoneInfo]::ConvertTimeBySystemTimeZoneId([DateTime]::Now,"W. Europe Standard Time")
		}
		Write-Output "Current time of VM after attempting adjusting the Time Zone to Operational-Timezone is: $TimeZoneAdjusted"
		### Current Time associations
		$Day = $TimeZoneAdjusted.DayOfWeek
		If ($Day -like "S*") {
			$TodayIsWeekend = $true
			$TodayIsWeekday = $false
		} else {
			$TodayIsWeekend = $false
			$TodayIsWeekday = $true
		}
		### Get Exclusions
		$Exclude = $false
		$Reason = ""
		$Exclusions = $($VM.Tags)."Operational-Exclusions"
		$Exclusions = $Exclusions.Split(',')
		foreach ($Exclusion in $Exclusions) {
			#Check excluded actions:
			If ($Exclusion.ToLower() -eq "stop") {$VMActionExcluded = "Stop"}
			If ($Exclusion.ToLower() -eq "start") {$VMActionExcluded = "Start"}
			#Check excluded days and compare with current day
			If ($Exclusion.ToLower() -like "*day") {
				if ($Exclusion -eq $Day) { $Exclude = $true; $Reason=$Day}
			}
			#Check excluded weekdays and copare with Today
			If ($Exclusion.ToLower() -eq "weekdays") {
					if ($TodayIsWeekday) {$Exclude = $true; $Reason="Weekday"}
			}
			#Check excluded weekends and compare with Today
			If ($Exclusion.ToLower() -eq "weekends") {
				if ($TodayIsWeekend) {$Exclude = $true; $Reason="Weekend"}
			}
			If ($Exclusion -eq (Get-Date -UFormat "%b %d")) {
				$Exclude = $true; $Reason = "Date Excluded"
			}
		}
		if (!$Exclude) {
			#Get values from Tags and compare to the current time
			if ($TodayIsWeekday) {
				$ScheduledTime = $($VM.Tags)."Operational-Weekdays"
				Write-Output "VM is scheduled to run between ($ScheduledTime) on Weekdays."
			} elseif ($TodayIsWeekend) {
				$ScheduledTime = $($VM.Tags)."Operational-Weekends"
				Write-Output "VM is scheduled to run between ($ScheduledTime) on Weekends."
			}
			if ($ScheduledTime) {
				$ScheduledTime = $ScheduledTime.Split("-")
				$ScheduledStart = $ScheduledTime[0]
				$ScheduledStop = $ScheduledTime[1]
				$ScheduledStartTime = Get-Date $ScheduledStart
				$ScheduledStopTime = Get-Date $ScheduledStop
				If (($TimeZoneAdjusted -gt $ScheduledStartTime) -and ($TimeZoneAdjusted -lt $ScheduledStopTime)) {
					#Current time is within the interval
					Write-Output "VM should be running now"
					$VMAction = "Start"
				} else {
					#Current time is outside of the operational interval
					Write-Output "VM should be stopped now"
					$VMAction = "Stop"
				}
				If ($VMAction -notlike "$VMActionExcluded") { #Make sure that action was not excluded
					#Get VM PowerState status
					$VMObject = Get-AzVM -ResourceGroupName $VM.ResourceGroupName -Name $VM.Name -Status
					$VMState = ($VMObject.Statuses | Where-Object Code -like "*PowerState*").DisplayStatus
					if (($VMAction -eq "Start") -and ($VMState -notlike "*running")) {
						Write-Output "Starting $($VM.Name)..."
						Start-AzVM -ResourceGroupName $VM.ResourceGroupName -Name $VM.Name
					} elseif (($VMAction -eq "Stop") -and ($VMState -notlike "*deallocated")) {
						Write-Output "Stopping $($VM.Name)..."
						Stop-AzVM -ResourceGroupName $VM.ResourceGroupName -Name $VM.Name -Force
					} else {
						Write-Output "VM $($VM.Name) status is: $VMState . No action will be performed ..."
					}
				} else {
					Write-Output "VM $($VM.Name) is Excluded from changes during this run because Operational-Exclusions Tag contains action $VMAction."
				}
			} else {
				Write-Output "Error: Scheduled Running Time for VM was not detected. No action will be performed..."
			}
		} else {
			Write-Output "VM $($VM.Name) is Excluded from changes during this run because Operational-Exclusions Tag contains exclusion $Reason."
		}
}
}
Write-Output "Runbook completed."