Speaking at Azure Global Bootcamp in Cluj
[/content/images/wordpress/2017/04/2017bootcamp.png] For the 5th year in a row, ITCamp Community is organising Global Azure Boot Camp (https
I was recently working on a proof of concept in Azure for a client that needed a couple of VMs to test if Azure is a viable candidate for their on-premise workloads. The client only needed those VMs at certain hours on weekdays and that meant that I needed to implement a method to remove any unnecessary run-time costs and that’s where Azure Automation and PowerShell comes in ?
For this particular proof of concept I chose to deploy ARM VMs and that was a good decision because one of the key differences between Azure Resource Manager and Azure Service Manager is parallelization and that’s a major difference. For example purposes the Classic model doesn’t let you start, stop, deploy multiple VMs or any other Azure goodies at the same time and if you try to do that you get a generic error like the one quoted below.
The virtual machine ‘VMNAME’ operation failed: ‘Windows Azure is currently performing an operation with x-ms-requestid
on this deployment that requires exclusive access.
Now that you know that the ARM model can do everything in parallel, you can now start writing PowerShell Workflows that allow you to use the -parallel parameter in foreach loops which basically allows you to run multiple tasks at once. So if you want to start 100 VMs or create 100 Storage Accounts in say 5 minutes, then that’s the way to go.
The scripts I wrote are to be considered as samples because they target Resource Groups which means that all the Virtual Machines in a Resource Group will start or stop, so please keep that in mind. The scripts can be adapted to target specific VMs without doing any hard coding in the scripts and that can be achieved by tagging the VMs which is another feature that ARM provides.
Changing the scripts to look for tags is very simple. Basically you add another mandatory parameter and add a filter in the Get-AzureRMVM cmdlet as shown below:
param( [Parameter(Mandatory = $true)] [string]$ResourceGroup, [Parameter(Mandatory = $true)] [string]$SubscriptionCredential, [Parameter(Mandatory = $true)] [string]$VMTag ) #$VMs = Get-AzureRmVM -ResourceGroupName "$ResourceGroup" $VMs = Get-AzureRmVM -ResourceGroupName "$ResourceGroup" | Where-Object {$_.Tags.Keys -EQ $VMTag}
All you need to do in order to make them work is to have an Azure Automation Account, an AAD user that’s co-administrator on the subscription to be configured as a credential asset in the automation account, the runbooks and a schedule or more.
Everything that I just mentioned can be done via the GUI or you can use PowerShell. I’ll add a extra script in this post that creates everything that you need to make them work but with one exception and that’s the creation of the AAD user and adding him to the subscription as co-admin. I chose to not include that part in the script because it requires an additional Azure Module to be installed and doing that for one single step is not worth it.
Here’s the script:
<# .Synopsis Preparation script for Azure Automation .DESCRIPTION This script will automate the steps required to create the necesary variables, accounts and credentials required for Azure Automation to work as intended. The script is easy to use as shown in the examples. The -StorageContainer parameter is set to false by default and if required it can be set to true by adding it to the final line and setting $true to it. .PARAMETER AutomationAccount Mandatory variable used to create a free Azure Automation account. The variable takes string input and that string input will be the name that will appear in the Azure Portal. .PARAMETER Location Mandatory parameter used to specify in which Azure datacenter should the automation resources reside .PARAMETER AzureUser Mandatory parameter that is used to specify the Azure co-admin user account that will be used to perform the automation tasks. .PARAMETER SubscriptionID Mandatory parameter that requires the subscription ID in order to automate tasks. Azure Automation requires either the subscription name or the ID and in some cases both, depending on the complexity of the automation runbook. .PARAMETER ResourceGroup Mandatory parameter that requires a resource group name that will be created or that already exists. .PARAMETER SubscriptionName Same as the SubscriptionID, this mandatory parameter requires the name of the Azure subscription eg: 'Azure in Open', 'Pay as you Go', 'Visual Studio Ultimate with MSDN' etc. .EXAMPLE Set-AzureRMVariables -ResourceGroup '<NAME>' -AutomationAccount '<NAME>' -Location 'West Europe' -AzureUser 'AzureAutomationAccount' -SubscriptionID <SubscriptionGUID> -SubscriptionName '<Subscription Name>' #> function Set-AzureRMVariables { [CmdletBinding(SupportsShouldProcess = $true, PositionalBinding = $false, ConfirmImpact = 'Medium')] [OutputType([String])] Param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [String] $AutomationAccount, [Parameter(Mandatory = $true)] [ValidatePattern('[a-z]*')] [ValidateSet('Central US', 'South Central US', 'East US', 'West US', 'North Central US', 'East US 2', 'North Europe', 'West Europe', 'Southeast Asia', 'East Asia', 'Japan West', 'Japan East', 'Brazil South')] [String] $Location, [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [String] $AzureUser, [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [String] $SubscriptionID, [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [String] $ResourceGroup, [Parameter(Mandatory = $false)] [ValidateNotNullOrEmpty()] [String] $SubscriptionName ) Begin { Write-Verbose -Message "Selecting the azure subscription - $SubscriptionName" Select-AzureRmSubscription -SubscriptionId $SubscriptionID -ErrorAction Stop Write-Verbose -Message "Checking if $ResourceGroup exists." $RGCheck = Get-AzureRmResourceGroup -Name $ResourceGroup -ErrorAction SilentlyContinue if (!$RGCheck) { Write-Warning -Message "Specified Resource Group does not exist. Creating the Resource Group: $ResourceGroup" New-AzureRmResourceGroup -Name $ResourceGroup -Location $Location } } Process { Write-Verbose -Message 'Creating the Azure resources that will be used for automation' New-AzureRmAutomationAccount -ResourceGroupName $ResourceGroup -Name $AutomationAccount -Location $Location -ErrorAction Stop New-AzureRmAutomationCredential -ResourceGroupName $ResourceGroup -Name $AzureUser -AutomationAccountName $AutomationAccount -Value (Get-Credential) New-AzureRmAutomationVariable -ResourceGroupName $ResourceGroup -Name 'SubscriptionID' -AutomationAccountName $AutomationAccount -Value $SubscriptionID -Encrypted $false New-AzureRmAutomationVariable -ResourceGroupName $ResourceGroup -Name 'SubscriptionName' -AutomationAccountName $AutomationAccount -Value $SubscriptionName -Encrypted $false } }
In order to run the scripts on your local machine, you need to have the latest Azure ARM PowerShell module which at the time of writing is at version 1.0.4. You can install the Azure ARM PowerShell module using PowerShell v5 if you’re already running Windows 10 by running Install-Module AzureRM and then Install-AzureRM cmdlets or you can get PowerShell V5 on older operating systems by installing WMF 5.
Windows Management Framework 5 can be installed on Windows Server 2012 R2, Windows Server 2012, Windows 2008 R2 SP1, Windows 8.1 and Windows 7 SP1 but at the time of writing this post WMF5 was pulled from the download center because of a bug that resets the PowerShell Module Environment to the default settings. You can read more about it here -> Blog post on WMF5 issue
Later Edit: WMF5 is back! Here’s the download link:
Now without further ado, here are the scripts ?
Merry Christmas!
#requires -Version 3 -Modules AzureRM.Compute, AzureRM.Profile workflow Start-Azure-VM { param( [Parameter(Mandatory = $true)] [string]$ResourceGroup, [Parameter(Mandatory = $true)] [string]$SubscriptionCredential, [Parameter(Mandatory = $false)] [string]$AzureSubscriptionID ) # Stop the script if run in the weekend $CurrentDay = (Get-Date).DayOfWeek if ($CurrentDay -eq 'Saturday' -or $CurrentDay -eq 'Sunday') { Write-Output -InputObject 'Not a business day, canceling the procedure' exit } $Credential = Get-AutomationPSCredential -Name $SubscriptionCredential $SubscriptionID = Get-AutomationVariable -Name $AzureSubscriptionID Add-AzureRmAccount -Credential $Credential Select-AzureRmSubscription -SubscriptionId $SubscriptionID $VMs = Get-AzureRmVM -ResourceGroupName "$ResourceGroup" # Start VMs in parallel if(!$VMs) { Write-Output -InputObject 'No VMs were found in the specified Resource Group.' } else { ForEach -parallel ($VM in $VMs) { $StartVM = Start-AzureRmVM -ResourceGroupName "$ResourceGroup" -Name $VM.Name -ErrorAction SilentlyContinue $Attempt = 1 if(($StartVM.StatusCode) -ne 'OK') { do { Write-Output -InputObject "Failed to start $($VM.Name). Retrying in 60 seconds..." Start-Sleep -Seconds 60 $StartVM = Start-AzureRmVM -ResourceGroupName "$ResourceGroup" -Name $VM.Name -ErrorAction SilentlyContinue $Attempt++ } while(($StartVM.StatusCode) -ne 'OK' -and $Attempt -lt 5) } if($StartVM) { Write-Output -InputObject "Start-AzureRmVM cmdlet for $($VM.Name) with StatusCode:$($StartVM.StatusCode) on attempt number $Attempt of 5." } } } }
#requires -Version 3 -Modules AzureRM.Compute, AzureRM.Profile workflow Stop-Azure-VM { param( [Parameter(Mandatory = $true)] [string]$ResourceGroup, [Parameter(Mandatory = $true)] [string]$SubscriptionCredential, [Parameter(Mandatory = $false)] [string]$AzureSubscriptionID ) # Stop the script if run in the weekend $CurrentDay = (Get-Date).DayOfWeek if ($CurrentDay -eq 'Saturday' -or $CurrentDay -eq 'Sunday') { Write-Output -InputObject 'Not a business day, canceling the procedure' exit } $Credential = Get-AutomationPSCredential -Name $SubscriptionCredential Add-AzureRmAccount -Credential $Credential $SubscriptionID = Get-AutomationVariable -Name $AzureSubscriptionID Select-AzureRmSubscription -SubscriptionId $SubscriptionID $VMs = Get-AzureRmVM -ResourceGroupName "$ResourceGroup" # Stop VMs in parallel if(!$VMs) { Write-Output -InputObject 'No VMs were found in the specified Resource Group.' } else { Foreach -parallel ($VM in $VMs) { $StopVM = Stop-AzureRmVM -ResourceGroupName "$ResourceGroup" -Name $VM.Name -ErrorAction SilentlyContinue -Force $Attempt = 1 if(($StopVM.StatusCode) -ne 'OK') { do { Write-Output -InputObject "Failed to stop $($VM.Name). Retrying in 60 seconds..." Start-Sleep -Seconds 60 $StopVM = Stop-AzureRmVM -ResourceGroupName "$ResourceGroup" -Name $VM.Name -ErrorAction SilentlyContinue $Attempt++ } while(($StopVM.StatusCode) -ne 'OK' -and $Attempt -lt 5) } if($StopVM) { Write-Output -InputObject "Stop-AzureRmVM cmdlet for $($VM.Name) with StatusCode:$($StopVM.StatusCode) on attempt number $Attempt of 5." } } } }