Finding RDP sessions on servers using PowerShell

image_pdfimage_print

PowerShellHave you ever needed to use RDP to get to a server console for some local admin work and then been bounced out because there are already active sessions? Or have you had your Active Directory account locked out because of an open RDP session with the old password sending locks to the domain?

Rather than having to run the Remote Desktop Manager and search between servers, I decided to build a little script which will scan Active Directory for computers running server OS and list out the active and disconnected sessions.

The magic of QWINSTA

One of the challenges with this activity is that PowerShell does not have a native CmdLet to extract RDP information from servers. Luckily, we can use a hybrid approach here to solve that problem.

WIndows ships with two tools named QWINSTA.exe and RWINSTA.exe for querying and resetting Remote Desktop Services sessions. For our purposes we will use QWINSTA (apparently stands for Query WINdows STAtion).

You can find the options in the command line by typing QWINSTA /? as shown here:

The Process

There is a simple flow to the script which is:

  1. Query Active Directory for Servers
  2. Run QWINSTA to extract the session information
  3. If a session exists, read the username and session type
  4. Log the username and session type to a variable
  5. Email the results

There are some setup options for email parameters that are done and most importantly the Import-Module CmdLet is used to load the Active Directory module so that we can use Get-ADComputer. This will require that you have the AD PowerShell environment on the workstation or server that you are running this process from.

I’ve added some sections in the script which are commented out but you can uncomment them to have them output to the console so that you can see what is happening while the script is running. This is handy for troubleshooting in the case that there are issues.

Adding the QWINSTA Command

Because we have to use an external command in our script we will simply use the command inline with other script actions. The interesting thing about this is that we can run the command easily and pass the computer name parameter which we have assigned to $ServerName:

qwinsta /server:$ServerName

To be able to leverage the PowerShell goodness we have to assign the output to a variable so that we can reuse it as needed:

$queryResults = qwinsta /server:$ServerName

Luckily PowerShell makes it just that easy to capture the output of external commands as well as native CmdLets.

Managing the QWINSTA Output

When you look at the script you can see that the QWINSTA command line is not quite as simple as I’ve shown above. This is because the output of the QWINSTA command isn’t in a neat and tidy one line format like we really would prefer:

This is why we have a much more complex command line in the script:

$queryResults = (qwinsta /server:$ServerName | foreach { (($_.trim() -replace “s+”,”,”))} | ConvertFrom-Csv)

We are taking each line of the output from the command, trimming the content and replacing the spaces with commas and then piping it to a ConvertFrom-CSV to put the content into a table array with the appropriate headers of USERNAME and SESSIONNAME which is the information we are looking for.

Logging and Emailing the Results

The logging of the output is quite simple. We just want to collect each line as we loop through the sessions and the add the content into a variable ($SessionList).

Once we have completed querying each of the computers, the loop exits and then we put together our email message using the Send-MailMessage CmdLet:

Send-MailMessage -To $emailTo -Subject $subject -Body $SessionList -SmtpServer $smtpServer -From $emailFrom -Priority $priority

The parameters for the Send-MailMessage command come from those defined in the static variables at the beginning of the script and with the $SessionList which was created during the script run.

The Script

Once we’ve put it all together it is a nifty and fairly lightweight script. I’ve put the script up on my TechNet Gallery and you can find it here: http://gallery.technet.microsoft.com/PowerShell-script-to-Find-d2ba4252

Here is the script in it’s full form to read through. I hope that you are able to make use of it. An ideal situation is to have it run as a batch process every night to scan the environment and email your Systems Admin team so that they know what sessions are left overnight.

 

# Import the Active Directory module for the Get-ADComputer CmdLet

Import-Module ActiveDirectory

# Get today’s date for the report

$today = Get-Date

# Setup email parameters

$subject = “ACTIVE SERVER SESSIONS REPORT – ” + $today

$priority = “Normal”

$smtpServer = “YourMailServer”

$emailFrom = “email@yourdomain.com

$emailTo = “email@yourdomain.com

# Create a fresh variable to collect the results. You can use this to output as desired

