Deallocate an Azure VM from itself
These days I’m dealing with the automation of starting and stopping Azure virtual machines. I do this to avoid unnecessary costs for customers running Citrix or RDS workers on Azure. I translated a piece of my work into a PowerShell script to de-allocate the VM on which it is running.
Azure Instance Metadata Service
To get information about the running VM I use Azure Instance Metadata Service (https://docs.microsoft.com/en-us/azure/virtual-machines/windows/instance-metadata-service). This information contains the public IP address, VM size, os type and a lot more. To identify this Azure VM for later deallocation later, I need some specific information: the vmId. You can get meta data using PowerShell:
1
|
$md = Invoke-RestMethod -Headers @{ "Metadata" = "true" } -URI http://169.254.169.254/metadata/instance ? api-version=2017-08-01 |
$md.compute.vmId contains the unique identifier for the running VM. I will use this id later to match the correct VM for deallocating.
Service Principal Account
I want to deallocate the VM automatically without logging-in myself. Therefore I have to use a service principal account. It is pretty easy to create a service principal account:
Go to the Azure portal, open the Azure Active Directory of your subscription(s) and choose „App registration“ – “New application registration”:
Give your app a name (e.g.: PowerShell-Services) and enter a sign-on URL. User http://localhost for this URL and click “Create”.
Select the previously created app, select “Keys” and go on: Enter a name, select an expiration time and save the configuration: Important: copy your key directly after saving it:
Also, copy the application id for later use:
And last: copy the tenant id from your Azure Active Directory by selecting it and click on “Properties”:
You have all the data you need to logon unattended now:
App-id:
21acad78-9006-4f22-9156-xxxxxxxxxxxxx
App-key:
sjsgUk7a5hgaTkZGuOGeLxxxxxxxxxxxxxxxJ0lXs2/o=
Tenant-id:
06522f94-0d15-4fba-aac8-xxxxxxxxxxxxxxx
Give the right permissions
In my case, I will use this single app/service principal to shut down and deallocate every VM in my Azure subscription (this can be different in your case and can be a security breach if another user gets the logon data from your PowerShell script). To give the app the right to work with VM I added in on subscription level: Select Subscriptions -> subscription -> Access Control (IAM) -> Add
Role: Virtual Machine Contributor*
Assign to: Azure AD user, group, or application
Select: You application/service principal
Save
* The app/service principal has the permission to start/stop/modify/… all VM’s in the subscription. If you need more granularity you can create custom roles (https://www.sepago.de/blog/2017/07/13/preventing-administrative-users-to-change-critical-network-settings-in-an-azure-hub)
The script
To find from your VM the corresponding one in Azure use this PowerShell script with the service principal credentials and deallocate it:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
|
$AppId = "21acad78-9006-4f22-9156-xxxxxxxxxxxxx" $AppKey = "sjsgUk7a5hgaTkZGuOGeLxxxxxxxxxxxxxxxJ0lXs2/o=" $TenantId = "06522f94-0d15-4fba-aac8-xxxxxxxxxxxxxxx" $md = Invoke-RestMethod -Headers @{ "Metadata" = "true" } -URI http://169.254.169.254/metadata/instance ? api-version=2017-08-01 $Cred = New-Object System.Management.Automation.PSCredential ( $AppId , ( ConvertTo-SecureString $AppKey -AsPlainText -Force )) Login-AzureRmAccount -Credential $cred -ServicePrincipal -TenantId $TenantId # enumerate subscriptions $subscritions = Get-AzureRmSubscription foreach ( $subscription in $subscritions ) { Write-Host ( "Working on subscription: $($subscription.name)" ) Get-AzureRmSubscription -SubscriptionId $subscription .Id |Out -Null $vms = Get-AzureRmVM Write-Host ( "Number of VMs: $($vms.Count)" ) $vm =@( $vms | where vmId -EQ $md .compute.vmId) if ( $vm -ne 0) { Write-Host ( "Deallocating $($vm.Name)" ) Stop-AzureRmVM -Id $vm [0].Id -Name $vm [0].Name -Force } else { Write-Host ( "VM not found in this subscription" ) } } |
Hint:
@skillriver wrote a blog shutting to Shutdown and Deallocate an Azure VM using Managed Service Identity. This avoids to create an Azure AD application: