
I am happy to release an update to my Windows Virtual Desktop (WVD) Start-Stop script for Windows Virtual Desktop updated for Spring 2020, or “WVD ARM.” This script uses an Azure Function to starts and stops WVD Session hosts in a host pool based on the user load.
This article and script apply to the WVD ARM, or Spring Update in Public Preview as of June 2020. You can find information on my previous version here.
UPDATE 1/9/2021 – Added the ability to change load balancing to breadth-first during peak time. See the post here for details.
UPDATE 4/6/2021 – Update with steps to uncomment the az module.
The GitHub page with the Function App code is located here:
https://github.com/tsrob50/WVD-Public
An overview of deploying the script is available in this post. A full overview and deployment walkthrough is located here:
Below is a list of the changes made to the updated script:
PowerShell Module – Refactored to use Az.DesktopVirtualization PowerShell module instead of the Microsoft.RDInfra.PowerShell module.
100% Function App – the Az.DesktopVirtualiztion module supports PowerShell Core. The script now runs as a PowerShell Function App. No more Azure Function, Azure Automation Mix. This includes using a System Managed Identity to start and stop the VM’s.
Updated Start/Stop code – Thanks to Kandice Hendricks for updating the start and stop functions in the previous version. Those changes are part of this script. The script will start and stop multiple VM’s per run.
Updated Peak Time Code – I’m not sure how I ever got this to work. The script now excepts a time zone instead of a UTC offset. The correct time is calculated, including DST.
The following is a list of steps to deploy and modify the Function App to run the start stop script. A host pool should be in place prior to configuring the script (see my video on setting one up for more information). This script works with one host pool. Deploy multiple functions within the Function App to managed multiple Host Pools.
I start with a GPO that will log out disconnected and idlesessions and apply it to the Session Host OU.This step could be skipped, but idle and disconnected sessions willprevent session hosts from shutting down.Be sure to set timeout values to a setting appropriate for yourenvironment.
From an account with privileges to create and manage a GPO,go to Windows Administrative Tools, Group Policy Management. Create a new GPO and give it a name. Right click and Edit the GPO.

Modify the settings under Computer Configuration >Policies > Administrative Templates > Windows Components > RemoteDesktop Services > Remote Desktop Session Hosts > Session TimeLimits. Enable and set the limit fordisconnected sessions and limit for active but idle RDS sessions.

Apply this GPO to the session host OU in Active directoryonce created.
This step creates a PowerShell based Function App on aConsumption plan. Modify as needed ifyou have an app service plan in place already.
Go to the Azure Portal and Create a Resource. Search for Function App and select FunctionApp.

Click Create at the Function App page and fill out theinformation in the Basics section. Itshould look like below once finished.
Select an existing Subscription and existing Resource Group, or create a new Resource Group.
Give the Function App a name.
Leave Publish as Code.
Change the Runtime Stack to PowerShell Core. Leave the version as 6.
Change the region. Best to select the same region as the Session Host Pool.

Click next to Hosting.
In hosting, leave the storage account to New, the OperatingSystem to Windows.
Set the Plan Type to Consumption. Use Premium or App Service Plan if yourenvironment requires the added functionality.Be aware that all options have a monthly fee associated with them. The Consumption plan is not free but coststhe least. See the link on thedeployment page for more details.

Once finished, click next to Monitoring.
Leave Application Insights enabled and create a newApplication Insights instance.Alternatively, you can select an existing if you use ApplicationInsights in your environment.

Click Next to Tags.Add Tags as needed for your environment.
In Review and Create, Click Create to deploy the FunctionApp.

Enable the AZ Module
New function apps require the az module for the auto start and stop solution. Go to the Function App, App Files and change the file to requirements.psd1.
Remove the comment “#” from the line ‘Az’ = ‘5.*’ as shown below and click Save.
Go back to the Overview page for and restart the Function App.

