List users logged on to your machines

Password policies are the best 😀 Sometimes they lead to account logouts when someone forgets to logout of a session somewhere on the network though. It might be the TS session they use once a quarter for reporting or maybe you know the feeling when you RDP to a server only to find that it is locked by 2 other admins who forgot to logoff when they left. (Off cause this never happens
 we all use PowerShell
) Anyway, this had me searching for a user session somewhere on the network. The worst thing is when my own password expires. I hate when my account ends up being locked. Therefor I made it a rule to just check all servers before I change password. There are multiple ways to do this but of course I tend to go the PowerShell route. 

Research

The originally method I used is from TechNet gallery

In short: Get-WmiObject -Class Win32_process

This basically finds all unique users running processes on the machine. This is cool because it finds everything even stuff running as a service but I'm not convinced it is the most efficient way.

Checking up with google I find a lot of creative ways to check who is logged on to your box.

peetersonline.nl/2008/11/oneliner-get-logged-on-users-with-powershell/ gave me the idea to check Win32_LoggedOnUser which seems obvious.

2015-08-28 (1)

This looks great and seems to work with Get-CimInstance too though the output is a little different.

2015-08-28

learn-powershell.net/.../Quick-hit-find-currently-logged-on-users/ took a little more old-school approach which I kind of like because it's a little rough and forces me to play with my template based parsing.

 2015-08-28 (2)

I'm not really sure which method is faster so why not try implementing all 3 in a module and test it out.

Sketching

It's always a good idea to begin by making a sketch of what you're trying to accomplish.

Pseudo code:
Get-ActiveUser -ComputerName [] -Method [Cim,Wmi,Query]

Wanted output:
Username                          ComputerName
--------                          ------------
TestUser1                         Svr3
TestUser3                         Svr3
DonaldDuck                        Client2

Now I have all the information I need to set up the GitHub repository.

github.com/mrhvid/Get-ActiveUser

Code

First of all the parameters I'm interested in are ComputerName and Method.

    Param
    (
        # Computer name, IP, Hostname
        [Parameter(Mandatory=$true,
                   ValueFromPipelineByPropertyName=$true,
                   Position=0)]
        [String[]]
        $ComputerName,

        # Choose method, WMI, CIM or Query
        [Parameter(Mandatory=$true,
            ValueFromPipelineByPropertyName=$true,
            Position=1)]
        [ValidateSet('WMI','CIM','Query')]
        [String]
        $Method
    )

I already have 3 possible Methods in mind so I set ValidateSet with the 3 possibilities. Then I don't have to worry about that input later.

    Process
    {
        switch ($Method)
        {
            'WMI' 
            {

            }
            'CIM' 
            {
                    
            }
            'Query' 
            {
                    
            }
        }
    }

In the Process part of my function I simply use a switch for the 3 different methods I allowed in the Parameter.

Now it's basic fill-in-the-blanks.

WMI

My old solution is simpel and works fine.

$WMI = Get-WmiObject -Class Win32_Process -ComputerName $ComputerName -ErrorAction Stop
$ProcessUsers = $WMI.getowner().user | Select-Object  -Unique

2015-08-28 (3)

But now that I found Win32_LoggedOnUser it seams wrong to do it this way. Lets look at the new idea instead.

2015-08-28 (4)

gwmi-Wmi32_LoggedOnUser_gm

This is all the right data but it seems to be in a string format so I'll have to do a little manipulation. This can be done in a million ways.

function Get-MyLoggedOnUsers
 {
  param([string]$Computer)
  Get-WmiObject Win32_LoggedOnUser -ComputerName $Computer | Select Antecedent -Unique | %{“{0}{1}” -f $_.Antecedent.ToString().Split(‘”‘)[1], $_.Antecedent.ToString().Split(‘”‘)[3]}
 }

Peter's aforementioned one-liner didn't seem very reader-friendly to me, which is ok for a one-liner, but I would like it to be a little more readable if possible.

$WMI = (Get-WmiObject Win32_LoggedOnUser).Antecedent
$ActiveUsers = @()
foreach($User in $WMI) {
    $StartOfUsername = $User.LastIndexOf('=') + 2
    $EndOfUsername = $User.Length - $User.LastIndexOf('=') -3
    $ActiveUsers += $User.Substring($StartOfUsername,$EndOfUsername)
}

 2015-08-28 (5)

 This seams right 🙂 I'll save the output in $ActiveUsers variable and do the same for CIM and Query.

CIM

Lets try with CIM.

2015-08-28 (6)

This looks way more structured.

2015-08-28 (7)

CIM ends up being an easy to understand one-liner 😀

$ActiveUsers = (Get-CimInstance Win32_LoggedOnUser -ComputerName $ComputerName).antecedent.name | Select-Object -Unique

Query

Using the good ol' Query.exe I found the template based parsing discussed earlier very useful.

$Template = @'
 USERNAME              SESSIONNAME        ID  STATE   IDLE TIME  LOGON TIME
>{USER*:jonas}                 console             1  Active    1+00:27  24-08-2015 22:22
 {USER*:test}                                      2  Disc      1+00:27  25-08-2015 08:26
'@

$Query = query.exe user
$ActiveUsers = $Query | ConvertFrom-String -TemplateContent $Template | Select-Object -ExpandProperty User

Output

Now I just need to format and output the users in a nice way. I want clean objects with ComputerName and UserName.

# Create nice output format
$UsersComputersToOutput = @()
foreach($User in $ActiveUsers) {
        $UsersComputersToOutput += New-Object psobject -Property @{
                                        ComputerName=$ComputerName;
                                        UserName=$User
                                    }
        }   
}

# output data
$UsersComputersToOutput

 

Testing

Now I have a problem. I can't test this as I don't have a bunch of test serveres at my disposal. All my testing has been done against my own Windows 10 box. It's seems that query is a lot faster running locally but WMI/CIM might give a more complete view of what services are running.  

get-activeuser_wmi_highlight

I have a bunch of standard service accounts running that might be nice to remove from the output. Also for this to be useful we will want to run it against a lot of machines.

get-activeuser_query

Combining Get-ActiveUser with Start-Multithread from last weeks post seems to be working as intended.

Start-Multithread -Script {
        param($C) 
        Get-ActiveUser -ComputerName $C -Method Query
    } -ComputerName ::1,Localhost | Out-GridView

 Piping the above to Out-GridView is proberbly my personal favorite way of accomplishing something truly useful.

get-activeuser_query_out-gridview

Now we have all the data in a nice searchable way and it's really easy to check if your user is logged in on some random machine. It also an easy way to check for rouge users on your network.

 

Publishing and feedback

The code is published on PowerShellGallery.

Please help me out by testing it for me. I would love to know if this works in the real world 🙂

# To install Get-ActiveUser
Install-Module Get-ActiveUser

#To install Start-Multithread
Install-Module Start-Multithread

 This should work when you have WMF 5 + installed and on Windows 10 out of the box. 

As this is my third blogpost ever I would love some feedback. Is there something I could do better or in a better format? Have you used this and for what? Please let me know in the comments 🙂

 

Contact me

Twitter @mrhvid
Web Jonas.SommerNielsen.dk