AXForum  
Вернуться   AXForum > Microsoft Dynamics AX > DAX Blogs
All
Забыли пароль?
Зарегистрироваться Правила Справка Пользователи Сообщения за день Поиск Все разделы прочитаны

 
 
Опции темы Поиск в этой теме Опции просмотра
Старый 23.10.2017, 00:14   #1  
Blog bot is offline
Blog bot
Участник
 
25,631 / 848 (80) +++++++
Регистрация: 28.10.2006
yetanotherdynamicsaxblog: Use Azure Automation to start and stop your VMs on a schedule
Источник: http://yetanotherdynamicsaxblog.blog...-and-stop.html
==============

This post is long overdue, and I have been meaning to post it over a year ago. I did present an early version of this script at the AXUG event in Stuttgart, but since then the API has changed around tags, and also it has become very easy to solve authentication using Run as Accounts. The code I am sharing here works on the latest version of the modules, and I hope it will keep working for years to come.

I few notes before I continue:
  • I base this script off from Automys own code, and it is heavily inspired by the commits done by other users out there in the community. I will refer to the project on GitHub where you will find contributors and authors.
  • I've only tested and used the script for ARM Resources
  • I removed references to credentials and certificates and it relies on using "Run As Account". Setting it "Run As Account" in Azure is very easy and quick to do.
You will find the Feature branch here:
https://github.com/skaue/Azure-Autom...s/RunAsAccount

Setup

I recommend starting by creating a new Automation Account. Yes, you can probably reuse an existing one, but creating a new account does not incur additional costs, and you can get this up and running fairly quick and easy just by following the steps in this blog post.



Make sure you select "Yes" on the option of creating "Azure Run as Account". Let it create all the artifacts and while you wait you can read the rest of this post.

When the Automation account is up and running, the next step is to create a new Runbook of type "PowerShell" - just straight up PowerShell, and no fancy stuff.

Then you grab the script from my feature branch based off the original trunk. You can either take the script from this post, or take the latest from GitHub. I probably won't maintain this blog post on any future updates of the script, but I might maintain the one on GitHub. I'll put a copy down below.

So with the script added as a PowerShell Runbook, and saved. Now you need to Schedule it. This is where a small cost may incur, because it is necessary to set the Runbook to run every hour. Yes - every hour. Using Automation for free only allow for a limited number of runs, and with the Runbook running every hour throughout the day, I believe it will stop running after 20 days - per month. There is a 500 minute limit per month for free, but the cost incurred when you exceed this is extremely low.

With the script running every hour you are ready to schedule "downtime". And this is easy.
You basically just either TAG the VM or the Resource Group holding a collection of VMs.

By TAG I mean you type on the downtime you want for your resource in the VALUE of a specific TAG. The script looks for a tag named "AutoShutdownSchedule". Example of value would be "20:00->06:00, Saturday, Sunday", and you can probably guess when the server will be shutdown with that value... That is correct, all weekdays between 8 pm at night and 6 am in the morning. You can imagine the flexibility this gives.

Added Features

In addition, the script is inspired by other nice ideas from the community, like providing a TimeZone for your schedule, just to ensure your 8 pm is consistent to when the script interprets the value.

Another feature added is the ability to use a "NeverStart" value keyword, to enforce the resource does not start. You can use this to schedule automatic shutdown that does not trigger startup again after the schedule ends. Example is the value "20:00->21:00,NeverStart". This would stop the resource at 8 pm, and when the RunBook runs again at 9 pm, the resource will not start even though the schedule has ended.

Finally, I want to comment the added feature of disabling the schedule without removing the schedule. If you provide an additional tag with the name "AutoShutdownDisabled" with a value of Yes/1/True. This means you can keep the schedule and temporarily disable the shutdown schedule altogether.

The script

