RDP : Find disconnected sessions


If you work with RDP (which stands for remote desktop protocol) usually to manage servers when you can’t do that task remotely, the envelope for this today is quite small however, some people have the old mindset of needing to be physically on the server to manage it.

If you have quite a few servers and you don’t disconnect correctly, you can end up with lots of disconnected sessions on all your servers, If you are not putting all these sessions through a gateway or a broker, you don’t have a clue where these sessions are because there’s no centralized log of where you’ve got lingering session.

If you do have a disconnected session on a server that doesn’t necessarily cause a problem until you change your password (the guidance on this is somewhat misinterpreted sometimes) when you do change your password (and this problem is not just limited to password changes) every disconnected session will still have the old password and it will cause you to have an account that continually locks out.

Password change policy

Obviously, your policies will be different depending on where you work, that being said, sometimes the policies can be very contradictory as they are not able to take into account complicated passwords, that will unlikely be reverse engineered (this is also known as cracking your password)

In this example consider for a moment, this is your password:

stLViK$+u&rlyecr#1pemAw#i3a

If you do have a password like that What’s funny is you will actually remember how to type that in with muscle memory once you touch enough times it will be quite easy, However, you change your password every month possibly every three months this is where the problem start….

Password will outdo any password policy that your company but the problem here occurs when the pass policy set by your company assumes people who do the bare minimum, so your extra secure password is also subject to the same policies because you probably have “One rule for everyone”

This will force you to do stupid things like set your password on the next change to exactly the same as before with one on the end as below:

stLViK$+u&rlyecr#1pemAw#i3a1

You still had a very secure password but now you’re applying unacceptable tactics to qualify to change password policy because you remember the last 15, now because you’ve added a “1” on the end you fall into the same trap everyone else does and probably the reason you have a password policy to start with.

If your password is very secure, you should really be exempting it from password changes as it will encourage wrong behaviors.

Course Trajectory : Back to RDP mission

My little outline on passwords is over let’s get back to the mission which is you need to find your disconnected RDP session so we have a couple of options here and they are : 

A: Scan a list of small RDP servers for disconnected sessions
B: Scan a list of  "all servers" from ADDS that match "server" and then scan that list
C: Scan a list of known RDP servers that are online and scan that list

Cavets/Notes

  1. If you scan the whole domain this will take sometime but will complete, however that will depend on how many servers you have
  2. If you scan the domain ensure the server listens on TCP:3389 as if it does not you will not have a disconnected RDP session
  3. If you scan the domain ensure the timeout is low so after 2 seconds it moves onto the next server
Script : RDPStatic-ScanandDisconnect.ps1

This will scan a small list of servers this is option A from above:

# Function to search for disconnected sessions on a server

function Get-DisconnectedSessions {
    param (
        [string]$ServerName,
        [string]$SessionID
    )

    $disconnectedSessions = @()

    # Redirecting the error output to $null to suppress "No User exists for *" messages
    $queryResult = query user /server:$ServerName 2>$null

    $queryResult | Where-Object { $_ -match " $SessionID " } | ForEach-Object {
        $fields = $_ -split '\s+'
        $userName = $fields[0]
        $sessionName = $fields[1]
        $id = $fields[2]
        $state = $fields[3]

        if ($state -eq "Disc") {
            $disconnectedSessions += [PSCustomObject]@{
                ServerName = $ServerName
                UserName = $userName
                SessionID = $id
            }
        }
    }

    return $disconnectedSessions
}

# Function to check if a server is listening on port 3389 using Test-NetConnection with timeout
function Test-Port {
    param (
        [string]$ServerName,
        [int]$Port,
        [int]$Timeout
    )

    $job = Start-Job -ScriptBlock {
        param ($ServerName, $Port)
        Test-NetConnection -ComputerName $ServerName -Port $Port -InformationLevel Quiet
    } -ArgumentList $ServerName, $Port

    $result = Wait-Job -Job $job -Timeout ($Timeout / 1000)
    if ($result) {
        $output = Receive-Job -Job $job
        Remove-Job -Job $job
        return $output
    } else {
        Stop-Job -Job $job
        Remove-Job -Job $job
        return $false
    }
}

# Display warning message
Write-Host "Warning: This script needs to check all the servers in the list, so it may take some time." -ForegroundColor Red

# Main script
$servers = @("server-a", "server-b", "server-c") 
$SessionID = Read-Host "Please enter the Session ID you want to search for"
$Timeout = 2000 # Timeout in milliseconds (2 seconds)
$Port = 3389 # RDP port

