Besoin de plafonner, par groupe AD, le nombre de sessions RDP concurrentes sur Windows Server 2022 ? Voici des approches concrètes (scripts, IAM/NAC, broker/load balancer), un mode opératoire PowerShell reproductible et des bonnes pratiques pour réduire la charge et les coûts d’infrastructure.
Limiter le nombre de connexions simultanées pour chaque groupe de sécurité (Windows Server 2022)
Vue d’ensemble de la question
Vous disposez de plusieurs centaines de groupes de sécurité Active Directory (AD) et souhaitez imposer, pour chacun d’eux, un plafond de connexions interactives simultanées (ex. : 35 utilisateurs connectés en même temps pour un groupe de 100 membres, 50 pour un autre de 250, etc.). L’objectif est de contrôler la charge et les coûts d’infrastructure.
Ce qu’il faut retenir en 30 secondes
- AD/GPO n’offrent pas nativement de quota de connexions simultanées par groupe.
- Deux voies réalistes : scripts PowerShell (rapide à déployer, plus de maintenance) ou plateforme IAM/NAC (coût supérieur, mais robuste et centralisée).
- Sur RDS/VDI, un broker ou un load balancer peut injecter une logique personnalisée dans le flux de connexion.
- Démarrer en mode audit, instrumenter, puis activer l’enforcement avec des messages d’avertissement et des logs exploitables.
Panorama des approches
Approche | Principe | Points clés | Limitations |
---|---|---|---|
Fonctionnalités natives AD / GPO | Aucune stratégie, attribut AD ou clé de registre ne permet de plafonner les connexions simultanées par groupe. Les options « Limit number of connections » de Remote Desktop Services ou les licences RDS s’appliquent au niveau du serveur/collection, pas du groupe. | Administration simple, pas d’outil tiers, pas de sur‑coût. | Ne répond pas au besoin « par groupe » ; risque de saturation si tout le monde se connecte. |
Script de supervision + coupure automatique | Un script PowerShell planifié ou déclenché à la connexion : interroge les hôtes RDS/VDI, compte les sessions actives par groupe, et déconnecte les sessions excédentaires (ou refuse immédiatement la connexion via un script d’ouverture de session). | 100 % personnalisable ; zéro licence ; rapide à mettre en place. | Maintenance interne, risques si le script tombe en panne, déconnexion potentiellement brutale (prévoir avertissement et délai de grâce). |
Solution IAM / NAC tierce | Plateforme IAM/NAC (Okta, ForgeRock, NetIQ, JumpCloud, Cisco ISE, etc.) branchée sur AD : applique des règles de session (limite simultanée par groupe/rôle), met en quarantaine ou refuse la connexion, offre des dashboards et des alertes. | Gestion centralisée, rapports, alertes, support multi‑protocoles (RDP, VPN, SSH, Web…), haute disponibilité. | Coût de licence et de projet ; intégration initiale (LDAP/RADIUS/SSO) à prévoir. |
Broker RDS/VDI + Load Balancer avec logique personnalisée | Dans des fermes RDS/VDI, le broker (ou le load balancer : F5, Citrix ADC, Kemp) peut appeler un script/API pour n’autoriser qu’un certain nombre de sessions issues d’un groupe donné avant de refuser l’accès. | Intégré au flux RDP/ICA/Gateway existant ; contrôle en temps réel sur le trafic géré. | Configuration plus complexe ; contrôle limité aux services derrière le broker/balancer. |
Recommandation pragmatique
- Qualifier la criticité : si les pics sont rares, un script PowerShell planifié (ex. chaque 2–5 min) suffit. Si les dépassements coûtent cher (SLA, licences, cloud), privilégier IAM/NAC ou une logique broker.
- Prototyper en mode audit (aucune coupure), valider le comptage, observer pendant 1–2 semaines, puis activer l’enforcement avec avertissement utilisateur et délai de grâce.
- Sécuriser et superviser : compte de service dédié, journaux signés, alertes en cas de quota atteint, traçabilité des déconnexions.
- Anticiper l’évolutivité : si les règles deviennent variées (plages horaires, exceptions, priorités), basculer vers une solution IAM/NAC (coût > 0, maintenance < scripts multiples).
Implémentation PowerShell prête à l’emploi (mode audit puis enforcement)
Principe
Le script suivant lit une configuration JSON (serveurs, groupes et plafonds), recense les sessions par hôte, associe chaque session aux groupes AD (membres directs et imbriqués), comptabilise par groupe et soit journalise (AUDIT), soit avertit puis déconnecte les sessions excédentaires (ENFORCE) selon une politique (plus longue inactivité ou plus ancienne connexion).
Pré‑requis
- Windows Server 2019/2022 sur les hôtes RDS/Session Host (ou VDI) avec
PowerShell 5.1+
. - Outils RSAT (module
ActiveDirectory
) sur le serveur d’orchestration du script. - Compte de service disposant des droits lecture AD et autorisé à exécuter
quser/qwinsta
etlogoff
à distance (administrateurs locaux des hôtes RDS recommandés). - Pare‑feu/WinRM ouverts si vous utilisez des commandes distantes.
Fichier de configuration JSON
Exemple C:\Ops\GroupSessionLimits.json
:
{
"Servers": ["RDSH01","RDSH02","RDSH03"],
"Groups": [
{ "Name": "SALES", "Max": 35, "NotifySeconds": 60, "Policy": "LongestIdle" },
{ "Name": "FINANCE", "Max": 50, "NotifySeconds": 60, "Policy": "OldestLogon" }
],
"ExcludeUsers": [ "svc_*", "adm_*" ],
"Mode": "Audit", // valeurs possibles : Audit ou Enforce
"LogPath": "C:\\Ops\\Logs\\GroupLimit.log"
}
Remarques : utilisez des jokers simples dans ExcludeUsers
(gérés comme expressions wildcard). Passez en Enforce
uniquement après validation en audit.
Script PowerShell (complet et commenté)
#requires -Version 5.1
<#
.SYNOPSIS
Plafonne les sessions simultanées par groupe AD sur un parc RDS/VDI.
.DESCRIPTION
- Lit un JSON de configuration
- Recense les sessions actives par hôte (quser/qwinsta)
- Compte par groupe (membres directs + imbriqués)
- Avertit puis déconnecte les sessions excédentaires (mode Enforce)
- Journalise tout (fichier + journal des événements en option)
#>
param(
\[Parameter(Mandatory=\$true)]
\[string]\$ConfigPath
)
# --- Utils --------------------------------------------------------------------
function Write-Log {
param(\[string]\$Message,\[string]\$Level="INFO")
\$ts = Get-Date -Format "yyyy-MM-dd HH\:mm\:ss"
\$line = "\[\$ts]\[\$Level] \$Message"
if (\$Global\:LogFile) { Add-Content -Path \$Global\:LogFile -Value \$line -Encoding UTF8 }
Write-Output \$line
}
function Test-WildcardMatch {
param(\[string]\$Value,\[string\[]]\$Patterns)
foreach (\$p in \$Patterns) {
if (\$Value -like \$p) { return \$true }
}
return \$false
}
# Convertit "1+23:45" ou "23:45" ou "." en TimeSpan
function Convert-IdleToTimeSpan {
param(\[string]\$Idle)
if (\[string]::IsNullOrWhiteSpace(\$Idle) -or \$Idle -eq ".") { return \[TimeSpan]::Zero }
if (\$Idle -match "^\d++\d{1,2}:\d{2}\$") {
\$d,\$t = \$Idle -split "+"
\$ts = \[TimeSpan]::Parse(\$t)
return (New-TimeSpan -Days \[int]\$d) + \$ts
}
return \[TimeSpan]::Parse(\$Idle)
}
# Parse robuste de 'quser' (multi-colonnes)
function Get-UserSessionsFromQuser {
param(\[string]\$ComputerName)
\$raw = & quser /server:\$ComputerName 2>&1
if (\$LASTEXITCODE -ne 0 -or -not \$raw) { return @() }
\$lines = \$raw | Select-Object -Skip 1
\$sessions = @()
foreach (\$line in \$lines) {
\# Normalise les espaces, évite la casse des colonnes locales
\$s = (\$line -replace "\s{2,}"," ").Trim()
\# Cas avec \ optionnel
\$parts = \$s -split " "
if (\$parts.Count -lt 6) { continue }
\# Heuristique : si le 2e token n'est pas numérique, c'est le SessionName
\$user = \$parts\[0]
\$hasSessionName = (\$parts\[1] -notmatch "^\d+\$")
if (\$hasSessionName) {
\$sessionName = \$parts\[1]
\$id = \[int]\$parts\[2]
\$state = \$parts\[3]
\$idle = \$parts\[4]
\$logon = (\$parts | Select-Object -Skip 5) -join " "
} else {
\$sessionName = \$null
\$id = \[int]\$parts\[1]
\$state = \$parts\[2]
\$idle = \$parts\[3]
\$logon = (\$parts | Select-Object -Skip 4) -join " "
}
\$sessions += \[PSCustomObject]@{
Server = \$ComputerName
UserName = \$user
SessionId = \$id
State = \$state
Idle = \$idle
IdleSpan = Convert-IdleToTimeSpan \$idle
LogonTime = \$logon
}
}
return \$sessions
}
function Send-SessionMessage {
param(\[string]\$Server,\[int]\$SessionId,\[int]\$Seconds,\[string]\$Text)
try { & msg \$SessionId /server:\$Server /time:\$Seconds \$Text | Out-Null } catch { }
}
function Logoff-Session {
param(\[string]\$Server,\[int]\$SessionId)
try { & logoff \$SessionId /server:\$Server | Out-Null } catch { }
}
# --- Chargement configuration --------------------------------------------------
if (-not (Test-Path -Path \$ConfigPath)) { throw "Config introuvable : \$ConfigPath" }
\$config = Get-Content -Path \$ConfigPath -Raw | ConvertFrom-Json
\$Global\:LogFile = \$config.LogPath
Write-Log "Mode = \$(\$config.Mode) | Serveurs = \$(\$config.Servers -join ', ')"
# --- Pré-chargement des membres par groupe (avec groupes imbriqués) -----------
Import-Module ActiveDirectory -ErrorAction Stop
\$groupMaps = @{}
foreach (\$g in \$config.Groups) {
\$members = @()
try {
\$members = Get-ADGroupMember -Identity \$g.Name -Recursive -ErrorAction Stop |
Where-Object { $\_.ObjectClass -eq 'user' } |
Select-Object -ExpandProperty SamAccountName
} catch {
Write-Log "Groupe introuvable ou accès refusé : \$(\$g.Name)" "WARN"
}
\$groupMaps\[\$g.Name] = \[System.Collections.Generic.HashSet\[string]]::new(\[StringComparer]::OrdinalIgnoreCase)
foreach (\$m in \$members) { \[void]\$groupMaps\[\$g.Name].Add(\$m) }
Write-Log "Groupe '\$(\$g.Name)' : \$(\$groupMaps\[\$g.Name].Count) membres (après expansion)"
}
# --- Récupération des sessions sur tous les hôtes -----------------------------
\$allSessions = foreach (\$s in \$config.Servers) { Get-UserSessionsFromQuser -ComputerName \$s }
# Exclusions (ex : comptes de service)
if (\$config.ExcludeUsers) {
\$allSessions = \$allSessions | Where-Object { -not (Test-WildcardMatch -Value $\_.UserName -Patterns \$config.ExcludeUsers) }
}
# --- Comptage par groupe + enforcement ----------------------------------------
foreach (\$g in \$config.Groups) {
\$name = \$g.Name
\$max = \[int]\$g.Max
\$policy = if (\$g.Policy) { \$g.Policy } else { "LongestIdle" }
\$notify = if (\$g.NotifySeconds) { \[int]\$g.NotifySeconds } else { 0 }
\$members = \$groupMaps\[\$name]
if (-not \$members) { continue }
\$sessionsForGroup = \$allSessions | Where-Object { \$members.Contains(\$*.UserName) -and \$*.State -match "Active|Actif" }
\$count = (\$sessionsForGroup | Measure-Object).Count
Write-Log ("{0,-22} : {1,3}/{2,3} sessions" -f \$name,\$count,\$max)
if (\$count -le \$max) { continue }
\$excess = \$count - \$max
if (\$config.Mode -eq "Audit") {
Write-Log "AUDIT : \$excess session(s) excédentaire(s) pour \$name — aucune action" "WARN"
continue
}
# Politique de sélection des sessions à évincer
\$ordered = switch (\$policy) {
"OldestLogon" { \$sessionsForGroup | Sort-Object { \[DateTime]$\_.LogonTime } } # plus anciennes d'abord
default { \$sessionsForGroup | Sort-Object IdleSpan -Descending } # plus longue inactivité d'abord
}
\$toEvict = \$ordered | Select-Object -First \$excess
foreach (\$sess in \$toEvict) {
\$msg = "Quota atteint pour le groupe '\$name' (max=\$max). Cette session sera déconnectée."
if (\$notify -gt 0) {
Send-SessionMessage -Server \$sess.Server -SessionId \$sess.SessionId -Seconds \$notify -Text \$msg
Write-Log "Averti \$(\$sess.UserName) sur \$(\$sess.Server)/\$(\$sess.SessionId) (\$policy, délai=\$notify s)"
Start-Sleep -Seconds \$notify
}
Logoff-Session -Server \$sess.Server -SessionId \$sess.SessionId
Write-Log "Déconnexion \$(\$sess.UserName) sur \$(\$sess.Server)/\$(\$sess.SessionId) (groupe \$name)"
}
}
Astuce : conservez le script en mode Audit une à deux semaines pour valider le comptage (heures de pointe, hôtes en maintenance, groupes imbriqués, comptes d’exclusion), puis passez en Enforce avec un délai d’avertissement (60–120 s) pour éviter la perte de données.
Mise en place via le Planificateur de tâches
- Copiez
Enforce-GroupLimit.ps1
et le JSON sur un serveur d’orchestration (ou le broker RDS). - Créez un compte de service dédié (ex.
DOM\svc_rdslimiter
), membre des administrateurs locaux des hôtes RDS. - Créez une tâche planifiée qui s’exécute en continu toutes les 2 minutes :
$act = New-ScheduledTaskAction -Execute "PowerShell.exe" -Argument '-NoProfile -ExecutionPolicy Bypass -File "C:\Ops\Enforce-GroupLimit.ps1" -ConfigPath "C:\Ops\GroupSessionLimits.json"'
$trg = New-ScheduledTaskTrigger -Once (Get-Date).AddMinutes(1) -RepetitionInterval (New-TimeSpan -Minutes 2) -RepetitionDuration ([TimeSpan]::MaxValue)
$prn = New-ScheduledTaskPrincipal -UserId "DOM\svc_rdslimiter" -RunLevel Highest
$set = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -ExecutionTimeLimit (New-TimeSpan -Minutes 5)
Register-ScheduledTask -TaskName "RDS-Group-Limiter" -Action $act -Trigger $trg -Principal $prn -Settings $set -Description "Plafond de sessions par groupe AD"
Bonnes pratiques : centralisez les logs (\\share\ops\logs
), surveillez l’état de la tâche (évitez les délais d’exécution > 75 % de l’intervalle), mettez en place une alerte si plus de N déconnexions/jour sont effectuées sur un même groupe.
Variante : refus immédiat à l’ouverture de session (script de logon)
Pour bloquer la connexion avant que l’utilisateur ne commence à travailler, attachez un script au « Logon » utilisateur (GPO sur les hôtes RDS). Le principe : si le quota du groupe est déjà atteint, on avertit puis on met fin à la session qui vient de s’ouvrir.
# Script de logon (exécuté côté hôte RDS)
param([string]$GroupName = "SALES",[int]$MaxSessions = 35,[int]$Notify = 10)
Import-Module ActiveDirectory -ErrorAction SilentlyContinue
\$members = Get-ADGroupMember -Identity \$GroupName -Recursive | Where-Object {$\_.ObjectClass -eq 'user'} | Select-Object -Expand SamAccountName
# Recense les sessions actives sur la ferme (liste des hôtes en dur, ou via un fichier)
\$servers = @("RDSH01","RDSH02")
\$sessions = foreach(\$s in \$servers){ & quser /server:\$s 2>&1 | Select-Object -Skip 1 | ForEach-Object {
\$l = ($\_ -replace "\s{2,}"," ").Trim() -split " "
\[PSCustomObject]@{ Server=\$s; User=\$l\[0] }
}}
\$active = (\$sessions | Where-Object { \$members -contains $\_.User }).Count
if (\$active -ge \$MaxSessions) {
\$text = "Quota atteint pour '\$GroupName' (max=\$MaxSessions). Votre session va se fermer."
try { & msg \* /time:\$Notify \$text } catch {}
Start-Sleep -Seconds \$Notify
# Déconnexion de la session courante
try {
\$me = \$env\:USERNAME
\$id = (& quser \$me 2>&1 | Select-Object -Skip 1 | ForEach-Object { ($\_ -replace "\s{2,}"," ").Trim().Split(" ")\[2] })\[0]
& logoff \$id | Out-Null
} catch {
& shutdown.exe /l
}
}
Important : cette variante est plus « brutale ». Réservez‑la aux cas où l’accès doit être strictement plafonné (ex. contrat Cloud avec pénalités).
Gouvernance, sécurité et observabilité
- Comptes de service : compte dédié, mot de passe géré (LAPS/ coffre‑fort), droits minimaux.
- Journaux : consignez les décisions (comptages, avertissements, déconnexions) avec timestamp, serveur, groupe, utilisateur, raison (politique).
- Alertes : e‑mail/Teams quand un quota est atteint, quand plus de X sessions sont coupées en < 1 h, quand le script échoue N fois.
- Respect utilisateur : message clair, délai de grâce configurable, exceptions pour les administrateurs et comptes de service.
- Revue périodique : ajustez les plafonds par groupe en fonction des pics observés (p. ex. percentile 95).
Checklist de mise en production
Élément | Action | Statut |
---|---|---|
Mode Audit | Actif 1–2 semaines, collecte métriques | □ |
Exceptions | Comptes de service/admin ajoutés à ExcludeUsers | □ |
Message utilisateur | Texte validé avec le support | □ |
Journalisation | Partage central + rétention | □ |
Plan de rollback | Désactivation tâche/GPO et restauration des settings | □ |
Alternative : Broker/Load Balancer et RD Gateway
Dans une ferme RDS/VDI, vous pouvez insérer la logique de quota « par groupe » au niveau du broker (connection broker RDS/Citrix) ou du load balancer (F5, Citrix ADC, Kemp). Le flux de connexion appelle un script ou une API :
- Lecture du token ou de l’entête SSO → détermination du groupe AD.
- Requête vers un service interne (Redis/SQL) qui maintient les compteurs par groupe.
- Si quota atteint : renvoi d’un message d’erreur (connexion refusée) ou redirection vers une file d’attente.
Avantage : décision au plus près de la connexion, avant l’ouverture de session. Inconvénient : complexité de l’intégration, contrôle limité aux services derrière le composant.
Alternative : IAM/NAC
Les suites IAM/NAC peuvent appliquer des règles d’accès et de session centralisées : limite par rôle/groupe, interdiction au‑delà d’un seuil, alerte en temps réel, audit complet. C’est la voie la plus robuste pour des environnements hétérogènes (RDP, VPN, SSH, Web, SaaS) et des besoins de conformité élevés.
- Quand l’envisager ? Multiples règles, exceptions fréquentes, haute dispo, rapports d’audit approfondis, multi‑protocoles.
- À prévoir : coût de licence, intégration (LDAP/AD, RADIUS, SSO), POC par périmètre critique.
FAQ technique
Les CAL RDS limitent‑elles le nombre de connexions par groupe ?
Non. Les CAL RDS gèrent le droit d’accès, pas un quota par groupe. Le plafonnement doit être conçu côté orchestration (script/broker/IAM).
Et Citrix ?
Citrix propose des politiques avancées côté Delivery Controller/ADC. Vous pouvez y implémenter un contrôle similaire (souvent via règles/policies et scripts), mais la logique « par groupe » n’est pas une case à cocher universelle : un développement/pilotage reste nécessaire.
Quid des groupes imbriqués ?
Le script ci‑dessus utilise -Recursive
pour étendre les membres. C’est crucial si vous avez des groupes « BU\_All » → « BU\_Sales » → utilisateurs.
Comment choisir les sessions à évincer ?
Deux stratégies courantes : LongestIdle (équitable, protège les sessions actives) ou OldestLogon (rotation plus rapide, utile en salles partagées). Choisissez par groupe selon l’usage.
Peut‑on gérer des plages horaires ?
Oui, en ajoutant un contrôle d’horaires (jour, tranche). Exemple : quota plus bas la nuit/week‑end pour réduire les coûts.
Et si un serveur est indisponible ?
Le compteur se base sur les sessions visibles. En production, surveillez la santé des hôtes et excluez temporairement les nœuds en maintenance (ou interrogez le broker).
Modèle de communication utilisateur
Préparez un message clair, court et actionnable :
« Le quota de connexions pour votre groupe <NomDuGroupe> est atteint (max = <N>). Votre session sera déconnectée dans <60> secondes. Merci de sauvegarder votre travail. »
Ajoutez un lien vers la procédure interne (FAQ) et le canal de support en cas de contestation.
Plan d’implémentation recommandé
- Cartographier les groupes à plafonner et estimer les quotas initiaux (historique des pics, 95e percentile).
- Déployer le script en Audit (JSON, tâche planifiée) et observer 1–2 semaines.
- Ajuster les quotas, préparer les exceptions, valider le message d’avertissement et la politique d’éviction.
- Activer le mode Enforce + délai de grâce ; brancher les logs au SIEM ; créer les alertes.
- Évoluer (si besoin) vers une intégration broker/IAM/NAC pour une gouvernance long terme.
Exemples de politiques par cas d’usage
Cas | Politique | Paramètres conseillés |
---|---|---|
Équipe back‑office (journée) | LongestIdle | Max = 35 ; Notify = 60 s ; Exclude = adm_*, svc_* |
Plateau support 24/7 | OldestLogon | Max = 50 ; Notify = 90 s ; rotation rapide des plus anciennes sessions |
Salle de formation | Refus à l’ouverture | Logon script ; message + logoff immédiat si quota atteint |
Limites et précautions
- Robustesse du parsing :
quser
varie selon la langue du système. En environnement non anglophone, validez le parseur (ou utilisez l’API du broker/PowerShell Remoting pour lister les sessions). - Données volatiles : entre deux passages du script, des sessions peuvent se connecter. Le délai d’exécution doit être court (2–5 min) et stable.
- Expérience utilisateur : l’éviction est disruptive. Prévenez, offrez un délai de sauvegarde, et tenez une liste d’exceptions temporaires.
- Conformité : journalisez pour être en capacité d’expliquer qui a été déconnecté, quand, pourquoi.
Conclusion
Windows Server 2022 et Active Directory n’offrent pas de contrôle direct du nombre de connexions simultanées par groupe. Pour maîtriser charge et coûts, deux voies dominent : un script PowerShell (rapide, économique, à encadrer) ou une solution IAM/NAC / broker (investissement plus élevé, mais gouvernance et observabilité de premier plan). Démarrez en mode audit, instrumentez, puis appliquez la politique avec une expérience utilisateur soignée et des métriques claires. Vous obtiendrez un plafonnement prévisible, transparent et soutenable.
Annexe : script minimal (exemple didactique)
Ce mini‑script compte les sessions d’un groupe donné sur une liste d’hôtes et déconnecte les plus inactives au‑delà du seuil. Il est volontairement simple ; utilisez le script complet pour la production.
$GroupName = "SALES"
$MaxSessions = 35
$Servers = @("RDSH01","RDSH02")
Import-Module ActiveDirectory
\$Members = Get-ADGroupMember -Identity \$GroupName -Recursive | Where-Object {$\_.ObjectClass -eq 'user'} | Select-Object -Expand SamAccountName
# Récupération sessions (Active uniquement)
\$sessions = foreach(\$srv in \$Servers){
& quser /server:\$srv 2>&1 | Select-Object -Skip 1 | ForEach-Object {
\$l = ($\_ -replace "\s{2,}"," ").Trim().Split(" ")
if (\$l.Count -ge 6) {
\[PSCustomObject]@{
Server = \$srv
User = \$l\[0]
Id = \[int]\$l\[2]
State = \$l\[3]
Idle = \$l\[4]
IdleSpan = (if(\$l\[4] -eq "."){ \[TimeSpan]::Zero } else { \[TimeSpan]::Parse(\$l\[4].Split("+")\[-1]) })
}
}
} | Where-Object { $\_.State -match "Active|Actif" }
}
\$groupSessions = \$sessions | Where-Object { \$Members -contains $\_.User }
\$count = \$groupSessions.Count
if (\$count -gt \$MaxSessions) {
\$excess = \$count - \$MaxSessions
\$toKick = \$groupSessions | Sort-Object IdleSpan -Descending | Select-Object -First \$excess
foreach(\$s in \$toKick){
try { & msg \$s.Id /server:\$s.Server "Quota atteint (\$GroupName \$MaxSessions). La session va être déconnectée." | Out-Null } catch {}
Start-Sleep -Seconds 30
& logoff \$s.Id /server:\$s.Server
}
}
Plan à moyen terme
- Si la logique devient complexe (multiples quotas, SLA, files d’attente), une plateforme IAM/NAC remplacera avantageusement les scripts.
- Testez en POC sur un périmètre réduit : compatibilité applicative (RDP, Citrix, VPN, SaaS), messages et expérience utilisateur, métriques de capacité.