在我工作的组织中,原则上禁止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会话完成后,连接到语言栏的用户消失在托盘中,要返回它,用户需要登录。事实证明,我们并不孤单:一,二,三。就这样。祝您和您的服务器健康。与往常一样,我希望在评论中提供反馈,并请您进行下面的小调查。资料来源