62

I am searching for a simple command to see logged on users on server. I know this one :

Get-WmiObject -Class win32_computersystem

but this will not provide me the info I need. It returns : domain Manufactureer Model Name (Machine name) PrimaryOwnerName TotalPhysicalMemory

I run Powershell 3.0 on a Windows 2012 server.

Also

Get-WmiObject Win32_LoggedOnUser -ComputerName $Computer | Select Antecedent -Unique

gives me not the exact answers I need. I would love to see as well the idle time, or if they are active or away.

RayofCommand
  • 4,054
  • 17
  • 56
  • 92

10 Answers10

136

In search of this same solution, I found what I needed under a different question in stackoverflow: Powershell-log-off-remote-session. The below one line will return a list of logged on users.

query user /server:$SERVER
Community
  • 1
  • 1
Quickmute
  • 1,583
  • 1
  • 11
  • 7
  • 5
    This is a much better solution than the accepted answer. The accepted answer gives you all the users that are running services and and applications, not active RDP sessions or console sessions. – TheCodeMonk Feb 10 '16 at 14:21
  • 3
    I would go ahead and agree that it is a better answer to the question, in that the OP wanted a `simple command`, and so I've upvoted it, but I was looking for more functionality and Google sent me here, so for my purposes, mjolinor's answer is more useful, so I've upvoted that one... hmmmm – Code Jockey Mar 08 '16 at 18:23
  • 1
    Brilliant and exactly what I was looking for! – SamAndrew81 Jul 14 '17 at 14:39
  • 1
    doesn't give you the machine name? – user2305193 Oct 19 '17 at 13:50
  • This give me: `query: The term 'query' is not recognized...`. So what am I missing here? – not2qubit May 01 '20 at 13:01
  • What version of Windows and Powershell are you running? Can you run "get-command query" and verify that it is located at c:\WINDOWS\system32\query.exe. – Quickmute Jun 12 '20 at 15:48
  • 1
    This method does not list users, that are logged on via SSH (built-in OpenSSH). – stackprotector Mar 29 '21 at 12:47
  • same Q as @user2305193 ; any way to see the client-name of the user who RDP (visible via Task Manager Users) – Tilo Sep 28 '21 at 17:08
  • for client-name see also other questions https://stackoverflow.com/q/48456277/1747983 ; seems PSTerminalServices has this option (first import module) – Tilo Sep 28 '21 at 17:14
31

Since we're in the PowerShell area, it's extra useful if we can return a proper PowerShell object ...

I personally like this method of parsing, for the terseness:

((quser) -replace '^>', '') -replace '\s{2,}', ',' | ConvertFrom-Csv

Note: this doesn't account for disconnected ("disc") users, but works well if you just want to get a quick list of users and don't care about the rest of the information. I just wanted a list and didn't care if they were currently disconnected.

If you do care about the rest of the data it's just a little more complex:

(((quser) -replace '^>', '') -replace '\s{2,}', ',').Trim() | ForEach-Object {
    if ($_.Split(',').Count -eq 5) {
        Write-Output ($_ -replace '(^[^,]+)', '$1,')
    } else {
        Write-Output $_
    }
} | ConvertFrom-Csv

I take it a step farther and give you a very clean object on my blog.

I ended up making this into a module.

VertigoRay
  • 5,935
  • 6
  • 39
  • 48
  • 3
    This needs more upvotes. Spent too long trying to get it to format the way I wanted. – KawaGreen Mar 15 '18 at 10:00
  • 2
    Indeed, this is BRILLIANT! – Rakha Jun 13 '18 at 11:50
  • `quser` is not recognized under PS. What's missing? – not2qubit May 01 '20 at 13:02
  • @not2qubit, are you running on Windows? If so, check your path. `quser` is an executable: `C:\WINDOWS\system32\quser.exe`. You can also try `query user`, it returns the exact same output. `query` is also an executable: `C:\WINDOWS\system32\query.exe` – VertigoRay May 09 '20 at 05:19
  • @VertigoRay I don't have those *.exe files on my system... (Win8.1). – not2qubit May 09 '20 at 19:15
  • @not2qubit Home Edition? If so, it's likely the issue or your installation is corrupt or incomplete. For help on that: https://answers.microsoft.com/en-us/windows/forum/all/quserexe-missing-from-windowssystem32-directory/1f885056-36fc-477f-997a-738e895fcd53 – VertigoRay May 15 '20 at 04:04
  • @VertigoRay No, that's not right. I don't have this command on *Win10 (Home)* either. So I think it might be a server specific command. – not2qubit May 16 '20 at 18:38
  • 1
    @not2qubit Like I said, it's not available on the Home SKUs. I have it available on Pro and Enterprise SKUs of Win10, and my brother confirmed that `query user` and `quser` are not available on Win10 1909 Home. Since you cannot RDP into a Home SKU, it's a bit irrelevant to run this command locally. – VertigoRay May 17 '20 at 03:54
  • @VertigoRay Thanks, but that was not clear at all. Also, there are several other and better ways than RDP, so definitely not *irrelevant*. – not2qubit May 18 '20 at 17:59
  • @not2qubit Apologies for not being clear. I'm curious what your other/better ways are. Tools like VNC/Teamviewer/LogMeIn connect to the console session, and `quser` isn't the right tool for seeing if someone is connected via those services. – VertigoRay May 20 '20 at 20:31
  • @not2qubit If you were not running Windows Home edition, you might be running PowerShell 32-bit on a 64-bit OS and unable to find `quser`. In which case, you would need to use `SysNative` to get to the real `System32` folder: `C:\Windows\SysNative\quser.exe` – VertigoRay Jun 11 '20 at 21:22
  • The correct answer here if you want to get an ps object that can be manipulated later – IonutS Mar 24 '23 at 18:31
