Auto Start and Stop Session Hosts in Windows Virtual Desktop Spring Update (ARM) Edition with an Azure Function

Auto Start and Stop Session Hosts in Windows Virtual Desktop Spring Update (ARM) Edition with an Azure Function

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.

Set the Auto-Logout GPO

I start with a GPO that will log out disconnected and idle sessions and apply it to the Session Host OU.  This step could be skipped, but idle and disconnected sessions will prevent session hosts from shutting down.  Be sure to set timeout values to a setting appropriate for your environment.

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.

GPO Object

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

Edit Time Limit

Apply this GPO to the session host OU in Active directory once created.

Create the Function App

This step creates a PowerShell based Function App on a Consumption plan.  Modify as needed if you have an app service plan in place already.

Go to the Azure Portal and Create a Resource.  Search for Function App and select Function App.

Create Function App

Click Create at the Function App page and fill out the information in the Basics section.  It should 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.

Function App Bsics

Click next to Hosting.

In hosting, leave the storage account to New, the Operating System to Windows.

Set the Plan Type to Consumption.  Use Premium or App Service Plan if your environment requires the added functionality.  Be aware that all options have a monthly fee associated with them.  The Consumption plan is not free but costs the least.  See the link on the deployment page for more details.

Function App Hosting

Once finished, click next to Monitoring.

Leave Application Insights enabled and create a new Application Insights instance.  Alternatively, you can select an existing if you use Application Insights in your environment.

Function App Monitoring

Click Next to Tags.  Add Tags as needed for your environment.

In Review and Create, Click Create to deploy the Function App.

Function App Deployment

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.

Enable the AZ Module

Go back to the Overview page for and restart the Function App.

Restart the Function App

Create the Managed Identity

A managed Identity provides an identity that the Function App will use to interact with the Session Hosts.  A Managed Identity is a special identity with a 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 Managed Identity is removed with it.

Go to the Function App once the deployment has finished. Click on Identity under Settings.

Managed Identity

From System Assigned Managed Identity, change the status to On 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 of the identity that will get RBAC rights to the Session Host Resource Group.

Enable System Managed Identity

The Object ID will display once the save has finished.

Managed Identity Object ID

Next, go to the Session Host VM Resource Group.  It is possible to deploy Session Host to a Resource Group different from the Windows Virtual Desktop Host Pool.  Be sure to set RBAC permissions on the VM Resource Group.

Open Access Control (IAM) from the Resource Group.

RBAC Access Control

Click Add a Role Assignment

Add a Role Assignment

Set the Role as Contributor and select the Managed Identity setup in the previous step.  Click Save when finished.

Save Role Assignment

Go back to the Function App, Identities, and click Azure Role Assignments.  The new role shows in the function app.

Azure Role Assignments

Create the Function

Now that the Function App is in place and has permissions to the Resource Group, we can move onto creating the Function and adding the code. 

From the Function App, go to Functions.

Create Function

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

New Timer Trigger

Give the new Function a name and leave the schedule as is.  The new timer trigger defaults to run every 5 minutes. 

Create Timer Trigger Function

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

Code and Test

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

Function App Code Page

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.

GitHub Raw

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

Paste Code of Function App

Next, go to the Variables section and make the following changes:

VerbosePreference – Verbose output was added for initial testing.  Set to “Continue” to see the output in the logs or “SilentlyContinue” to suppress the verbose information.

ServerStartThreshold – This is the spare session capacity the host pool has available between runs.  If the number of available sessions is less then this amount, a server will be started.  If the number of available sessions is greater than this amount, a server will be stopped.

UsePeak – Peak will modify the threshold value, so more sessions are available during peak business hours.  Set this to “Yes” if using and modify the peak threshold, start and stop time, time zone and Peak days as shown in the code.

HostPoolName – The name of the host pool to monitor.

HostPoolRG and SessionHostVmRg – the Host Pool Resource Group and the Session Host VM Resource Group.  These are typically the same but could be different.  Find the Host Pool Resource Group in Windows Virtual Desktop under Host Pool

Host Pool Resource Group

Host Pool Resource Group

Session Host VM Resource Group from Virtual Machines in the Azure Portal.

Session Host VM Resource Group

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

Save Code

Test the code by running it with the Test/Run button (shown above)

At the Input Output Screen, click Run at the bottom.

Run the Code

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

202 Response

That’s it, the script is in place and running.  Below are some tips for managing the function app now that it’s deployed.

Disable the Function App

I like to shut my lab session hosts down when not in use.  This script will start them back up (that’s what it’s for!).  To disable the script, to go to the Functions Overview windows and click Disable.  This will stop the schedule, but it can still be run manually.