A managed Identity provides an identity that the FunctionApp will use to interact with the Session Hosts. A Managed Identity is a special identity witha life cycle that matches the Function App.The function app uses a System Managed Identity to start and stop VM’s. When the Function App is deleted, the ManagedIdentity is removed with it.
Go to the Function App once the deployment has finished.Click on Identity under Settings.

From System Assigned Managed Identity, change the status toOn and click Save.
A verification box will appear. Click Yes to create the Managed Identity. Note the name in the box; this is the name ofthe identity that will get RBAC rights to the Session Host Resource Group.

The Object ID will display once the save has finished.

Next, go to the Session Host VM Resource Group. It is possible to deploy Session Host to aResource Group different from the Windows Virtual Desktop Host Pool. Be sure to set RBAC permissions on the VMResource Group.
Open Access Control (IAM) from the Resource Group.

Click Add a Role Assignment

Set the Role as Contributor and select the Managed Identitysetup in the previous step. Click Savewhen finished.

Go back to the Function App, Identities, and click AzureRole Assignments. The new role shows inthe function app.

Now that the Function App is in place and has permissions tothe Resource Group, we can move onto creating the Function and adding thecode.
From the Function App, go to Functions.

Click Add to add a new Function. Select Timer Trigger.

Give the new Function a name and leave the schedule asis. The new timer trigger defaults torun every 5 minutes.

The Overview page displays once created. Go to Code + Test to view and change thecode.

Delete everything below param($Timer). The param($Timer) line has to stay in thecode for the Function App to run.

Next, go to the GitHub Repo and copy the script. The code can is located here Click the Raw option to view and copy the code without formatting.

Paste the script into the Function App Code page under theparam($Timer) line like below.

Next, go to the Variables section and make the followingchanges:
VerbosePreference – Verbose output was added for initialtesting. Set to “Continue” to see theoutput in the logs or “SilentlyContinue” to suppress the verbose information.
ServerStartThreshold – This is the spare session capacitythe host pool has available between runs.If the number of available sessions is less then this amount, a serverwill be started. If the number ofavailable sessions is greater than this amount, a server will be stopped.
UsePeak – Peak will modify the threshold value, so moresessions are available during peak business hours. Set this to “Yes” if using and modify thepeak threshold, start and stop time, time zone and Peak days as shown in thecode.
HostPoolName – The name of the host pool to monitor.
HostPoolRG and SessionHostVmRg – the Host Pool Resource Groupand the Session Host VM Resource Group.These are typically the same but could be different. Find the Host Pool Resource Group in WindowsVirtual Desktop under Host Pool
Host Pool Resource Group

Session Host VM Resource Group from Virtual Machines in theAzure Portal.

Once finished, click Save at the top of the Code + Test Page

Test the code by running it with the Test/Run button (shownabove)
At the Input Output Screen, click Run at the bottom.

The Function should show a 202 HTTP response indicating itwas accepted. Close the Run window once accepted.

That’s it, the script is in place and running. Below are some tips for managing the functionapp now that it’s deployed.
Disable the Function App
I like to shut my lab session hosts down when not inuse. This script will start them back up(that’s what it’s for!). To disable thescript, to go to the Functions Overview windows and click Disable. This will stop the schedule, but it can stillbe run manually.

View the Logs
Go to Monitor in the Function. This shows the log of each run. Note that it can take up to 5 minutes for thelogs to show up after a job runs. IfVerbose is enabled, those messages will show up here, along with informationand error messages. Verbose Logging isenabled by changing the $verbosPreferance in the script to “Continue” andediting the host.json file.
To enable Verbose Logging, go to the Function App, App Files, and select host.json. Add the section of code starting with “logging” to the host.json file. (Be sure to add the comma as pointed out in the image below).
"logging": { "logLevel": { "default": "Trace" } }

Click Save to finish
Change Run Interval
The default run interval for a time trigger is 5minutes. Set the time interval with aCron expression in the function.json file.Information on how to format a Cron expression is in the Functions readme.mdfile.

To change the schedule, modify the Cron expression in thefunction.json file.
