- Create a certificate-signed RDP shortcut via Group Policy - Fri, Aug 9 2019
- Monitor web server uptime with a PowerShell script - Tue, Aug 6 2019
- How to build a PowerShell inventory script for Windows Servers - Fri, Aug 2 2019
There are a number of reasons to do this, including employee termination, as a troubleshooting measure or simply to free up some sessions on a remote desktop server. Knowing how to do this from the command line will prevent either having to yell at users over the phone to log off, or having to access their remote consoles via VNC. Using the command line also gives an admin the opportunity to perform this task across many machines or users at once, if necessary.
The first way is by using the logoff command. This command has been around a long time. Many admins are not aware of this command, let alone that it has the ability to perform remote logoffs!
The logoff utility can log off users remotely but requires an extra step of finding a session ID. So first, we must find this ID. We can do this by using the quser utility and the server argument as you can see below.
Notice the value of 2 under the ID field. That is the session ID we need. However, unfortunately, since quser is not a PowerShell command that would return a structured object, we'll have to parse this string to pull out that value. To pull out this value by itself, we can use the Where-Object command and do a little regular expression matching to make it happen.
$userName = 'administrator' $sessionId = ((quser /server:DC | Where-Object { $_ -match $userName }) -split ' +')[2] $sessionId
Above, I am searching for any user with the name of administrator and only returning the session ID. Once I have the session ID, I can now use this ID with the logoff command to log off the administrator account remotely on the DC computer.
logoff $sessionId /server:DC
Another method is to use WMI/CIM and the Win32Shutdown() method. This method doesn't force you to find a user session first but also doesn't give you the ability to pick a user either. This method will just log off all users interactively logged on to the remote computer. You can do this on a single line in PowerShell.
Below, you can see that I'm invoking a CIM method on the DC computer again and specifying 4 as a flag. This forcefully logs off the user. You can use 0 here too to perform a "graceful" logoff.
Invoke-CimMethod -ClassName Win32_Operatingsystem -ComputerName DC -MethodName Win32Shutdown -Arguments @{ Flags = 4 }
There's one last method if the client system from which you're invoking the command is Windows 10. In this case, you should have the Invoke-RDUserLogoff command available to you. Using this method is similar to using the logoff utility. You must first obtain the session ID using quser or the qwinsta utility if the remote server is not running Remote Desktop Services.
$userName = 'administrator' $sessionId = ((quser /server:DC | Where-Object { $_ -match $userName }) -split ' +')[2]
After acquiring the session ID, you can pass it to Invoke-RDUserLogoff with the UnifiedSessionId parameter and use the HostServer parameter to pass along the remote computer name.
Subscribe to 4sysops newsletter!
Invoke-RDUserLogoff -HostServer DC -UnifiedSessionId $sessionId
By now, I hope you've seen enough ways to log off a user from a remote computer. Each method performs the task in a different way but in the end gets the same job done.
You actually need to have
$sessionId = ((quser /server:DC | Where-Object { $_ -match $userName }) -split ‘ +’)[3]
instead of the 2 on the end otherwise your output will be consolename not id
Actually, sometimes you need the second column, sometimes you need the third column. Using “quser” in scripts is simply not doable, since sometimes (as in the screenshot shown in this blog) SESSIONNAME is blank, but other times it has a value (either the number of the RDP session, or “console”).
Here is the final revised functional script that is being used at my company. I found a PC today with 16 users currently logged on!!! The script contains a loop so you can logoff more than one person on the targeted PC:
The script Alfred posted is my new favorite thing. Thank you!
I love this script.. but what could I add as an option to say log off “all users”?
we have a process that allows 50 people to connect, and I don’t want to list each session ID one at a time..
An option to remove all users by typing “All” instead of a number would make this flawless!
thanks so much..
A few notes for everyone:
It’s easy to process the output with powershell since the output is just strings.
One other utility that can be incredibly helpful is the tslogoff.exe command from C-A-D consulting (http://www.ctrl-alt-del.com.au/CAD_TSUtils.htm), it has multiple options such as tslogoff * /server:<servername> to logoff everyone, or tslogoff /disc /server:<servername> to logoff disconnected sessions, etc.
David F.
I wrote this: It will scan all servers you select for users that are logged on and lets you select users to log off.
Function YesNo
{
Param($provide)
$a = new-object -comobject wscript.shell
$popup = “Do you want to Select the servers to scan ?”
$intAnswer = $a.popup($popup, `
0,$provide,4)
If ($intAnswer -eq 6) {
$answer = “yes”
} else {
$answer = $null
}
return $Answer
}
Function DisplayInfo
{
Param($Servers)
cls
write-host
$count = $servers.count
write-host $count -ForegroundColor Yellow -NoNewline
write-host ” servers selected to investigate.” -ForegroundColor Green
write-host “The total of servers in the AD is :” -ForegroundColor Green -NoNewline
Write-Host (get-adcomputer -filter {Operatingsystem -like “Windows Server*”} | select name).count -ForegroundColor Red
Write-Host
}
Function Selection
{
#$servers = (get-adcomputer -filter {Operatingsystem -like “Windows Server*”} | sort name | select name).name
$servers = (get-adcomputer -LDAPFilter “(&(objectCategory=computer)(operatingSystem=Windows Server*)(!serviceprincipalname=*MSClusterVirtualServer*)(!(userAccountControl:1.2.840.113556.1.4.803:=2)))” -Property name | sort-object Name).name
if (yesNo -eq “Yes”)
{
$servers = $servers | out-gridview -passthru
}
return $servers
}
Function DoIt
{
Param($Servers)
$completelist = @()
foreach ($server in $servers)
{
$serverdisp = $server.PadRight(($servers | Measure-Object -Maximum -Property Length).Maximum)
$Skip = 0
Write-host “Checking server ” -ForegroundColor Green -NoNewline; write-host $Server -ForegroundColor Yellow -NoNewline
if (Test-Connection $server -count 1 -Quiet)
{
Write-Host ” —— (online)” -ForegroundColor Green
$sessionId = $null
try
{
#$ErrorActionPreference = “SilentlyContinue”
$SessionIds = quser /server:$server 2>&1
#$ErrorActionPreference = “Continue”
}
catch
{
Write-Host ” – ” -ForegroundColor Green -NoNewline
write-host “$serverdisp seems inaccessible.” -ForegroundColor Red -NoNewline
Write-Host ” No data available.” -ForegroundColor Yellow
write-host ” ———————- ” -ForegroundColor green
$skip = 1
}
if ($Skip -eq 0)
{
$nr = $sessionIds.count-2
$c=0
do
{
$c++
$user = (($sessionIds[$c]) -split ‘ +’)[1]
$session = (($sessionIds[$c]) -split ‘ +’)[2]
$State = (($sessionIds[$c]) -split ‘ +’)[3]
$IdleT = (($sessionIds[$c]) -split ‘ +’)[4]
$LogonT = (($sessionIds[$c]) -split ‘ +’)[5] + ” ” + (($sessionIds[$c]) -split ‘ +’)[6]
Write-Host ” – ” -ForegroundColor Green -NoNewline
write-host $serverdisp -ForegroundColor Yellow -NoNewline
Write-Host ” – ” -ForegroundColor Green -NoNewline
write-host $user -ForegroundColor Magenta -NoNewline
Write-Host ” – ” -ForegroundColor Green -NoNewline
write-host $session -ForegroundColor Cyan -NoNewline
Write-Host ” – ” -ForegroundColor Green -NoNewline
write-host $State -ForegroundColor Cyan -NoNewline
Write-Host ” – ” -ForegroundColor Green -NoNewline
write-host $LogonT -ForegroundColor Cyan -NoNewline
Write-Host ” => ” -ForegroundColor Green -NoNewline
write-host $IdleT -ForegroundColor Cyan
# $Ser+=@($server); $use+=@($user);$Ses+=@($session);$sta+=@($state);$Idl+=@($IdleT);$Log+=@($LogonT)
$completelist += @{server=$server;user=$user;session=$session;State=$state;IdleTime=$idleT;LogonTime=$logonT}
}
while ($c -le $nr)
write-host ” ———————- ” -ForegroundColor green
}
}
else
{
Write-Host ” —— (offline)” -ForegroundColor Red
Write-Host ” – No data available.” -ForegroundColor Yellow
write-host ” ———————- ” -ForegroundColor green
}
}
<# $completeList = New-Object PSObject
$completeList | Add-Member NoteProperty Server $Ser
$completeList | Add-Member NoteProperty User $use
$completeList | Add-Member NoteProperty Session $ses
$completeList | Add-Member NoteProperty State $sta
$completeList | Add-Member NoteProperty IdleTime $Idl
$completeList | Add-Member NoteProperty Logon $Log
$completelist = @(server=$ser;user=$use;session=$ses;State=$sta;IdleTime=$idl;LogonTime=$log) #>
return $completeList
}
Function YesNo2
{
Param($Completelist)
$List = $completelist |% {New-Object psobject -Property $_}
$Users = ($completelist |% {New-Object psobject -Property $_}).user | sort -Unique
$a = new-object -comobject wscript.shell
$intAnswer = $a.popup(“Do you want to Log Off (A) certain User(s)?”, `
0,”Logoff Users”,4)
If ($intAnswer -eq 6)
{
$answer2 = $Users | Out-GridView -PassThru
$step = 0
do
{
$logs = $list | where {$_.user -eq $answer2}
foreach ($log in $logs)
{
write-host $log.user -ForegroundColor green -NoNewline
write-host ” logged on to device ” -ForegroundColor Yellow -NoNewline
write-host $log.server -ForegroundColor Green -NoNewline
Write-Host ” since ” -ForegroundColor Yellow -NoNewline
write-host $log.Logontime -ForegroundColor Cyan
write-host “Attempt log off:” -ForegroundColor Yellow -NoNewline
$connect = $log.server
try{Invoke-RDUserLogoff -HostServer $connect -UnifiedSessionId $Log.session -Force ;$logof=”OK”}
catch{$logof=”NOK”}
if($Logof -eq “OK”){ write-host ” was logged off!” -ForegroundColor Green}
else { write-host ” wasn’t logged off!” -ForegroundColor red; write-host “Please check manually.” -ForegroundColor red}
}
$step++
}
while ($step -lt $answer2.count)
}
}
# SO now let’s play!
$Servers = Selection
Displayinfo $servers
$completelist = DoIt $servers
yesno2 $completelist
Koen, thank you! This script is really … really very useful!
Koen,
How would I edit this to search through all computers on an AD Domain?
Many Thanks
Danny,
if you really want all AD-devices, then alter line 30 to:
$servers = (get-adcomputer -Filter * -Property name | sort-object Name).name
grts
Koen
Thank you @Adam B for this post! Very Helpful!
This piece of code is exactly what I need, it just won’t work if the user is not connected, the catch part isn’t working.
@Derli Dias Campos Junior
Try this in the catch section:
For Alfred and Keon's scripts I get the below error:
[%servername%] Connecting to remote server l%servername% failed with the following error
message : The client cannot connect to the destination specified in the request. Verify that the service on the
destination is running and is accepting requests. Consult the logs and documentation for the WS-Management service
running on the destination, most commonly IIS or WinRM. If the destination is the WinRM service, run the following
command on the destination to analyze and configure the WinRM service: "winrm quickconfig". For more information, see
the about_Remote_Troubleshooting Help topic.
+ CategoryInfo : OpenError: (%servername%:String) [], PSRemotingTransportException
+ FullyQualifiedErrorId : CannotConnect,PSSessionStateBroken
@Bob
Please try to see if port 5985 is reachable on the remote computer.
If your local workstation runs on Windows 8 or above:
If your local workstation runs Windows 7
Install the Telnet Client feature and run this:
@ Koen It really very useful script,Thank you very much for it
I tried to use it for all AD computers but it shows "[5]:Access – is – denied." when Checking server,However it is working fine with servers
my user is domain admins,it there any thing to do from my side to fix this issue.Last thank you again for this fantastic script
I commonly need to remove all other folks on a machine, so this is what I created for it. I'm posting it here since I haven't seen something that deals with quser's nullable session name. I've found that only that column is nullable, so you can count the number of populated fields to determine where the session ID is:
The post below seems to be a replication of this work, Adam. It doesn't appear to give you credit for it. I am uncertain if this is authorized or not, but thought I would mention it.
https://www.heelpbook.net/2018/force-a-user-logoff-remotely-with-powershell/
If you are running an RDS farm environment, the simple thing to do is combine the Get-RDUserSession cmdlet (which returns the UnifiedSessionId) with Invoke-RDUserLogoff.
If there is no farm, you can also take the quser output and pipe it to ConvertFrom-String to have a simple object to work with. You can avoid regex this way.