21

There's no "simple command" to do that. You can write a function, or take your choice of several that are available online in various code repositories. I use this:

function get-loggedonuser ($computername){

#mjolinor 3/17/10

$regexa = '.+Domain="(.+)",Name="(.+)"$'
$regexd = '.+LogonId="(\d+)"$'

$logontype = @{
"0"="Local System"
"2"="Interactive" #(Local logon)
"3"="Network" # (Remote logon)
"4"="Batch" # (Scheduled task)
"5"="Service" # (Service account logon)
"7"="Unlock" #(Screen saver)
"8"="NetworkCleartext" # (Cleartext network logon)
"9"="NewCredentials" #(RunAs using alternate credentials)
"10"="RemoteInteractive" #(RDP\TS\RemoteAssistance)
"11"="CachedInteractive" #(Local w\cached credentials)
}

$logon_sessions = @(gwmi win32_logonsession -ComputerName $computername)
$logon_users = @(gwmi win32_loggedonuser -ComputerName $computername)

$session_user = @{}

$logon_users |% {
$_.antecedent -match $regexa > $nul
$username = $matches[1] + "\" + $matches[2]
$_.dependent -match $regexd > $nul
$session = $matches[1]
$session_user[$session] += $username
}


$logon_sessions |%{
$starttime = [management.managementdatetimeconverter]::todatetime($_.starttime)

$loggedonuser = New-Object -TypeName psobject
$loggedonuser | Add-Member -MemberType NoteProperty -Name "Session" -Value $_.logonid
$loggedonuser | Add-Member -MemberType NoteProperty -Name "User" -Value $session_user[$_.logonid]
$loggedonuser | Add-Member -MemberType NoteProperty -Name "Type" -Value $logontype[$_.logontype.tostring()]
$loggedonuser | Add-Member -MemberType NoteProperty -Name "Auth" -Value $_.authenticationpackage
$loggedonuser | Add-Member -MemberType NoteProperty -Name "StartTime" -Value $starttime

$loggedonuser
}

}
mjolinor
  • 66,130
  • 7
  • 114
  • 135
  • 1
    I added the missing } in the end and changed the computername variable and set it to my hostname. But still i can't make this run. somehow it tries to use my machinename as cmdlet. If i explicitly Set-Variable -Name computername -Value “mymachinename” i get and RPC server is unavailable. I am still newbie, what do i miss? – RayofCommand Apr 22 '14 at 13:08
  • Sorry about the missing bracket (copy/paste fail). Fixed. Not sure why it's not working. After the function is loaded, you should be able to just run" get-loggedonuser . For the local machine, you can run: get-loggedonuser localhost – mjolinor Apr 22 '14 at 13:29
  • still wasn't able to make it work. even if i replace the $computername directly using my machinename or localhost i didnt get any result. Is there a way to see the info of how long the user is logged in? – RayofCommand Apr 24 '14 at 14:38
  • If you look at that function, you'll see it also returns the starttime of the session, so you can calculate from that. But that doesn't help if it won't work on your machine, and I can't reproduce the error your getting to debug it. – mjolinor Apr 24 '14 at 14:51
  • hmm i think you missed 40% of your own script. i just took the one from you posted on technet page. this as well has the session start time – RayofCommand Apr 25 '14 at 13:31
  • You're right. That's what I get for copying code out of my profile. Sorry about that. I updated the answer. Does that code to what you need? – mjolinor Apr 25 '14 at 13:37
  • yep. thanks. Unfortunately I am not now able to select only the RemoteInteractive Users, can you help me out :)? – RayofCommand Apr 28 '14 at 08:53
  • Simple Where-Object filter. Get-LoggedOnuser | Where {$_.LogonType -eq 'RemoteInteractive'} – mjolinor Apr 28 '14 at 12:05
  • thanks. But we need to use Type instead of LogonType. now it works as expected – RayofCommand Apr 28 '14 at 12:50
  • Is there a way to just pull the usernames and throw them on one line? I don't care much to know the session and start time, etc for certain things. – Aaron Sep 01 '15 at 19:32
  • Fun fact: linux equivalent: the 'w' command. of course, with Winblows, nothing has to be simple if it can be complicated. – Michahell Oct 23 '15 at 11:53
  • 5
    Well, that's 30 seconds of my life I'll never get back. – mjolinor Oct 23 '15 at 21:30
  • 1
    @mjolinor Hi Your script is working fine but on running the same it shows only my name i.e. {Session : 39648199 Type : Local System Auth : StartTime : 1/1/1601 5:30:00 AM}. I want to see how many users are currently logged in and are active (say in last 30 minutes), how can I do that ? Could you please help me with that ? – Kumar Sambhav Pandey Jun 23 '16 at 07:03
  • This listed 18 sessions, including some users as Interactive that aren't even logged in. Also, the sessions were in the millions, when I was expecting single-digits... – Carl Walsh Jan 26 '19 at 17:16
