Delegar la gestión de sesiones RDP


En la organización donde trabajo, udalenka está prohibido en principio. Era. Hasta la semana pasada. Ahora tenía que implementar urgentemente la solución. Desde negocios: adaptación de procesos a un nuevo formato de trabajo, desde nosotros: PKI con códigos PIN y tokens, VPN, registro detallado y mucho más.
Entre otras cosas, participé en la configuración de la infraestructura de escritorio remoto de Terminal Services. Tenemos varias implementaciones de RDS en diferentes centros de datos. Una de las tareas era permitir que colegas de departamentos de TI relacionados se conectaran a sesiones de usuario de forma interactiva. Como sabe, existe un mecanismo de sombra RDS regular para esto y la forma más fácil de delegarlo es otorgando derechos de administrador local en los servidores RDS.
Respeto y valoro a mis colegas, pero soy muy codicioso por la distribución de los derechos de administrador. :) Los que están de acuerdo conmigo, por favor, debajo del gato.

Bueno, la tarea es clara, ahora al grano.

Paso 1


Creemos el grupo de seguridad RDP_Operators en Active Directory e incluyamos las cuentas de aquellos usuarios a quienes queremos delegar derechos:

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

Si tiene varios sitios de AD, antes de continuar con el siguiente paso, debe esperar hasta que se replique en todos los controladores de dominio. Por lo general, no lleva más de 15 minutos.

Paso 2


Le damos al grupo el derecho de administrar sesiones de terminal en cada uno de los 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")
        }
    }
}


Paso 3


Agregue el grupo al grupo local de usuarios de Escritorio remoto en cada uno de los servidores RDSH. Si sus servidores se combinan en una colección de sesiones, lo hacemos a nivel de colección:

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

Para servidores individuales, habilitamos la política de grupo , esperando hasta que se aplique a los servidores. Aquellos que son demasiado vagos para esperar pueden forzar el proceso usando el viejo gpupdate, preferiblemente de manera centralizada .

Paso 4


Prepararemos el siguiente guión 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 que el script PS sea conveniente de ejecutar, crearemos un shell en forma de archivo cmd con el mismo nombre que el script PS:

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


Ponemos ambos archivos en una carpeta que estará disponible para los "gerentes" y les pedimos que inicien sesión. Ahora, después de haber lanzado el archivo cmd, podrán conectarse a las sesiones de otros usuarios en el modo RDS Shadow y obligarlos a cerrar sesión (es útil cuando el usuario no puede finalizar de forma independiente la sesión "bloqueada").

Se ve algo como esto:

Para el "gerente"


Para el usuario


Algunos comentarios al final


Matiz 1 . Si la sesión de usuario a la que estamos tratando de obtener el control se inició antes de que el script Set-RDSPermissions.ps1 funcionara en el servidor, el "administrador" recibirá un error de acceso. La solución aquí es obvia: espere hasta que el usuario administrado inicie sesión.

Matiz 2 . Después de varios días de trabajar con RDP, Shadow notó un error o característica interesante: después de completar la sesión de sombra, el usuario que estaba conectado a la barra de idioma desaparece en la bandeja y para devolverlo, el usuario debe iniciar sesión. Al final resultó que, no estamos solos: uno , dos , tres .

Eso es todo. Les deseo a usted y a sus servidores buena salud. Como siempre, espero recibir comentarios en los comentarios y pedirle que realice una pequeña encuesta a continuación.

Fuentes



All Articles