![WVD Function App](https://i0.wp.com/www.ciraltos.com/wp-content/uploads/2020/06/WVDFunctionApp.png?resize=101%2C182&ssl=1)
(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:
https://github.com/tsrob50/WVD-Public/blob/master/StopSH-MultiHostPools.ps1
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.
Disconnect GPO
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.
![](https://i0.wp.com/www.ciraltos.com/wp-content/uploads/2021/05/GPO-Settings.png?resize=791%2C184&ssl=1)
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.
![](https://i0.wp.com/www.ciraltos.com/wp-content/uploads/2021/05/Create-a-new-Function-App.png?resize=286%2C282&ssl=1)
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.
![](https://i0.wp.com/www.ciraltos.com/wp-content/uploads/2021/05/Create-Function-App-Settings-1.png?resize=935%2C847&ssl=1)
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.
![](https://i0.wp.com/www.ciraltos.com/wp-content/uploads/2021/05/Go-To-Resource.png?resize=575%2C508&ssl=1)
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.
![](https://i0.wp.com/www.ciraltos.com/wp-content/uploads/2021/05/App-Files.png?resize=246%2C208&ssl=1)
From the dropdown, select the requirements.psd1 file and remove the “#” from the line: ‘Az’ = ‘5.*’.
![](https://i0.wp.com/www.ciraltos.com/wp-content/uploads/2021/05/Requirements.psd1_.png?resize=963%2C368&ssl=1)
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.
![](https://i0.wp.com/www.ciraltos.com/wp-content/uploads/2021/05/Restart-Function-App.png?resize=622%2C227&ssl=1)
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.
![](https://i0.wp.com/www.ciraltos.com/wp-content/uploads/2021/05/Settings-Identity.png?resize=305%2C306&ssl=1)
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.
![](https://i0.wp.com/www.ciraltos.com/wp-content/uploads/2021/05/System-Assigned-Identity.png?resize=598%2C488&ssl=1)
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).
![](https://i0.wp.com/www.ciraltos.com/wp-content/uploads/2021/05/Add-Role-Assignments.png?resize=542%2C444&ssl=1)
On the next page, set the Scope to Subscription, Verify the correct subscription is added, and search for and select Desktop Virtualization Reader.
![](https://i0.wp.com/www.ciraltos.com/wp-content/uploads/2021/05/Add-Roles.png?resize=1024%2C756&ssl=1)
Click Save to add the Desktop Virtualization Reader role.
Follow the same steps to add the Virtual Machine Contributor role.
![](https://i0.wp.com/www.ciraltos.com/wp-content/uploads/2021/05/Virtual-Machine-Contributor.png?resize=375%2C233&ssl=1)
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.
![](https://i0.wp.com/www.ciraltos.com/wp-content/uploads/2021/05/Functions.png?resize=201%2C210&ssl=1)
Click Add to add a Function.
![](https://i0.wp.com/www.ciraltos.com/wp-content/uploads/2021/05/Add-a-Function.png?resize=190%2C253&ssl=1)
From the list of templates, select Timer Trigger. This will trigger the Function based on a schedule.
![](https://i0.wp.com/www.ciraltos.com/wp-content/uploads/2021/05/Timer-Trigger.png?resize=645%2C299&ssl=1)
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.
![](https://i0.wp.com/www.ciraltos.com/wp-content/uploads/2021/05/Function-App-Schedule.png?resize=629%2C298&ssl=1)
See the link below for more details on scheduling for a Function App.
https://docs.microsoft.com/en-us/azure/azure-functions/functions-bindings-timer?tabs=csharp&WT.mc_id=AZ-MVP-5004159#ncrontab-expressions
Click Add to add the function app.
From within the Function, go to Code + Test under Developer.
![](https://i0.wp.com/www.ciraltos.com/wp-content/uploads/2021/05/Code-Test.png?resize=244%2C228&ssl=1)
If you need to change the schedule, go to the Function.json file in the Function drop-down.
![](https://i0.wp.com/www.ciraltos.com/wp-content/uploads/2021/05/Function-json.png?resize=267%2C145&ssl=1)
Change the schedule as needed.
![](https://i0.wp.com/www.ciraltos.com/wp-content/uploads/2021/05/Change-the-Schedule.png?resize=420%2C266&ssl=1)
Go back to the Run.ps1 file and replace the existing code with the StopSH-MultiHostPools.ps1 code. The code can be found here:
https://github.com/tsrob50/WVD-Public/blob/master/StopSH-MultiHostPools.ps1
Go to the Variables section. Update the Host Pool name and Resource Group for the Personal or Pooled Host Pool.
![](https://i0.wp.com/www.ciraltos.com/wp-content/uploads/2021/06/Variables1.png?resize=499%2C225&ssl=1)
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.
![](https://i0.wp.com/www.ciraltos.com/wp-content/uploads/2021/06/Host-Pool-Code-Block.png?resize=473%2C138&ssl=1)
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.
![](https://i0.wp.com/www.ciraltos.com/wp-content/uploads/2021/06/Two-Host-Pools-Variables.png?resize=392%2C292&ssl=1)
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
![](https://i0.wp.com/www.ciraltos.com/wp-content/uploads/2021/06/Get-Active-Session-Hosts.png?resize=926%2C374&ssl=1)
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.
![](https://i0.wp.com/www.ciraltos.com/wp-content/uploads/2021/06/Shut-Down-Session-Hosts-with-no-Sessions.png?resize=855%2C550&ssl=1)
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.
![](https://i0.wp.com/www.ciraltos.com/wp-content/uploads/2021/05/Test-Pool-Before-Script-Run.png?resize=704%2C195&ssl=1)
Open logs in the Function so we can see the activity.
![](https://i0.wp.com/www.ciraltos.com/wp-content/uploads/2021/05/Open-Logs.png?resize=635%2C204&ssl=1)
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.
![](https://i0.wp.com/www.ciraltos.com/wp-content/uploads/2021/05/Test-Run.png?resize=355%2C68&ssl=1)
![](https://i0.wp.com/www.ciraltos.com/wp-content/uploads/2021/05/Run-the-Function-Manually.png?resize=473%2C745&ssl=1)
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.
![](https://i0.wp.com/www.ciraltos.com/wp-content/uploads/2021/05/Download-PowerShell-Az-Module.png?resize=1024%2C134&ssl=1)
Once it finishes running, the output will indicate any Session Hosts that are shut down in the function output.
![](https://i0.wp.com/www.ciraltos.com/wp-content/uploads/2021/05/Function-Output.png?resize=1024%2C328&ssl=1)
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.
![](https://i0.wp.com/www.ciraltos.com/wp-content/uploads/2021/05/Session-Host-Unavailable.png?resize=689%2C204&ssl=1)
That, my friends, is how to shut down Session Hosts in a Personal Host Pool with no active sessions.
39 thoughts on “Shut Down Unused Session Hosts in a Windows Virtual Desktop Pooled or Personal Host Pool”
The link for the code is missing. Thanks!
Thank you! I added the link. Here it is also:
https://github.com/tsrob50/AzureFunctionDemos/tree/master/AutoStopPersonalHostPool
Great stuff! Now that autostart on connect is available for pooled hostpools as well, could we adapt this for that use case?
I have not tired it, but I think it should. I hope to test it soon.
-Travis
Love it! And now you can target Pooled host pools with auto-start!
https://docs.microsoft.com/en-us/azure/virtual-desktop/start-virtual-machine-connect
I have a large demo environment so I will be adapting this script to target all WVD demo session hosts.
Thanks! Let me know how it works in a pooled host pool. I’ll test it myself eventually but tied up with some other projects at the moment.
Hi Travis! Wonderful script! I am using in a Pooled, but I had a little problem with the name of the VM. In the Pooled Host Pool, the VM Name is not the ComputerName because it uses a prefix name of the host pool. When the script tries to shutdown the VM, It is using the ComputerName. (—> (($sessionHost).name). I dont know if this mess is my fault, because I didnt use the computer name in my AD with the prefix name of VDI. So, Windows VDI shows my AD ComputerName in the Host Pool, but, outside of the VM, that is what care for the script to shut it down… it does not works.
Is there a way to bring convert my Computer Name to the VM Name ? Please, if you can…. let me know (I would like to contribute ($) if you could help me! Thanks!
Hi Travis, is there a possibility that this Function App will work if the managed ID only has permissions on the Resource Group instead of the whole Subscription? In the organization I currently work for I am unable to set permissions on a subscription level, because of security precautions. I am able to set permissions on the Resource Group where the AVD host pool is though.
I haven’t tested that but it should work. Desktop virtualization reader will need to be assigned to the RG of the host pool and virtual machine contributor needs needs to be assigned to the RG the VM’s are in. They may both be in the same RG, but could be different.
Thank you for your reply. I can confirm that this script also works with permissions on RG level. It seems that when you have multiple subscriptions in your tenant, it will randomly pick the first available subscription. This was not the subscription where my session hosts were running, so that’s why it failed.
I was able to solve this by adding the -SubscriptionId in the Get-AzWvdUserSession and Get-AzWvdSessionHost lines.
It now works like a charm!
I like the auto shutdown of session hosts provided by this solution. I am also looking for a way of having session hosts start when total session count reaches a certain threshold across all active session hosts. This would alleviate the users having to wait for session hosts to start or at least lower the time. It seems this process could be used to start them as well with some code modification. The script could count the total max session limit across all session hosts. If total session count was less then 5 from the max, start another session host. Does this sound possible or am I going down a rabbit hole? I was looking into scale sets too, but it seems to be based around percent of processor which doesn’t meet my requirements.
I have another script designed to start and stop session hosts based on available sessions. Take a look here: https://www.ciraltos.com/update-with-breadth-first-auto-scale-session-hosts-in-windows-virtual-desktop-spring-update-arm-with-azure-function/
Thanks! It’s like you created this script based on my requirements 🙂 I throw it in the dev subscription and kick the tires.
Thanks! It’s like you created this script based on my requirements 🙂 I’ll throw it in the dev subscription and kick the tires.
I would like to have a configurable variable to keep x session hosts running. I don’t see that baked into the script but it would be nice. Lets say peak time is 6AM-6PM. You have some rogue user that logs in at 5:00AM and then has to wait for the server to spin up. If at least one host was running it would be the optimal end user experience.
Hello Travis,
As far as i read the topic i only needed to change 2 parrameters right?
I am tryinjg to apply this script on a test environment but getting below error, what am i doing wrong am i missing something?
2021-07-06T11:53:22.713 [Error] Timeout value of 00:05:00 exceeded by function ‘Functions.TimerTrigger1’ (Id: ‘8dadd015-612a-46b9-ba50-2bcdc77c210e’). Initiating cancellation.
2021-07-06T11:53:22.779 [Error] Executed ‘Functions.TimerTrigger1’ (Failed, Id=8dadd015-612a-46b9-ba50-2bcdc77c210e, Duration=300112ms)Timeout value of 00:05:00 was exceeded by function: Functions.TimerTrigger1
Hope to hear from you.
The error indicates the script is running beyond the 5 minute limit. It should not take that long for the script to finish, unless you have a very large environment. For a single host pool, only the host pool name and resource group needs to be updated. If there are multiple host pools, additional $allhostpools blocks need to be added.
Have you tried to restart the function app?
I tried to restart it and after that it was in the same state.
The current environment is only 2 AVD for testing purposes.
Can i see the output of the script where it is hanging on or debug it on my local powershell somehow?
I had the AZ requirement set to 5 as your print screen states as the standard was 6 but this also did not change anything.
I am kind of new to this so perhaps i miss something somewhere.
Hi Travis,
I kept testing and found out this has nothing to do with your script (Offcourse as others said it was wokring)
I was having a other Script which worked on a other Tenant but is not working in this tenant.
So i think you will not be able to help me in this but thank you for the script!
Hi Travis,
Just found out the first step what i missed (For people like me new to this):
You should also do the following:
In your functional app go to CORS
Add the following allowed origins:
https://functions.azure.com
https://functions-staging.azure.com
https://functions-next.azure.com
After that and a bit of time the scripts are working fine!
Hi Travis!
I am using your script to a single hostpool pooled (Depth), and sometimes, it is shuting down the vms with Active Sessions. Could you help me?
“timestamp [UTC]”,message,logLevel
“7/15/2021, 7:30:00.040 PM”,”Executing ‘Functions.CheckWVDVazio’ (Reason=’Timer fired at 2021-07-15T19:30:00.0127369+00:00′, Id=c051a287-e33e-42ec-ac1e-b8e6f90e90b1)”,Information
“7/15/2021, 7:30:15.028 PM”,”OUTPUT:”,Information
“7/15/2021, 7:30:15.358 PM”,”OUTPUT: Account SubscriptionName TenantId Environment”,Information
“7/15/2021, 7:30:15.359 PM”,”OUTPUT: ——- —————- ——– ———–“,Information
“7/15/2021, 7:30:15.361 PM”,”OUTPUT:”,Information
“7/15/2021, 7:30:15.361 PM”,”OUTPUT: MSI@50342 Microsoft Azure (Nononono): #1480349 xxxxxx-yyyy-0000-0000-0000000 AzureCloud”,Information
“7/15/2021, 7:30:18.527 PM”,”INFORMATION: Loaded Module ‘Az.Accounts'”,Information
“7/15/2021, 7:30:25.468 PM”,”INFORMATION: Loaded Module ‘Az.DesktopVirtualization'”,Information
“7/15/2021, 7:30:26.566 PM”,,Error
“7/15/2021, 7:30:26.566 PM”,”ERROR: Collection was modified; enumeration operation may not execute.
Exception :
Type : System.InvalidOperationException
TargetSite :
Name : ThrowInvalidOperationException_InvalidOperation_EnumFailedVersion
DeclaringType : System.ThrowHelper, System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e
MemberType : Method
Module : System.Private.CoreLib.dll
StackTrace :
at System.Collections.Generic.Dictionary`2.Enumerator.MoveNext()
at System.Net.Http.Headers.HttpHeaders.GetEnumeratorCore()+MoveNext()
at Microsoft.WindowsAzure.Commands.Utilities.Common.GeneralUtilities.ConvertHttpHeadersToWebHeaderCollection(HttpHeaders headers)
at Microsoft.WindowsAzure.Commands.Utilities.Common.GeneralUtilities.GetHttpRequestLog(String method, String requestUri, HttpHeaders headers, String body, IList`1 matchers)
at Microsoft.WindowsAzure.Commands.Utilities.Common.GeneralUtilities.GetLog(HttpRequestMessage request, IList`1 matchers)
at Microsoft.WindowsAzure.Commands.Utilities.Common.GeneralUtilities.GetLog(HttpRequestMessage request)
at Microsoft.Azure.Commands.Common.AzModule.c__DisplayClass21_0.b__0()
at Microsoft.Azure.PowerShell.Cmdlets.DesktopVirtualization.Cmdlets.GetAzWvdUserSession_List.c__DisplayClass66_0.b__1()
at Microsoft.Azure.PowerShell.Cmdlets.DesktopVirtualization.Cmdlets.GetAzWvdUserSession_List.Microsoft.Azure.PowerShell.Cmdlets.DesktopVirtualization.Runtime.IEventListener.Signal(String id, CancellationToken token, Func`1 messageData)
at Microsoft.Azure.Commands.Common.AzModule.OnBeforeCall(String id, CancellationToken cancellationToken, Func`1 getEventData, Func`4 signal, String processRecordId)
at Microsoft.Azure.Commands.Common.AzModule.EventListener(String id, CancellationToken cancellationToken, Func`1 getEventData, Func`4 signal, InvocationInfo invocationInfo, String parameterSetName, String correlationId, String processRecordId, Exception exception)
at Microsoft.Azure.PowerShell.Cmdlets.DesktopVirtualization.Module.Signal(String id, CancellationToken token, Func`1 getEventData, Func`4 signal, InvocationInfo invocationInfo, String parameterSetName, String correlationId, String processRecordId, Exception exception)
at Microsoft.Azure.PowerShell.Cmdlets.DesktopVirtualization.Cmdlets.GetAzWvdUserSession_List.Microsoft.Azure.PowerShell.Cmdlets.DesktopVirtualization.Runtime.IEventListener.Signal(String id, CancellationToken token, Func`1 messageData)
at Microsoft.Azure.PowerShell.Cmdlets.DesktopVirtualization.DesktopVirtualizationClient.UserSessionsListByHostPool_Call(HttpRequestMessage request, Func`3 onOk, Func`3 onDefault, IEventListener eventListener, ISendAsync sender)
at Microsoft.Azure.PowerShell.Cmdlets.DesktopVirtualization.DesktopVirtualizationClient.UserSessionsListByHostPool_Call(HttpRequestMessage request, Func`3 onOk, Func`3 onDefault, IEventListener eventListener, ISendAsync sender)
at Microsoft.Azure.PowerShell.Cmdlets.DesktopVirtualization.DesktopVirtualizationClient.UserSessionsListByHostPool(String subscriptionId, String resourceGroupName, String hostPoolName, String Filter, Func`3 onOk, Func`3 onDefault, IEventListener eventListener, ISendAsync sender)
at Microsoft.Azure.PowerShell.Cmdlets.DesktopVirtualization.Cmdlets.GetAzWvdUserSession_List.ProcessRecordAsync()
at Microsoft.Azure.PowerShell.Cmdlets.DesktopVirtualization.Cmdlets.GetAzWvdUserSession_List.ProcessRecordAsync()
Message : Collection was modified; enumeration operation may not execute.
Source : System.Private.CoreLib
HResult : -2146233079
CategoryInfo : NotSpecified: (:) [Get-AzWvdUserSession_List], InvalidOperationException
FullyQualifiedErrorId : Microsoft.Azure.PowerShell.Cmdlets.DesktopVirtualization.Cmdlets.GetAzWvdUserSession_List
InvocationInfo :
MyCommand : Get-AzWvdUserSession_List
ScriptLineNumber : 47
OffsetInLine : 1
HistoryId : 1
ScriptName : C:\home\site\wwwroot\CheckWVDVazio\run.ps1
Line : $activeShs = (Get-AzWvdUserSession -HostPoolName $personalHp -ResourceGroupName $personalHpRg).name
PositionMessage : At C:\home\site\wwwroot\CheckWVDVazio\run.ps1:47 char:1
+ $activeShs = (Get-AzWvdUserSession -HostPoolName $personalHp -Resourc …
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
PSScriptRoot : C:\home\site\wwwroot\CheckWVDVazio
PSCommandPath : C:\home\site\wwwroot\CheckWVDVazio\run.ps1
InvocationName : Get-AzWvdUserSession
CommandOrigin : Internal
ScriptStackTrace : at Get-AzWvdUserSession, C:\home\data\ManagedDependencies\210709125841223.r\Az.DesktopVirtualization\3.0.0\exports\ProxyCmdletDefinitions.ps1: line 2220
at , C:\home\site\wwwroot\CheckWVDVazio\run.ps1: line 47
PipelineIterationInfo :”,Error
“7/15/2021, 7:30:27.240 PM”,”INFORMATION: Server iwvmbr-0 is not active, shut down”,Information
“7/15/2021, 7:30:30.849 PM”,”OUTPUT:”,Information
“7/15/2021, 7:30:31.514 PM”,”INFORMATION: Server iwvmbr-1 is not active, shut down”,Information
“7/15/2021, 7:30:31.877 PM”,”OUTPUT: RequestId IsSuccessStatusCode StatusCode ReasonPhrase”,Information
“7/15/2021, 7:30:31.878 PM”,”OUTPUT: 6b54ab6e-5440-44b5-b4bf-388b36d8bd0c True Accepted Accepted”,Information
“7/15/2021, 7:30:31.878 PM”,”OUTPUT: ——— ——————- ———- ————“,Information
“7/15/2021, 7:30:31.878 PM”,”OUTPUT: 8b4af1a6-11c0-4feb-81c9-010b174387d1 True Accepted Accepted”,Information
“7/15/2021, 7:30:32.069 PM”,”INFORMATION: Server iwvmbr-2 is not active, shut down”,Information
“7/15/2021, 7:30:32.302 PM”,”OUTPUT: a0baac22-164b-4105-a776-c59182fff1b5 True Accepted Accepted”,Information
“7/15/2021, 7:30:32.681 PM”,”OUTPUT:”,Information
“7/15/2021, 7:30:32.713 PM”,”Executed ‘Functions.CheckWVDVazio’ (Succeeded, Id=c051a287-e33e-42ec-ac1e-b8e6f90e90b1, Duration=32693ms)”,Information
Hello Denilson,
Sorry you’re having problems. I reviewed the error and see what’s happening but not sure why. The get-azwvdusersession command is failing for some reason. That sets user sessions to nothing and the logic later in the script acts as if no one is logged in and shuts down the SH’s.
I’m reviewing and trying to replicate the issue but not having any luck. You could try to restart the function app or start over with a new function app. I’ll let you know if I find a solution.
Thanks,
Travis
I updated the script to exit on error. That will prevent the shutdown but doesn’t address the root issue.
Hi Travis! Dont need to apologizes! Your stuffs are amazing!
I am updating with your new code. If it is possible, a suggestion on error. Is it possible to send an notification? Imagine If the function fail… and my hostpool keep running… $$$ kkkkk
In time… I am trying to change the TImer Trigger. Could you tell me if this syntax is right:
“0 */30 0-8,19-23 * * *”
I will schedule to run this function only 0-8 AM and 7-11Pm. Is it right? 🙂
Thanks
Denilson
There are some cron expression translators online. This one will show a result https://crontab.cronhub.io/.
I have Log Analytics integration on my short list of projects. That will be able to log and alert on errors.
Thanks,
Travis
hello Travis, love your work
I am quite keen to get this going but I get the following , not sure why? Any help is appreciated
Connected!
2021-08-26T03:48:29 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-26T03:49:29 No new trace in the past 1 min(s).
2021-08-26T03:49:34.500 [Error] Executed ‘Functions.TimerTrigger1’ (Failed, Id=eb733a8e-b723-4bb0-b8e4-457b5eaf5888, Duration=66490ms)Result: FailureException: Failed to install function app dependencies. Error: ‘Failed to install function app dependencies. Error: ‘The running command stopped because the preference variable “ErrorActionPreference” or common parameter is set to Stop: Unable to save the module ‘Az’.”Stack: at Microsoft.Azure.Functions.PowerShellWorker.DependencyManagement.DependencyManager.WaitOnDependencyInstallationTask() in /home/vsts/work/1/s/src/DependencyManagement/DependencyManager.cs:line 246at Microsoft.Azure.Functions.PowerShellWorker.DependencyManagement.DependencyManager.WaitForDependenciesAvailability(Func`1 getLogger) in /home/vsts/work/1/s/src/DependencyManagement/DependencyManager.cs:line 164at Microsoft.Azure.Functions.PowerShellWorker.RequestProcessor.ProcessInvocationRequest(StreamingMessage request) in /home/vsts/work/1/s/src/RequestProcessor.cs:line 245
2021-08-26T03:51:29 No new trace in the past 1 min(s).
Hello Jace,
I’ve seen this in some other functions I’ve created recently. It’s having problems downloading the az modules prior to running the function. The issue usually resolves itself after a couple of hours. Try restarting the function app. If it’s still a problem, change the az module in the requirements.psd1 file in App Files from 6.* to 5.* and restart the function app for the change to take effect.
-Travis
Hi,
Thanks for your script, it work great for running but unused VM… just i need to use another script to deallocate stopped VM (when the user do a Shutdown and not a Logoff).
Would be great to have all in a single script (as the purpose is the same) : shutdown and deallocate unused running VM, but also deallocate unused stopped VM 🙂
hi – trying to this for AVD and getting following issues in og, any advise you can give would be appreciated thanks.
2021-11-29T13:33:20.894 [Information] Executing ‘Functions.TimerTrigger1′ (Reason=’This function was programmatically called via the host APIs.’, Id=be4d0183-6207-45c3-bd84-6a82a7880fdd)
2021-11-29T13:33:21.018 [Warning] The first managed dependency download is in progress, function execution will continue when it’s done. Depending on the content of requirements.psd1, this can take a few minutes. Subsequent function executions will not block and updates will be performed in the background.
2021-11-29T13:35:03 No new trace in the past 1 min(s).
2021-11-29T13:36:03 No new trace in the past 2 min(s).
2021-11-29T13:37:03 No new trace in the past 3 min(s).
2021-11-29T13:37:23.131 [Error] Executed ‘Functions.TimerTrigger1’ (Failed, Id=be4d0183-6207-45c3-bd84-6a82a7880fdd, Duration=242223ms)Result: FailureException: Failed to install function app dependencies. Error: ‘Failed to install function app dependencies. Error: ‘The running command stopped because the preference variable “ErrorActionPreference” or common parameter is set to Stop: Unable to save the module ‘Az’.”Stack: at Microsoft.Azure.Functions.PowerShellWorker.DependencyManagement.DependencyManager.WaitOnDependencyInstallationTask() in /home/vsts/work/1/s/src/DependencyManagement/DependencyManager.cs:line 246at Microsoft.Azure.Functions.PowerShellWorker.DependencyManagement.DependencyManager.WaitForDependenciesAvailability(Func`1 getLogger) in /home/vsts/work/1/s/src/DependencyManagement/DependencyManager.cs:line 164at Microsoft.Azure.Functions.PowerShellWorker.RequestProcessor.ProcessInvocationRequest(StreamingMessage request) in /home/vsts/work/1/s/src/RequestProcessor.cs:line 245
2021-11-29T13:39:03 Nonew trace in the past 1 min(s).
2021-11-29T13:40:03 No new trace in the past 2 min(s).
2021-11-29T13:41:03 No new trace in the past 3 min(s).
2021-11-29T13:42:03 No new trace in the past 4 min(s).
I have seen this a couple times in my testing. I think it takes too long for the AZ module to install. Typically, it will work if you wait about 30 minutes and try again. Downgrading the AZ version is also an option.
This is working now 🙂 – had to amend requirements.ps1 from “‘az’ = ‘6.*’ to ‘az’ = ‘5.*’ and perform a couple of restarts. – fingers crossed, looking god thanks
Hi Travis,
This is working great for personal pools but not on pooled. I get the message “has an active session, won’t shut down” but I can confirm that the session is not active.
We found many VMs shutdown by end-users from the OS. . The above function app is not deallocating those VMs because they became unavailable status under host pool but in stopped conditions. How do we deallocate those VMs?
Please suggest.
Hi Travis, lot of time saved with your script!
it works like a charm if you set ‘Az’ = ‘6.*’ in requirements.psd1
anything above and it does not work, do you plan to update the script?
Thanks again!!!
I know this post is fairly old, but I have attempted implementing it, and while it does run (I can see the expected log output and follow the executions in the Monitor section), I am facing the issue that it does not actually shut down the session hosts. In one of my pooled Host Pools, I have a session host called “NE-SH-7”. This session host does not have any connections, which is acknowledged by the script, evident in the log output:
2023-04-25 00:35:28.777 INFORMATION: Server NE-SH-7 is not active, shut down
2023-04-25 00:35:28.777 OUTPUT: Stopping Session Host NE-SH-7
However, after the execution has finished, the VM is still running. If I run the line from the script that shuts down the VM directly in the Azure Cloud Shell, it does shut down the VM correctly:
Get-azvm -ErrorAction Stop -Name “NE-SH-7” | Stop-AzVM -ErrorAction Stop -Force -NoWait
Do you have any idea what I might be missing? Is there some sort of permissions that could interfere with the script’s ability to communicate with the resource?
.. replying to my own question here, it seems I mixed up the role assignments. I accidentally assigned the “Desktop Virtualization Reader” role and the “Desktop Virtualization Contributor” role, where as the latter is obviously not the one that is needed, but should be the “Virtual Machine Contributor” role instead.
Well, if anyone is facing a similar issue, check that you didn’t make the same mistake as me.
Hi TRAVIS
I am getting below error on function app
system.private.CoreLib:Access to the path ‘c:\home\sitte\wwwroot\host.json’ is denied
and scrip is not working
can you help on ths?
HI Travis
I am seeing below error on function app
System.Private.COreLib: Access to the path ‘c:\home\site\wwwroot\host.json is denied
can you please share the latest code for this function as i am getting below error now adays without changes in code/APp:
RROR: Error getting a list of user sessions: Cannot bind argument to parameter ‘SubscriptionId’ because it is null. Exception : Type : Microsoft.PowerShell.Commands.WriteErrorException Message : Error getting a list of user sessions: Cannot bind argument to parameter ‘SubscriptionId’ because it is null. HResult : -2146233087 CategoryInfo : NotSpecified: (:) [Write-Error], WriteErrorException FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException,_TimerTrigger1_ InvocationInfo : MyCommand : _TimerTrigger1_ HistoryId : 1 InvocationName : _TimerTrigger1_ CommandOrigin : Internal ScriptStackTrace : at , C:\home\site\wwwroot\TimerTrigger1\run.ps1: line 100 PipelineIterationInfo :