'
if($TimeRange -like '*->*')
{
$timeRangeComponents = $TimeRange -split '->' | foreach {$_.Trim()}
if($timeRangeComponents.Count -eq 2)
{
$rangeStart = Get-Date $timeRangeComponents[0]
$rangeEnd = Get-Date $timeRangeComponents[1]

# Check for crossing midnight
if($rangeStart -gt $rangeEnd)
{
# If current time is between the start of range and midnight tonight, interpret start time as earlier today and end time as tomorrow
if($currentTime -ge $rangeStart -and $currentTime -lt $midnight)
{
$rangeEnd = $rangeEnd.AddDays(1)
}
# Otherwise interpret start time as yesterday and end time as today
else
{
$rangeStart = $rangeStart.AddDays(-1)
}
}
}
else
{
Write-Output "`tWARNING: Invalid time range format. Expects valid .Net DateTime-formatted start time and end time separated by '->'"
}
}
# Otherwise attempt to parse as a full day entry, e.g. 'Monday' or 'December 25'
else
{
# If specified as day of week, check if today
if([System.DayOfWeek].GetEnumValues() -contains $TimeRange)
{
if($TimeRange -eq (Get-Date).DayOfWeek)
{
$parsedDay = Get-Date '00:00'
}
else
{
# Skip detected day of week that isn't today
}
}
# Otherwise attempt to parse as a date, e.g. 'December 25'
else
{
$parsedDay = Get-Date $TimeRange
}

if($parsedDay -ne $null)
{
$rangeStart = $parsedDay # Defaults to midnight
$rangeEnd = $parsedDay.AddHours(23).AddMinutes(59).AddSeconds(59) # End of the same day
}
}
}
catch
{
# Record any errors and return false by default
Write-Output "`tWARNING: 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
}

# Check if current time falls within range
if($currentTime -ge $rangeStart -and $currentTime -le $rangeEnd)
{
return $true
}
else
{
return $false
}

} # End function Test-ScheduleEntry


# Function to handle power state assertion for resources
function Assert-ResourcePowerState
{
param(
[Parameter(Mandatory=$true)]
[object]$Resource,
[Parameter(Mandatory=$true)]
[string]$DesiredState,
[bool]$Simulate
)

$processor = $ResourceProcessors | Where-Object ResourceType -eq $Resource.ResourceType
if(-not $processor) {
throw ('Unable to find a resource processor for type ''{0}''. Resource: {1}' -f $Resource.ResourceType, ($Resource | ConvertTo-Json -Depth 5000))
}
# If should be started and isn't, start resource
$currentPowerState = & $processor.PowerStateAction -Resource $Resource -DesiredState $DesiredState
if($DesiredState -eq 'Started' -and $currentPowerState -notmatch 'Started|Starting|running')
{
if($Simulate)
{
Write-Output "`tSIMULATION -- Would have started resource. (No action taken)"
}
else
{
Write-Output "`tStarting resource"
& $processor.StartAction -ResourceId $Resource.ResourceId
}
}

# If should be stopped and isn't, stop resource
elseif($DesiredState -eq 'StoppedDeallocated' -and $currentPowerState -notmatch 'Stopped|deallocated')
{
if($Simulate)
{
Write-Output "`tSIMULATION -- Would have stopped resource. (No action taken)"
}
else
{
Write-Output "`tStopping resource"
& $processor.DeallocateAction -ResourceId $Resource.ResourceId
}
}

# Otherwise, current power state is correct
else
{
Write-Output "`tCurrent power state [$($currentPowerState)] is correct."
}
}

