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 , 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 .
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"
For the user
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: , , .
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. , 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
