远程会话退出的PowerShell日志记录

37

我正在尝试制定一个Powershell命令来远程注销用户。我们有一台带有非常不稳定程序的终端服务器,有时会锁定会话。我们需要远程注销用户,但我正在尝试编写一个PowerShell语句,将注销运行该脚本的人。我在Google上搜到了这个命令:

Invoke-Command -ComputerName MyServer -Command {shutdown -l}

但是,该命令返回“不正确的函数”错误。我可以在括号中成功运行其他命令,例如Get-Process。

我的想法是将其放入用户可以运行的脚本中,以使他们注销服务器(因为当它锁定时,他们无法访问开始菜单或ALT + CTRL + END来通过GUI进行操作)。

流程如下: Bob通过RDP登录MyServer,但他的会话卡住了。在他的本地桌面上,他可以运行包含类似上述命令的MyScript,这将注销他在MyServer上的会话。


请查看此处 https://dev59.com/FKDia4cB1Zd3GeqPJtLu#43352906 - LT-
12个回答

55

也许令人惊讶的是,您可以使用logoff命令注销用户。

C:\> <b>logoff /?</b>
Terminates a session.

LOGOFF [sessionname | sessionid] [/SERVER:servername] [/V] [/VM]

  sessionname         The name of the session.
  sessionid           The ID of the session.
  /SERVER:servername  Specifies the Remote Desktop server containing the user
                      session to log off (default is current).
  /V                  Displays information about the actions performed.
  /VM                 Logs off a session on server or within virtual machine.
                      The unique ID of the session needs to be specified.

会话 ID 可以通过 qwinsta (query session) 或者 quser (query user) 命令来确定 (参见此处):

$server   = 'MyServer'
$username = $env:USERNAME

$session = ((quser /server:$server | ? { $_ -match $username }) -split ' +')[2]

logoff $session /server:$server

实际上,它可以在非终端服务器上运行,但不能在终端服务器连接上运行。我需要调整什么吗? - jlacroix
你可能需要管理员权限才能注销终端服务器上的用户。我明天会检查一下。 - Ansgar Wiechers
@jlacroix,您可以通过在RDP会话中映射本地驱动器并在终端服务器上的登录脚本中运行quser | find "%USERNAME%" >D:\path\to\session.txt来解决此问题(将D:\path\to\session.txt替换为映射驱动器上输出文件的路径)。这样客户端上的脚本就可以从本地文件中读取会话ID。 - Ansgar Wiechers
喜欢在“quser”的输出上使用正则表达式分割。我如何能够获取多个会话名称[2]以记录远程服务器上的多个或甚至所有会话? - Jaans
1
@Jaans 将 ? { $_ -match $username } 替换为 select -Skip 1 - Ansgar Wiechers

18
这是一个很棒的脚本解决方案,可以远程或本地登出用户。我使用qwinsta获取会话信息并将给定输出构建成数组。这使得通过每个条目进行迭代并仅登出实际用户(而不是系统或RDP监听器,它通常只会抛出访问被拒绝的错误)变得非常容易。
$serverName = "Name of server here OR localhost"
$sessions = qwinsta /server $serverName| ?{ $_ -notmatch '^ SESSIONNAME' } | %{
$item = "" | Select "Active", "SessionName", "Username", "Id", "State", "Type", "Device"
$item.Active = $_.Substring(0,1) -match '>'
$item.SessionName = $_.Substring(1,18).Trim()
$item.Username = $_.Substring(19,20).Trim()
$item.Id = $_.Substring(39,9).Trim()
$item.State = $_.Substring(48,8).Trim()
$item.Type = $_.Substring(56,12).Trim()
$item.Device = $_.Substring(68).Trim()
$item
} 

foreach ($session in $sessions){
    if ($session.Username -ne "" -or $session.Username.Length -gt 1){
        logoff /server $serverName $session.Id
    }
}
在此脚本的第一行中,给$serverName赋予适当的值,如果是在本地运行则为localhost。我使用这个脚本在自动化过程尝试移动一些文件夹之前踢出用户,这可以防止出现“文件正在使用”错误。另外需要注意的是,必须以管理员用户身份运行此脚本,否则您可能因尝试注销某人而被拒绝访问。希望这可以帮到你!