# Main runbook content
try
{
$currentTime = GetCurrentDate
Write-Output "Runbook started. Version: $VERSION"
if($Simulate)
{
Write-Output '*** Running in SIMULATE mode. No power actions will be taken. ***'
}
else
{
Write-Output '*** Running in LIVE mode. Schedules will be enforced. ***'
}
Write-Output "Current UTC/GMT time [$($currentTime.ToString('dddd, yyyy MMM dd HH:mm:ss'))] will be checked against schedules"


$Conn = Get-AutomationConnection -Name AzureRunAsConnection
$resourceManagerContext = Add-AzureRMAccount -ServicePrincipal -Tenant $Conn.TenantID -ApplicationId $Conn.ApplicationID -CertificateThumbprint $Conn.CertificateThumbprint

$resourceList = @()
# Get a list of all supported resources in subscription
$ResourceProcessors | % {
Write-Output ('Looking for resources of type {0}' -f $_.ResourceType)
$resourceList += @(Find-AzureRmResource -ResourceType $_.ResourceType)
}

$ResourceList | % {
if($_.Tags -and $_.Tags.ContainsKey($autoShutdownOrderTagName) ) {
$order = $_.Tags | % { if($_.ContainsKey($autoShutdownOrderTagName)) { $_.Item($autoShutdownOrderTagName) } }
} else {
$order = $defaultOrder
}
Add-Member -InputObject $_ -Name ProcessingOrder -MemberType NoteProperty -TypeName Integer -Value $order
}

$ResourceList | % {
if($_.Tags -and $_.Tags.ContainsKey($autoShutdownDisabledTagName) ) {
$disabled = $_.Tags | % { if($_.ContainsKey($autoShutdownDisabledTagName)) { $_.Item($autoShutdownDisabledTagName) } }
} else {
$disabled = '0'
}
Add-Member -InputObject $_ -Name ScheduleDisabled -MemberType NoteProperty -TypeName String -Value $disabled
}

# Get resource groups that are tagged for automatic shutdown of resources
$taggedResourceGroups = Find-AzureRmResourceGroup -Tag @{ "AutoShutdownSchedule" = $null }
$taggedResourceGroupNames = @($taggedResourceGroups | select Name)

Write-Output "Found [$($taggedResourceGroupNames.Count)] schedule-tagged resource groups in subscription"

if($DefaultScheduleIfNotPresent) {
Write-Output "Default schedule was specified, all non tagged resources will inherit this schedule: $DefaultScheduleIfNotPresent"
}

# For each resource, determine
# - Is it directly tagged for shutdown or member of a tagged resource group
# - Is the current time within the tagged schedule
# Then assert its correct power state based on the assigned schedule (if present)
Write-Output "Processing [$($resourceList.Count)] resources found in subscription"
foreach($resource in $resourceList)
{
$schedule = $null

if ($resource.ScheduleDisabled)
{
$disabledValue = $resource.ScheduleDisabled
if ($disabledValue -eq "1" -or $disabledValue -eq "Yes"-or $disabledValue -eq "True")
{
Write-Output "[$($resource.Name)]: `r`n`tIGNORED -- Found direct resource schedule with $autoShutdownDisabledTagName value: $disabledValue."
continue
}
}

# Check for direct tag or group-inherited tag
if($resource.Tags.Count -gt 0 -and $resource.Tags.ContainsKey($autoShutdownTagName) -eq $true)
{
# Resource has direct tag (possible for resource manager deployment model resources). Prefer this tag schedule.
$schedule = $resource.Tags.Item($autoShutdownTagName)
Write-Output "[$($resource.Name)]: `r`n`tADDING -- Found direct resource schedule tag with value: $schedule"
}
elseif($taggedResourceGroupNames -contains $resource.ResourceGroupName)
{
# resource belongs to a tagged resource group. Use the group tag
$parentGroup = $resourceGroups | Where-Object Name -eq $resource.ResourceGroupName
$schedule = $parentGroup.Tags.Item($AUTOSHUTDOWNSCHEDULE_KEYWORD)
Write-Output "[$($resource.Name)]: `r`n`tADDING -- Found parent resource group schedule tag with value: $schedule"
}
elseif($DefaultScheduleIfNotPresent)
{
$schedule = $DefaultScheduleIfNotPresent
Write-Output "[$($resource.Name)]: `r`n`tADDING -- Using default schedule: $schedule"
}
else
{
# No direct or inherited tag. Skip this resource.
Write-Output "[$($resource.Name)]: `r`n`tIGNORED -- Not tagged for shutdown directly or via membership in a tagged resource group. Skipping this resource."
continue
}

# Check that tag value was succesfully obtained
if($schedule -eq $null)
{
Write-Output "[$($resource.Name) `- $($resource.ProcessingOrder)]: `r`n`tIGNORED -- Failed to get tagged schedule for resource. Skipping this resource."
continue
}

# Parse the ranges in the Tag value. Expects a string of comma-separated time ranges, or a single time range
$timeRangeList = @($schedule -split ',' | foreach {$_.Trim()})

# Check each range against the current time to see if any schedule is matched
$scheduleMatched = $false
$matchedSchedule = $null
$neverStart = $false #if NeverStart is specified in range, do not wake-up machine
foreach($entry in $timeRangeList)
{
if((Test-ScheduleEntry -TimeRange $entry) -eq $true)
{
$scheduleMatched = $true
$matchedSchedule = $entry
break
}

if ($entry -eq "NeverStart")
{
$neverStart = $true
}
}
Add-Member -InputObject $resource -Name ScheduleMatched -MemberType NoteProperty -TypeName Boolean -Value $scheduleMatched
Add-Member -InputObject $resource -Name MatchedSchedule -MemberType NoteProperty -TypeName Boolean -Value $matchedSchedule
Add-Member -InputObject $resource -Name NeverStart -MemberType NoteProperty -TypeName Boolean -Value $neverStart
}

foreach($resource in $resourceList | Group-Object ScheduleMatched) {
if($resource.Name -eq '') {continue}
$sortedResourceList = @()
if($resource.Name -eq $false) {
# meaning we start resources, lower to higher
$sortedResourceList += @($resource.Group | Sort ProcessingOrder)
} else {
$sortedResourceList += @($resource.Group | Sort ProcessingOrder -Descending)
}

foreach($resource in $sortedResourceList)
{
# Enforce desired state for group resources based on result.
if($resource.ScheduleMatched)
{
# Schedule is matched. Shut down the resource if it is running.
Write-Output "[$($resource.Name) `- P$($resource.ProcessingOrder)]: `r`n`tASSERT -- Current time [$currentTime] falls within the scheduled shutdown range [$($resource.MatchedSchedule)]"
Add-Member -InputObject $resource -Name DesiredState -MemberType NoteProperty -TypeName String -Value 'StoppedDeallocated'

}
else
{
if ($resource.NeverStart)
{
Write-Output "[$($resource.Name)]: `tIGNORED -- Resource marked with NeverStart. Keeping the resources stopped."
Add-Member -InputObject $resource -Name DesiredState -MemberType NoteProperty -TypeName String -Value 'StoppedDeallocated'
}
else
{
# Schedule not matched. Start resource if stopped.
Write-Output "[$($resource.Name) `- P$($resource.ProcessingOrder)]: `r`n`tASSERT -- Current time falls outside of all scheduled shutdown ranges. Start resource."
Add-Member -InputObject $resource -Name DesiredState -MemberType NoteProperty -TypeName Boolean -Value 'Started'
}
}
Assert-ResourcePowerState -Resource $resource -DesiredState $resource.DesiredState -Simulate $Simulate
}
}

Write-Output 'Finished processing resource schedules'
}
catch
{
$errorMessage = $_.Exception.Message
throw "Unexpected exception: $errorMessage"
}
finally
{
Write-Output "Runbook finished (Duration: $(('{0:hh\:mm\:ss}' -f ((GetCurrentDate) - $currentTime))))"
}



