Delegate management of RDP sessions

Delegate management of RDP sessions
In the organization where I work, remote work is prohibited in principle. Was. Until last week. Now I had to urgently implement a solution. From business - adaptation of processes to a new format of work, from us - PKI with pin codes and tokens, VPN, detailed logging and much more.
Among other things, I was involved in setting up Remote Desktop Infrastructure aka Terminal Services. We have several RDS deployments in different data centers. One of the goals was to enable colleagues from related IT departments to connect to user sessions interactively. As you know, there is a regular RDS Shadow mechanism for this and the easiest way to delegate it is to give local administrator rights on RDS servers.
I respect and appreciate my colleagues, but I am very greedy for distributing admin rights. 🙂 Those who agree with me, please under cat.

Well, the task is clear, now - to business.

Step 1

Create a security group in Active Directory RDP_Operators and include in it the accounts of those users to whom we want to delegate rights:

$Users = @(
    "UserLogin1",
    "UserLogin2",
    "UserLogin3"
)
$Group = "RDP_Operators"
New-ADGroup -Name $Group -GroupCategory Security -GroupScope DomainLocal
Add-ADGroupMember -Identity $Group -Members $Users

If you have multiple AD sites, then before moving on to the next step, you need to wait until it is replicated to all domain controllers. This usually takes no more than 15 minutes.

Step 2

Let's give the group the rights to manage terminal sessions on each of the RDSH servers:

Set-RDSPermissions.ps1