12

Maybe you can do something with

get-process -includeusername
js2010
  • 23,033
  • 6
  • 64
  • 66
  • 6
    Like the lateral thinking. I know it isn't the "correct" answer, but it does present a potential workaround solution. `Get-Process -IncludeUserName | Select-Object -Unique -Property UserName` – Jaans Jun 05 '17 at 01:46
  • It's clever (note that elevation is required for `-IncludeUsername`), but it isn't the same as the set of users that have (active or disconnected) _window stations_ (desktop sessions); for instance, processes run with a different user identity in the window station of a given user or via a service show up separately, e.g. with `runas.exe` or PowerShell remoting sessions. – mklement0 May 01 '23 at 19:40
4

If you want to find interactively logged on users, I found a great tip here :https://p0w3rsh3ll.wordpress.com/2012/02/03/get-logged-on-users/ (Win32_ComputerSystem did not help me)

$explorerprocesses = @(Get-WmiObject -Query "Select * FROM Win32_Process WHERE Name='explorer.exe'" -ErrorAction SilentlyContinue)
If ($explorerprocesses.Count -eq 0)
{
    "No explorer process found / Nobody interactively logged on"
}
Else
{
    ForEach ($i in $explorerprocesses)
    {
        $Username = $i.GetOwner().User
        $Domain = $i.GetOwner().Domain
        Write-Host "$Domain\$Username logged on since: $($i.ConvertToDateTime($i.CreationDate))"
    }
}
DKH
  • 452
  • 7
  • 15
  • 2
    Unless the user is using a custom shell other than explorer.exe https://learn.microsoft.com/en-us/windows-hardware/customize/enterprise/shell-launcher – as9876 Aug 16 '17 at 18:05
  • 2
    Pulling the username off a process, other than your own, requires admin rights. – VertigoRay May 09 '20 at 05:15
2

Here is my Approach based on DarKalimHero's Suggestion by selecting only on Explorer.exe processes

Function Get-RdpSessions 
{
    param(
        [string]$computername 
    )

    $processinfo = Get-WmiObject -Query "select * from win32_process where name='explorer.exe'" -ComputerName $computername

    $processinfo | ForEach-Object { $_.GetOwner().User } | Sort-Object -Unique | ForEach-Object { New-Object psobject -Property @{Computer=$computername;LoggedOn=$_} } | Select-Object Computer,LoggedOn
}
Matthias Güntert
  • 4,013
  • 6
  • 41
  • 89
1

Another solution, also based on query user, but can handle variations in culture (as far as I can tell) and produces strongly-typed results (i.e. TimeSpan and DateTime values):

# Invoke "query user", it produces an output similar to this, but might be culture-dependant!
#
#  USERNAME              SESSIONNAME        ID  STATE   IDLE TIME  LOGON TIME
# >jantje                rdp-tcp#55          2  Active          .  3/29/2021 4:24 PM
#  pietje                                    4  Disc     49+01:01  4/14/2021 9:26 AM
$result = (&query 'user' | Out-String -Stream)