$SessionList = “ACTIVE SERVER SESSIONS REPORT – ” + $today + “`n`n”

# Query Active Directory for computers running a Server operating system

$Servers = Get-ADComputer -Filter {OperatingSystem -like “*server*”}

# Loop through the list to query each server for login sessions

ForEach ($Server in $Servers) {

$ServerName = $Server.Name

# When running interactively, uncomment the Write-Host line below to show which server is being queried

# Write-Host “Querying $ServerName”

# Run the qwinsta.exe and parse the output

$queryResults = (qwinsta /server:$ServerName | foreach { (($_.trim() -replace “s+”,”,”))} | ConvertFrom-Csv)

# Pull the session information from each instance

ForEach ($queryResult in $queryResults) {

$RDPUser = $queryResult.USERNAME

$sessionType = $queryResult.SESSIONNAME

# We only want to display where a “person” is logged in. Otherwise unused sessions show up as USERNAME as a number

If (($RDPUser -match “[a-z]”) -and ($RDPUser -ne $NULL)) {

# When running interactively, uncomment the Write-Host line below to show the output to screen

# Write-Host $ServerName logged in by $RDPUser on $sessionType

$SessionList = $SessionList + “`n`n” + $ServerName + ” logged in by ” + $RDPUser + ” on ” + $sessionType   }

}

}

# Send the report email

Send-MailMessage -To $emailTo -Subject $subject -Body $SessionList -SmtpServer $smtpServer -From $emailFrom -Priority $priority

# When running interactively, uncomment the Write-Host line below to see the full list on screen

# $SessionList

 

 

DiscoPosse

People, Process, and Technology. Powered by Community!

You might also like