Источник: http://yetanotherdynamicsaxblog.blog...-and-stop.html
__________________
Расскажите о новых и интересных блогах по Microsoft Dynamics, напишите личное сообщение администратору.
 

Похожие темы
Тема Автор Раздел Ответов Посл. сообщение
atinkerersnotebook: Branding the Azure Active Directory login page Blog bot DAX Blogs 0 07.04.2017 16:12
organicax: Production Order – Operations vs Job Schedule Blog bot DAX Blogs 0 04.04.2017 02:15
kurthatlevik: How I saved thousands of dollars on my Azure environments! Blog bot DAX Blogs 1 28.06.2016 00:14
lcs: Microsoft Dynamics AX in Azure Runbook code samples for resource managment Blog bot DAX Blogs 0 26.08.2015 05:18
dynamics-community-at: NAV/CRM Connector: Tip: Quick Start/Stop Connector Service Blog bot Dynamics CRM: Blogs 0 09.06.2011 16:44
Опции темы Поиск в этой теме
Поиск в этой теме:

Расширенный поиск
Опции просмотра
Комбинированный вид Комбинированный вид

Ваши права в разделе
Вы не можете создавать новые темы
Вы не можете отвечать в темах
Вы не можете прикреплять вложения
Вы не можете редактировать свои сообщения

BB коды Вкл.
Смайлы Вкл.
[IMG] код Вкл.
HTML код Выкл.
Быстрый переход

Рейтинг@Mail.ru
Часовой пояс GMT +3, время: 02:57.