Cloud & infrastructure

AVD scaling plans: autoscale session hosts and Start VM on Connect

Configure an Azure Virtual Desktop power management scaling plan, Start VM on Connect and disconnected-session limits so pooled session hosts are deallocated when nobody needs them.

12 min read
On this page

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.

PhaseTypical purposeWhat you configure
Ramp-upStart of the day as users arriveStart time, load balancing, minimum % of hosts, capacity threshold
PeakHighest usageStart time, load balancing (threshold carries over from ramp-up)
Ramp-downUsers leaveStart time, load balancing, minimum % of hosts, capacity threshold, force sign-out or stop rules
Off-peakOvernightStart 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/write on 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 @parameters

The 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, MaxSessionLimit

The 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 @scheduleParams

The 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,ScalingPlanEnabled

Step 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:$true
az desktopvirtualization hostpool update \
     --resource-group rg-avd \
     --name hp-avd-pooled-01 \
     --start-vm-on-connect true

Users 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, ConfigSchedulePhase

And find failed evaluations with their error details:

WVDAutoscaleEvaluationPooled
| where ResultType != "Succeeded"
| join kind=leftouter WVDErrors on CorrelationId
| order by _ResourceId asc, TimeGenerated asc

The 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

Questions people ask

Which role does Azure Virtual Desktop autoscale need?

Power management autoscale needs the Desktop Virtualization Power On Off Contributor role assigned to the Azure Virtual Desktop service principal or the host pool's managed identity, with the subscription as the scope. Microsoft states that assigning it at a lower scope, such as a resource group, prevents autoscale from working properly.

Can I use autoscale and Start VM on Connect together?

Yes. Start VM on Connect is configured per host pool and needs the Desktop Virtualization Power On Contributor role. For pooled host pools it starts a VM only when none are running, then more only when the first reaches its session limit, at most one VM every five minutes.

Does autoscale respect drain mode during maintenance?

No. For pooled host pools autoscale overwrites drain mode. Add the scaling plan's exclusion tag, for example excludeFromScaling, to session hosts you're maintaining so autoscale doesn't start, stop or change their drain mode.

Do deallocated session hosts still cost money?

Compute stops being billed when a VM is deallocated, but managed disks and some networking resources continue to incur charges. Autoscale saves the compute portion of the session host cost.

Azure Virtual DesktopScaling PlansStart VM on ConnectAzure Cost Management
  1. AVD pooled host pool with FSLogix profiles on Azure Files (Entra Kerberos)

    Build an Azure Virtual Desktop pooled host pool with Microsoft Entra joined session hosts and FSLogix profile containers stored on Azure Files, using Microsoft Entra Kerberos instead of domain controllers.

  2. Azure Cost Optimization Checklist: Reservations, Right-Sizing and Cleanup

    A practical order of work for cutting an Azure bill: budgets first, then orphaned resources, idle VMs, Advisor right-sizing, Azure Hybrid Benefit, and only then reservations and savings plans.

  3. Azure Policy Tag Enforcement: Require, Inherit and Remediate Tags

    Enforce CostCenter and Owner tags with built-in Azure Policy definitions: deny untagged resource groups, inherit tags onto resources, and remediate existing resources with managed identities.