- Poll: How reliable are ChatGPT and Bing Chat? - Tue, May 23 2023
- Pip install Boto3 - Thu, Mar 24 2022
- Install Boto3 (AWS SDK for Python) in Visual Studio Code (VS Code) on Windows - Wed, Feb 23 2022
In a previous post, I explained how you can enable interactive logon logging in Active Directory. Today, I’ll show you how to use PowerShell to retrieve the information from the corresponding Active Directory attributes.
If you want to try the examples in this article on your desktop, you’ll have to install the Active Directory module for Windows PowerShell. This module is already available on every domain controller in an Active Directory domain with a functional level of Windows Server 2008 R2 or higher.
Note that I will only discuss the last interactive logon attributes in this article. However, you can also use the examples in this post for the lastLogon and lastLogontimeStamp attributes, which are useful for listing inactive accounts if you replace the attributes in the commands accordingly.
As discussed in my previous article, you can use the interactive logon attributes for gathering real-time information about the last successful (msDS-LastSuccessfulInteractiveLogonTime) and unsuccessful (msDS-LastFailedInteractiveLogonTime) user logon times. In addition, you can retrieve the number of failed logons (msDS-FailedInteractiveLogonCountAtLastSuccessfulLogon) of a user account since the last successful logon, as well as the failed logons since the feature was enabled (msDS-FailedInteractiveLogonCount).
Note: After I finished this post, I noticed that the msDS-FailedInteractiveLogonCountAtLastSuccessfulLogon attribute does not store the correct value. Even though the information is correctly displayed on the logon screen, msDS-FailedInteractiveLogonCountAtLastSuccessfulLogon always has the same value as msDS-FailedInteractiveLogonCount. This appears to be a bug in Windows Server 2012 R2. It is not a PowerShell issue because I also see the wrong values in ADUC. Please let me know if you get the same results in your tests.
The cmdlet we need to gather the information is Get-ADUser, which enables you to query information about Active Directory user objects. The easiest case would be if you want to know the number of failed logons since the last successful logon for a particular user. For example:
Get-ADUser -Filter {Name -eq "Administrator"} -Properties * | Select-Object Name, msDS-FailedInteractiveLogonCountAtLastSuccessfulLogon
You can use the Filter parameter to search for user objects that have a certain attribute value. In the example, we restrict the output to the Administrator account. The Properties parameter is required because the interactive logon attributes are not included in the default set of properties (attributes) that the Get-ADUser cmdlet retrieves. By using the asterisk, we display all of the attributes that are set on the user object. We then pipe the output to the Select-Object cmdlet, which restricts the output to the name of the user account and the number of failed logons at the last successful logon.
If you want to find out what account logout threshold you should configure in your network, you could create a list that is sorted according to the number of failed logons at the last successful logon:
Get-ADUser -Filter * -Properties * | Select-Object Name, msDS-FailedInteractiveLogonCountAtLastSuccessfulLogon | Sort-Object -Descending msDS-FailedInteractiveLogonCountAtLastSuccessfulLogon
If you run the above command every day for a week or so, you will get a feeling for how often users mistype their passwords.
Or perhaps you just want to know how many of your users would fall prey to your account logout threshold. The command below counts the number of users who mistyped their password more than three times since the last successful logon:
Get-ADUser -Filter * -Properties * | Where-Object msDS-FailedInteractiveLogonCountAtLastSuccessfulLogon -gt 3 | Measure-Object | Select-Object Count
Things get a bit trickier if you want to know the time of the last failed logon. The problem is that the attribute stores this information in the Windows file time format, a 64-bit value representing the number of 100-nanosecond intervals since January 1, 1601. If you don’t happen to be Rain Man, you have to let the computer convert the value into a human-readable format. For this, we use the .NET method DateTime.FromFileTime, which converts the Windows file time to an equivalent local time. This is how you could display the last failed interactive logon time for the Administrator account:
Get-ADUser -Filter {Name -eq "Administrator"} -Properties * | Select-Object Name, @{Name="Last failed logon time";Expression={[datetime]::FromFileTime($_.'msDS-LastFailedInteractiveLogonTime')}}
The @{} syntax creates a hash table, which is just a collection of key-value pairs. In our case, we have two pairs: the Name key, which has the value “Last failed logon time,” and the Expression key, for which we calculate the value with the above-mentioned .NET method. “$_” stands for the user object that we pipe to the Select-Object cmdlet. We use this hash table as a calculated property that allows us to select the property and convert it into a human-readable format.
If you suspect that someone is trying to hack accounts in your network by guessing passwords, you might want to create a list of all user accounts with all four interactive logon attributes. If you notice that some accounts have unusually high numbers of failed logons since you activated the feature, but those accounts were never locked, someone might be aware of your password account threshold and patiently tries just a few passwords every day. Of course, another explanation would be that your users really need new keyboards. 😉
Get-ADUser -Filter * -Properties * | Select-Object -Property Name, @{Name="Total failed logons";Expression="msDS-FailedInteractiveLogonCount"}, @{Name="Recent failed logons";Expression="msDS-FailedInteractiveLogonCountAtLastSuccessfulLogon"}, @{"Name"="Last failed logon";Expression={[datetime]::FromFileTime($_.'msDS-LastFailedInteractiveLogonTime')}}, @{"Name"="Last successful logon";Expression={[datetime]::FromFileTime($_.'msDS-LastSuccessfulInteractiveLogonTime')}} | Sort-Object Name | Format-Table
If you see “31/12/1600” as a date, it doesn’t mean that one of your users possesses a time machine. This user just never logged in interactively.
Another piece of interesting information could be the time difference between the last successful logon and the last unsuccessful one.
foreach ($User in Get-ADUser -Filter * -Properties *) { $TimeSpan = "{0:dd\:hh\:mm\:ss}" -f ([timespan]::fromticks($User.'msDS-LastSuccessfulInteractiveLogonTime' - $User.'msDS-LastFailedInteractiveLogonTime')) $LastFailedLogon= [datetime]::FromFileTime($User.'msDS-LastFailedInteractiveLogonTime') $LastSuccessfulLogon= [datetime]::FromFileTime($User.'msDS-LastSuccessfulInteractiveLogonTime') @{"User" = $User.Name; "Last failed logon" = $LastFailedLogon; "Last successfull logon" = $LastSuccessfulLogon; "Time span" = $TimeSpan }.GetEnumerator() | Sort -Descending -Property Name | Format-Table }
The first line calculates the time difference using a .NET function and then formats the value in human-readable format (days, hours, minutes, seconds). In the last line, we again use a hash table to display the result. We need the GetEnumerator() method so we can sort our hash table.
Please let me know if you could confirm the bug regarding the failed logon counts I mentioned above.
All I got for results were { }. Maybe it’s because I’m still running 2000 Native?
Marc, are you serious? Domain functional level Windows 2000 native? You are telling me that you didn’t upgrade your Active Directory in 15 years? You need functional level Windows Server 2008 R2 or higher and you have to enable the feature first with Group Policy.
Michael, boy do I feel sheepish! In my defence, I’ve only been here for 7 years (and a sysadmin for 3)… It’s actually on my project plan this year! Still trying to decide on whether or not to virtualize the DC’s. If going physical and we can get some new hardware I’d like to do 2012 but if not then 2008 R2.
Marc, don’t move to 2008 R2. This is yesterday’s technology. If you are going to virtualize your domain controllers you will want to have latest OS that is adapted to this environment.
Michael,
Is there anywhere I can ask you a few questions other than these comments?
Marc, you can ask questions in the forum.
It would be better to retrieve only the 2 extra properties you need rather than all of them. In a large AD environment performance could be awful.
Adam, that’s a good point!
I got all dates as 12/31/1600 and i’m on DFL 08 R2…strange stuff.
I too got all dates as 12/31/1600 and i’m on DFL 2012.
Same here – all showing 01/01/1601, DFL 2008R2
This is what worked for me:
Get-ADUser -Filter * -Properties * | Select-Object Name, @{Name=”Last Successful Logon”;Expression={[datetime]::FromFileTime($_.’lastLogonTimeStamp’)}} | Sort-Object “Last Successful Logon”
How to find the last user logged onto a computer in Active Directory?
I also want to get who/which account made this logon. For example:
I need to have 2 reports (csv or txt):
one that includes “Active users” and their “last login date”
another that included “de-active users” and their “last login date”.
I know I can use “get-aduser -filter ‘enabled -eq $false’ to generate a listing in powershell, but that only shows users without last login date, and is not exported to (csv or txt).
thanks
Have a look at this:
https://bestitsm.wordpress.com/2018/06/11/how-to-get-last-logon-date-value-of-users-in-your-ad-domain/
Get-ADUser -Filter * -Properties * | Select-Object Name, @{Name=”Last Successful Logon”;Expression={[datetime]::FromFileTime($_.’lastLogonTimeStamp’)}} | Sort-Object “Last Successful Logon” | Write-output c:\temp\lastloggedon.csv
That is what worked for me to get all the last logons so I could see accounts that have not logged in for over a year so I could disable them.
Hello Michael, This might be a dumb question, but since I can't seem to find the answer elsewhere I thought I would ask you directly. By means of background, I am analysing an estate with a massive amount of service accounts… and over time a lot of these have been used by IT staff to login interactively to the servers running the services the service accounts are being used for. Lazy practice and definitely not good security policy for sure… but we are cleaning this up. We are now denying interactive logons via group policy but only after thorough investigation of each active service account. To summarise, when I pull the attribute msDS-LastSuccessfulInteractiveLogonTime, I am still seeing a very large number of my service accounts with a value being set every few minutes…. and this value is matching the 'LastLogon' attribute. It's as if 'Lastlogon' is being set as 'msDS-LastSuccessfulInteractiveLogonTime' as well. I've confirmed this isn't just in Powershell… it's reflected in the ADUC Attribute Editor tab too for these accounts. And yes, I have confirmed there is literally no way all of these are in fact being used for interactive logons… in fact most are in the deny policy.
So I guess the question from all of the above is… what else OTHER than an actual interactive logon to the console or via RDP would result in the 'msDS-LastSuccessfulInteractiveLogonTime' attribute being set?
As far as I know, the attribute exactly stores the info that its name suggests. Maybe there is something wrong with your deny policy?
I tested your theory about the bug with ‘msDS-FailedInteractiveLogonCountAtLastSuccessfulLogon’ and it’s not a bug, it just looks like one. ‘msDS-FailedInteractiveLogonCount’ will always be the same as long as the user has succesfully logged on. The logon screen is showing you the difference between the two attributes but as soon as you click OK AD updates the ‘msDS-FailedInteractiveLogonCountAtLastSuccessfulLogon’ to the same value – you can see this by entering a bad password on a user object one or more times but DO NOT Logon – then you can use this powershell to see that the two attributes values are different.