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."