$Group = "RDP_Operators"
$Servers = @(
    "RDSHost01",
    "RDSHost02",
    "RDSHost03"
)
ForEach ($Server in $Servers) {
    #Делегируем право на теневые сессии
    $WMIHandles = Get-WmiObject `
        -Class "Win32_TSPermissionsSetting" `
        -Namespace "rootCIMV2terminalservices" `
        -ComputerName $Server `
        -Authentication PacketPrivacy `
        -Impersonation Impersonate
    ForEach($WMIHandle in $WMIHandles)
    {
        If ($WMIHandle.TerminalName -eq "RDP-Tcp")
        {
        $retVal = $WMIHandle.AddAccount($Group, 2)
        $opstatus = "успешно"
        If ($retVal.ReturnValue -ne 0) {
            $opstatus = "ошибка"
        }
        Write-Host ("Делегирование прав на теневое подключение группе " +
            $Group + " на сервере " + $Server + ": " + $opstatus + "`r`n")
        }
    }
}

Step 3

Add a group to a local group Remote Desktop Users on each of the RDSH servers. If your servers are combined into collections of sessions, then we do this at the collection level:

$Group = "RDP_Operators"
$CollectionName = "MyRDSCollection"
[String[]]$CurrentCollectionGroups = @(Get-RDSessionCollectionConfiguration -CollectionName $CollectionName -UserGroup).UserGroup
Set-RDSessionCollectionConfiguration -CollectionName $CollectionName -UserGroup ($CurrentCollectionGroups + $Group)

For single servers, use group policy, waiting for it to be applied on the servers. For those too lazy to wait, you can force the process with good old gpupdate, preferably centrally.

Step 4

Let's prepare the following PS-script for the "managers":

RDSManagement.ps1

$Servers = @(
    "RDSHost01",
    "RDSHost02",
    "RDSHost03"
)

function Invoke-RDPSessionLogoff {
    Param(
        [parameter(Mandatory=$True, Position=0)][String]$ComputerName,
        [parameter(Mandatory=$true, Position=1)][String]$SessionID
    )
    $ErrorActionPreference = "Stop"
    logoff $SessionID /server:$ComputerName /v 2>&1
}

function Invoke-RDPShadowSession {
    Param(
        [parameter(Mandatory=$True, Position=0)][String]$ComputerName,
        [parameter(Mandatory=$true, Position=1)][String]$SessionID
    )
    $ErrorActionPreference = "Stop"
    mstsc /shadow:$SessionID /v:$ComputerName /control 2>&1
}

Function Get-LoggedOnUser {
    Param(
        [parameter(Mandatory=$True, Position=0)][String]$ComputerName="localhost"
    )
    $ErrorActionPreference = "Stop"
    Test-Connection $ComputerName -Count 1 | Out-Null
    quser /server:$ComputerName 2>&1 | Select-Object -Skip 1 | ForEach-Object {
        $CurrentLine = $_.Trim() -Replace "s+"," " -Split "s"
        $HashProps = @{
            UserName = $CurrentLine[0]
            ComputerName = $ComputerName
        }
        If ($CurrentLine[2] -eq "Disc") {
            $HashProps.SessionName = $null
            $HashProps.Id = $CurrentLine[1]
            $HashProps.State = $CurrentLine[2]
            $HashProps.IdleTime = $CurrentLine[3]
            $HashProps.LogonTime = $CurrentLine[4..6] -join " "
            $HashProps.LogonTime = $CurrentLine[4..($CurrentLine.GetUpperBound(0))] -join " "
        }
        else {
            $HashProps.SessionName = $CurrentLine[1]
            $HashProps.Id = $CurrentLine[2]
            $HashProps.State = $CurrentLine[3]
            $HashProps.IdleTime = $CurrentLine[4]
            $HashProps.LogonTime = $CurrentLine[5..($CurrentLine.GetUpperBound(0))] -join " "
        }
        New-Object -TypeName PSCustomObject -Property $HashProps |
        Select-Object -Property UserName, ComputerName, SessionName, Id, State, IdleTime, LogonTime
    }
}

$UserLogin = Read-Host -Prompt "Введите логин пользователя"
Write-Host "Поиск RDP-сессий пользователя на серверах..."
$SessionList = @()
ForEach ($Server in $Servers) {
    $TargetSession = $null
    Write-Host "  Опрос сервера $Server"
    Try {
        $TargetSession = Get-LoggedOnUser -ComputerName $Server | Where-Object {$_.UserName -eq $UserLogin}
    }
    Catch {
        Write-Host "Ошибка: " $Error[0].Exception.Message -ForegroundColor Red
        Continue
    }
    If ($TargetSession) {
        Write-Host "    Найдена сессия с ID $($TargetSession.ID) на сервере $Server" -ForegroundColor Yellow
        Write-Host "    Что будем делать?"
        Write-Host "      1 - подключиться к сессии"
        Write-Host "      2 - завершить сессию"
        Write-Host "      0 - ничего"
        $Action = Read-Host -Prompt "Введите действие"
        If ($Action -eq "1") {
            Invoke-RDPShadowSession -ComputerName $Server -SessionID $TargetSession.ID
        }
        ElseIf ($Action -eq "2") {
            Invoke-RDPSessionLogoff -ComputerName $Server -SessionID $TargetSession.ID
        }
        Break
    }
    Else {
        Write-Host "    сессий не найдено"
    }
}

To make it convenient to run the PS script, we will make a shell for it in the form of a cmd file with the same name as the PS script:

RDSManagement.cmd

@ECHO OFF
powershell -NoLogo -ExecutionPolicy Bypass -File "%~d0%~p0%~n0.ps1" %*

We put both files in a folder that will be available to the "managers" and ask them to re-login. Now, by running the cmd file, they will be able to connect to the sessions of other users in RDS Shadow mode and force them to log out (it is useful when the user cannot end the “hung” session on his own).

It looks something like this:

For "manager"Delegate management of RDP sessions

For the userDelegate management of RDP sessions

A few last remarks

Nuance 1. If the session of the user to which we are trying to gain control was started before the Set-RDSPermissions.ps1 script was executed on the server, then the "manager" will receive an access error. The solution here is obvious: wait for the managed user to relogin.

Nuance 2. After several days of working with RDP Shadow, they noticed an interesting either a bug or a feature: after the end of the shadow session, the user to which they connected disappears the language bar in the tray, and in order to return it, the user needs to re-login. As it turns out, we are not alone: time, two, three.

That's all. I wish health to you and your servers. As always, I look forward to your feedback in the comments and ask you to take a short survey below.

Sources of

Only registered users can participate in the survey. Sign in, you are welcome.

What do you use?

  • 8,1%AMMYY Admin5

  • 17,7%AnyDesk11

  • 9,7%DameWare6

  • 24,2%Radmin15

  • 14,5%RDS Shadow9

  • 1,6%Quick Assist / Windows Remote Assistance1

  • 38,7%TeamViewer24

  • 32,3%VNC20

  • 32,3%other20

  • 3,2%LiteManager2

62 users voted. 22 users abstained.

Source: habr.com

Add a comment