2
这个解决方案非常好,可以根据几乎任何条件进行过滤,例如部分用户名匹配或仅断开的会话。您还可以将整个内容包装在另一个foreach循环中,以便对多个服务器运行。 - cscracker
'^' 在第二行的 '^ SESSIONNAME' 部分是什么意思?好奇的是它似乎可以带或不带运行。 - HDCerberus
这是一个正则表达式字符,意味着行的开头或字符串的开头。在大多数正则表达式实现中都是通用的。这里有一个关于这个问题的主题。在某些情况下,它可能并不是必需的,但可以帮助使匹配更具体。https://dev59.com/Z2035IYBdhLWcg3wSN4G - Jason Slobotski
1
工作得很好,但是那个“if语句”是多余的,没有必要检查空和短长度。如果它的意思是将当前用户放入-ne过滤器中以避免注销自己,则需要将-or更改为-and,以便长度过滤器适用。 - WhoIsRich
我真的不记得我是什么时候写的了,因为这已经很久以前的事情了。现在看起来,你可能是对的。可能是我在尝试让事情正常工作时添加的其中一项内容,然后在它达到我想要的效果后就没有改变它。 - Jason Slobotski

15

如果有人愿意,可以添加普通的DOS命令。 是的,这对Win 8和Server 2008 + Server 2012仍然有效。

Query session /server:Server100

将返回:

SESSIONNAME       USERNAME                 ID  STATE   TYPE        DEVICE
rdp-tcp#0         Bob                       3  Active  rdpwd
rdp-tcp#5         Jim                       9  Active  rdpwd
rdp-tcp                                 65536  Listen

要注销会话,请使用:

Reset session 3 /server:Server100

7

这种方法比较老旧,早于PowerShell,但我已经使用qwinsta / rwinsta组合很多年来远程注销过期的RDP会话。它至少在Windows XP及更高版本中内置(可能更早)。

确定会话ID:

qwinsta /SERVER:<NAME>

移除该会话:

rwinsta <SESSION_ID> /SERVER:<NAME>

有没有批量执行的方法?即如何删除所有会话? - Jaans
@joshua_mckinnon 当命令从本地机器发出时,它可以完美运行,但仅使用服务器名称是不够的。如果远程机器在域上,是否有不同的语法?或者它永远无法工作,因为它假定是远程桌面主机,而不仅仅是在网络上登录到计算机的用户? - undrline - Reinstate Monica

6

您可以使用Invoke-RDUserLogoff

以下是注销特定组织单位的Active Directory用户的示例:

$users = Get-ADUser -filter * -SearchBase "ou=YOUR_OU_NAME,dc=contoso,dc=com"

Get-RDUserSession | where { $users.sAMAccountName -contains $_.UserName } | % { $_ | Invoke-RDUserLogoff -Force }

在管道的末端,如果您尝试仅使用 foreach(%),它将仅注销一个用户。但是使用 foreach 和管道的这种组合:

|%{ $_ | command }

将按预期工作。

附:以管理员身份运行。


3
非常好,但似乎仅限于Windows Server 2012 R2 / Windows 8.1? - Jaans
@Jaans 的确,来自 http://technet.microsoft.com/en-us/library/jj215468.aspx ,"适用于:Windows 8.1、Windows PowerShell 4.0、Windows Server 2012 R2"。 - Gauss

4

3

我修改了Casey的答案,只注销已断开连接的会话,具体操作如下:

   foreach($Server in $Servers) {
    try {
        query user /server:$Server 2>&1 | select -skip 1 | ? {($_ -split "\s+")[-5] -eq 'Disc'} | % {logoff ($_ -split "\s+")[-6] /server:$Server /V}
    }
    catch {}
    }

不确定你的最后一行是干什么用的。它看起来并不完全像有效的PS代码,也不像一个很好的解释。 - Nathan Tuggy

3

注销机器上的所有用户:

try {
   query user /server:$SERVER 2>&1 | select -skip 1 | foreach {
     logoff ($_ -split "\s+")[-6] /server:$SERVER
   }
}
catch {}

