委派RDP会话管理


在我工作的组织中,原则上禁止udalenka。它是。直到上周。现在,我不得不紧急实施该解决方案。从业务-流程适应到新的工作格式,从我们-带有密码和令牌的PKI,VPN,详细的日志记录等等。
除其他事项外,我还参与了配置终端服务远程桌面基础结构的工作。我们在不同的数据中心中有多个RDS部署。任务之一是使相关IT部门的同事能够以交互方式连接到用户会话。如您所知,对此有一种标准的RDS影子机制,最简单的委派方式是授予RDS服务器上的本地管理员权限。
我尊重并珍惜我的同事,但对管理员权限的分配非常贪婪。:)请那些同意我的人在猫下。

好吧,任务很明确,现在到了重点。

步骤1


让我们在Active Directory中创建RDP_Operators安全组,并包括我们要向其委派权限的那些用户的帐户:

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

如果您有多个AD站点,则在继续下一步之前,需要等待,直到将其复制到所有域控制器。通常不超过15分钟。

第2步


我们授予该小组在每个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")
        }
    }
}


第三步


将该组添加到每个RDSH服务器上的本地“ 远程桌面用户”如果您的服务器合并在一个会话集合中,那么我们将在集合级别执行此操作:

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

对于单个服务器,我们使用组策略,直到将其应用到服务器上为止。那些懒于等待的人可以使用旧的gpupdate强制执行该过程,最好是集中使用

第四步


我们将为“经理”准备以下PS脚本:

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


为了使PS脚本易于运行,我们将以cmd文件的形式为其制作一个外壳,其名称与PS脚本相同:

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


我们将两个文件都放入一个文件夹,供“管理员”使用,并要求他们登录。现在,启动cmd文件后,他们将能够在RDS阴影模式下连接到其他用户的会话并强制他们注销(当用户无法独立结束“挂起”会话时,这很有用)。

看起来像这样:

对于“经理”


对于用户


最后的几点评论


细微差别1如果在服务器上运行Set-RDSPermissions.ps1脚本之前启动了我们要获取控制权的用户会话,则“管理员”将收到访问错误。解决方案显而易见:等待受管用户登录。

细微差别2在使用RDP数天后,Shadow注意到了一个有趣的错误或功能:Shadow会话完成后,连接到语言栏的用户消失在托盘中,要返回它,用户需要登录。事实证明,我们并不孤单:

就这样。祝您和您的服务器健康。与往常一样,我希望在评论中提供反馈,并请您进行下面的小调查。

资料来源



All Articles