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.
This looks great and seems to work with Get-CimInstance too though the output is a little different.
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.
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
But now that I found Win32_LoggedOnUser it seams wrong to do it this way. Lets look at the new idea instead.
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) }
 This seams right đ I'll save the output in $ActiveUsers variable and do the same for CIM and Query.
CIM
Lets try with CIM.
This looks way more structured.
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. Â
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.
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.
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