Disable Schedule

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 the logs to show up after a job runs.  If Verbose is enabled, those messages will show up here, along with information and error messages.  Verbose Logging is enabled by changing the $verbosPreferance in the script to “Continue” and editing 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"
    }
  }
Logging host.json

Click Save to finish

Change Run Interval

The default run interval for a time trigger is 5 minutes.  Set the time interval with a Cron expression in the function.json file.  Information on how to format a Cron expression is in the Functions readme.md file.

readme.md Cron

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

function.json

60 thoughts on “Auto Start and Stop Session Hosts in Windows Virtual Desktop Spring Update (ARM) Edition with an Azure Function”

  1. I get error: Cannot bind argument to parameter ‘SubscriptionId’ because it is null

    [Error] ERROR: _TimerTrigger1_ : Error getting host pool details: Cannot bind argument to parameter ‘SubscriptionId’ because it is null.
    + CategoryInfo : NotSpecified: (:) [Write-Error], WriteErrorException
    + FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException,_TimerTrigger1_

    Script stack trace:
    at , D:\home\site\wwwroot\TimerTrigger1\run.ps1: line 156

    Microsoft.PowerShell.Commands.WriteErrorException: Error getting host pool details: Cannot bind argument to parameter ‘SubscriptionId’ because it is null.

    2020-06-19T09:49:14Z [Information] Executed ‘Functions.TimerTrigger1’ (Succeeded, Id=ffb0bf3b-1bf1-414d-a1b6-0b3b416aa56f)
    2020-06-19T09:50:00Z [Information] Executing ‘Functions.TimerTrigger1′ (Reason=’Timer fired at 2020-06-19T09:49:59.9974449+00:00’, Id=fa2e49d5-970a-46cc-b0d5-71ed7211c611)
    2020-06-19T09:50:00Z [Error] ERROR: _TimerTrigger1_ : Error getting host pool details: Cannot bind argument to parameter ‘SubscriptionId’ because it is null.
    + CategoryInfo : NotSpecified: (:) [Write-Error], WriteErrorException
    + FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException,_TimerTrigger1_

    Script stack trace:
    at , D:\home\site\wwwroot\TimerTrigger1\run.ps1: line 156

    Microsoft.PowerShell.Commands.WriteErrorException: Error getting host pool details: Cannot bind argument to parameter ‘SubscriptionId’ because it is null.

    2020-06-19T09:50:00Z [Information] Executed ‘Functions.TimerTrigger1’ (Succeeded, Id=fa2e49d5-970a-46cc-b0d5-71ed7211c611)
    2020-06-19T09:49:14Z [Error] ERROR: _TimerTrigger1_ : Error getting host pool details: Cannot bind argument to parameter ‘SubscriptionId’ because it is null.
    + CategoryInfo : NotSpecified: (:) [Write-Error], WriteErrorException
    + FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException,_TimerTrigger1_

    Script stack trace:
    at , D:\home\site\wwwroot\TimerTrigger1\run.ps1: line 156

    Microsoft.PowerShell.Commands.WriteErrorException: Error getting host pool details: Cannot bind argument to parameter ‘SubscriptionId’ because it is null.

    1. Had the same error, the managed identity and roll assignment was missing,
      after fixing this everything was running as expected

  2. Pingback: WVD Weekly blog post 14th June 2020 - 21st June 2020 - WVD Community Website

  3. I appreciate your hard work.. It’s really amazing.

    I have query related to “Updated Peak Time Code”, does it execute based current Region where session hosts is hosted , for example US EAST
    Or it execute based on the time set on VM or does it execute based on the current time zone from where you are executing – for example: I’m located in India and hosted session hosts in US EAST, so can I set India time zone or US EAST or Timze zone set on Server.

    2. Is there any script or software which tell the user what is the current bandwidth from their logged in VDI desktop..

  4. Thanks, Travis

    Can you let me know how to roll out newly updated Golden image to Session hosts?

    I see there is way using shared image gallery, but getting error while rolling out to session hosts (after completing the rolling out shared image version of Image, VM shows “Unavailable”

    1. The PeakServerStartThreshold is the number of user sessions that should be available between each run, not the number of session hosts that should stay running. By design, this script will try to minimize the number of servers running. With Depth-first load balancing WVD would not allocate sessions to a new server until one with active connections hits the max session count. You could keep an extra server running by durring peak by setting the threshold to the max session count +1.

  5. Hello Travis,

    Can I know how to start the same session host at different time zone. For example: user from the same organization, want to login to a session host from the different Time zone. so I want to do auto start and auto stop.

  6. Hello Travis,

    I watched the video and looked at the script. The reasons you mentioned for it to work “only” with the Depth First were around costs, not technical. On the math equations you are always looking at the total amount of session on all hosts not individually. Looking at the script I do not see a reason for it not to work with Breadth First other than manually specifying a MaxSession value. Other than cost, Is there any technical reason why the script would not work with Breadth first?.

    Thanks

    1. Depth first will direct new clients to the host with the most connections, up to the maximum connection. This acts to consolidate clients to a minimal number of hosts. This is a more economical way to handle sessions, both in money and in compute utilization. Breadth-first distributes new connections across all available hosts, making it difficult to target session hosts to shut down. That would require additional logic to put session hosts in drain mode.
      Microsoft has a similar script that works with Breadth-first and Depth-first. I originally build my script as a simpler alternative to this one. If you are using Breadth-first, it may have the logic you need. https://docs.microsoft.com/en-us/azure/virtual-desktop/virtual-desktop-fall-2019/set-up-scaling-script

      1. Thanks for your answer. I appreciate your suggestion on the other script. We are already using it but that is for Fall-2019. We want to migrate to Spring-2020..
        I also think, even though your script is simpler it can be applied to a broader scenario (with some minor variations). Your script will start/stop at any time depending on the number of sessions That other script is limited to starting host during peak time and turning off during non peak time. If for some reason your load goes up after hours or weekends it will not scale up.
        I agree with you, if money is the “main” concern depth-first is the winner. But, we are looking for a balance between performance, user experience and cost savings. We think depth first is not the right solution for us given some particular needs. As far as draining, we don’t want to force users to logoff. Just let users disconnect/logoff on their own. The GPO will take care of closing the sessions as they disconnect. Just as you do on your script. We may only need the drain to prevent users from logging in while shutting down which you have less probability on the depth-first as you suggested.. Maybe just setting drain mode on right before shutting down and then back on right after would do it (Just a thought. As you said some additional logic could be needed).
        In any case, I just thought there was no need to limit the script to just one load balancing mechanism as long as you understand the risks and your particular use case.. Great script, simple and functional.

        Thanks,

  7. Hi Travis,

    Great work – thank you for sharing it.

    Will the script shutdown all VMs when there are no active users or will leave one VM running ready to accept connections?

  8. Hi Travis ,

    I have couple of questions , i am bit confused about max threshould ,/minimum session threshould.

    Let me tell you our environment example :

    Example 1: In our environment i have deployed 2 wvd in host pool with configuration : Standard D8s v3 (8 vcpus, 32 GiB memory) .

    10- 15 people will be using it in peak hours and 2 guys will use in off peak hours

    Max host pool session configuration is 21 .

    Example 2 :

    i have deployed 4 wvd in host pool with configuration : Standard D8s v3 (8 vcpus, 32 GiB memory) .

    35 -40 people will be using it in peak hours and 3- 4 guys will use in non peak hours

    Max host pool session configuration is 50

    What setting u recommend me to set for peak hours / non peak hours so your scripts calculation works perfect for me.

    Also how we can achive if 0 session are logged in all 2 will get shutdown and when anyone tries to connect to wvd it will power ok the machine ? If this is not possible then how we can shift left this activity to service desk team which option i need to choose in access control so that they only can do power on power off wvd.

    Note : GPO is already deployed for kill the disconnected or inactive session.

    1. Hello

      Thanks for the question. To start, this solution is designed to only use Depth First. Depth first will fill one session host to the max number of connections before moving sessions to the next session hosts. As users log in and out, new sessions are directed to the session host with the most number of users up to the maximum session limit.

      If the max session limit is 21 with 10-15 users, the second session host would not be used. If you change that to 10, the 11th connection would go to the second session host.

      The script has a Server Start Threshold. This sets the spare capacity that should be available between script runs. It’s a spare capacity setting to accommodate new logins. It essentially turns on new session hosts once the active ones get close to full.

      For 40 users and 4 VM’s for example, setting the Max Session Limit to 10 would accommodate 40 users. If the Server Start Threshold was set to 2, a server would start after the 9th, 19th and 29th login.

      The peak hours is to accommodate more aggressive logins during busy times. In the previous example, if the peak time server start threshold was set to 10, one extra server would be turned on.

      As for shutting down all session hosts, I have heard some people will set the server start threshold to 0 during normal hours and above 0 for peak or office hours. This will shut down all session hosts as users log off. Once the last one is down, no one will be able to log in until the peak time when a session host starts again.

      I hope this helps.

      1. Thanks a lot . can you please tell me how we can change the MAX session limit on non- arm WVD . Which PowerShell command we need to use ?

        Example: Suppose for Standard D8s v3 (8 vcpus, 32 GiB memory) VM ( NON – AR ) . It will wait till the session reaches to 24 ( its taking default values based on VM size I guess ) and then only it will spin up a new WVD . How we can set up a limit as per our convenience ?

  9. Thank you so much for this. Works great! I have one question. Can the script be modified to start or stop more than one session host? I am using a large shared pool of Windows 7 (I know, but the client needs it) with the max sessions set to one. The script works exactly as expected, but will only start/stop one session host per run. For example if I have the threshold set to 50 and there are 25 hosts running with one session (the max) each, the pool is 25 sessions short. Each time the script runs, it will only start just one host. The new logins can easily overrun the script. Thank you in advance for responding.
    -Tony

  10. Hi Travis,

    If I have 2 host pools inside 2 differents Resource Groups, I have to create 2 different functions or I have to include inside the script function like example below?

    Thank You

    Example:

    # Host Pool Name
    $hostPoolName = ‘WVDHP1, WVDHP2’

    # Session Host Resource Group
    # Session Hosts and Host Pools can exist in different Resource Groups, but are commonly the same
    # Host Pool Resource Group and the resource group of the Session host VM’s.
    $hostPoolRg = ‘RGHOSTPOOL1, RGHOSTPOOL2’
    $sessionHostVmRg= ‘RGHOSTPOOL1, RGHOSTPOOL2’

  11. This is brilliant – thank you so much for all your hard work!

    I found a typo on line approx 88:
    if ($hostsToStart -gt $offSessionHostsCount) {
    $hostsToStart = $offSessionHosts
    }
    Should be:
    if ($hostsToStart -gt $offSessionHostsCount) {
    $hostsToStart = $offSessionHostsCount
    }

    Also, my work’s peak time is overnight so $startPeakTime is PM and $endPeakTime is AM. I added a comparison to find this and adjust for it:
    if ($startPeakTime -gt $endPeakTime -and $dateDay -in $peakDay) {
    Write-Verbose “Peak hours are overnight, adjusting peak times”
    Write-Verbose $startPeakTime
    $endPeakTime = $endPeakTime.AddHours(24)
    Write-Verbose $endPeakTime
    }
    Not sure if you wanted to add this to your template.

    I am so grateful for your work, this is so very cool. Thankyou thankyou thankyou.

  12. Hi Travis,

    Thank you for the great article. I have tried to configure this and getting bellow error even though I have imported all the required modules. Would really appreciate if you have any insights about it.

    “ERROR: Get-AutomationVariable : The term ‘Get-AutomationVariable’ is not recognized as the name of a cmdlet, function, script file, or operable program.Check the spelling of the name, or if a path was included, verify that the path is correct and try again.

    1. Hello, verify you are using the script with the name WVDARM_ScaleHostPoolVMs.ps1. You may have used the version for Fall 2019 that uses Azure Automation. I updated the comments in the read me so the difference between the two are more clear.

      Thanks,
      Travis

      1. Hi Travis,

        Thank you for the prompt response. Yes, you are correct. I was using the old script. Thank you again.

        I have a last question. If I am using 2 session hosts in a single host pool, is it possible to shut down both the session hosts if there is no any active sessions?

  13. Hey Travis,

    This is fantastic, and works great on my first hostpool setup!

    Quick question is it possible to use the same function app, with a new function per host pool?

    thanks again!

    ~Ty

        1. I followed the aka, and added a new application setting to fix this.

          Function app > Settings in Config > New Application Setting

          FUNCTIONS_WORKER_PROCESS_COUNT with a value of 2.

          Seems happy now.

  14. M Chandrasekhar Reddy

    Hi Travis,

    I have followed the same steps that you mentioned in this article, however when I run the scrip, it says that the PowerShell Module is not available as shown below. Where this Module should be installed ? Please help me out on this

    ERROR: Error getting host pool details: The term ‘Get-AzWvdHostPool’ is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or if a path was included, verify that the path is correct and try again. Exception : Type : Microsoft.PowerShell.Commands.WriteErrorException Message : Error getting host pool details: The term ‘Get-AzWvdHostPool’ is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or if a path was included, verify that the path is correct and try again. HResult : -2146233087 CategoryInfo : NotSpecified: (:) [Write-Error], WriteErrorException FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException,_TimerTrigger1_ InvocationInfo : MyCommand : _TimerTrigger1_ HistoryId : 1 InvocationName : _TimerTrigger1_ CommandOrigin : Internal ScriptStackTrace : at , D:\home\site\wwwroot\TimerTrigger1\run.ps1: line 148

    1. The function app downloads the az modules needed the first time it runs. I just tested with a default timer trigger app and added “get-azcontext”. The first time it runs, there is a log message that indicates it’s downloading the module. Not sure why you would get that message.

  15. Thank you so much for sharing your great work! The Function script works to scale out 1 host and scale in 1 host based on the number of sessions hosts that should be available compared to the number of hosts that are running. Curious if it could be configured to scale out and scale in a specific count of hosts for normal time and/or for peak time. Example for a lab with 15 hosts. During peak time, scale out count of 2 hosts at once based on the number of sessions hosts that should be available compared to the number of hosts that are running to help a prevent failed sessions due of a flood of students accessing a lab before the script would run again..

  16. Hi,

    Thanks for the script. For some reason i doesn’t work when trying to start a session host but works when shutting one down.
    I have one session host available but shutdown.

    2020-11-10T15:21:39.805 [Error] ERROR: Start threshold met, but there are no hosts available to startException :Type : Microsoft.PowerShell.Commands.WriteErrorExceptionMessage : Start threshold met, but there are no hosts available to startHResult : -2146233087CategoryInfo : NotSpecified: (:) [Write-Error], WriteErrorExceptionFullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException,Start-SessionHostInvocationInfo :MyCommand : Start-SessionHostScriptLineNumber : 225OffsetInLine : 5HistoryId : 1ScriptName : C:\home\site\wwwroot\WVDTimerTrigger\run.ps1Line : Start-SessionHost -sessionHosts $sessionHosts -hostsToStart $hostsToStartPositionMessage : At C:\home\site\wwwroot\WVDTimerTrigger\run.ps1:225 char:5+ Start-SessionHost -sessionHosts $sessionHosts -hostsToStart $host …+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~PSScriptRoot : C:\home\site\wwwroot\WVDTimerTriggerPSCommandPath : C:\home\site\wwwroot\WVDTimerTrigger\run.ps1InvocationName : Start-SessionHostCommandOrigin : InternalScriptStackTrace : at Start-SessionHost, C:\home\site\wwwroot\WVDTimerTrigger\run.ps1: line 85at , C:\home\site\wwwroot\WVDTimerTrigger\run.ps1: line 225Microsoft.Azure.WebJobs.Script.Workers.Rpc.RpcException : Result: ERROR: Start threshold met, but there are no hosts available to startException :Type : Microsoft.PowerShell.Commands.WriteErrorExceptionMessage : Start threshold met, but there are no hosts available to startHResult : -2146233087CategoryInfo : NotSpecified: (:) [Write-Error], WriteErrorExceptionFullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException,Start-SessionHostInvocationInfo :MyCommand : Start-SessionHostScriptLineNumber : 225OffsetInLine : 5HistoryId : 1ScriptName : C:\home\site\wwwroot\WVDTimerTrigger\run.ps1Line : Start-SessionHost -sessionHosts $sessionHosts -hostsToStart $hostsToStartPositionMessage : At C:\home\site\wwwroot\WVDTimerTrigger\run.ps1:225 char:5+ Start-SessionHost -sessionHosts $sessionHosts -hostsToStart $host …+

  17. Hi,

    Thanks for the script. For some reason it doesn’t work when trying to scaleup and start a session host but works when shutting one down.
    I have one session host available but shutdown ready to be started. Cant see why its not working. Please help !!

    2020-11-10T15:21:39.805 [Error] ERROR: Start threshold met, but there are no hosts available to startException :Type : Microsoft.PowerShell.Commands.WriteErrorExceptionMessage : Start threshold met, but there are no hosts available to startHResult : -2146233087CategoryInfo : NotSpecified: (:) [Write-Error], WriteErrorExceptionFullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException,Start-SessionHostInvocationInfo :MyCommand : Start-SessionHostScriptLineNumber : 225OffsetInLine : 5HistoryId : 1ScriptName : C:\home\site\wwwroot\WVDTimerTrigger\run.ps1Line : Start-SessionHost -sessionHosts $sessionHosts -hostsToStart $hostsToStartPositionMessage : At C:\home\site\wwwroot\WVDTimerTrigger\run.ps1:225 char:5+ Start-SessionHost -sessionHosts $sessionHosts -hostsToStart $host …+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~PSScriptRoot : C:\home\site\wwwroot\WVDTimerTriggerPSCommandPath : C:\home\site\wwwroot\WVDTimerTrigger\run.ps1InvocationName : Start-SessionHostCommandOrigin : InternalScriptStackTrace : at Start-SessionHost, C:\home\site\wwwroot\WVDTimerTrigger\run.ps1: line 85at , C:\home\site\wwwroot\WVDTimerTrigger\run.ps1: line 225Microsoft.Azure.WebJobs.Script.Workers.Rpc.RpcException : Result: ERROR: Start threshold met, but there are no hosts available to startException :Type : Microsoft.PowerShell.Commands.WriteErrorExceptionMessage : Start threshold met, but there are no hosts available to startHResult : -2146233087CategoryInfo : NotSpecified: (:) [Write-Error], WriteErrorExceptionFullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException,Start-SessionHostInvocationInfo :MyCommand : Start-SessionHostScriptLineNumber : 225OffsetInLine : 5HistoryId : 1ScriptName : C:\home\site\wwwroot\WVDTimerTrigger\run.ps1Line : Start-SessionHost -sessionHosts $sessionHosts -hostsToStart $hostsToStartPositionMessage : At C:\home\site\wwwroot\WVDTimerTrigger\run.ps1:225 char:5+ Start-SessionHost -sessionHosts $sessionHosts -hostsToStart $host …+

  18. Hi Travis, firstly, thanks so much for the Scaling Script – it has been working fine for me for months now.

    I have just today run into a problem with it though – after I added some new VM’s to my host pools.

    When these new VM’s are shutdown by the scaling script, they show a status of “Shutdown” instead of “Unavailable”, and so then they cannot be re-started again by the scaling script when additional VM’s need to be started due to additional load. I have seen this with two different (previously fine) Host Pools and I have tried manually removing the new VM’s from the Host Pool and re-installing the Agent & BootLoader, but still no joy.

    The status of “shutdown” kind of makes more sense than “Unavailable” (which is what powered off VM’s showed before), so I wondered if there has been a change in the way the VM’s agent reports it’s status to WVD? If so, this would break the scaling script and it would need updating to suit, but I don’t see that this can be the case, as my original VM’s in the Host Pool are running the latest agent and show a status of “Unavailable” when they are powered off.

    Have you or anyone else seen this, or have any advice?

  19. Hey Travis,
    Not sure if you are still looking here at comments, but I am struggling to wrap my head around the serverStartThreshold and peakserverStartThreshold. I have 5 hosts in the Pool with a Max Session value of 10 sessions per host. I want to turn the entire environment off at 10pm and back on at 8am Eastern Standard Time, When the Function App runs the Script I’m receiving a message that the Threshold was met but there are no sessions to start. I would like to have at least 1 box turn on at 8am and be waiting for connections, then if 1 user connects have 10 more available. For the life of me I can’t seem to get the additional hosts to fire even if I get 4 concurrent users on 1 host that I had to manually start. Any chance you could help explain it for the lament brain. my environment is in higher ed. and I’m trying to figure out how to provide this to a class that meets but I don’t want all 5 boxes on full time.

    Thanks
    Travis

    1. Hello, there was a new “shutdown” status for shutdown session hosts that the script was not evaluating. The Github code has been updated and it’s working with initial testing. This will likely address the issue.

  20. Hi Travis,

    I see you have not (yet?) published or replied to the question I posted yesterday about new VM’s appearing in the “shutdown” state when powered off which breaks your Scaling Script as it cannot start these powered off VM’s.

    I thought you might like to know that this behavior has been confirmed by another person on the WVD Techcommunity site after I posted about it – https://techcommunity.microsoft.com/t5/windows-virtual-desktop/get-azwvdsessionhost-returns-status-of-quot-shutdown-quot/m-p/1880518

    Hopefully Christiaan Brinkhoff or Dean Cefola can explain what this status means (as the docs don’t) so that the scaling script can be modified to deal with it.

    1. The script did not account for the new “Shutdown” status. I updated the code and did some initial testing and it seems to be working now. The updates are now available on Github. Thanks for letting me know about this issue!

      1. Many thanks for the speedy script update Travis – I can confirm that the fix works as I made a similar change myself last week.

        If the WVD Product Group get back to me, I will update this thread with the reason they decided to change the Status of powered off VM’s from “Unavailable” to “Shutdown”, and perhaps even what the Status’s mean (as the docs don’t detail this).

  21. Any reason why I am getting this after using the updated PS script even though I still rely on Depth first? Is there a way to get access to the original script that didnt include Breadth first as this seems to have caused issues in my environment.

    No parameter defined in the script or function for the input binding ‘Timer’.Stack

    1. As long as it doesn’t specify to use Breathe-First, it shouldn’t matter. Did you leave the line “param($timer)” at the start of the function? That is needed for the function to run.

  22. Hi, we want to us your script with Microsoft’s “Start VM on Connect”.
    But before the user can connect to the session the script is shutting down the vm again.
    How can we delay the shutdown of the vm?

      1. Hi, Thanks for the additional info…
        I’m still using your script with the “breadth-first during peak time” but I’m setting the “$serverStartThreshold” to zero (0). This will shut down all VMs until the next Peak time, or if someone calls the “start VM on connect”.
        I’m testing with 15 minute interval values, but will probably increase it to 20 or 30 minutes.
        The combination “Peak time with Breath-First mode” & “Depth mode” & “Start VM on Connect” actually works really well for us. In this way we combine performance with availability and cost savings !!

  23. Hi Travis,

    at first I have to thank you for your great work. It makes the daily work so much easier.
    Unfortunately I’m facing some issues regarding the function app itself.
    We do have at least four host pools, two located in an own Resource Group in East US and the other two in another RG hosted in West Europe.
    The function app and every other component is hosted in the West Europe region, so in the same RG as the two European host pools.

    All four functions are configured in one function app. The East US part works perfectly fine, but the West Europe not. See the details:
    ERROR: [AuthorizationFailed] : The client ‘478b0548-1703-4807-bb30-384d844df4e8’ with object id ‘478b0548-1703-4807-bb30-384d844df4e8’ does not have authorization to perform action ‘Microsoft.DesktopVirtualization/hostPools/read’ over scope ‘/subscriptions/Sub_ID/resourceGroups/WE-RG/providers/Microsoft.DesktopVirtualization/hostPools/WE-POOLED-002’ or the scope is invalid.
    If access was recently granted, please refresh your credentials.
    Exception : Type : System.Exception Message : [AuthorizationFailed] : The client ‘478b0548-1703-4807-bb30-384d844df4e8’ with object id ‘478b0548-1703-4807-bb30-384d844df4e8’ does not have authorization to perform action ‘Microsoft.DesktopVirtualization/hostPools/read’ over scope ‘/subscriptions/Sub_ID/resourceGroups/WE-RG/providers/Microsoft.DesktopVirtualization/hostPools/WE-POOLED-002’ or the scope is invalid.
    If access was recently granted, please refresh your credentials. HResult : -2146233088 TargetObject : { SubscriptionId = System.String[], ResourceGroupName = WE-RG, Name = WE-POOLED-002 } CategoryInfo : InvalidOperation: ({ SubscriptionId = …-WE-HP-POOLED-002 }:f__AnonymousType4`3) [Get-AzWvdHostPool_Get], Exception FullyQualifiedErrorId : AuthorizationFailed,Microsoft.Azure.PowerShell.Cmdlets.DesktopVirtualization.Cmdlets.GetAzWvdHostPool_Get
    ErrorDetails : The client ‘478b0548-1703-4807-bb30-384d844df4e8’ with object id ‘478b0548-1703-4807-bb30-384d844df4e8’ does not have authorization to perform action ‘Microsoft.DesktopVirtualization/hostPools/read’ over scope ‘/subscriptions/Sub_ID/resourceGroups/WE-RG/providers/Microsoft.DesktopVirtualization/hostPools/WE-POOLED-002’ or the scope is invalid.
    If access was recently granted, please refresh your credentials. InvocationInfo : MyCommand : Get-AzWvdHostPool_Get ScriptLineNumber : 162 OffsetInLine : 5 HistoryId : 1 ScriptName : C:\home\site\wwwroot\WEU-Timer-Pooled002\run.ps1 Line : $hostPool = Get-AzWvdHostPool -ResourceGroupName $hostPoolRg -HostPoolName $hostPoolName PositionMessage : At C:\home\site\wwwroot\WEU-Timer-Pooled002\run.ps1:162 char:5 + $hostPool = Get-AzWvdHostPool -ResourceGroupName $hostPoolRg -Hos … + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ PSScriptRoot : C:\home\site\wwwroot\WEU-Timer-Pooled002 PSCommandPath : C:\home\site\wwwroot\WEU-Timer-Pooled002\run.ps1 InvocationName : Get-AzWvdHostPool CommandOrigin : Internal ScriptStackTrace : at Get-AzWvdHostPool, C:\home\data\ManagedDependencies\210618092729441.r\Az.DesktopVirtualization\3.0.0\exports\ProxyCmdletDefinitions.ps1: line 1293 at , C:\home\site\wwwroot\WEU-Timer-Pooled002\run.ps1: line 162 PipelineIterationInfo :

    I did authorize the system account the same way as in East US and already checked for typos in the script (misspelled RG and so on) bit didn’t find any hint.
    Do you have any idea?

    1. I assume the host pools are in the same subscription. Are permissions granted at the Resource Group? If so, the Managed Identity will need the RBAC role assigned to the all host pool and server RG’s. You could also verify permissions at the RG by viewing the RBAC role and verify the Managed Identity has the correct roles. Also, try to restart the function app.

      1. Hi Travis,

        I digged a little bit deeper and compared all the IDs in the error message and guess what: It was the wrong Subscription ID…
        Therefore we’re using different subscriptions for different regions in one tenant, the script always took the first available which was in our case the East US one and not the West Europe one.
        Adding a simple “Set-AZContext” in the beginnung of the script solved the issue.

        Nevermind that the error message itself is a little bit misleading due to the pert with “does not have authorization” and not “Resource Group in Subscription not found”.

        Thank you for your help and the quick reply!

  24. Hi Travis,
    great post and well documented.

    i am trying to set this up first time in my test environment and have encountered a an issue where in my test run results in the below output:

    Connected!
    2021-08-02T04:41:41 Welcome, you are now connected to log-streaming service. The default timeout is 2 hours. Change the timeout with the App Setting SCM_LOGSTREAM_TIMEOUT (in seconds).
    2021-08-02T04:41:41.043 [Information] Executing ‘Functions.AVDTimerTrigger’ (Reason=’This function was programmatically called via the host APIs.’, Id=2437fa83-9ab3-4f88-a5cb-cc7554c8ab5e)
    2021-08-02T04:41:55.969 [Information] OUTPUT:
    2021-08-02T04:41:56.677 [Information] OUTPUT: Account SubscriptionName TenantId Environment
    2021-08-02T04:41:56.679 [Information] OUTPUT: ——- —————- ——– ———–
    2021-08-02T04:41:56.681 [Information] OUTPUT: MSI@50342 Visual Studio Enterprise Subscription 38e78bd3-5c71-44d7-991c-416baa18a1e9 AzureCloud
    2021-08-02T04:41:56.681 [Information] OUTPUT:
    2021-08-02T04:42:00.331 [Information] INFORMATION: Loaded Module ‘Az.Accounts’
    2021-08-02T04:42:06.526 [Information] INFORMATION: Loaded Module ‘Az.DesktopVirtualization’
    2021-08-02T04:42:12.369 [Information] OUTPUT:
    2021-08-02T04:42:13.239 [Information] OUTPUT: RequestId IsSuccessStatusCode StatusCode ReasonPhrase
    2021-08-02T04:42:13.241 [Information] OUTPUT: ——— ——————- ———- ————
    2021-08-02T04:42:13.241 [Information] OUTPUT: b9bb03bd-6195-46a8-8ec1-cd27f436c7f5 True Accepted Accepted
    2021-08-02T04:42:13.241 [Information] OUTPUT:
    2021-08-02T04:42:13.250 [Information] Executed ‘Functions.AVDTimerTrigger’ (Succeeded, Id=2437fa83-9ab3-4f88-a5cb-cc7554c8ab5e, Duration=32232ms)
    2021-08-02T04:43:41 No new trace in the past 1 min(s).
    2021-08-02T04:44:41 No new trace in the past 2 min(s).
    2021-08-02T04:45:41 No new trace in the past 3 min(s).
    2021-08-02T04:46:41 No new trace in the past 4 min(s).

    I have used your code from https://github.com/tsrob50/WVD-Public/blob/master/WVDARM_ScaleHostPoolVMs.ps1. The difference from your post is that the requirements.psd1 file in your code is set to ‘Az’ = ‘5.*’ whereas mine is as below

    # This file enables modules to be automatically managed by the Functions service.
    # See https://aka.ms/functionsmanageddependency for additional information.
    #
    @{
    # For latest supported version, go to ‘https://www.powershellgallery.com/packages/Az’.
    # To use the Az module in your function app, please uncomment the line below.
    ‘Az’ = ‘6.*’
    }
    The hostpools are in the same subscription as the function app.
    not sure if I am missing anything here…

    Have you tested this recently with the newer version? Would appreciate your feedback

    1. You can change the 6 to a 5 and restart the Function App. That will run the previous AZ module version. I’m not sure that is the issue however. Try changing the logging level to Trace (View the Logs section of the blog post). Restart the Function App and it should give you more information about what it’s doing when it runs.

  25. Hi Travis,
    This is a great scrips. Thank you for your hard work.

    The Default Az.Compute and Az.Desktopvirtualization PowerShell Modules were not working as expected. It complains : The term “Get-AzwvdHostPool” is no recognized as the name……….. I had to Add Modules manually and then worked successfully.
    Thank you

  26. First – many thanks for your sharing.
    It would be nice if the function script could handle, if you would like to place one server in maintenance state or in you put the sever in drain mode then the function will not shut the server down.

  27. this is a cracking script thank you, we have had a client go live with AVD in UK South and as there is no scaling capacity in the UK it has been a lifesaver.

    I would like to monitor for the no sessions available message and alert for it but I can’t work out how to do that, do you have something simple you can send me to show how I can do this?

    Thanks

  28. Has something changed recently? For some reason the Az module seems to refuse to load at all so this doesn’t run

Leave a Comment

Your email address will not be published. Required fields are marked *

November 2024
M T W T F S S
 123
45678910
11121314151617
18192021222324
252627282930  
Scroll to Top