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" }
}
}