Recently I was asked how to show all logged on users. So I had the idea to make a function out of it. And now I’ll share this function to the community. Who logged on to which computer and when? That is the question for this article. Actually, the main question is: Who is currently logged in?
Prerequisite for this article is a tidy and clean Active Directory environment. Why? Because it takes about 4 seconds to query a computer’s logged on user. If there are still computer accounts in the database which are no longer used, it will take needless longer. Especially if you try to query the entire domain. Let’s dive in.
The Goal
The target is a function that shows all logged on users by computer name or OU. It’s also possible to query all computers in the entire domain. Note that this could take some time. In my test environment it took about 4 seconds per computer on average.
In Action
Computer
Getting the logged on user of client01. It’s Petra. She logged in at 06:41 PM.
Get-UserLogon -Computer client01
OU
Let’s say we have an OU Workstations. If you want to retrieve all logged on users of all computers in this OU run
Get-UserLogon -OU 'ou=Workstations,dc=sid-500,dc=com'
The second example shows the current logged on user on all Domain Controllers.
Ok I have to admit that my screen is a little boring. I’m in in a small Active Directory testing environment. 😉
Entire Domain
Which brings me to the last parameter. It’s a switch parameter. Don’t provide a value.
Get-UserLogon -All
Ok, that’s it. I think this is quite helpful for many of us.
The Function Get-UserLogon
And here is the function itself:
function Get-UserLogon { [CmdletBinding()] param ( [Parameter ()] [String]$Computer, [Parameter ()] [String]$OU, [Parameter ()] [Switch]$All ) $ErrorActionPreference="SilentlyContinue" $result=@() If ($Computer) { Invoke-Command -ComputerName $Computer -ScriptBlock {quser} | Select-Object -Skip 1 | Foreach-Object { $b=$_.trim() -replace '\s+',' ' -replace '>','' -split '\s' If ($b[2] -like 'Disc*') { $array= ([ordered]@{ 'User' = $b[0] 'Computer' = $Computer 'Date' = $b[4] 'Time' = $b[5..6] -join ' ' }) $result+=New-Object -TypeName PSCustomObject -Property $array } else { $array= ([ordered]@{ 'User' = $b[0] 'Computer' = $Computer 'Date' = $b[5] 'Time' = $b[6..7] -join ' ' }) $result+=New-Object -TypeName PSCustomObject -Property $array } } } If ($OU) { $comp=Get-ADComputer -Filter * -SearchBase "$OU" -Properties operatingsystem $count=$comp.count If ($count -gt 20) { Write-Warning "Search $count computers. This may take some time ... About 4 seconds for each computer" } foreach ($u in $comp) { Invoke-Command -ComputerName $u.Name -ScriptBlock {quser} | Select-Object -Skip 1 | ForEach-Object { $a=$_.trim() -replace '\s+',' ' -replace '>','' -split '\s' If ($a[2] -like '*Disc*') { $array= ([ordered]@{ 'User' = $a[0] 'Computer' = $u.Name 'Date' = $a[4] 'Time' = $a[5..6] -join ' ' }) $result+=New-Object -TypeName PSCustomObject -Property $array } else { $array= ([ordered]@{ 'User' = $a[0] 'Computer' = $u.Name 'Date' = $a[5] 'Time' = $a[6..7] -join ' ' }) $result+=New-Object -TypeName PSCustomObject -Property $array } } } } If ($All) { $comp=Get-ADComputer -Filter * -Properties operatingsystem $count=$comp.count If ($count -gt 20) { Write-Warning "Search $count computers. This may take some time ... About 4 seconds for each computer ..." } foreach ($u in $comp) { Invoke-Command -ComputerName $u.Name -ScriptBlock {quser} | Select-Object -Skip 1 | ForEach-Object { $a=$_.trim() -replace '\s+',' ' -replace '>','' -split '\s' If ($a[2] -like '*Disc*') { $array= ([ordered]@{ 'User' = $a[0] 'Computer' = $u.Name 'Date' = $a[4] 'Time' = $a[5..6] -join ' ' }) $result+=New-Object -TypeName PSCustomObject -Property $array } else { $array= ([ordered]@{ 'User' = $a[0] 'Computer' = $u.Name 'Date' = $a[5] 'Time' = $a[6..7] -join ' ' }) $result+=New-Object -TypeName PSCustomObject -Property $array } } } } Write-Output $result }
Important Note regarding the Operating System Language
Be aware that the function above uses the quser command that outputs plain text. There are differences between e.g. German servers and English servers. This means, that you’ll get the output shown above only on Englisch operating systems.
Make it permanent
If you like my approach open PowerShell ISE. Copy the function into your ISE session. Create a folder in C:\Program Files\Windows PowerShell\Modules and save the code as psm1 file. Make sure that your file name and folder name match.
From now on, PowerShell will load the custom module each time PowerShell is started.
Acknowledements
Thanks to Jaap Brasser (MVP) for his awesome function Get-LoggedOnUser. His function was a great help for me and it inspired me to get a step further and call all logged on users by OU or the entire domain.
His function can be found here:
https://gallery.technet.microsoft.com/scriptcenter/Get-LoggedOnUser-Gathers-7cbe93ea
Categories: PowerShell, Windows Server
What date is the date shown?
Could it be the lastlogondate From User or Computer?
Or is that the date for startup of computer?
LikeLike
It’s just the lastlogon for currently logged users. It’s not the uptime or the lastlogonDate for computer.
If you’re looking for the LastLogonDate for the computer or the Uptime for a computer, ti’s not the same query
i.e :
Get-CimInstance -ClassName Win32_OperatingSystem | Select-Object -ExpandProperty LastBootUpTime
LikeLiked by 1 person
Hi everyone,
a huge thanks to Patrick for the great implementation and publication of this script.
But I have one question:
Is it possible to display the result in a grid or csv instead of in the PS shell?
Best, Maiko
LikeLike
Hi Patrick,
Pretty good job thank you very much. It was clear, usefull and fun to read :).
In my case, when I have to use every possibility to get some data to another computer on a domain (Invoke-Command, Get-CimInstance, Get-WmiObject), I take care to be sure that WinRM is enable on all my computers. Just loose so much time beacause of that ahah
LikeLiked by 1 person
Hi Patrick, great work… I am trying to come up with the script to be able query logoff all users whos password is expired…. Any way to do it ?
thank you
LikeLike
That’s more extensive. Don’t know if there is an password expires attribute.
LikeLike
#Use WMI
try {
$Searcher = [WmiSearcher]’SELECT * FROM Win32_ComputerSystem’
$Searcher.Options.TimeOut = “0:0:35”
$ConnectionOptions = New-Object Management.ConnectionOptions
$ManagementScope = New-Object Management.ManagementScope(“\\$NAMECOMPUTER\root\cimv2”, $ConnectionOptions)
$Searcher.Scope = $ManagementScope
$User_Login = $Searcher.Get() | select username
}catch {Write-Output “$Nombre_VDI Error (terminating): $($Error[0])”}
$User_Login
#Att Mike
LikeLike
hello, getting error when running Get-UserLogon -All. Any idea what am I doing wrong? Get-UserLogon : ‘
The term ‘Get-UserLogon’ is not recognized as the name of a cmdlet, function, script file, or operable
program. Check the spelling of the name, or if a path was included, verify that the path is correct and try again.
At line:1 char:1
+ Get-UserLogon -All
+ ~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (Get-UserLogon:String) [], CommandNotFoundException
+ FullyQualifiedErrorId : CommandNotFoundException
LikeLike
Hi, follow the guide at the end of the post to implement the function correctly.
LikeLike
When I run the command it looks like it’s running (says there is XXX amount of computers in the OU which is correct) however it doesn’t output anything. I’m left with a PowerShell screen waiting for an new input.
Am I doing something incorrectly for this to happen? I know the module is loaded correctly because I’ve imported it manually (import-module “C:\Location\File.PSM1”).
Environment: Server 2016, Clients are Windows 10.
Thanks in advance.
LikeLike
Hi, the script is intended only for small environments. Maybe you run into a timeout?
LikeLike
Ok how small is small? Only have 25 workstations and about 100 users.
LikeLike
WinRM on Clients enabled?
LikeLike