(UPDATED 6/17/2021 with code for multiple pooled and personal host pools) Azure Virtual Desktop (previously Windows Virtual Desktop (WVD)) has a new option in preview that starts session hosts in a personal or pooled host pool when a user connects. It won’t, however, shut down the session hosts when the user logs out. The script outlined in this video will evaluate all running and available session hosts in a personal or pooled host pool and shut down and deallocate session hosts without an active connection. Deallocating Session hosts while not in use can save money on compute costs.
This script is intended to be used with the auto start on connect preview feature. It uses an Azure Function with a system assigned managed identity to query AVD/WVD and shut down the Session Hosts. It uses an Azure Function because support for a managed identity is GA, and the schedule can run more frequently than every hour. The script could also run in an Azure Automation runbook.
Auto Start WVD Pooled and Personal Host Pool Session Hosts:
One important step required for this script is a GPO, or some other method to set a time limit on disconnected sessions in the Host Pool. The script will not shut down Session Hosts with any type of active session, including disconnected sessions.
Also, I recommend not running the script more frequently than every 30 minutes. There is a short window when a user connects, and the Session Host has started, but the login has not fully processed. The script could interpret the Session Host as powered on with no active connects and shut it down.
Start by creating a GPO that sets a time limit for disconnected sessions and apply it to the Session Host OU. The settings are located in:
Computer Configuration > Administrative Templets > Windows Components > Remote Desktop Services > Remote Desktop Session Host > Session Time limits
At minimum, enable “Set time limit for disconnected sessions” to a time limit that works for your organization. Set it too high, and charges will accumulate for resources not in use, set it too low and end users may get frustrated when they step away for a short time and have to wait for their Session Host to start.
Also, consider enabling “Set time limit for active but idle Remote Desktop Services session”. Configuring that setting will disconnect a user who may have stepped away and left the remote desktop client open.
Set the Max Session Limit
Auto Start on Connect will only power on a Session Host in a Pooled Host Pool if no Session Host are powered on or, if the powered on Session Hosts have reached the Max Session Limit. A Pooled Host Pool in Breadth-First Load Balancing requires a max session limit when used with the start on connect feature.
Create Function App
Let’s start by creating the function app. From the Azure Portal, go to Function Apps and add a new function app.
In the Create Function App page, create a new or use an existing resource group and give the Function app a name. Set the runtime stack to PowerShell core and set your region. Once finished, it should look similar to the image below.
Hosting, Monitoring, and tags can be left as default. Go to Review and Create to deploy the Function App.
Go to the resource when finished.
Configure the Function App
Next, we will configure the function app to use the PowerShell Az. Module and configure a System Assigned Managed Identity.
Configure Az Module
From the Function App, go to App files under Functions.
From the dropdown, select the requirements.psd1 file and remove the “#” from the line: ‘Az’ = ‘5.*’.
Click Save and go back to the Function App Overview.
From the overview page, restart the function app. This will apply configuration changes we just made.
Configure the Managed Identity
Next, we’ll configure the managed identity used by the Function App to interact with resources in the subscription.
Go to Identity under Settings in the Function App.
From the System Assigned Identity page, change the status to On and save, that will create a new identity, and the Object ID will display similar to below, without the blur.
Next, we give the identity rights to view properties of the WVD environment and manage virtual machines in the subscription. Go to Add Role Assignments.
From the Add Role Assignments page, click Add Role Assignment (preview).
On the next page, set the Scope to Subscription, Verify the correct subscription is added, and search for and select Desktop Virtualization Reader.
Click Save to add the Desktop Virtualization Reader role.
Follow the same steps to add the Virtual Machine Contributor role.
Once finished, go back to the Function App.
Create the Function
Next, we’ll create the Function. A Function App can have many functions of the same type. For example, we can have multiple PowerShell Functions in this Function App.
From the Azure Function App, go to Functions under Functions.
Click Add to add a Function.
From the list of templates, select Timer Trigger. This will trigger the Function based on a schedule.
Go to Template Details, give it a new name if needed, then change the schedule by replacing the 5 with a 30. This will set the timer to run every 30 minutes on the hour. You can change this to a different frequency, 45 to run every 45 minutes for example.
See the link below for more details on scheduling for a Function App.
Click Add to add the function app.
From within the Function, go to Code + Test under Developer.
If you need to change the schedule, go to the Function.json file in the Function drop-down.
Change the schedule as needed.
Go back to the Run.ps1 file and replace the existing code with the StopSH-MultiHostPools.ps1 code. The code can be found here:
Go to the Variables section. Update the Host Pool name and Resource Group for the Personal or Pooled Host Pool.
If the function is running againt multple Pooled or Personal Host Pools, copy the block of code from the commends and paste it imediatly after the block updated in the previous step.
Add a new block of code for each host pool targeted by the function. When finished, the variable section will look similar to the code below.
When the Function runs, a while loop is used for each host pool. It starts by getting all the active sessions in the Host Pool. It then creates a list of all active Session Host names, using a for each loop to remove duplicate values
Next, it creates a list of all Session Hosts that are powered on and not in drain mode. Setting drain mode will exclude the Session Host from the script. This way, if a Session Host has to be on for a few hours for maintenance, such as to pick up software updates, the script won’t shut it down.
It then compares the Session Hosts that are powered on with the Session Hosts that have active connections. If the Session Host is on but has no active connections, it’s shut down.
After that, the script loops through the next instance of a Host Pool until the end.
Once updated with your Host Pool and Resource Group information, save the Function.
Run the Script
Test it before you trust it!
My lab had three Session Hosts. All were powered on and available with two users logged in.
Open logs in the Function so we can see the activity.
Check the time! As configured in this example, the script will run every 30 minutes on the hour. Close to run time, you can wait for it to run on its own.
Otherwise, go to Run/Test and click Run to start it manually.
Close the Run window after it starts.
The Function will take a couple of extra minutes to finish on the first run as it has to download and add the PowerShell Az module.
Once it finishes running, the output will indicate any Session Hosts that are shut down in the function output.
My example now shows one Session Host is off and unavailable. With Start VM on Connect enabled, the Session Host will start the next time an assigned user logs in.
That, my friends, is how to shut down Session Hosts in a Personal Host Pool with no active sessions.