1- param (
1+ <#
2+ . SYNOPSIS
3+ This Azure Automation runbook automates the scheduled shutdown and startup of virtual machines in an Azure subscription.
4+
5+ . DESCRIPTION
6+ The runbook implements a solution for scheduled power management of Azure virtual machines in combination with tags
7+ on virtual machines or resource groups which define a shutdown schedule. Each time it runs, the runbook looks for all
8+ virtual machines or resource groups with a tag named "AutoShutdownSchedule" having a value defining the schedule,
9+ e.g. "10PM -> 6AM". It then checks the current time against each schedule entry, ensuring that VMs with tags or in tagged groups
10+ are shut down or started to conform to the defined schedule.
11+
12+ This is a PowerShell runbook, as opposed to a PowerShell Workflow runbook.
13+
14+ This runbook requires the "Azure" and "AzureRM.Resources" modules which are present by default in Azure Automation accounts.
15+ For detailed documentation and instructions, see:
16+
17+ https://automys.com/library/asset/scheduled-virtual-machine-shutdown-startup-microsoft-azure
18+
19+ . PARAMETER AzureCredentialName
20+ The name of the PowerShell credential asset in the Automation account that contains username and password
21+ for the account used to connect to target Azure subscription. This user must be configured as co-administrator and owner
22+ of the subscription for best functionality.
23+
24+ By default, the runbook will use the credential with name "Default Automation Credential"
25+
26+ For for details on credential configuration, see:
27+ http://azure.microsoft.com/blog/2014/08/27/azure-automation-authenticating-to-azure-using-azure-active-directory/
28+
29+ . PARAMETER AzureSubscriptionName
30+ The name or ID of Azure subscription in which the resources will be created. By default, the runbook will use
31+ the value defined in the Variable setting named "Default Azure Subscription"
32+
33+ . PARAMETER Simulate
34+ If $true, the runbook will not perform any power actions and will only simulate evaluating the tagged schedules. Use this
35+ to test your runbook to see what it will do when run normally (Simulate = $false).
36+
37+ . EXAMPLE
38+ For testing examples, see the documentation at:
39+
40+ https://automys.com/library/asset/scheduled-virtual-machine-shutdown-startup-microsoft-azure
41+
42+ . INPUTS
43+ None.
44+
45+ . OUTPUTS
46+ Human-readable informational and error messages produced during the job. Not intended to be consumed by another runbook.
47+ #>
48+
49+ param (
50+ [parameter (Mandatory = $false )]
251 [String ] $AzureCredentialName = " Use *Default Automation Credential* Asset" ,
3- [String ] $AzureSubscriptionName = " Use *Default Azure Subscription* Variable Value"
52+ [parameter (Mandatory = $false )]
53+ [String ] $AzureSubscriptionName = " Use *Default Azure Subscription* Variable Value" ,
54+ [parameter (Mandatory = $false )]
55+ [bool ]$Simulate = $false
456)
557
58+ $VERSION = " 2.0"
59+
660# Define function to check current time against specified range
761function CheckScheduleEntry ([string ]$TimeRange )
862{
@@ -96,19 +150,20 @@ function AssertVirtualMachinePowerState
96150 [Object ]$VirtualMachine ,
97151 [string ]$DesiredState ,
98152 [Object []]$ResourceManagerVMList ,
99- [Object []]$ClassicVMList
153+ [Object []]$ClassicVMList ,
154+ [bool ]$Simulate
100155 )
101156
102157 # Get VM depending on type
103158 if ($VirtualMachine.ResourceType -eq " Microsoft.ClassicCompute/virtualMachines" )
104159 {
105160 $classicVM = $ClassicVMList | where Name -eq $VirtualMachine.Name
106- AssertClassicVirtualMachinePowerState - VirtualMachine $classicVM - DesiredState $DesiredState
161+ AssertClassicVirtualMachinePowerState - VirtualMachine $classicVM - DesiredState $DesiredState - Simulate $Simulate
107162 }
108163 elseif ($VirtualMachine.ResourceType -eq " Microsoft.Compute/virtualMachines" )
109164 {
110165 $resourceManagerVM = $ResourceManagerVMList | where Name -eq $VirtualMachine.Name
111- AssertResourceManagerVirtualMachinePowerState - VirtualMachine $resourceManagerVM - DesiredState $DesiredState
166+ AssertResourceManagerVirtualMachinePowerState - VirtualMachine $resourceManagerVM - DesiredState $DesiredState - Simulate $Simulate
112167 }
113168 else
114169 {
@@ -121,21 +176,36 @@ function AssertClassicVirtualMachinePowerState
121176{
122177 param (
123178 [Object ]$VirtualMachine ,
124- [string ]$DesiredState
179+ [string ]$DesiredState ,
180+ [bool ]$Simulate
125181 )
126182
127183 # If should be started and isn't, start VM
128184 if ($DesiredState -eq " Started" -and $VirtualMachine.PowerState -notmatch " Started|Starting" )
129185 {
130- Write-Output " [$ ( $VirtualMachine.Name ) ]: Starting VM"
131- $VirtualMachine | Start-AzureVM
186+ if ($Simulate )
187+ {
188+ Write-Output " [$ ( $VirtualMachine.Name ) ]: SIMULATION -- Would have started VM. (No action taken)"
189+ }
190+ else
191+ {
192+ Write-Output " [$ ( $VirtualMachine.Name ) ]: Starting VM"
193+ $VirtualMachine | Start-AzureVM
194+ }
132195 }
133196
134197 # If should be stopped and isn't, stop VM
135198 elseif ($DesiredState -eq " StoppedDeallocated" -and $VirtualMachine.PowerState -ne " Stopped" )
136199 {
137- Write-Output " [$ ( $VirtualMachine.Name ) ]: Stopping VM"
138- $VirtualMachine | Stop-AzureVM - Force
200+ if ($Simulate )
201+ {
202+ Write-Output " [$ ( $VirtualMachine.Name ) ]: SIMULATION -- Would have stopped VM. (No action taken)"
203+ }
204+ else
205+ {
206+ Write-Output " [$ ( $VirtualMachine.Name ) ]: Stopping VM"
207+ $VirtualMachine | Stop-AzureVM - Force
208+ }
139209 }
140210
141211 # Otherwise, current power state is correct
@@ -150,7 +220,8 @@ function AssertResourceManagerVirtualMachinePowerState
150220{
151221 param (
152222 [Object ]$VirtualMachine ,
153- [string ]$DesiredState
223+ [string ]$DesiredState ,
224+ [bool ]$Simulate
154225 )
155226
156227 # Get VM with current status
@@ -161,15 +232,29 @@ function AssertResourceManagerVirtualMachinePowerState
161232 # If should be started and isn't, start VM
162233 if ($DesiredState -eq " Started" -and $currentStatus -notmatch " running" )
163234 {
164- Write-Output " [$ ( $VirtualMachine.Name ) ]: Starting VM"
165- $resourceManagerVM | Start-AzureRmVM
235+ if ($Simulate )
236+ {
237+ Write-Output " [$ ( $VirtualMachine.Name ) ]: SIMULATION -- Would have started VM. (No action taken)"
238+ }
239+ else
240+ {
241+ Write-Output " [$ ( $VirtualMachine.Name ) ]: Starting VM"
242+ $resourceManagerVM | Start-AzureRmVM
243+ }
166244 }
167245
168246 # If should be stopped and isn't, stop VM
169247 elseif ($DesiredState -eq " StoppedDeallocated" -and $currentStatus -ne " deallocated" )
170248 {
171- Write-Output " [$ ( $VirtualMachine.Name ) ]: Stopping VM"
172- $resourceManagerVM | Stop-AzureRmVM - Force
249+ if ($Simulate )
250+ {
251+ Write-Output " [$ ( $VirtualMachine.Name ) ]: SIMULATION -- Would have stopped VM. (No action taken)"
252+ }
253+ else
254+ {
255+ Write-Output " [$ ( $VirtualMachine.Name ) ]: Stopping VM"
256+ $resourceManagerVM | Stop-AzureRmVM - Force
257+ }
173258 }
174259
175260 # Otherwise, current power state is correct
@@ -185,7 +270,15 @@ $VerbosePreference = "Continue"
185270try
186271{
187272 $currentTime = (Get-Date ).ToUniversalTime()
188- Write-Output " Runbook started"
273+ Write-Output " Runbook started. Version: $VERSION "
274+ if ($Simulate )
275+ {
276+ Write-Output " *** Running in SIMULATE mode. No power actions will be taken. ***"
277+ }
278+ else
279+ {
280+ Write-Output " *** Running in LIVE mode. Schedules will be enforced. ***"
281+ }
189282 Write-Output " Current UTC/GMT time [$ ( $currentTime.ToString (" dddd, yyyy MMM dd HH:mm:ss" )) ] will be checked against schedules"
190283
191284 # Retrieve credential
213306 $account = Add-AzureAccount - Credential $azureCredential
214307
215308 # Check for returned userID, indicating successful authentication
216- if (-not (Get-AzureAccount - Name $azureCredential.UserName ))
309+ if (Get-AzureAccount - Name $azureCredential.UserName )
310+ {
311+ Write-Output " Successfully authenticated as user: $ ( $azureCredential.UserName ) "
312+ }
313+ else
217314 {
218315 throw " Authentication failed. Ensure a valid Azure Active Directory user account is specified which is configured as a co-administrator on the target subscription. Verify you can log into the Azure portal using these credentials."
219316 }
281378 {
282379 # VM has direct tag (possible for resource manager deployment model VMs). Prefer this tag schedule.
283380 $schedule = ($vm.Tags | where Name -eq " AutoShutdownSchedule" )[" Value" ]
284- Write-Output " [$ ( $vm.Name ) ]: Found direct schedule tag with value: $schedule "
381+ Write-Output " [$ ( $vm.Name ) ]: Found direct VM schedule tag with value: $schedule "
285382 }
286383 elseif ($taggedResourceGroupNames -contains $vm.ResourceGroupName )
287384 {
@@ -325,13 +422,13 @@ try
325422 {
326423 # Schedule is matched. Shut down the VM if it is running.
327424 Write-Output " [$ ( $vm.Name ) ]: Current time [$currentTime ] falls within the scheduled shutdown range [$matchedSchedule ]"
328- AssertVirtualMachinePowerState - VirtualMachine $vm - DesiredState " StoppedDeallocated" - ResourceManagerVMList $resourceManagerVMList - ClassicVMList $classicVMList
425+ AssertVirtualMachinePowerState - VirtualMachine $vm - DesiredState " StoppedDeallocated" - ResourceManagerVMList $resourceManagerVMList - ClassicVMList $classicVMList - Simulate $Simulate
329426 }
330427 else
331428 {
332429 # Schedule not matched. Start VM if stopped.
333430 Write-Output " [$ ( $vm.Name ) ]: Current time falls outside of all scheduled shutdown ranges."
334- AssertVirtualMachinePowerState - VirtualMachine $vm - DesiredState " Started" - ResourceManagerVMList $resourceManagerVMList - ClassicVMList $classicVMList
431+ AssertVirtualMachinePowerState - VirtualMachine $vm - DesiredState " Started" - ResourceManagerVMList $resourceManagerVMList - ClassicVMList $classicVMList - Simulate $Simulate
335432 }
336433 }
337434
@@ -344,5 +441,5 @@ catch
344441}
345442finally
346443{
347- Write-Output " Runbook finished"
444+ Write-Output " Runbook finished (Duration: $ ( ( " {0:hh\:mm\:ss} " -f (( Get-Date ).ToUniversalTime() - $currentTime )) ) ) "
348445}
0 commit comments