$totalServers = $servers.Count
$processedServers = 0
$allDisconnectedSessions = @()

Write-Host "Total servers found: $totalServers"

foreach ($server in $servers) {
    $processedServers++
    Write-Progress -Activity "Processing servers" -Status "Checking server $processedServers of $totalServers" -PercentComplete (($processedServers / $totalServers) * 100)

    if (Test-Port -ServerName $server -Port $Port -Timeout $Timeout) {
        $disconnectedSessions = Get-DisconnectedSessions -ServerName $server -SessionID $SessionID
        if ($disconnectedSessions) {
            $allDisconnectedSessions += $disconnectedSessions
        }
    }
}

Write-Progress -Activity "Processing servers" -Status "Completed" -PercentComplete 100

if ($allDisconnectedSessions.Count -gt 0) {
    Write-Host "Disconnected sessions found on the following servers:"
    $allDisconnectedSessions | ForEach-Object {
        Write-Host "Server: $($_.ServerName), User: $($_.UserName), Session ID: $($_.SessionID)"
    }

    $response = Read-Host "Do you want to disconnect all these sessions? (yes/no)"

    if ($response -eq "yes") {
        $allDisconnectedSessions | ForEach-Object {
            logoff $_.SessionID /server:$($_.ServerName)
            Write-Host "Session $($_.SessionID) on server $($_.ServerName) for user $($_.UserName) has been disconnected."
        }
    } else {
        Write-Host "No action taken."
    }
} else {
    Write-Host "No disconnected sessions found with the provided ID on any server."
}

This is how that looks: 


If you get a disconnected session it looks like this, if you say "yes" then it will disconnect that session (or all sessions)


Script : RDP-ScanandDisconnect.ps1

This is option B where it will scan all the server in ADDS and then check for the port TCP:3389 to be open and then work though all those servers, this one may take some time to run!

# Import the Active Directory module
Import-Module ActiveDirectory

# Function to search for disconnected sessions on a server
function Get-DisconnectedSessions {
    param (
        [string]$ServerName,
        [string]$SessionID
    )

    $disconnectedSessions = @()

    # Redirecting the error output to $null to suppress "No User exists for *" messages
    $queryResult = query user /server:$ServerName 2>$null

    $queryResult | Where-Object { $_ -match " $SessionID " } | ForEach-Object {
        $fields = $_ -split '\s+'
        $userName = $fields[0]
        $sessionName = $fields[1]
        $id = $fields[2]
        $state = $fields[3]

        if ($state -eq "Disc") {
            $disconnectedSessions += [PSCustomObject]@{
                ServerName = $ServerName
                SessionID = $id
            }
        }
    }

    return $disconnectedSessions
}

# Function to check if a server is listening on port 3389 using Test-NetConnection with timeout
function Test-Port {
    param (
        [string]$ServerName,
        [int]$Port,
        [int]$Timeout
    )

    $job = Start-Job -ScriptBlock {
        param ($ServerName, $Port)
        Test-NetConnection -ComputerName $ServerName -Port $Port -InformationLevel Quiet
    } -ArgumentList $ServerName, $Port

    $result = Wait-Job -Job $job -Timeout ($Timeout / 1000)
    if ($result) {
        $output = Receive-Job -Job $job
        Remove-Job -Job $job
        return $output
    } else {
        Stop-Job -Job $job
        Remove-Job -Job $job
        return $false
    }
}

# Display warning message
Write-Host "Warning: This script needs to check all the servers in the domain, so it may take some time." -ForegroundColor Red

# Main script
$SessionID = Read-Host "Please enter the Session ID you want to search for"
$Timeout = 2000 # Timeout in milliseconds (2 seconds)
$Port = 3389 # RDP port

# Find all servers in Active Directory
$servers = Get-ADComputer -Filter { OperatingSystem -like "*Server*" } | Select-Object -ExpandProperty Name

$totalServers = $servers.Count
$processedServers = 0
$allDisconnectedSessions = @()

Write-Host "Total servers found: $totalServers"

foreach ($server in $servers) {
    $processedServers++
    Write-Progress -Activity "Processing servers" -Status "Checking server $processedServers of $totalServers" -PercentComplete (($processedServers / $totalServers) * 100)

    if (Test-Port -ServerName $server -Port $Port -Timeout $Timeout) {
        $disconnectedSessions = Get-DisconnectedSessions -ServerName $server -SessionID $SessionID
        if ($disconnectedSessions) {
            $allDisconnectedSessions += $disconnectedSessions
        }
    }
}