# Take the header text and insert a '|' before the start of every HEADER - although defined as inserting a bar after 
# every 2 or more spaces, or after the space at the start.
$fencedHeader = $result[0] -replace '(^\s|\s{2,})', '$1|'

# Now get the positions of all bars.
$fenceIndexes = ($fencedHeader | Select-String '\|' -AllMatches).Matches.Index

$timeSpanFormats = [string[]]@("d\+hh\:mm", "h\:mm", "m")
$entries = foreach($line in $result | Select-Object -Skip 1)
{
    # Insert bars on the same positions, and then split the line into separate parts using these bars.
    $fenceIndexes | ForEach-Object { $line = $line.Insert($_, "|") }
    $parts = $line -split '\|' | ForEach-Object { $_.Trim() }

    # Parse each part as a strongly typed value, using the UI Culture if needed.
    [PSCustomObject] @{
        IsCurrent   = ($parts[0] -eq '>');
        Username    = $parts[1];
        SessionName = $parts[2];
        Id          = [int]($parts[3]);
        State       = $parts[4];
        IdleTime    = $(if($parts[5] -ne '.') { [TimeSpan]::ParseExact($parts[5], $timeSpanFormats, [CultureInfo]::CurrentUICulture) } else { [TimeSpan]::Zero });
        LogonTime   = [DateTime]::ParseExact($parts[6], "g", [CultureInfo]::CurrentUICulture);
    }
}

