Skip to content

Commit 11d290d

Browse files
committed
Added simulation mode
Specify -Simulate as $true will run the schedule evaluation reporting actions that would be taken normally. By default -Simulate is $false, which means runbook will enforce schedules.
1 parent 07371b8 commit 11d290d

File tree

1 file changed

+118
-21
lines changed

1 file changed

+118
-21
lines changed

Assert-AutoShutdownSchedule.ps1

Lines changed: 118 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,62 @@
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
761
function 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"
185270
try
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
@@ -213,7 +306,11 @@ try
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
}
@@ -281,7 +378,7 @@ try
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
}
345442
finally
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

Comments
 (0)