Public/OS/Get-UserSession.ps1

FUNCTION Get-UserSession {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $false)]
        [string[]]$ComputerName = $env:COMPUTERNAME
    )

    $Sessions = @()

    foreach ($computer in $ComputerName) {
        Write-Verbose "Querying $computer..."

        try {
            $quserOutput = quser /server:$computer 2>$null
            if (!$quserOutput) { 
                Write-Verbose "No sessions or quser failed on $computer"
                continue 
            }

            # Skip header line
            $lines = $quserOutput | Select-Object -Skip 1

            foreach ($line in $lines) {
                # Clean > symbol for current session and collapse multiple spaces
                $cleanLine = $line -replace '^\s*>', '' -replace '\s{2,}', ' '

                # Split into tokens
                $tokens = $cleanLine.Trim() -split '\s+'

                # Handle two formats:
                # Active/connected: Username SessionName ID State IdleTime LogonTime...
                # Disconnected: Username ID State IdleTime LogonTime...
                if ($tokens.Count -ge 5) {
                    $idx = 0
                    $session = [PSCustomObject]@{
                        UserName     = $tokens[$idx++].Trim()
                        SessionName  = $null
                        ID           = $null
                        State        = $null
                        IdleTime     = $null
                        LogonTime    = $null
                    }

                    # If second token looks like session name (contains letters or #)
         
                    IF ($tokens[2] -eq "Disc") { }
                    ELSEIF ($tokens[$idx] -match 'rdp-tcp|console|#|\w') {
                        $session.SessionName = $tokens[$idx++]
                    }                    

                    $session.ID    = $tokens[$idx++]
                    $session.State = $tokens[$idx++]

                    # Idle time is next
                    $idleRaw = $tokens[$idx++]

                    # Remaining tokens are logon time (may contain spaces)
                    $session.LogonTime = ($tokens[$idx..($tokens.Count-1)]) -join ' '

                    if ($idleRaw -eq 'none' -or $idleRaw -eq '.') {
                        $session.IdleTime = $null
                    }
                    elseif ($idleRaw -match '^(\d+)$') {
                        # Just minutes
                        $session.IdleTime = [TimeSpan]::FromMinutes([int]$Matches[1])
                    }
                    elseif ($idleRaw -match '^(\d+):(\d+)$') {
                        # hours:minutes
                        $session.IdleTime = [TimeSpan]::FromHours([int]$Matches[1]) + 
                                                [TimeSpan]::FromMinutes([int]$Matches[2])
                    }
                    elseif ($idleRaw -match '^(\d+)\+(\d+):(\d+)$') {
                        # days+hours:minutes
                        $session.IdleTime = [TimeSpan]::FromDays([int]$Matches[1]) + 
                                                [TimeSpan]::FromHours([int]$Matches[2]) + 
                                                [TimeSpan]::FromMinutes([int]$Matches[3])
                    }
                    else {
                        # Fallback - try to interpret as minutes
                        if ([int]::TryParse($idleRaw, [ref]$null)) {
                            $session.IdleTime = [TimeSpan]::FromMinutes([int]$idleRaw)
                        }
                        else {
                            $session.IdleTime = $null
                            Write-Verbose "Could not parse idle time: $idleRaw for $($session.UserName)"
                        }
                    }

                    $Sessions += $session
                }
            }
        }
        catch {
            Write-Verbose "Error querying $computer : $_"
        }
    }

    RETURN $Sessions | Sort-Object -Property State, IdleTime, UserName
}