详情:

  • 当没有用户在服务器上时,使用try/catch,如果query返回错误。但是,如果您不介意看到错误字符串,则可以删除2>&1部分并删除try/catch
  • select -skip 1 移除头行
  • 内部的foreach记录每个用户的注销
  • ($_ -split "\s+") 将字符串拆分为仅包含文本项的数组
  • [-6] 索引获取会话ID,并且是从数组的反向计数第6个字符串开始,您需要这样做,因为query输出将具有8或9个元素,具体取决于用户连接或断开终端会话

这个“query”是什么东西? - bwerks
我的代码使用的是索引[-4]而不是[-6],以及使用的是"\s\s+"而不是"\s+"。 - Gauss

1
以下脚本适用于活动和断开的会话,只要用户可以远程访问运行注销命令。您需要在第4行更改服务器名称为“YourServerName”。
param (
    $queryResults = $null,
    [string]$UserName = $env:USERNAME,
    [string]$ServerName = "YourServerName"
)


if (Test-Connection $ServerName -Count 1 -Quiet) {  
    Write-Host "`n`n`n$ServerName is online!" -BackgroundColor Green -ForegroundColor Black


    Write-Host ("`nQuerying Server: `"$ServerName`" for disconnected sessions under UserName: `"" + $UserName.ToUpper() + "`"...") -BackgroundColor Gray -ForegroundColor Black

        query user $UserName /server:$ServerName 2>&1 | foreach {  

            if ($_ -match "Active") {
                Write-Host "Active Sessions"
                $queryResults = ("`n$ServerName," + (($_.trim() -replace ' {2,}', ','))) | ConvertFrom-Csv -Delimiter "," -Header "ServerName","UserName","SessionName","SessionID","CurrentState","IdealTime","LogonTime"


                $queryResults | ft
                Write-Host "Starting logoff procedure..." -BackgroundColor Gray -ForegroundColor Black

                $queryResults | foreach {
                    $Sessionl = $_.SessionID
                    $Serverl = $_.ServerName
                    Write-Host "Logging off"$_.username"from $serverl..." -ForegroundColor black -BackgroundColor Gray
                    sleep 2
                    logoff $Sessionl /server:$Serverl /v

                }


            }                
            elseif ($_ -match "Disc") {
                Write-Host "Disconnected Sessions"
                $queryResults = ("`n$ServerName," + (($_.trim() -replace ' {2,}', ','))) |  ConvertFrom-Csv -Delimiter "," -Header "ServerName","UserName","SessionID","CurrentState","IdealTime","LogonTime"

                $queryResults | ft
                Write-Host "Starting logoff procedure..." -BackgroundColor Gray -ForegroundColor Black

                $queryResults | foreach {
                    $Sessionl = $_.SessionID
                    $Serverl = $_.ServerName
                    Write-Host "Logging off"$_.username"from $serverl..."
                    sleep 2
                    logoff $Sessionl /server:$Serverl /v

                }
            }
            elseif ($_ -match "The RPC server is unavailable") {

                Write-Host "Unable to query the $ServerName, check for firewall settings on $ServerName!" -ForegroundColor White -BackgroundColor Red
            }
            elseif ($_ -match "No User exists for") {Write-Host "No user session exists"}

    }
}
else {

    Write-Host "`n`n`n$ServerName is Offline!" -BackgroundColor red -ForegroundColor white
    Write-Host "Error: Unable to connect to $ServerName!" -BackgroundColor red -ForegroundColor white
    Write-Host "Either the $ServerName is down or check for firewall settings on server $ServerName!" -BackgroundColor Yellow -ForegroundColor black
}





Read-Host "`n`nScript execution finished, press enter to exit!"

一些样例输出。对于活动会话: 输入图像描述 对于已断开连接的会话: 输入图像描述 如果没有找到会话: 输入图像描述 此外,还可以查看此解决方案,以查询所有 AD 服务器以获取您的用户名,并仅注销已断开连接的会话。该脚本还将告诉您是否连接或查询服务器时出现错误。 Powershell 查找已断开连接的 RDP 会话并同时注销

1
我相信我的代码会更简单且运行更快。
    $logon_sessions = get-process -includeusername | Select-Object -Unique -Property UserName, si | ? { $_ -match "server" -and $_ -notmatch "admin" }

    foreach ($id in $logon_sessions.si) {
        logoff $id
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接