10

When I run the qwinsta /server:somesrv command in cmd I can get a listing of all the current RDP sessions that are logged into a particular Windows server.

 SESSIONNAME       USERNAME                 ID  STATE   TYPE        DEVICE
 console                                     0  Conn    wdcon
 rdp-tcp                                 65536  Listen  rdpwd
 rdp-tcp#594       tom1                      1  Active  rdpwd
 rdp-tcp#595       bob1                      2  Active  rdpwd

Is it possible to get a list like this on a remote server from Powershell so that the data can be used elsewhere?

leeand00
  • 25,510
  • 39
  • 140
  • 297

3 Answers3

15

There are multiple alternatives:

  • Use the Terminal Services PowerShell Module. Easy solution.
  • Writing a powershell wrapper that parses the output of qwinsta to objects. Easy solution. See example below
  • Use the Cassia.DLL .Net wrapper to access the native APIs that qwinsta runs behind the scene. This is the class that the TS Module uses. More difficult, but will have the benefit of being customized to your needs.
  • Go crazy and use the Native Methods that Cassia.DLL accesses using P/Invoke (wtsapi32.dll, kernel32.dll, winsta.dll). Hard and overcomplicated.

PowerShell-wrapper for qwinsta

function Get-TSSessions {
    param(
        $ComputerName = 'localhost'
    )

    $output = qwinsta /server:$ComputerName
    if ($null -eq $output) {
        # An error occured. Abort
        return
    }

    # Get column names and locations from fixed-width header
    $columns = [regex]::Matches($output[0],'(?<=\s)\w+')
    $output | Select-Object -Skip 1 | Foreach-Object {
        [string]$line = $_

        $session = [ordered]@{}
        for ($i=0; $i -lt $columns.Count; $i++) {
            $currentColumn = $columns[$i]
            $columnName = $currentColumn.Value

            if ($i -eq $columns.Count-1) {
                # Last column, get rest of the line
                $columnValue = $line.Substring($currentColumn.Index).Trim()
            } else {
                $lengthToNextColumn = $columns[$i+1].Index - $currentColumn.Index
                $columnValue = $line.Substring($currentColumn.Index, $lengthToNextColumn).Trim()
            }

            $session.$columnName = $columnValue.Trim()
        }

        # Return session as object
        [pscustomobject]$session
    }
}

Get-TSSessions -ComputerName "localhost" | Format-Table -AutoSize

SESSIONNAME USERNAME ID STATE  TYPE DEVICE
----------- -------- -- -----  ---- ------
services             0  Disc
console     Frode    1  Active

#This is objects, so we can manipulate the results to get the info we want. Active sessions only:
Get-TSSessions -ComputerName "localhost" | Where-Object State -eq 'Active' | Format-Table -AutoSize SessionName, UserName, ID

SESSIONNAME USERNAME ID
----------- -------- --
console     Frode    1
Frode F.
  • 52,376
  • 9
  • 98
  • 114
  • 1
    How did I not see that the output was that wrong? :O. Well well, it's just a sample/PoC – Frode F. Apr 20 '16 at 21:30
  • Updated to a working sample function :-) – Frode F. Feb 25 '23 at 09:01
  • Parsing exe output is error prone because different windows versions, updates or different locales can change the output. So I disagree with the API approach being overcomplicated and whilst the parsing option is easy it's just error prone. – Remko Feb 25 '23 at 09:12
  • This is why the module is suggested first as an easy option. The function is just a sample and for many users, who might not get an external module approved, good enough. It's not version nor language dependent and qwinsta is too old to be changed significantly. There are different requirements for production code vs ex. a helpdesk tool. :-) – Frode F. Feb 25 '23 at 19:11
1

I used to use Terminal Services PowerShell Module (now in codeplex archive), but it was two years ago. I can't put my hand on it, but it also exists a function on gitshub or another site that embeded QWinsta/RmWinsta.

JPBlanc
  • 70,406
  • 17
  • 130
  • 175
-1

I did it for myself, but I think you will understand

function seans ($server)
{
    function path1 ($server, $user)
    {
        if ((Test-Path -Path "\\$server\c$\users\$user") -eq $true)
        {
            "\\$server\c$\users\$user"
        }
        elseif ((Test-Path -Path ("\\$server\d$\users\$user")) -eq $true)
        {
            "\\$server\d$\users\$user"
        }
        else
        {
            "\\$server\e$\users\$user"
        }
    }
    $seans = qwinsta /server:$server | ForEach-Object { $_.Trim() -replace "\s+", "," } | ConvertFrom-Csv
    $seans = $seans | select-object @{ Name = "User";   Expression =        { $_.username } },
                                    @{ Name = "Id";     Expression =        { $_.id } },
                                    @{ Name = "Status"; Expression =        { $_.state } },
                                    @{ Name = "Path"; Expression =          { $null } }
    $seans = $seans | Where-object -Property status -eq "Active"
    $seans | foreach-object { $_.path = path1 $server $_.user }
    $seans
}
xXDIZELXx
  • 1
  • 2