Public/Imported/Get-UserSession.ps1

#Author: Warren Frame
# https://github.com/RamblingCookieMonster/PowerShell/blob/master/Get-UserSession.ps1
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) | ForEach-Object {

                #Start to build the custom object
                $temp = "" | Select-Object 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" }
    }
}