Delegando o gerenciamento de sessões RDP


Na organização em que trabalho, udalenka é proibido em princípio. Isso foi. Até a semana passada. Agora eu tinha que implementar urgentemente a solução. Desde negócios - adaptação de processos a um novo formato de trabalho, entre nós - PKI com códigos e tokens de pinos, VPN, registro detalhado e muito mais.
Entre outras coisas, participei da configuração da Infraestrutura de Área de Trabalho Remota dos Serviços de Terminal. Temos várias implantações de RDS em diferentes data centers. Uma das tarefas era permitir que colegas de departamentos de TI relacionados se conectassem às sessões do usuário interativamente. Como você sabe, existe um mecanismo RDS Shadow padrão para isso e a maneira mais fácil de delegá-lo é conceder direitos de administrador local nos servidores RDS.
Eu respeito e valorizo ​​meus colegas, mas muito ganancioso pela distribuição dos direitos de administrador. :) Aqueles que concordam comigo, por favor, debaixo do gato.

Bem, a tarefa é clara, agora ao ponto.

Passo 1


Vamos criar o grupo de segurança RDP_Operators no Active Directory e incluir as contas dos usuários aos quais queremos delegar direitos:

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

Se você possui vários sites do AD, antes de prosseguir para a próxima etapa, é necessário aguardar até que seja replicado para todos os controladores de domínio. Geralmente, não leva mais de 15 minutos.

Passo 2


Damos ao grupo o direito de gerenciar sessões de terminal em cada um dos servidores RDSH:

Set-RDSPermissions.ps1
$Group = "RDP_Operators"
$Servers = @(
    "RDSHost01",
    "RDSHost02",
    "RDSHost03"
)
ForEach ($Server in $Servers) {
    #    
    $WMIHandles = Get-WmiObject `
        -Class "Win32_TSPermissionsSetting" `
        -Namespace "root\CIMV2\terminalservices" `
        -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")
        }
    }
}


etapa 3


Adicione o grupo ao grupo Usuários de Área de Trabalho Remota local em cada um dos servidores RDSH. Se seus servidores forem combinados em uma coleção de sessões, faremos isso no nível da coleção:

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

Para servidores únicos, usamos a política de grupo , aguardando até que seja aplicada nos servidores. Aqueles com preguiça de esperar podem forçar o processo usando o bom e velho gpupdate, de preferência centralmente .

Passo 4


Prepararemos o seguinte script PS para "gerentes":

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 "      "
    }
}


Para facilitar a execução do script PS, criaremos um shell para ele na forma de um arquivo cmd com o mesmo nome que o script PS:

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


Colocamos os dois arquivos em uma pasta que estará disponível para "gerentes" e pedimos que eles entrem. Agora, após o lançamento do arquivo cmd, eles poderão se conectar às sessões de outros usuários no modo RDS Shadow e forçá-los a sair (é útil quando o usuário não pode terminar independentemente a sessão "travada").

Parece algo como isto:

Para o "gerente"


Para o usuário


Alguns comentários no final


Nuance 1 . Se a sessão do usuário para a qual estamos tentando obter o controle foi iniciada antes do script Set-RDSPermissions.ps1 funcionar no servidor, o "gerente" receberá um erro de acesso. A solução aqui é óbvia: aguarde até o usuário gerenciado efetuar login.

Nuance 2 . Após vários dias trabalhando com o RDP, o Shadow notou um bug ou recurso interessante: após a conclusão da sessão de sombra, o usuário que estava conectado à barra de idiomas desaparece na bandeja e, para devolvê-lo, o usuário precisa fazer o login. Como se viu, não estamos sozinhos: um , dois , três .

Isso é tudo. Desejo a você e seus servidores boa saúde. Como sempre, aguardo comentários nos comentários e peço que você faça uma pequena pesquisa abaixo.

Fontes



All Articles