Как правило, для отключения неактивных и завершения отключенных сессий на серверах сеансов служб удалённых рабочих столов Remote Desktop Session Host в Windows Server 2012 R2 администраторы используют возможности групповых политик домена Active Directory. Однако иногда может возникать потребность в управлении неактивными сеансами по хитрым правилам, которые невозможно уложить в рамки стандартных механизмов GPO или даже GPP. В таких случаях для управления сеансами можно прибегнуть к возможностям PowerShell.
В рассматриваем примере стоит задача организовать управление неактивными сеансами в ферме RD Connection Broker (RDCB) с несколькими серверами RD Session Host (RDSH) исходя из того условия, что все пользователи в ферме RDS делятся на две категории:
- Стандартные пользовали, для которых используются одинаковые правила сессионных таймаутов вне зависимости от каких-либо факторов. Правила :
- Отключение простаивающих сеансов - через 1 час
- Завершение отключённых сеансов - через 2 часа
- Пользователи со специальным режимом сессионных таймаутов, который действует только в определённые дни месяца (с 25 числа каждого месяца по 5 число каждого последующего месяца). В эти дни данная группа пользователей выполняет круглосуточные расчёты, в том числе и в отключенных сеансах, поэтому сессии не должны отключаться. В остальные дни месяца сессионные таймауты применяются по аналогии со стандартными пользователями из п.1.
Отделение пользователей со специальным режимом выполняется с помощью членства в доменной группе безопасности Active Directory.
Пример реализации в виде PS-скрипта RDS-Logoff-Inactive.ps1:
# Требования к модулям PS: ActiveDirectory, RemoteDesktop # # Члены специальной группы $SpecialGroup не затрагиваются при отключении # простаивающих и завершении отключенных сессий в дни месяца из $SpecialDays # Блок переменных # $SpecialDays - Дни месяца, в которые члены группы $SpecialGroup не отключаются # $MaxConnectedInactiveTime - Время простоя в подключенном состоянии, затем отключение сессии (мс) # $MaxDisconnectedTime - Время простоя с момента отключения сессии (мс) # $ConnectionBroker = "" $SessionHostCollection = "RDCollection1" $SpecialGroup = "RDS-Extended-Session-Users" $SpecialDays = @(01,02,03,04,05,25,26,27,28,29,30,31) $MaxConnectedInactiveTime = 3600000 # 1 час $MaxDisconnectedTime = 7200000 # 2 часа $LogFilePath = $($script:MyInvocation.MyCommand.Path).Replace('.ps1','.log') # Функция загрузки модуля PowerShell # Function Load-Module ($MName) { $retVal = $true If (!(Get-Module -Name $MName)) { $retVal = Get-Module -ListAvailable | Where { $_.Name -eq $MName } If ($retVal) { Try { Import-Module $MName -ErrorAction SilentlyContinue } Catch { $retVal = $false } } Else { Write-Host $MName "Module does not exist. Please check that the module is installed." } } Return $retVal } # Функция записи в лог-файл # Function WriteLog ($Text) { $TimeStamp = (Get-Date).ToString("dd.MM.yyyy HH:mm:ss") Write-Host $Text Add-Content $LogFilePath "$($TimeStamp)`t $Text" } # Загрузка модулей PowerShell # If (!(Load-Module "ActiveDirectory")) {return} If (!(Load-Module "RemoteDesktop")) {return} $GroupMembers = Get-ADGroupMember -Identity $SpecialGroup -Recursive $ToDayIsSpecial = $SpecialDays -contains $(Get-Date -Format dd) If ($ConnectionBroker -eq "") { $HAFarm = Get-RDConnectionBrokerHighAvailability $ConnectionBroker = $HAFarm.ActiveManagementServer } $Sessions = Get-RDUserSession -ConnectionBroker $ConnectionBroker -CollectionName $SessionHostCollection ForEach ($Session in $Sessions) { # Пропускаем пользователей из специальной группы в специальные дни # If ($ToDayIsSpecial -eq $true -and $GroupMembers.SamAccountName -contains $Session.UserName){Continue} # Пропускаем активные сессии # If ($Session.SessionState -eq "STATE_ACTIVE"){Continue} # Отключаем простаивающие сессии # If ($Session.SessionState -eq "STATE_CONNECTED" -and $Session.IdleTime -ge $MaxConnectedInactiveTime) { Try { WriteLog "Disconnect RD User: $($Session.UserName) `t Server: $($Session.HostServer) `t Session disconnect time: $($Session.DisconnectTime.ToString("dd.MM.yyyy HH:mm:ss")) `t Idle time: $([TimeSpan]::FromMilliseconds($Session.IdleTime).ToString())" Disconnect-RDUser -HostServer $Session.HostServer -UnifiedSessionID $Session.UnifiedSessionId -Force -ErrorAction Stop } Catch { WriteLog "ERROR! Can't disconnect RD User: $($Session.UserName) `t Server: $($Session.HostServer) `n $($_)" } Continue } # Завершаем отключенные сессии # If ($Session.SessionState -eq "STATE_DISCONNECTED" -and $Session.IdleTime -ge $MaxDisconnectedTime) { Try { WriteLog "Logoff RD User: $($Session.UserName) `t Server: $($Session.HostServer) `t Session disconnect time: $($Session.DisconnectTime.ToString("dd.MM.yyyy HH:mm:ss")) `t Idle time: $([TimeSpan]::FromMilliseconds($Session.IdleTime).ToString())" Invoke-RDUserLogoff -HostServer $Session.HostServer -UnifiedSessionID $Session.UnifiedSessionId -Force -ErrorAction Stop } Catch { WriteLog "ERROR! Can't logoff RD User: $($Session.UserName) `t Server: $($Session.HostServer) `n $($_)" } } }
Скрипт не имеет обработки входных параметров, поэтому все исходные данные мы указываем в начале скрипта в блоке переменных. В ходе выполнения скрипт создаёт лог-файл о результатах отключения и завершения сессий в том же каталоге, где размещён сам скрипт.
Скрипт размещаем на каким-нибудь сервере, отличном от серверов RDSH, на которых будет выполняться скриптовая обработка сессий. Например, можно разместить этот скрипт на сервере с ролью RD Connection Broker (RDCB), если эта роль работает на выделенном сервере. Для выполнения скрипта на выбранном сервере потребуется установка PowerShell-модулей RemoteDesktop и ActiveDirectory. Если первый модель уже присутствует на сервере RDCB, то второй можно доустановить PS-командой:
Install-WindowsFeature -Name "RSAT-AD-PowerShell"
Автоматический периодический запуск скрипта можно настроить в Task Scheduler от имени специально созданной сервисной учётной записи Group Managed Service Account (gMSA). Пример того, как создать и установить учётную запись gMSA можно найти в статьях Вики:
В нашем примере в домене AD создана учётная запись gMSA с именем KOM\s-S06$ и установлена на сервере RDCB. Эта учётная запись должна быть включена в локальную группу Administrators на всех серверах RDSH, сессиями которых мы планируем управлять из скрипта. Также учётной записи gMSA на сервере RDCB потребуется дать права на чтение каталога со скриптом и права на запись в файл лога (для этого потребуется предварительно создать пустой лог-файл).
Прежде, чем создавать задание планировщика по запуску скрипта, выполним его проверочный запуск от имени учётной записи gMSA с помощью утилиты PsExec:
PsExec64.exe -i -u KOM\s-S06$ -p ~ cmd.exe
Запустив в контексте пользователя gMSA интерпретатор командной строки cmd.exe, попробуем выполнить скрипт:
powershell.exe -NoProfile -command "C:\Scripts\RDS-Logoff-Inactive.ps1"
Если скрипт отработал как надо и создал записи в лог-файл об отключенных и завершённых сессиях в ферме RD Connection Broker, выполняем его добавление в планировщик заданий Task Scheduler:
$Action = New-ScheduledTaskAction -Execute "PowerShell.exe" -Argument "-NoProfile -command `"C:\Scripts\RDS-Logoff-Inactive.ps1`"" $Trigger = New-ScheduledTaskTrigger -Once -At (Get-Date) -RepetitionInterval (New-TimeSpan -Minutes 15) -RepetitionDuration ([System.TimeSpan]::MaxValue) $SvcUser = New-ScheduledTaskPrincipal -UserID KOM\s-S06$ -LogonType Password Register-ScheduledTask -TaskName "RDS Logoff Inactive Users" -Action $Action -Trigger $Trigger -Principal $SvcUser
Таким образом, задание планировщика каждые 15 минут будет подключаться к ферме RDCB, получать из фермы информацию о всех сессиях пользователей на серверах RDSH и отключать простаивающие и завершать отключенные сессии пользователей.
Спасибо, любопытная статья. И очень насущная (ведь в каждом предприятии есть бухгалтерский отдел, не терпящий прерываний в работе).
Но есть вопросы:
1) Нужно ли выставлять в настройках сессии значения таймаутов? https://imgur.com/yhUcKOB
Или же эти параметры задаются лишь в скрипте?
2) Можно ли сделать так, чтобы пользователей из группы $SpecialGroup не отключало от сеанса про простою? Сколько ни тестирую — всё равно возникает окно "Таймер входа в систему истек" — а после и отключение сеанса ("Удаленный компьютер не получил от вас никакого ввода").
Как я понял, сессия получает время простоя (ненулевое значение $Session.IdleTime) только после отключения (и ее статус становится не STATE_ACTIVE, а STATE_CONNECTED) — следовательно, пользователя все равно отключит от сеанса, даже если этот пользователь состоит в группе $SpecialGroup?
1) Так как фактически управление отключением простаивающих и завершением отключенных сессий берёт на себя скрипт, настройки в прочих местах (в свойствах коллекции сеансов или в локальных/доменных групповых политиках), как минимум, не требуются. Самое главное, что если такие настройки в других местах и есть, то они по своей логике не должны конфликтовать с настройками в скрипте. Иначе получите полную неразбериху.
2) В дни, указанные в переменной $SpecialDays, пользователи из группы $SpecialGroup, согласно логике скрипта, не должны отключаться вовсе. Если в Вашем случае это происходит, то ищите проблему в другом месте (в свойствах коллекции сеансов или в групповых политиках). Сообщение "Таймер входа в систему истек" намекает на то, что где-то в другом месте настроено ограничение времени сеанса (в не зависимости от его состояния).
Спасибо, буду разбираться. Если полностью отключить в настройках ограничения сеансов, то тогда (в моем случае) сессии "висят" сколь угодно долго (оставлял на ночь). Групповые политики управления временем сеансов не настраивал.
Разобрался со скриптом, пришлось немного его видоизменить: значения простоя сессий получаю через встроенную утилиту quser (query user) — там таймауты активных сессий (STATE_ACTIVE) видны нормально, в отличие от командлета Get-RDUserSession. В остальном все так же. Проверка на тестовом сервере не выявила проблем, скрипт отключает «обычных» пользователей, не трогая привилегированных.
Если интересно, могу приложить свой вариант скрипта.
Можете выслать на почту (адрес внизу страницы), выложу скрипт в Вики.
Обратная ссылка: Решение проблемы с некорректным IdleTime в Get-RDUserSession /
А как быть с такой ситуацией: У удаленного сотрудника лагает интернет. Он отключен уже 10 минут или дольше. Но его сессия на сервере до сих пор active. И ее надо вручную делать disconnect. Иначе он заходит в новую сессию, и не видит своих открытых программ. Если ограничить юзера, одной сессией вообще, то он не может войти в старую Активную сессию, так как она не переходит в состояние disconnect.
Вопрос, каким образом происходит проверка, подключен пользователь или нет, и почему в ней возникают такие сбои.
Если сессия активна, то пользователь должен подключиться " в неё", даже если заходит с другого устройства. Попробуйте очистить профиль пользователя.