How do you find out when was the last time a particular user logged on?
(Get-QADUser username).lastLogon
looks like an obvious answer but there are a few gotchas to be aware of.
The main of them: lastLogon
attribute is actually not replicated between domain controllers so if you have more than one DC (which I am sure you do) you need to get it from all of them and get the latest of them.
Here’s the PowerShell code which does that:
Get-QADComputer -ComputerRole DomainController | foreach { (Get-QADUser -Service $_.Name -SamAccountName username).LastLogon } | Measure-Latest
Basically, we are getting a list of all DCs in the company, then prompting each of them for the user’s lastLogon time, and then picking the latest of the values (I am using my Measure-Latest function – just copy/paste if before executing this command or put in your script.)
Note that there are utilities which can do that querying and comparison for you. NetWrix guys even have a PowerShell cmdlet described here, so you can do something like:
Get-NCInactiveUsers -domain example.com -days 15
You should also keep in mind that if your users do not log off and simply lock their workstations they do not log on either – Kuma is describing here how he has a script logging off users every night to avoid this.
Another alternative is using lastLogonTimeStamp
attribute instead. This one does indeed get replicated. It was introduced in Windows 2003 (make sure your schema is 2003-level or later). But keep in mind that this one is not real-time as it is only replicated every 9-14 days.
So as long as you are looking for users who have not logged on for something bigger than 2 weeks you should be good using Shay’s script for locating inactive users:
$now=get-date $daysSinceLastLogon=60 Get-QADUser -sizeLimit 0 | where { $_.lastlogontimestamp -and (($now-$_.lastlogontimestamp).days -gt $daysSinceLastLogon) } | Format-Table Name, LastLogonTimeStamp
Finally, you can speed things up considerably by constructing an LDAP query and thus doing all the filtering on the server side:
# calculate a deadline date. (now minus 60 days) $deadline = (Get-Date).AddDays(-60).ToFileTimeUtc() #construct a ldap query $ldapQuery = '(|(!(lastLogonTimeStamp=*))(lastLogonTimeStamp<=' + $deadline + '))' #run this query Get-QADUser -Enabled -SizeLimit 0 -LdapFilter $ldapQuery
Tags: AD, AD cmdlets, Active Directory, PowerShell, one-liner, oneliner
When running the cmdlet for the last logon, i just get a blank prompt…
I pasted the measure-latest and then the code you have above (removed line breaks), and saved it as a ps1 file to run. Anything extra i need to do. (Yes i’m using the ActiveRoles mgmt shell)
Sorry to sound a little beginner, i usually don’t have any trouble with using scripts like this, but for some reason this one is just not working for me. Anything with the extended attributes of the QADUsers just gives me trouble. Any tips?!
Cheers,
Chris
Chris,
The easiest option might be to just copy/paste the function into your PowerShell profile: https://dmitrysotnikov.wordpress.com/2008/01/25/dude-wheres-my-powershell-profile/
This would make it available in all your PowerShell sessions.
If you want to keep it in a separate ps1 file, then you need to load (dot-source: put dot, then space, then full path to the ps1 file) the file in each PowerShell session before you use it, e.g.:
. c:\scripts\measure-latest.ps1
Get-QADComputer -ComputerRole DomainController | foreach {
(Get-QADUser -Service $_.Name -SamAccountName username).LastLogon.Value
} | Measure-Latest
Dmitry
Hi,
Great stuff, that is really useful for me!
By the way, in your code towards the end:
$now=get-date
$daysSinceLastLogon=60
Get-QADUser -sizeLimit 0 | where {
$_.lastlogontimestamp.value -and (($now-$_.lastlogontimestamp.value).days -gt $daysSinceLastLogon)
} Format-Table Name, LastLogonTimeStamp
it’s missing a pipeline before format-table. Just a minor thing, but thought I should point it out in case anyone new to Powershell wanted to use the code.
Thanks!
Jonathan
Very useful. I love netwrix Logon reporter.
Jonathan,
Good catch! I’ve updated the script. I have no idea how I lost the pipeline symbol, but it is back now.
Thanks a lot for reporting that!
Dmitry
What does it mean when I run this script and the values it returns for users when it comes to last logon times are hours in the future. I can run get date in my powershell window and it returns the correct time and date but it shows some users that supposedly logged on 4-5 hours from now.
Disregard my last post. As you can tell from that post there is obviously something wrong with my PC as I actually posted it at 11:07 a.m.
My guess is that the time is reported in GMT so it appears to be ahead of what you have in your timezone.
How can i get a month history instead of just some last logons for one certain user?
Get-QADComputer -ComputerRole DomainController | foreach {
(Get-QADUser -Service $_.Name -SamAccountName username).LastLogon.Value
}
Can i specify one month here somewhere instead of LastLogon?
Eero,
Unfortunately Active Directory does not store this information and only keeps the last logon date.
Depending on how granular you need to get (and your budget ;)) you could either set your script to pull the data from DCs on some kind of schedule (e.g. daily – obviously you will lose some data in that case if users log on more often than that) or use a commercial AD auditing solution such as http://www.quest.com/changeauditor-for-active-directory/
Dmitry
Dmitry, I have found this script to be unreliable. I can compare the results with a commercial application and it finds other inactive users which are enabled and have a lastlogontimestamp value older than 60 days. I was told there is a bug in get-qaduser cmdlet, would you happen to know a reliable workaround to finding inactive users?
Thanks!
Tony,
What I would do is try to troubleshoot these few accounts which don’t get included – there should be something different in them. Let’s use the AD PowerShell forum at PowerGUI.org to do this.
Dmitry
Cool Dmitry, I already started one under RequestScript! Cya there!
http://powergui.org/thread.jspa?threadID=8218&tstart=0
Dmitry,
May a suggest a more efficient way for the second script? In fact, you fetch all users from ActiveDirectoy (huge!) and then you filter.
I think it’s better to let ActiveDirectoy to filter by its own doing something such as:
1# $daysSinceLastLogon=60
2# $d=(get-date).AddDays(-$daysSinceLastLogon).ToFiletimeUTC()
3# Get-QADUser -sizelimit 0 -ldapFilter “(lastLogonTimeStamp<=$d)"
The only difference should be borderline users since sour script rounds the dates difference (days method returns an integer).
Now, let’s look at performance:
1# (Measure-Command {$now=get-date; $daysSinceLastLogon=60; Get-QADUser -sizeLimit 0 | where {$_.lastlogontimestamp.value -and (($now-$_.lastlogontimestamp.value).days -gt $daysSinceLastLogon)}}).TotalSeconds
72.3227027
2# (Measure-Command {$daysSinceLastLogon=60; $d=(get-date).AddDays(-$daysSinceLastLogon).ToFiletimeUTC(); Get-QADUser -sizelimit 0 -ldapFilter "(lastLogonTimeStamp<=$d)"}).TotalSeconds
5.8164012
Not bad, isn’t it?
Regards,
Rafael
Rafael,
Yes, using ldapfilter and thus filtering on the server site is a MUCH better way to go. Thanks for the excellent point.
Dmitry
Thanks guys – this is a very helpful post.
In my case, we expire accounts after 180 days so I need to show an ExpirationDate for each account so the person who is reviewing the accounts knows which ones are going to expire soon. Here’s how I did it:
# return accounts in the same OU as the user that will expire in 7 days
$maxAccountAgeDays = 180
$daysSinceLastLogon = $maxAccountAgeDays – 7
$d=(get-date).AddDays(-$daysSinceLastLogon).ToFiletimeUTC()
$ldapFilter = “(lastLogonTimeStamp<=$d)”
$searchRoot = (get-qadUser $env:UserName).ParentContainerDN
# Get the users
Get-qadUser -ldapFilter $ldapFilter -SizeLimit 0 -SearchScope Subtree -SearchRoot $searchRoot -ErrorAction SilentlyContinue |
Add-Member -MemberType ScriptProperty -Name ExpirationDate -PassThru -Value {
[datetime]::Parse($this.LastLogonTimeStamp).AddDays(180).ToLongDateString()
} | Format-Table Name, LastLogonTimeStamp, ExpirationDate
Wow, I posting my questions here very poorly. Now that I am at the right post:
How can you feed the “Measure-Latest” value of the latest logon query into a csv with the rest of other say, get-qadgroupmember information (ie, members of group X logonname, pwdlastset, ‘latest logon value’)?
Thanks!
Hi – thats great that we can get a users last logon, but can you do the same to get a computer last logon to the domain keeping in mind we have more than one DC so it would have to query them to, thanks in advance
Mike,
See if this helps: https://dmitrysotnikov.wordpress.com/2007/09/07/locate-obsolete-computer-records-in-ad/
Dmitry
Jim,
Please use the AD PowerShell forums for questions like that: http://powergui.org/forum.jspa?forumID=173
Of the top of my head this is what you would probably want to do:
Get-QADGroupMember -Name mygroup | foreach {
$user = $_
Get-QADComputer -ComputerRole DomainController | foreach {
$latestlogon = (Get-QADUser -Service $_.Name -SamAccountName $user.SamAccountName).LastLogon.Value
} | Measure-Latest
$user | Add-Member -MemberType NoteProperty -Name ‘LatestLogon’ -Value $latestlogon -PassThru
} | Export-CSV
This is going to be a pretty slow script. You can somewhat optimize it
We have a few weeks off for the holiday and I would like to get a list of user accounts with passwords that will expire over the holiday so I can proactively warn them now.
clubidentityplatform,
Could you post this question to the Active Directory and PowerShell forum at http://powergui.org/forum.jspa?forumID=173
Dmitry
For some reason, the first script does not work for me. Maybe it’s now different because I have newer versions of the scripting software. Anyway, for me it works if I leave out the .Value.
Get-QADComputer -ComputerRole DomainController | foreach {
(Get-QADUser -Service $_.Name -SamAccountName $SAMan).LastLogon} | Measure-Latest
You are right Roman. Value is no longer required. I’ve updated the scripts.
We have too many DC, so I use:
Get-QADComputer -ComputerRole DomainController | Where-Object {$_.Name -Match “MY_DC”} | Foreach-Object{
$dc = $_.Name
Get-QADUser -Service $dc -SamAccountName $SamAccountName
} | Select-Object Name,Lastlogon
So you have a bunch of DCs in your site, they all start with MY_DC and you want to search across all of them?
In that case this would work faster than Where-Object:
Get-QADComputer -ComputerRole DomainController -Name MY_DC*
Maybe you can help me out.. I’m using the Locateoldusersnotlogon.ps1 file in the Win2008 Resource kit. Our domain is huge and would like to isolate the search to a specific OU. How would I modify the script to have it search a specific OU?
Jesse,
If you want to limit to specific OUs, in the examples above simply use:
Get-QADUser -SearchRoot mydomain.local/myOU
See more information on Get-QADUser here: http://wiki.powergui.org/index.php/Get-QADUser
I am curious how you work out the actual days since the last logon. I dont want the lastlogon time (All DC’s Windows 2000) I actually want the amount of days listed. I cannot work out how to perform this calculation.
Thanks
Are you asking on how to get the number of days between 2 dates? Simply subtract one from another:
$a = get-date
$b = get-date ’01/01/2011′
$a – $b
I cannot seem to do (get-date – $_.lastlogon) , do I need to change the formating or something?
Alan,
PowerShell parser thinks that the minus is a parameter for your get-date cmdlet and goes crazy. Just use brackets to make it easier for the parser:
(get-date) – ($_.lastlogon)
Dmitry
Cheers Dmitry, thats got it.
Dmitry,
I’ve been trying to run the script:
Get-QADComputer -ComputerRole DomainController | foreach {
(Get-QADUser -Service $_.Name -SamAccountName username).LastLogon
} | Measure-Latest
Using the ActiveRoles Management Shell, and have not been able to obtain results, first of all, Measure-Latest have no idea how to run it, I followed your instructions and still I am unable to do this I downloaded the PowerGUI and still. The last attempt that I ran was this:
Get-QADComputer -ComputerRole DomainController | foreach {
(Get-QADUser -Service $_.Name -SamAccountName username).LastLogon
} | Format-Table and I did not see any resultst displaying on the screen.
Any Ideas?
I’m looking to read in a list of users from a csv and then search for their lastlogin time. How would I do that with the above ? I feel like i am lost in foreach loops
Get-QADComputer -ComputerRole DomainController | foreach {
(Get-QADUser -Service $_.Name -SamAccountName username).LastLogon
} | Measure-Latest
has anyone every had luck querying for a specific day?
I’m trying to do
$accountlist1 = Get-QADUser -Proxy -SearchRoot $Ou -enabled | where {$_.lastlogontimestamp -ne $null -and $_.lastlogontimestamp -eq ($currentdate).AddDays(-60)}
I’m building a script for email notifications before account expiration and it’s going to run daily – which means I have to check for account not used in xxx days.