Write-Progress -Activity "Processing servers" -Status "Completed" -PercentComplete 100

if ($allDisconnectedSessions.Count -gt 0) {
    Write-Host "Disconnected sessions found on the following servers:"
    $allDisconnectedSessions | ForEach-Object {
        Write-Host "Server: $($_.ServerName), Session ID: $($_.SessionID)"
    }

    $response = Read-Host "Do you want to disconnect all these sessions? (yes/no)"

    if ($response -eq "yes") {
        $allDisconnectedSessions | ForEach-Object {
            logoff $_.SessionID /server:$($_.ServerName)
            Write-Host "Session $($_.SessionID) on server $($_.ServerName) has been disconnected."
        }
    } else {
        Write-Host "No action taken."
    }
} else {
    Write-Host "No disconnected sessions found with the provided ID on any server."
}

While it is scanning the domain you will you will see, here you can see we need to wait for nearly 2000 scans:



Script : RDP-List-ScanandDisconnect.ps1

This is option C where you need to have a servers.txt file in the same folder as the script (unless you ammend the script)

# Function to search for disconnected sessions on a server
function Get-DisconnectedSessions {
    param (
        [string]$ServerName,
        [string]$SessionID
    )

    $disconnectedSessions = @()

    # Redirecting the error output to $null to suppress "No User exists for *" messages
    $queryResult = query user /server:$ServerName 2>$null

    $queryResult | Where-Object { $_ -match " $SessionID " } | ForEach-Object {
        $fields = $_ -split '\s+'
        $userName = $fields[0]
        $sessionName = $fields[1]
        $id = $fields[2]
        $state = $fields[3]

        if ($state -eq "Disc") {
            $disconnectedSessions += [PSCustomObject]@{
                ServerName = $ServerName
                UserName = $userName
                SessionID = $id
            }
        }
    }

    return $disconnectedSessions
}

# Function to check if a server is listening on port 3389 using Test-NetConnection with timeout
function Test-Port {
    param (
        [string]$ServerName,
        [int]$Port,
        [int]$Timeout
    )

    $job = Start-Job -ScriptBlock {
        param ($ServerName, $Port)
        Test-NetConnection -ComputerName $ServerName -Port $Port -InformationLevel Quiet
    } -ArgumentList $ServerName, $Port

    $result = Wait-Job -Job $job -Timeout ($Timeout / 1000)
    if ($result) {
        $output = Receive-Job -Job $job
        Remove-Job -Job $job
        return $output
    } else {
        Stop-Job -Job $job
        Remove-Job -Job $job
        return $false
    }
}

# Display warning message
Write-Host "Warning: This script needs to check all the servers in the list, so it may take some time." -ForegroundColor Red

# Main script
$servers = Get-Content -Path "servers.txt"  # Read server names from servers.txt
$SessionID = Read-Host "Please enter the Session ID you want to search for"
$Timeout = 2000 # Timeout in milliseconds (2 seconds)
$Port = 3389 # RDP port

$totalServers = $servers.Count
$processedServers = 0
$allDisconnectedSessions = @()

Write-Host "Total servers found: $totalServers"

foreach ($server in $servers) {
    $processedServers++
    Write-Progress -Activity "Processing servers" -Status "Checking server $processedServers of $totalServers" -PercentComplete (($processedServers / $totalServers) * 100)

    if (Test-Port -ServerName $server -Port $Port -Timeout $Timeout) {
        $disconnectedSessions = Get-DisconnectedSessions -ServerName $server -SessionID $SessionID
        if ($disconnectedSessions) {
            $allDisconnectedSessions += $disconnectedSessions
        }
    }
}

Write-Progress -Activity "Processing servers" -Status "Completed" -PercentComplete 100

if ($allDisconnectedSessions.Count -gt 0) {
    Write-Host "Disconnected sessions found on the following servers:"
    $allDisconnectedSessions | ForEach-Object {
        Write-Host "Server: $($_.ServerName), User: $($_.UserName), Session ID: $($_.SessionID)"
    }

    $response = Read-Host "Do you want to disconnect all these sessions? (yes/no)"

    if ($response -eq "yes") {
        $allDisconnectedSessions | ForEach
Previous Post Next Post

نموذج الاتصال