Délégation de la gestion de session RDP


Dans l'organisation où je travaille, l'uddalenka est en principe interdite. C'était. Jusqu'à la semaine dernière. Maintenant, je devais mettre en œuvre d'urgence la solution. De l'entreprise - adaptation des processus à un nouveau format de travail, de nous - PKI avec codes PIN et jetons, VPN, journalisation détaillée et bien plus encore.
Entre autres choses, j'ai participé à la configuration de l'infrastructure de bureau à distance aka Terminal Services. Nous avons plusieurs déploiements RDS dans différents centres de données. L'une des tâches consistait à permettre aux collègues des services informatiques concernés de se connecter aux sessions utilisateur de manière interactive. Comme vous le savez, il existe un mécanisme standard RDS Shadow pour cela et la façon la plus simple de le déléguer est de donner des droits d'administrateur local sur les serveurs RDS.
Je respecte et apprécie mes collègues, mais très gourmand pour la répartition des droits d'administrateur. :) Ceux qui sont d'accord avec moi, s'il vous plaît, sous le chat.

Eh bien, la tâche est claire, maintenant au point.

Étape 1


Créons le groupe de sécurité RDP_Operators dans Active Directory et incluons les comptes des utilisateurs auxquels nous voulons déléguer des droits:

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

Si vous avez plusieurs sites AD, puis avant de passer à l'étape suivante, vous devez attendre qu'il soit répliqué sur tous les contrôleurs de domaine. Cela ne prend généralement pas plus de 15 minutes.

Étape 2


Nous donnons au groupe le droit de gérer les sessions de terminal sur chacun des serveurs 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")
        }
    }
}


Étape 3


Ajoutez le groupe au groupe local d'utilisateurs du Bureau à distance sur chacun des serveurs RDSH. Si vos serveurs sont combinés dans une collection de sessions, nous le faisons au niveau de la collection:

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

Pour les serveurs uniques, nous utilisons la stratégie de groupe , en attendant qu'elle soit appliquée sur les serveurs. Ceux qui sont trop paresseux pour attendre peuvent forcer le processus en utilisant le bon vieux gpupdate, de préférence de manière centralisée .

Étape 4


Nous préparerons le script PS suivant pour les "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 "      "
    }
}


Pour rendre le script PS pratique à exécuter, nous allons créer un shell sous la forme d'un fichier cmd avec le même nom que le script PS:

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


Nous plaçons les deux fichiers dans un dossier qui sera disponible pour les "managers" et leur demandons de se connecter. Maintenant, après avoir lancé le fichier cmd, ils pourront se connecter aux sessions des autres utilisateurs en mode RDS Shadow et les forcer à se déconnecter (c'est utile lorsque l'utilisateur ne peut pas terminer indépendamment la session "bloquée").

Cela ressemble à ceci:

Pour le "manager"


Pour l'utilisateur


Quelques commentaires à la fin


Nuance 1 . Si la session utilisateur à laquelle nous essayons d'obtenir le contrôle a été démarrée avant que le script Set-RDSPermissions.ps1 ne fonctionne sur le serveur, le «gestionnaire» recevra une erreur d'accès. La solution ici est évidente: attendez que l'utilisateur géré se connecte.

Nuance 2 . Après plusieurs jours de travail avec RDP, Shadow a remarqué un bug ou une fonctionnalité intéressante: après la fin de la session shadow, l'utilisateur qui était connecté à la barre de langue disparaît dans le bac et pour le renvoyer, l'utilisateur doit se connecter. Il s'est avéré que nous ne sommes pas seuls: un , deux , trois .

C'est tout. Je vous souhaite, ainsi qu'à vos serveurs, une bonne santé. Comme toujours, j'ai hâte de recevoir vos commentaires dans les commentaires et je vous demande de parcourir un petit sondage ci-dessous.

Sources



All Articles