Skip to main content

PowerShell script to retrieve all user sessions from local or remote computers(s). Requires query.exe in order to run.

function Get-UserSession {
<#
.SYNOPSIS
    Retrieves all user sessions from local or remote computers(s)

.DESCRIPTION
    Retrieves all user sessions from local or remote computer(s).

    Note:   Requires query.exe in order to run
    Note:   This works against Windows Vista and later systems provided the following registry value is in place
            HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Terminal Server\AllowRemoteRPC = 1
    Note:   If query.exe takes longer than 15 seconds to return, an error is thrown and the next computername is processed.  Suppress this with -erroraction silentlycontinue
    Note:   If $sessions is empty, we return a warning saying no users.  Suppress this with -warningaction silentlycontinue

.PARAMETER computername
    Name of computer(s) to run session query against

.parameter parseIdleTime
    Parse idle time into a timespan object

.parameter timeout
    Seconds to wait before ending query.exe process.  Helpful in situations where query.exe hangs due to the state of the remote system.

.FUNCTIONALITY
    Computers

.EXAMPLE
    Get-usersession -computername "server1"

    Query all current user sessions on 'server1'

.EXAMPLE
    Get-UserSession -computername $servers -parseIdleTime | ?{$_.idletime -gt [timespan]"1:00"} | ft -AutoSize

    Query all servers in the array $servers, parse idle time, check for idle time greater than 1 hour.

.NOTES
    Thanks to Boe Prox for the ideas - http://learn-powershell.net/2010/11/01/quick-hit-find-currently-logged-on-users/

.LINK
    http://gallery.technet.microsoft.com/Get-UserSessions-Parse-b4c97837

#>
    [cmdletbinding()]
    Param(
        [Parameter(
            Position = 0,
            ValueFromPipeline = $True)]
        [string[]]$computername = "localhost",

        [switch]$parseIdleTime,

        [validaterange(0,120)]$timeout = 15
    )

    ForEach($computer in $computername) {

        #start query.exe using .net and cmd /c.  We do this to avoid cases where query.exe hangs

            #build temp file to store results.  Loop until this works
                Do{
                    $tempFile = [System.IO.Path]::GetTempFileName()
                    start-sleep -Milliseconds 300
                }
                Until(test-path $tempfile)

            #Record date.  Start process to run query in cmd.  I use starttime independently of process starttime due to a few issues we ran into
                $startTime = Get-Date
                $p = Start-Process -FilePath C:\windows\system32\cmd.exe -ArgumentList "/c query user /server:$computer > $tempfile" -WindowStyle hidden -passthru

            #we can't read in info or else it will freeze.  We cant run waitforexit until we read the standard output, or we run into issues...
            #handle timeouts on our own by watching hasexited
                $stopprocessing = $false
                do{

                    #check if process has exited
                    $hasExited = $p.HasExited

                    #check if there is still a record of the process
                    Try { $proc = get-process -id $p.id -ErrorAction stop }
                    Catch { $proc = $null }

                    #sleep a bit
                    start-sleep -seconds .5

                    #check if we have timed out, unless the process has exited
                    if( ( (Get-Date) - $startTime ).totalseconds -gt $timeout -and -not $hasExited -and $proc){
                        $p.kill()
                        $stopprocessing = $true
                        remove-item $tempfile -force
                        Write-Error "$computer`: Query.exe took longer than $timeout seconds to execute"
                    }
                }
                until($hasexited -or $stopProcessing -or -not $proc)
                if($stopprocessing){ Continue }

                #if we are still processing, read the output!
                $sessions = get-content $tempfile
                remove-item $tempfile -force

        #handle no results
        if($sessions){

            1..($sessions.count -1) | % {

                #Start to build the custom object
                $temp = "" | Select ComputerName, Username, SessionName, Id, State, IdleTime, LogonTime
                $temp.ComputerName = $computer

                #The output of query.exe is dynamic.
                #strings should be 82 chars by default, but could reach higher depending on idle time.
                #we use arrays to handle the latter.

                if($sessions[$_].length -gt 5){
                    #if the length is normal, parse substrings
                    if($sessions[$_].length -le 82){

                        $temp.Username = $sessions[$_].Substring(1,22).trim()
                        $temp.SessionName = $sessions[$_].Substring(23,19).trim()
                        $temp.Id = $sessions[$_].Substring(42,4).trim()
                        $temp.State = $sessions[$_].Substring(46,8).trim()
                        $temp.IdleTime = $sessions[$_].Substring(54,11).trim()
                        $logonTimeLength = $sessions[$_].length - 65
                        try{
                            $temp.LogonTime = get-date $sessions[$_].Substring(65,$logonTimeLength).trim()
                        }
                        catch{
                            $temp.LogonTime = $sessions[$_].Substring(65,$logonTimeLength).trim() | out-null
                        }

                    }
                    #Otherwise, create array and parse
                    else{
                        $array = $sessions[$_] -replace "\s+", " " -split " "
                        $temp.Username = $array[1]

                        #in some cases the array will be missing the session name.  array indices change
                        if($array.count -lt 9){
                            $temp.SessionName = ""
                            $temp.Id = $array[2]
                            $temp.State = $array[3]
                            $temp.IdleTime = $array[4]
                            $temp.LogonTime = get-date $($array[5] + " " + $array[6] + " " + $array[7])
                        }
                        else{
                            $temp.SessionName = $array[2]
                            $temp.Id = $array[3]
                            $temp.State = $array[4]
                            $temp.IdleTime = $array[5]
                            $temp.LogonTime = get-date $($array[6] + " " + $array[7] + " " + $array[8])
                        }
                    }

                    #if specified, parse idle time to timespan
                    if($parseIdleTime){
                        $string = $temp.idletime

                        #quick function to handle minutes or hours:minutes
                        function convert-shortIdle {
                            param($string)
                            if($string -match "\:"){
                                [timespan]$string
                            }
                            else{
                                New-TimeSpan -minutes $string
                            }
                        }

                        #to the left of + is days
                        if($string -match "\+"){
                            $days = new-timespan -days ($string -split "\+")[0]
                            $hourMin = convert-shortIdle ($string -split "\+")[1]
                            $temp.idletime = $days + $hourMin
                        }
                        #. means less than a minute
                        elseif($string -like "." -or $string -like "none"){
                            $temp.idletime = [timespan]"0:00"
                        }
                        #hours and minutes
                        else{
                            $temp.idletime = convert-shortIdle $string
                        }
                    }

                    #Output the result
                    $temp
                }
            }
        }
        else{ Write-warning "$computer`: No sessions found" }
    }
}