12 Comments

  • Clay Perrine
    October 30, 2012 at 10:23 am

    The parse line doesn’t work correctly for some servers. It returns 6 field headers for the CSV, then only adds the populated fields for the sessions, so the output ends up showing login sessions that don’t exist.

    SERVER4 logged in by Disc on 0
    SERVER4 logged in by Down on 1

    Running the qwinsta /server:SERVER4 command manually produces this:

    SESSIONNAME USERNAME ID STATE TYPE DEVICE
    0 Disc rdpwd
    rdp-tcp 65536 Listen rdpwd
    console 2 Conn wdcon
    1 Down

    Running the qwinsta command manually without the convertfrom-csv returns this:

    qwinsta /server:SERVER4 | foreach { (($_.trim() -replace “s+”,”,”))
    SESSIONNAME,USERNAME,ID,STATE,TYPE,DEVICE
    0,Disc,rdpwd
    rdp-tcp,65536,Listen,rdpwd
    console,2,Conn,wdcon
    1,Down

    So the csv query for the first line returns “0” as the sessionname, and “Disc” as the username. That produces erroneous output.

    I think that you would need to parse this as an array rather than a csv. I have been looking at it, but I was distracted by other duties.

    • DiscoPosse
      Eric
      October 31, 2012 at 4:22 am

      Hi Clay,

      Thanks for the note on this. I wasn’t as clear on the output for disconnected sessions. For my environment I actually have hung disconnected sessions sometimes so I didn’t do any additional management for that result.

      To get around this we can adjust the If statement which generates the results to return to the console/email:

      Currrent:

      If (($RDPUser -match “[a-z]“) -and ($RDPUser -ne $NULL)) {

      Remove results if the Username is Disc:

      If ($RDPUser -match “[a-z]“ -And $RDPUser -notmatch “Disc” -and $RDPUser -ne $NULL) {

      Now that I look at it, I had some unnecessary brackets in the statement 🙂

      Hopefully I’ll have some time to update the post to reflect that change. Thanks for the input!

      Eric

  • Clay Perrine
    October 31, 2012 at 10:02 am

    Here is what I did based on your suggestion.

    # Pull the session information from each instance
    ForEach ($queryResult in $queryResults) {
    $RDPUser = $queryResult.USERNAME
    $sessionType = $queryResult.SESSIONNAME
    If ($sessionType -ne $NULL ) {$ICA = $sessionType.startswith(“ica”)}

    # We only want to display where a “person” is logged in. Otherwise unused sessions show up as USERNAME as a number
    If ($RDPUser -match “[a-z]“ -And $RDPUser -notmatch “Disc” -And $RDPUser -notmatch “Down” -and $RDPUser -ne $NULL -and $ICA -match “False”) {
    # When running interactively, uncomment the Write-Host line below to show the output to screen
    Write-Host $ServerName logged in by $RDPUser on $sessionType
    $SessionList = $SessionList + “`n`n” + $ServerName + ” logged in by ” + $RDPUser + ” on ” + $sessionType
    }
    }
    }

    I also excluded any ICA sessions, as we have a bunch of citrix servers, and we just want to audit the admins who leave RDP sessions open.

  • Clay Perrine
    October 31, 2012 at 10:04 am

    And I almost forgot. This script was almost exactly what I needed. Just some minor tweaking and it works great.

    Thanks!

  • Brent
    November 16, 2012 at 2:57 pm

    Eric – Great Script. is there anyway for it to show me inactive or inactive/disconnected sessions. I believe it was built only to show active sessions?

    Thanks!

    • DiscoPosse
      Eric
      December 13, 2012 at 6:04 pm

      Hi Brent,

      It does pull in disconnected sessions which display in the output as a user “disc”. If it isn’t, let me know what OS you are querying and I can give it a whirl. It seems to be working for 2008/2008 R2 instances for me.

      Thanks,

      Eric

  • Tom
    March 12, 2013 at 2:03 pm

    We are trying to use QWINSTA to verify that our batch systems user-ids are logged in via the console. However, I am seeing a variety of output, in that some with session 0 show as “console” and others don’t. Which conclusively indicates that it is a console user – the “console” designation or being session “0”?

    -Thanks-

    • DiscoPosse
      Eric
      March 26, 2013 at 5:20 am

      Hi Tom,

      The QWINSTA output always shows the console user as SESSIONNAME=console. There can be an ID of 0 which could be another user like a service or something. It’s a little confusing sometimes.

      I think I need to do some different use cases and might have to tweak the script a little.

      Thanks,

      Eric

  • Travis
    November 19, 2013 at 3:56 pm

    Is there anyway to instead of polling AD, to pull a list of servers from a text file?

    • DiscoPosse
      Eric
      November 19, 2013 at 4:05 pm

      Hi Travis,

      You sure can! You can change the line that populates the $servers variable to import a file from CSV.

      To remove the AD portion, comment out (add a # to the beginning of the line) the Import-Module Active Directory line and also replace the following line:
      $Servers = Get-ADComputer -Filter {OperatingSystem -like “*server*”}
      with something like this:
      $Servers = Import-CSV “YourImportFileHere.CSV”
      which will query the file for the input. Your file should have a header row which says Name, and the list of servers will be on each line below it. When the script assigns the $Server.Name as it loops through the array, it will use your CSV file. Make sure to use the full path to the CSV, or use no path and ensure the file is in the same folder as the script.

      As usual, test, test and re-test but this is a non-intrusive script, so even if the input doesn’t work on the first try, it is easy to experiment.

      Hope that helps!

      Eric

  • Name
    October 30, 2015 at 3:31 pm

    Great script!
    I changed two things, however:
    1) Output of qwinsta is language dependent and thus the properties created by ConvertFrom-Csv may get different names. I resorted to replacing strings but that doesn’t resolve the problem of not beeing able to force a given language in Windows / Powershell entirely, it’s just a mitigation for a known language.
    2) Output of qwinsta may have empty fields and thus break parsing. I parsed it using fixed number of characters
    ( -replace “^(………………)(……………..)(…………)(……..)(……..)(.*)$”,’$1,$2,$3,$4,$5,$6′ … )

    Otherwise great, thanks for posting it.

    Markus

    • DiscoPosse
      DiscoPosse
      November 1, 2015 at 7:48 pm

      Thanks for the follow up and notes on language, Markus!

LEAVE A COMMENT

Proudly Sponsored By

Advertisement

GC On-Demand

Subscribe to the Blog

Enter your email address to subscribe to this blog and receive notifications of new posts by email.

Upcoming events:

Archives