Delegieren der RDP-Sitzungsverwaltung


In der Organisation, in der ich arbeite, ist Udalenka grundsätzlich verboten. Es war. Bis letzte Woche. Jetzt musste ich die Lösung dringend umsetzen. Vom Geschäft - Anpassung von Prozessen an ein neues Arbeitsformat, von uns - PKI mit PIN-Codes und Token, VPN, detaillierter Protokollierung und vielem mehr.
Unter anderem war ich an der Konfiguration der Remote-Desktop-Infrastruktur von Terminal Services beteiligt. Wir haben mehrere RDS-Bereitstellungen in verschiedenen Rechenzentren. Eine der Aufgaben bestand darin, Kollegen aus verwandten IT-Abteilungen die Möglichkeit zu geben, sich interaktiv mit Benutzersitzungen zu verbinden. Wie Sie wissen, gibt es hierfür einen regulären RDS-Schattenmechanismus. Der einfachste Weg, ihn zu delegieren, besteht darin, lokalen Administratorrechten auf RDS-Servern zu erteilen.
Ich respektiere und schätze meine Kollegen, bin aber sehr gierig auf die Verteilung von Administratorrechten. :) Diejenigen, die mir zustimmen, bitte unter der Katze.

Nun, die Aufgabe ist klar, jetzt auf den Punkt.

Schritt 1


Lassen Sie uns die Sicherheitsgruppe RDP_Operators in Active Directory erstellen und die Konten der Benutzer einschließen, an die wir Rechte delegieren möchten:

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

Wenn Sie mehrere AD-Standorte haben, müssen Sie warten, bis sie auf alle Domänencontroller repliziert werden, bevor Sie mit dem nächsten Schritt fortfahren. Normalerweise dauert es nicht länger als 15 Minuten.

Schritt 2


Wir geben der Gruppe das Recht, Terminalsitzungen auf jedem der RDSH-Server zu verwalten:

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


Schritt 3


Fügen Sie die Gruppe der lokalen Remotedesktopbenutzer Gruppe an jedem der RDSH Servern. Wenn Ihre Server in einer Sammlung von Sitzungen zusammengefasst sind, tun wir dies auf Sammlungsebene:

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

Für einzelne Server verwenden wir Gruppenrichtlinien und warten, bis sie auf die Server angewendet werden. Diejenigen, die zu faul sind, um zu warten, können den Prozess mit dem guten alten gpupdate erzwingen, vorzugsweise zentral .

Schritt 4


Wir bereiten das folgende PS-Skript für "Manager" vor:

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


Damit das PS-Skript bequem ausgeführt werden kann, erstellen wir eine Shell in Form einer Cmd-Datei mit demselben Namen wie das PS-Skript:

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


Wir legen beide Dateien in einem Ordner ab, der "Managern" zur Verfügung steht, und bitten sie, sich anzumelden. Nachdem sie die cmd-Datei gestartet haben, können sie im RDS-Schattenmodus eine Verbindung zu den Sitzungen anderer Benutzer herstellen und sie zum Abmelden zwingen (dies ist nützlich, wenn der Benutzer die "hängengebliebene" Sitzung nicht unabhängig beenden kann).

Es sieht ungefähr so ​​aus:

Für den "Manager"


Für den Benutzer


Ein paar Kommentare am Ende


Nuance 1 . Wenn die Benutzersitzung, für die wir versuchen, die Kontrolle zu erlangen, gestartet wurde, bevor das Skript Set-RDSPermissions.ps1 auf dem Server ausgeführt wurde, erhält der „Manager“ einen Zugriffsfehler. Die Lösung liegt auf der Hand: Warten Sie, bis sich der verwaltete Benutzer anmeldet.

Nuance 2 . Nach mehreren Tagen Arbeit mit RDP bemerkte Shadow einen interessanten Fehler oder eine interessante Funktion: Nach Abschluss der Schattensitzung verschwindet der Benutzer, der mit der Sprachleiste verbunden war, in der Taskleiste. Um sie zurückzugeben, muss sich der Benutzer anmelden. Wie sich herausstellte, sind wir nicht allein: eins , zwei , drei .

Das ist alles. Ich wünsche Ihnen und Ihren Servern gute Gesundheit. Wie immer freue ich mich auf Feedback in den Kommentaren und bitte Sie, unten eine kleine Umfrage durchzugehen.

Quellen



All Articles