Powershell script to see currently logged in users (domain and machine) + status (active, idle, away)
Asked Answered
G

10

67

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.

Gliwice answered 22/4, 2014 at 12:32 Comment(0)
A
145

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
Arlo answered 18/6, 2015 at 14:2 Comment(6)
doesn't give you the machine name?Carmody
This give me: query: The term 'query' is not recognized.... So what am I missing here?Knipe
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.Arlo
This method does not list users, that are logged on via SSH (built-in OpenSSH).Revest
same Q as @Carmody ; any way to see the client-name of the user who RDP (visible via Task Manager Users)Kuykendall
for client-name see also other questions https://mcmap.net/q/104757/-cmd-command-to-display-connected-user-and-client-names/1747983 ; seems PSTerminalServices has this option (first import module)Kuykendall
T
34

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.

Timothy answered 1/3, 2018 at 4:44 Comment(10)
quser is not recognized under PS. What's missing?Knipe
@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.exeTimothy
@Timothy I don't have those *.exe files on my system... (Win8.1).Knipe
@Knipe Home Edition? If so, it's likely the issue or your installation is corrupt or incomplete. For help on that: answers.microsoft.com/en-us/windows/forum/all/…Timothy
@Timothy 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.Knipe
@Knipe 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.Timothy
@Timothy Thanks, but that was not clear at all. Also, there are several other and better ways than RDP, so definitely not irrelevant.Knipe
@Knipe 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.Timothy
@Knipe 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.exeTimothy
The correct answer here if you want to get an ps object that can be manipulated laterFireman
U
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
}

}
Upholstery answered 22/4, 2014 at 12:47 Comment(14)
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?Gliwice
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 <computername>. For the local machine, you can run: get-loggedonuser localhostUpholstery
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?Gliwice
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.Upholstery
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 timeGliwice
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?Upholstery
yep. thanks. Unfortunately I am not now able to select only the RemoteInteractive Users, can you help me out :)?Gliwice
Simple Where-Object filter. Get-LoggedOnuser <machine> | Where {$_.LogonType -eq 'RemoteInteractive'}Upholstery
thanks. But we need to use Type instead of LogonType. now it works as expectedGliwice
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.Currin
Fun fact: linux equivalent: the 'w' command. of course, with Winblows, nothing has to be simple if it can be complicated.Carinthia
Well, that's 30 seconds of my life I'll never get back.Upholstery
@Upholstery 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 ?Berke
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...Single
D
14

Maybe you can do something with

get-process -includeusername
Doralia answered 4/6, 2017 at 15:27 Comment(2)
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 UserNameOtway
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.Naxos
P
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))"
    }
}
Pironi answered 22/6, 2017 at 9:55 Comment(2)
Unless the user is using a custom shell other than explorer.exe learn.microsoft.com/en-us/windows-hardware/customize/enterprise/…Phenomenalism
Pulling the username off a process, other than your own, requires admin rights.Timothy
M
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
}
Micheal answered 7/3, 2018 at 9:14 Comment(1)
Which would not work, if user's shell is NOT explorer.exe (sorry for stating the obvious).Ie
G
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
Guelph answered 2/6, 2021 at 8:58 Comment(3)
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 :(Encephalomyelitis
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?Guelph
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 checkingEncephalomyelitis
B
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 *


Billi answered 15/10, 2021 at 9:38 Comment(0)
K
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
    }
    }
    
    }
Kakalina answered 26/5, 2022 at 12:0 Comment(0)
C
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
Carpo answered 5/3, 2021 at 16:19 Comment(1)
Note that this requires Admin rights in order to runSeften

© 2022 - 2024 — McMap. All rights reserved.