Apr 21 2021 08:44 AM - edited Jul 28 2021 02:21 AM
Recently we've announced the public preview of start VM on connect which will allow your deallocated virtual machines getting started automatically when the assigned user tries to connect. Let us have a look how we can further optimize our cost by deallocating the VM when it is not used anymore.
Let's first have a high level view on the needed steps:
So let's start by creating our custom role:
Open the Azure portal, go to Subscriptions and select the appropriate subscription
Go to Access control (IAM) and select Add a custom role.
Next, name the custom role and add a description. In this example I'll call it "Deallocate VM on logoff"
On the Permissions tab, add the following permission to the subscription you're assigning the role to:
When you're finished, select Ok.
If you prefer a JSON definition, please use the following template:
{
"properties": {
"roleName": "Deallocate VM on logoff",
"description": "This custom role will allow your virtual machines to be deallocated when the user logs off.",
"assignableScopes": [
"/subscriptions/<<<SubscriptionID>>>"
],
"permissions": [
{
"actions": [
"Microsoft.Compute/virtualMachines/deallocate/action"
],
"notActions": [],
"dataActions": [],
"notDataActions": []
}
]
}
}
After we've created our custom role, we'll need to create a managed identity for our virtual machines. By this managed identities we don't need to store any credentials locally on the virtual machine or in an Azure KeyVault and can assign each virtual machine granular permission to shutdown only itself.
As this can be a bigger task, depending on the number of virtual machines you have in your personal host pools, I've prepared a script that will utilize the Azure PowerShell modules to assign that fine-grained permissions, so you may need to install those modules first:
Install-Module -Name Az.Account,Az.Compute,Az.DesktopVirtualization,Az.Resources
The script itself takes the host pool name, associated resource group and the role definition name selected above as parameters. It will then iterate through all virtual machines assigned to the specified host pool, create a managed identity when not already present and create a role assignment limited to the virtual machine itself:
$hostPoolName = "<<<HostPoolName>>>"
$resourceGroupName = "<<<ResourceGroupName>>>"
$roleDefinitionName = "<<<RoleDefinitionName>>>"
Connect-AzAccount
$sessionHosts = Get-AzWvdSessionHost -HostPoolName $hostPoolName -ResourceGroupName $resourceGroupName
foreach ($sessionHost in $sessionHosts) {
<# get virtual machine by session host reference #>
$resource = Get-AzResource -ResourceId $sessionHost.ResourceId
$vm = Get-AzVM -ResourceGroupName $resource.ResourceGroupName -Name $resource.Name
<# create system-assigned managed identiy unless it already exists #>
$managedIdentity = ($vm.Identity | where Type -eq "SystemAssigned").PrincipalId
if ($managedIdentity -eq $Null) {
Update-AzVM -ResourceGroupName $vm.ResourceGroupName -VM $vm -IdentityType SystemAssigned
$managedIdentity = ((Get-AzVM -ResourceGroupName $vm.ResourceGroupName -VMName $vm.Name).Identity | where Type -eq "SystemAssigned").PrincipalId
}
<# create role-assignment unless it already exists #>
if ((Get-AzRoleAssignment -RoleDefinitionName $roleDefinitionName -ObjectId $managedIdentity) -eq $Null) {
New-AzRoleAssignment -ObjectId $managedIdentity -RoleDefinitionName $roleDefinitionName -Scope $vm.Id
}
}
Next we'll configure our session host to disconnect idle sessions and logoff disconnected sessions after a certain period of time:
Connect remotely to the VM that you want to set the policy for.
Open the Group Policy Editor, then go to Local Computer Policy > Computer Configuration > Administrative Templates > Windows Components > Remote Desktop Services > Remote Desktop Session Host > Session Time Limits.
Find the policy that says Set time limit for disconnected sessions, then change its value to Enabled.
After you've enabled the policy, select your preferred time limit at End a disconnected session.
The above settings also ensure that an user will get a warning message two minutes before reaching the specified time limit so he can press a key or move the mouse to prevent getting disconnected.
In the last step we'll create the PowerShell script initiating the deallocation and configure it as a logoff script.
The PowerShell script will query the details of your virtual machine using the Azure instance metadata, connect to Azure using the created managed identity and initiate the actual deallocation via REST API:
$metadata = Invoke-RestMethod -Headers @{"Metadata"="true"} -Method GET -Proxy $Null -Uri "http://169.254.169.254/metadata/instance?api-version=2021-01-01"
$authorizationToken = Invoke-RestMethod -Headers @{"Metadata"="true"} -Method Get -Proxy $Null -Uri "http://169.254.169.254/metadata/identity/oauth2/token?api-version=2021-01-01&resource=https://management.azure.com/"
$subscriptionId = $metadata.compute.subscriptionId
$resourceGroupName = $metadata.compute.resourceGroupName
$vmName = $metadata.compute.name
$accessToken = $authorizationToken.access_token
$RestartEvents = Get-EventLog -LogName System -After (Get-Date).AddMinutes(-1) |? {($_.EventID -eq 1074) -and ($_.Message -match "restart" )}
$SessionCount = (query user | Measure-Object | select Count).count - 1 # remove headline
if (($SessionCount -gt 1) -or ($RestartEvents.count -ge 1))
{
# skip deallocate because of user-sessions or initiated reboot
} else {
Invoke-WebRequest -UseBasicParsing -Headers @{ Authorization ="Bearer $accessToken"} -Method POST -Proxy $Null -Uri https://management.azure.com/subscriptions/$subscriptionId/resourceGroups/$resourceGroupName/providers/Microsoft.Compute/virtualMachines/$vmName/deallocate?api-version=2021-03-01 -ContentType "application/json"
}
To have that script executed upon logoff, we need to configure it:
Connect remotely to the VM that you want to set the policy for.
Open the Group Policy Editor, then go to Local Computer Policy > User Configuration > Windows Settings > Scripts (Logon/Logoff).
Find the item that says Logoff.
Specify the script you've created on the PowerShell Scripts tab.
When rolling out to bigger host-pools, ideally place the logoff script on a centrally accessible location, e.g. SYSVOL-share.
Hope this short tutorial will help you take full benefit of the start VM on connect feature. Happy to read your feedback and comments below.
Apr 23 2021 05:23 AM - edited Apr 23 2021 05:24 AM
@BerndLoehlein This is awesome and I appreciate you sharing your scripts! Do you know if this will be baked into the WVD offering in the future?
One thing I wanted to call out for the community is you have to ensure you don't allow your users the ability to restart the VMs from within the OS as this will cause the script to kick off and deallocate the VMs without ever rebooting the VM. You can block this with a GPO as well.
May 13 2021 02:07 PM
@bking0100 Isn't it problematic to not allow users to restart the VM on demand? I can see a few situations where for instance post install of software, etc. where a restart is needed.
Jun 16 2021 08:38 AM
@BerndLoehlein
very good solution. Thank you.
I try to implement the script fro create managedid and AzRoleAssgniment in a azure function.
Which Azure Role permission i need for:
New-AzRoleAssignment -ObjectId $managedIdentity -RoleDefinitionName $roleDefinitionName -Scope $vm.Id
Because i become the following error:
New-AzRoleAssignmentParameterSetName : EmptyParameterSetContent-Type : application/json; charset=utf-8Content-Length : 116Response :StatusCode : ForbiddenReasonPhrase : ForbiddenContent : {"odata.error":{"code":"Authorization_RequestDenied","message":{"lang":"en","value":"Insufficient privileges to complete the operation."}
Jun 17 2021 05:19 AM
Assignments of roles requires the Microsoft.Authorization/roleAssignments/write permissions which are only included in the pre-defined roles of "User Access Administrator" or "Owner".
Jul 28 2021 02:23 AM
Jul 29 2021 06:41 PM
Jul 30 2021 05:06 AM
Hi @krayste,
For an AD-joined host pool I would recommend configuring the mentioned settings via central group policy and targeting this to the OU where your session-hosts reside. You would then also make the script accessible on a central location, e.g. the SYSVOL share, so you don't need to deploy the script on every host.
For AAD-only environments there may be an option to automate it using MEM/Intune.
Regards,
Bernd
Aug 12 2021 02:35 AM
Aug 23 2021 12:03 AM
@HenryGelderbloem You use the actually Remote Desktop App Version?
Aug 23 2021 03:09 AM
@WVDExpert We do, and it happens on different OSes too. SO the macOS and Windows version are affected as well as the iPadOS version.
Aug 31 2021 11:25 PM
@HenryGelderbloem same here. Tried also an option with an Azure Function which checks for active sessions and shut down the VM with the Stop-AzVM PowerShell command. Same issue. The start on connect feature throws the same error.
Can you probably share which method you finally used to avoid this error?
Sep 06 2021 02:06 AM
Sep 20 2021 07:35 AM
Solution@HenryGelderbloem @PatrickBrack
Could you please try to update the custom role used for Start VM on Connect and add Microsoft.Compute/virtualMachines/instanceView/read to it? This should ensure that sessions are removed in the service upon deallocation of the virtual machine.
We've already updated our docs for Start VM on Connect, but could be easily missed by customers already using this feature.
Jul 06 2022 12:11 AM
@BerndLoehlein ,need your help i am getting this error ,however i have followed the same.
Invoke-WebRequest : {"error":{"code":"AuthorizationFailed","message":"The client
'aa8367c5-e8a0-3333-a1e9-b377d7c0922a' with object id 'aa8367c5-e8a0-3333-a1e9-b377d7c0922a' does not have authorization to perform action.
Jul 06 2022 02:08 AM
@lokeshchouksey This sounds like the virtual machine doesn't have permissions to deallocate itself. Can you run the script in the initial post again to assign proper permissions to the system managed identities?
Jul 11 2022 02:26 AM - edited Jul 11 2022 04:27 AM
@BerndLoehlein yes now machine are deallocating after assigned contributor rights to Service principle.but now there is another problem happening , i can see machines are deallocating but after 2-3 min they are automatically starting and running, is there any thing i have missed or doing wrong ? also i have noticed it is happening only if myself shutting down/logoff/restarting the machine.
it is working only if i leave the machine and machine itself disconnected and logoff via set policy.
Jul 28 2022 10:32 AM - edited Jul 28 2022 10:36 AM
Disregard, previous comment was RE: PowerShell 7 and could not get script to run on PS 5.1
Aug 18 2022 07:49 AM
Oct 04 2022 09:19 AM
Sep 20 2021 07:35 AM
Solution@HenryGelderbloem @PatrickBrack
Could you please try to update the custom role used for Start VM on Connect and add Microsoft.Compute/virtualMachines/instanceView/read to it? This should ensure that sessions are removed in the service upon deallocation of the virtual machine.
We've already updated our docs for Start VM on Connect, but could be easily missed by customers already using this feature.