# Yields the following result:
#
# IsCurrent Username SessionName Id State  IdleTime    LogonTime           
# --------- -------- ----------- -- -----  --------    ---------           
#      True jantje   rdp-tcp#32   2 Active 00:00:00    3/29/2021 4:24:00 PM
#     False pietje                4 Disc   48.11:06:00 4/14/2021 9:26:00 AM
$entries | Format-Table -AutoSize
Leon Bouquiet
  • 4,159
  • 3
  • 25
  • 36
  • On my system - no idle time is designated as "none" and your script breaks. PS C:\WINDOWS\system32> query user USERNAME SESSIONNAME ID STATE **IDLE TIME** LOGON TIME >some.user console 1 Active **none** 8/17/2021 5:32 AM Sorry - comment formatting won't show it as correctly formatted :( – dragonspeed Aug 18 '21 at 14:39
  • 1
    Ok, off the top of my head, you could try changing `if($parts[5] -ne '.')` into `if(@('.', 'none') -notcontains $parts[5])` and see if that works? – Leon Bouquiet Aug 19 '21 at 11:27
  • Changed it to: $(if($parts[5] -ne '.' -and $parts[5] -ne 'none') - All good - works for remote (although there is no "current" user in remote checking – dragonspeed Aug 19 '21 at 15:04
1

Team!

I have pretty nice solution to get local session as [PSObject].

Function Get-LocalSession {
<#
    .DESCRIPTION
        Get local session. Pasre output of command - 'query session'.
#>
    [OutputType([PSObject[]])]
    [CmdletBinding()]
    Param(
        
    )
    try {
        #region functions
        #endregion
        $Result = @()
        $Output = . query.exe 'session' | select-object -skip 1

        #use regex to parse
        $pattern = '^(?<This>.)(?<SessionName>[^\s]*)\s*(?<UserName>[a-z]\w*)?\s*(?<Id>[0-9]*)\s*(?<State>\w*)\s*((?<Type>\w*)\s*)?(?<Device>\w*)?'

        foreach ( $line in $output ){
            $match = [regex]::Matches( $line, $pattern )
            if ( $match ){
                $PSO = [PSCustomObject]@{
                    This        = $match[0].groups['This'].Value
                    SessionName = $match[0].groups['SessionName'].Value
                    UserName    = $match[0].groups['UserName'].Value
                    Id          = $match[0].groups['Id'].Value
                    State       = $match[0].groups['State'].Value
                    Type        = $match[0].groups['Type'].Value
                    Device      = $match[0].groups['Device'].Value
                }

                $Result += $PSO
            }
            Else {
                write-host "Unable to process line [$line] in function [Get-LocalSession]!"
            }
        }  
    }
    catch {
        #Get-ErrorReporting -Trap $PSItem
        write-host $PSItem
    }

    return $Result
}

#Run it

$SessionObject = Get-LocalSession
$SessionObject | format-table -autosize -property *


1

I have edited mjolinor script to remove duplicate records, and dummy account names such as system, network services,...etc
If you want to get all users

function get-loggedonuser ($computername){

$regexa = '.+Domain="(.+)",Name="(.+)"$'
$regexd = '.+LogonId="(\d+)"$'

$logontype = @{
"0"="Local System"
"2"="Interactive" #(Local logon)
"3"="Network" # (Remote logon)
"4"="Batch" # (Scheduled task)
"5"="Service" # (Service account logon)
"7"="Unlock" #(Screen saver)
"8"="NetworkCleartext" # (Cleartext network logon)
"9"="NewCredentials" #(RunAs using alternate credentials)
"10"="RemoteInteractive" #(RDP\TS\RemoteAssistance)
"11"="CachedInteractive" #(Local w\cached credentials)
}

$logon_sessions = @(gwmi win32_logonsession -ComputerName $computername)
$logon_users = @(gwmi win32_loggedonuser -ComputerName $computername)

$session_user = @{}

$logon_users |% {
$_.antecedent -match $regexa > $nul
$username = $matches[1] + "\" + $matches[2]
$_.dependent -match $regexd > $nul
$session = $matches[1]
$session_user[$session] += $username
}


$logon_sessions |%{
$starttime = [management.managementdatetimeconverter]::todatetime($_.starttime)
if ($session_user[$_.logonid] -notin $loggedonuser.user -and $session_user[$_.logonid] -notlike "*$*"){
$loggedonuser = New-Object -TypeName psobject
$loggedonuser | Add-Member -MemberType NoteProperty -Name "Session" -Value $_.logonid
$loggedonuser | Add-Member -MemberType NoteProperty -Name "User" -Value $session_user[$_.logonid]
$loggedonuser | Add-Member -MemberType NoteProperty -Name "Type" -Value $logontype[$_.logontype.tostring()]
$loggedonuser | Add-Member -MemberType NoteProperty -Name "Auth" -Value $_.authenticationpackage
$loggedonuser | Add-Member -MemberType NoteProperty -Name "StartTime" -Value $starttime

$loggedonuser
}
}

}

if you want to have only domain users

function get-loggedonuser ($computername){

    $HST= hostname
    $regexa = '.+Domain="(.+)",Name="(.+)"$'
    $regexd = '.+LogonId="(\d+)"$'
    
    $logontype = @{
    "0"="Local System"
    "2"="Interactive" #(Local logon)
    "3"="Network" # (Remote logon)
    "4"="Batch" # (Scheduled task)
    "5"="Service" # (Service account logon)
    "7"="Unlock" #(Screen saver)
    "8"="NetworkCleartext" # (Cleartext network logon)
    "9"="NewCredentials" #(RunAs using alternate credentials)
    "10"="RemoteInteractive" #(RDP\TS\RemoteAssistance)
    "11"="CachedInteractive" #(Local w\cached credentials)
    }
    
    $logon_sessions = @(Get-WmiObject win32_logonsession -ComputerName $computername)
    $logon_users = @(Get-WmiObject win32_loggedonuser -ComputerName $computername)
    
    $session_user = @{}
    
    $logon_users |ForEach-Object {
    $_.antecedent -match $regexa > $nul
    $username = $matches[1] + "\" + $matches[2]
    $_.dependent -match $regexd > $nul
    $session = $matches[1]
    $session_user[$session] += $username
    }
    
    
    $logon_sessions |ForEach-Object{
    if ($session_user[$_.logonid] -notin $loggedonuser.user -and $session_user[$_.logonid] -notlike "*$*" -and $session_user[$_.logonid] -notlike "*$HST*"){
    $loggedonuser = New-Object -TypeName psobject
    $loggedonuser | Add-Member -MemberType NoteProperty -Name "Session" -Value $_.logonid
    $loggedonuser | Add-Member -MemberType NoteProperty -Name "User" -Value $session_user[$_.logonid]
    $loggedonuser | Add-Member -MemberType NoteProperty -Name "Type" -Value $logontype[$_.logontype.tostring()]
    $loggedonuser | Add-Member -MemberType NoteProperty -Name "Auth" -Value $_.authenticationpackage
    $loggedonuser | Add-Member -MemberType NoteProperty -Name "StartTime" -Value $starttime
    
    $loggedonuser
    }
    }
    
    }
Ahmad Abuhasna
  • 261
  • 4
  • 15
0

This is what I just figured out and works out great!

Get-Process -IncludeUserName | Select-Object -Unique | Where-Object {$_.UserName -notlike 'NT AUTHORITY\SYSTEM' -and $_.UserName -notlike 'NT AUTHORITY\NETWORK SERVICE' -and $_.UserName -notlike 'NT AUTHORITY\LOCAL SERVICE'} | Format-Table -Wrap -AutoSize
stackprotector
  • 10,498
  • 4
  • 35
  • 64
itmadez
  • 87
  • 1
  • 3