Automating the Disconnected Session Time Limit Policy with PowerShell and Microsoft Graph
Reclaiming host pool capacity from disconnected sessions is one of those AVD hygiene tasks that’s easy to configure once by hand in Intune, and easy to forget to replicate consistently across every host pool, tenant, or DTAP environment you manage. If you’re standing up host pools programmatically — or you just want this baked into your onboarding pipeline — it makes sense to push the “Set time limit for disconnected sessions” policy through Microsoft Graph instead of clicking through the admin center every time.
This post walks through doing exactly that: creating and assigning a Settings Catalog configuration profile via Graph, end to end, in PowerShell.

Why automate this one
Disconnected sessions quietly eat into pooled host pool capacity. A user disconnects instead of signing out, the session stays resident, and your load balancer keeps counting that host as partially occupied. Enforcing a disconnected-session timeout is a cheap capacity win — but only if it’s actually applied everywhere it should be. Automating it means every new host pool’s device group inherits the setting the moment it’s created, with no manual step to forget.
Prerequisites
- Microsoft Graph PowerShell SDK (Microsoft.Graph.Authentication, Microsoft.Graph.DeviceManagement)
- An app registration or delegated account with at minimum: DeviceManagementConfiguration.ReadWrite.All, and Group.Read.All (to resolve the target Azure AD group for assignment)
- The target Azure AD group containing your session host devices already created
Install-Module Microsoft.Graph -Scope CurrentUser
Connect-MgGraph -Scopes "DeviceManagementConfiguration.ReadWrite.All","Group.Read.All"
Step 1: Find the Settings Catalog setting definition
The Intune UI path in the walkthrough (Administrative templates > Windows Components > Remote Desktop Services > Remote Desktop Session Host > Session Time Limits > Set time limit for disconnected sessions) maps to an ADMX-backed setting in the Settings Catalog. Rather than hardcoding a setting definition ID that can drift between Graph API versions, resolve it dynamically:
$searchTerm = "disconnected sessions"
$definitions = Invoke-MgGraphRequest -Method GET `
-Uri "https://graph.microsoft.com/beta/deviceManagement/configurationSettings?$filter=contains(displayName,'$searchTerm')"
$definitions.value | Select-Object id, displayName, categoryId
Confirm you’ve got the disconnected-session setting (not the active-session or idle-session variants — the display names are similar) and note its id. This is your settingDefinitionId for the payload below.
Step 2: Build and create the configuration policy
Settings Catalog policies take a settingsInstance payload per setting. For a choice-type setting like this one (Enabled + a duration from a dropdown), the body looks like this:
$settingDefinitionId = "<id from step 1>"
$timeoutMinutes = 60 # match this to whichever dropdown value you selected in testing
$body = @{
name = "AVD - Disconnected Session Timeout"
description = "Signs out disconnected sessions after $timeoutMinutes minutes to reclaim host pool capacity"
platforms = "windows10"
technologies = "mdm"
settings = @(
@{
"@odata.type" = "#microsoft.graph.deviceManagementConfigurationSetting"
settingInstance = @{
"@odata.type" = "#microsoft.graph.deviceManagementConfigurationChoiceSettingInstance"
settingDefinitionId = $settingDefinitionId
choiceSettingValue = @{
"@odata.type" = "#microsoft.graph.deviceManagementConfigurationChoiceSettingValue"
value = "$settingDefinitionId_1" # enabled option, confirm exact value string from step 1's options
children = @()
}
}
}
)
} | ConvertTo-Json -Depth 10
$policy = Invoke-MgGraphRequest -Method POST `
-Uri "https://graph.microsoft.com/beta/deviceManagement/configurationPolicies" `
-Body $body -ContentType "application/json"
$policy.id
A quick note here: the exact value string for the “enabled” choice and the child setting carrying the duration will follow the pattern <settingDefinitionId>_<optionIndex> and a nested simpleSettingCollectionValue or choiceSettingValue for the timeout duration. Pull the full option list from deviceManagementConfigurationSettings/{id}/options in step 1 and inspect it once interactively before hardcoding — Microsoft occasionally revises these enumerations, so scripting the lookup rather than pinning literal GUIDs keeps this resilient across tenants.
Step 3: Assign the policy to your host pool device group
$groupId = "<object ID of your AVD session host Azure AD group>"
$assignBody = @{
assignments = @(
@{
target = @{
"@odata.type" = "#microsoft.graph.groupAssignmentTarget"
groupId = $groupId
}
}
)
} | ConvertTo-Json -Depth 5
Invoke-MgGraphRequest -Method POST `
-Uri "https://graph.microsoft.com/beta/deviceManagement/configurationPolicies/$($policy.id)/assign" `
-Body $assignBody -ContentType "application/json"
Step 4: Verify and force a policy sync
Rather than waiting for the default 8-hour MDM check-in, trigger sync on the affected hosts so you can validate immediately:
$deviceIds = (Get-MgGroupMember -GroupId $groupId).Id
foreach ($deviceId in $deviceIds) {
Invoke-MgGraphRequest -Method POST `
-Uri "https://graph.microsoft.com/beta/deviceManagement/managedDevices/$deviceId/syncDevice"
}
Then confirm application state per device:
Invoke-MgGraphRequest -Method GET `
-Uri "https://graph.microsoft.com/beta/deviceManagement/configurationPolicies/$($policy.id)/deviceStatuses"
Restart requirement
As with the manual Intune flow, the registry change (MaxDisconnectionTime under HKLM\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services) only takes effect after a host restart. If this is part of a host pool provisioning pipeline, fold the restart into your image finalization or scaling plan sequence rather than issuing it as a separate manual step — that’s the difference between “policy exists” and “policy is actually enforced” on session hosts that are already live.
Wrapping up
This pattern — resolve setting definition dynamically, build the Settings Catalog payload, assign to group, force sync, verify status — generalizes to pretty much any Administrative Template policy you want to push through Graph instead of the console. Once you’ve got the setting definition ID and its option values captured, this whole block drops cleanly into a host pool provisioning script or a scheduled compliance-remediation job.