An Azure Virtual Desktop scaling plan powers pooled session hosts on and off on a schedule you define, split into ramp-up, peak, ramp-down and off-peak phases, so that you only pay for compute when users need it. To set one up, assign the Desktop Virtualization Power On Off Contributor role to the Azure Virtual Desktop service principal at subscription scope, set a real max session limit on the host pool, create a power management scaling plan with weekday and weekend schedules, and assign it to the host pool. Add Start VM on Connect so users can still sign in when every host is deallocated.
Who this is for and what you will have
This guide is for administrators running pooled AVD host pools on Azure who want to cut compute spend without hurting the sign-in experience. You need a working host pool already; if you don't have one, start with building an AVD host pool with FSLogix profiles on Azure Files.
At the end you will have:
- The correct role assignments for autoscale and Start VM on Connect.
- A scaling plan with a weekday schedule and a weekend schedule assigned to your host pool.
- Start VM on Connect enabled as a safety net.
- A policy that signs out long-disconnected sessions so hosts can drain.
- Log Analytics queries and a Cost Management view to confirm the plan is doing what you expect.
How autoscale decides when to start and stop hosts
For pooled host pools, autoscale works with three numbers:
- Available host pool capacity = running, available session hosts multiplied by the host pool's max session limit.
- Used host pool capacity = user sessions divided by available host pool capacity.
- Capacity threshold = the percentage of used capacity that triggers a scaling action.
Autoscale starts another session host when used capacity goes above the capacity threshold. It stops a host only when the used capacity would stay at or below the threshold afterwards, and only hosts with no user sessions, unless you're in ramp-down with forced sign-out enabled. It never stops hosts during ramp-up.
Microsoft's example: six hosts, a max session limit of five, a 30% capacity threshold and two hosts running gives 10 sessions of capacity. Three users is 30% used, so nothing happens. A fourth user makes it 40%, so autoscale starts a third host, bringing used capacity back to 27%.
The minimum percentage of hosts keeps a floor of running hosts in each phase. Autoscale rounds up, so 10% of seven hosts keeps one running.
| Phase | Typical purpose | What you configure |
|---|---|---|
| Ramp-up | Start of the day as users arrive | Start time, load balancing, minimum % of hosts, capacity threshold |
| Peak | Highest usage | Start time, load balancing (threshold carries over from ramp-up) |
| Ramp-down | Users leave | Start time, load balancing, minimum % of hosts, capacity threshold, force sign-out or stop rules |
| Off-peak | Overnight | Start time, load balancing (threshold carries over from ramp-down) |
During ramp-down, autoscale assumes disconnected users may reconnect, so it calculates required hosts from active plus disconnected sessions. That's why a disconnected-session time limit matters later in this guide.
Two behaviours catch people out. Autoscale ignores the load balancing algorithm in the host pool settings and uses the one in the current phase. And for pooled host pools it overwrites drain mode, so you need the exclusion tag during maintenance.
Prerequisites
- A pooled host pool with standard management on Azure. Power management autoscaling is the method for host pools without a session host configuration; dynamic autoscaling, which also creates and deletes hosts, is only for pooled host pools that use a session host configuration.
- The scaling plan must be created in the same Azure region as the host pool's metadata. Session hosts can be in any supported region.
- No other scaling tool on the same host pool, including the Azure Automation and Logic Apps scaling solution or third-party tools.
- A configured max session limit on the host pool; Microsoft says not to use the default value.
Microsoft.Authorization/roleAssignments/writeon the subscription (Owner or User Access Administrator) to assign roles to the service principal.- Az.DesktopVirtualization 4.2.0 or later if you use PowerShell.
Step 1: Assign roles to the Azure Virtual Desktop service principal
Autoscale needs Desktop Virtualization Power On Off Contributor, assigned to the Azure Virtual Desktop service principal or a managed identity on the host pool, with the subscription as the scope. Each subscription that contains host pools or session hosts you want to scale needs the assignment.
The service principal's display name begins with either Azure Virtual Desktop or Windows Virtual Desktop depending on when you registered the resource provider. Identify it by its application ID, 9cdead84-a844-4324-93f2-b2e6bb768d07.
$subId = "<SubscriptionID>"
$parameters = @{
RoleDefinitionName = "Desktop Virtualization Power On Off Contributor"
ApplicationId = "9cdead84-a844-4324-93f2-b2e6bb768d07"
Scope = "/subscriptions/$subId"
}
New-AzRoleAssignment @parametersThe Azure CLI equivalent:
subId="<SubscriptionID>"
az role assignment create \
--assignee "9cdead84-a844-4324-93f2-b2e6bb768d07" \
--role "Desktop Virtualization Power On Off Contributor" \
--scope "/subscriptions/$subId"Start VM on Connect uses a separate role, Desktop Virtualization Power On Contributor. Microsoft now says it can be scoped lower than the subscription as long as the scope contains the session host VMs, but a single subscription-scope assignment is simpler when the subscription is dedicated to AVD.
Step 2: Set the host pool's max session limit
Autoscale's capacity maths depends on the max session limit. If you created the host pool with breadth-first load balancing, the default is 999999, so used capacity stays close to zero and the capacity threshold is effectively never reached. Set a value that reflects how many users a session host of your VM size can really carry: open the host pool, select Properties, enter Max session limit and select Save. With PowerShell:
$parameters = @{
Name = 'hp-avd-pooled-01'
ResourceGroupName = 'rg-avd'
LoadBalancerType = 'BreadthFirst'
MaxSessionLimit = '12'
}
Update-AzWvdHostPool @parameters
Get-AzWvdHostPool -Name 'hp-avd-pooled-01' -ResourceGroupName 'rg-avd' | Format-Table Name, LoadBalancerType, MaxSessionLimitThe value 12 is only an example. AVD Insights shows sessions per host and host performance, which helps you pick a realistic number.
Step 3: Create the scaling plan
In the Azure portal search for Azure Virtual Desktop, select Scaling Plans > Create, and complete Basics:
- Scaling plan name and Location (same region as the host pool).
- Time zone. The plan only operates in this time zone.
- Host pool type: Pooled.
- Exclusion tag: for example
excludeFromScaling. Hosts with this tag aren't started, stopped or drained by autoscale, but still count toward the minimum percentage of hosts. Don't put personal data in the tag. - Scaling method: Power management autoscaling.
On Schedules, select Add schedule, name it and choose Repeat on days. A sensible pattern is one weekday schedule and one weekend schedule. If you leave days unselected, the last off-peak settings carry over until the next ramp-up, so a Monday-to-Friday-only plan keeps Friday's off-peak settings all weekend.
Fill in the phases. Microsoft recommends breadth-first in ramp-up so arriving users spread out and sign in quickly, and depth-first in off-peak so sessions pack onto fewer hosts. For ramp-down choose whether to Force logoff of users:
- Forced: autoscale picks the host with the fewest sessions, puts it in drain mode, sends your notification message, waits the configured delay, signs users out and deallocates the VM.
- Not forced: choose to stop hosts when they have no active or disconnected sessions or no active sessions.
Either way, autoscale only stops hosts if all sessions can be consolidated without exceeding the capacity threshold and while respecting the minimum percentage of hosts.
On Host pool assignments tick your host pool. Changes to a plan that's already assigned apply immediately. Add tags, then Review + create.
The same plan in PowerShell, using Microsoft's example timings (ramp-up 06:30, peak 08:30, ramp-down 16:00, off-peak 22:45):
$scalingPlanParams = @{
ResourceGroupName = 'rg-avd'
Name = 'sp-avd-weekday'
Location = '<AzureRegion>'
HostPoolType = 'Pooled'
TimeZone = 'GMT Standard Time'
HostPoolReference = @(@{'hostPoolArmPath' = '/subscriptions/<subscription-id>/resourceGroups/rg-avd/providers/Microsoft.DesktopVirtualization/hostPools/hp-avd-pooled-01'; 'scalingPlanEnabled' = $true;})
}
$scalingPlan = New-AzWvdScalingPlan @scalingPlanParams
$scheduleParams = @{
ResourceGroupName = 'rg-avd'
ScalingPlanName = 'sp-avd-weekday'
ScalingPlanScheduleName = 'weekdays'
DaysOfWeek = 'Monday','Tuesday','Wednesday','Thursday','Friday'
RampUpStartTimeHour = '6'
RampUpStartTimeMinute = '30'
RampUpLoadBalancingAlgorithm = 'BreadthFirst'
RampUpMinimumHostsPct = '20'
RampUpCapacityThresholdPct = '60'
PeakStartTimeHour = '8'
PeakStartTimeMinute = '30'
PeakLoadBalancingAlgorithm = 'BreadthFirst'
RampDownStartTimeHour = '16'
RampDownStartTimeMinute = '0'
RampDownLoadBalancingAlgorithm = 'DepthFirst'
RampDownMinimumHostsPct = '10'
RampDownCapacityThresholdPct = '90'
RampDownForceLogoffUser = $true
RampDownWaitTimeMinute = '30'
RampDownNotificationMessage = 'Please save your work and sign out. This session host will shut down in 30 minutes.'
RampDownStopHostsWhen = 'ZeroSessions'
OffPeakStartTimeHour = '22'
OffPeakStartTimeMinute = '45'
OffPeakLoadBalancingAlgorithm = 'DepthFirst'
}
New-AzWvdScalingPlanPooledSchedule @scheduleParamsThe percentages here are starting points to tune, not recommendations from Microsoft. A low threshold in ramp-up starts hosts early so users aren't waiting; a high threshold and low minimum in ramp-down consolidates sessions aggressively at the end of the day.
Add a second schedule for Saturday and Sunday with a low minimum percentage of hosts, then confirm the assignment:
(Get-AzWvdScalingPlan -ResourceGroupName rg-avd -Name sp-avd-weekday).HostPoolReference | FL HostPoolArmPath,ScalingPlanEnabledStep 4: Enable Start VM on Connect
If your off-peak minimum is low, there will be times when no host is running. Start VM on Connect lets the first user's connection start one. For pooled host pools it starts a VM only when none are running, and further VMs only when the first reaches its session limit. It starts at most one VM every five minutes; other users trying to connect in that window see No resources available and should retry a few minutes later.
Enable it on an existing host pool in Host pools > your pool > Properties > Start VM on connect > Yes, or:
Update-AzWvdHostPool -ResourceGroupName rg-avd -Name hp-avd-pooled-01 -StartVMOnConnect:$trueaz desktopvirtualization hostpool update \
--resource-group rg-avd \
--name hp-avd-pooled-01 \
--start-vm-on-connect trueUsers connecting through Windows App or the Remote Desktop app are told a VM is being powered on. Connecting straight to a VM by IP address or name, outside the AVD service, doesn't start it.
Step 5: Sign out disconnected sessions
Because ramp-down counts disconnected sessions as users who may come back, a few forgotten sessions can keep hosts running all night. Configure Set time limit for disconnected sessions on the session hosts:
- Intune: create a configuration profile for Windows 10 and later, browse to Administrative templates > Windows Components > Remote Desktop Services > Remote Desktop Session Host > Session Time Limits, enable Set time limit for disconnected sessions and pick a value.
- Group Policy: the same setting under Computer Configuration > Policies > Administrative Templates > Windows Components > Remote Desktop Services > Remote Desktop Session Host > Session Time Limits.
Restart the session hosts after the policy applies.
Step 6: Handle maintenance with the exclusion tag
Before patching or reimaging a host, add a tag named after your exclusion tag (for example excludeFromScaling) to the VM, then set drain mode. Autoscale leaves tagged hosts alone. Remove the tag when you're done. If you tag every host and turn them all off, autoscale can't start anything and users get "No resources available".
Verify autoscale is working
Send autoscale logs to Log Analytics. Open the host pool, select Diagnostic settings > Add diagnostic setting, select at least Checkpoint, Error, Management, Connection, HostRegistration, AgentHealthStatus and Autoscale logs for pooled host pools, and send them to the Log Analytics workspace your AVD Insights uses. Then open the host pool's Insights and choose the Autoscale tab to see power state changes over time.
Query the evaluations directly:
WVDAutoscaleEvaluationPooled
| where ResultType == "Succeeded"
| extend properties = parse_json(Properties)
| extend BeganStartVmCount = toint(properties.BeganStartVmCount)
| extend BeganDeallocateVmCount = toint(properties.BeganDeallocateVmCount)
| extend BeganForceLogoffOnSessionHostCount = toint(properties.BeganForceLogoffOnSessionHostCount)
| summarize sum(BeganStartVmCount), sum(BeganDeallocateVmCount), sum(BeganForceLogoffOnSessionHostCount) by _ResourceId, bin(TimeGenerated, 1d), ConfigScheduleName, ConfigSchedulePhaseAnd find failed evaluations with their error details:
WVDAutoscaleEvaluationPooled
| where ResultType != "Succeeded"
| join kind=leftouter WVDErrors on CorrelationId
| order by _ResourceId asc, TimeGenerated ascThe ScalingReasonMessage column explains each decision, which is the quickest way to understand why a host did or didn't start.
Measure the saving
Deallocated VMs aren't billed for compute, but disks and some networking continue to incur charges, so autoscale reduces the VM line rather than the whole AVD bill. In Cost Management > Cost analysis, scope to the session hosts' resource group, use a daily granularity and group by resource or service to compare compute cost before and after the plan went live. Add a budget on the resource group so an autoscale failure that leaves every host running shows up quickly.
Troubleshooting
Autoscale never starts or stops anything. Check the Power On Off Contributor assignment is on the subscription, not a resource group, and that it targets the right service principal (application ID 9cdead84-a844-4324-93f2-b2e6bb768d07).
You can't assign the plan to a host pool. The plan and host pool must be in the same region and the plan's host pool type must match the pool.
Hosts in drain mode come back into service. Autoscale overwrote drain mode. Use the exclusion tag.
Hosts stay on overnight. Disconnected sessions are being counted. Apply the disconnected session time limit, or enable forced sign-out in ramp-down.
Users see "No resources available" first thing in the morning. Start VM on Connect only starts one VM every five minutes. Move ramp-up earlier or raise its minimum percentage of hosts.
Load balancing isn't what you set on the host pool. Expected: the scaling plan's per-phase algorithm wins.
Closing checklist
- Power On Off Contributor on the AVD service principal at subscription scope; Power On Contributor for Start VM on Connect.
- Max session limit set from real load testing.
- Scaling plan in the host pool's region, correct time zone, weekday and weekend schedules, assigned and enabled.
- Exclusion tag defined and used for maintenance.
- Start VM on Connect enabled.
- Disconnected session time limit applied.
- Autoscale logs flowing to Log Analytics and a cost view or budget on the session host resource group.
References
- Create and assign an autoscale scaling plan for Azure Virtual Desktop
- Configure host pool load balancing in Azure Virtual Desktop
- Autoscale scaling plans and example scenarios
- Assign Azure RBAC roles to the Azure Virtual Desktop service principals
- Configure Start VM on Connect
- Monitor Autoscale operations with Insights
- Set up diagnostics for Autoscale
- States and billing status of Azure Virtual Machines
- Quickstart: Start using Cost analysis