[updated with optimization from comment from JR]
Cleaning up Active Directory is a necessary evil. You need to stay under your CAL count and it can be difficult to figure out which computers (or users) have not logged in to the domain recently.
Windows Server 2003 introduced the lastLogonTimestamp attribute which replicates between all DCs in the domain. Now, this isn’t real-time data. In fact it can be up to 14 days behind the current date, depending on your domain settings. If you want that, you’re going to have to get yourself a good syslog server, but for general cleanup and auditing purposes it works great. You can read more about this attribute on Microsoft’s TechNet Blog.
I’ve written a couple very simple PowerShell scripts that will 1) search the entire domain for all computers with a lastLogonTimestamp before a certain date 2) return a computer’s lastLogonTimestamp value in a human readable local format. It’s not so easy to just go out and get the time stamp, because the format that AD stores it UTC (GMT) format, so it needs some converting to human readable, which my scripts do.
get_lastLogonTimestamp_from_host.ps1
# Gets host and lastLogonTimestamp in UTC of specified host # get Name $hostname = Read-host "Enter a hostname" # grab the lastLogonTimestamp attribute Get-ADComputer $hostname -Properties lastlogontimestamp | # output hostname and timestamp in human readable format Select-Object Name,@{Name="Stamp"; Expression={[DateTime]::FromFileTime($_.lastLogonTimestamp)}}
———————————————————–
get_stale_hosts_lastLogonTimestamp.ps1
# Gets time stamps for all computers in the domain that have NOT logged in since after specified date $time = Read-host "Enter a date in format mm/dd/yyyy" $time = get-date ($time) $date = get-date ($time) -UFormat %d.%m.%y # Get all AD computers with lastLogonTimestamp less than our time Get-ADComputer -Filter {LastLogonTimeStamp -lt $time} -Properties LastLogonTimeStamp | # Output hostname and lastLogonTimestamp into CSV select-object Name,@{Name="Stamp"; Expression={[DateTime]::FromFileTime($_.lastLogonTimestamp)}} | export-csv .all_old_computers_timestamps_older_than-$time.csv -notypeinformation
These are two scripts that I use pretty often when I’m trying to determine if I should disable/delete computer accounts in AD. Hope it helps someone else.
I’ve been using ‘dsquery computer -inactive’. Is this method more accurate? I’ve often been puzzled by some of the computers that come up under that query.
I believe that "-inactive" queries the pwdLastSet attribute which is not replicated across all domain controller and it can be as much as 30 to 60 days off depending on domain settings (when you have computers renewing their "passwords"). But I could be wrong. I just never got great results from that command. Also, PowerShell give so much more flexibility in output formatting :)
mate, thanks so much for this! legendary stuff
Thanks, Dominic.
When I run the get_stale_hosts_lastLogonTimestamp.ps1 script most of my machines report the following stamp 12/31/1600 7:00:00 PM. What does this stamp mean?
I had to tweak this just a little for my specific needs but this helped me out a lot. Thanks.
Joe – That means that a null value is in the fieldMike – Glad it helped :)
Great script, only thing I would change is the end fo the line to:export-csv .all_old_computers_timestamps_older_than-$time.csv -notypeinformationThe headers then look right in Excel and you can run the report for several different dates without it overwriting the original file.
Great suggestion, thanks! I’ve updated the script.
Specifying your Filter and Properties up front through Get-ADComputer should be easier on your DCs and your script won’t need to check every computer since you’ll already have the records you want.
$time = Get-Date ($time)
Get-ADComputer -Filter {LastLogonTimeStamp -gt $time} -Properties LastLogonTimeStamp
For example, the above takes me 9.4 seconds to return about 3874 records (we haven’t cleaned up in a while). An unfiltered return of all records takes 13.9 seconds for 6327 records. Getting all computers and then getting the LastLogonTimeStamp through Get-ADObject took nearly 7 minutes.
Or use the correct operator “-lt”. Duh on my part. Another AD quick AD search option the Internet reminded me of is this: “Search-ADaccount -AccountInactive -Timespan 90:00:00:00 -ComputersOnly” where 90 is the number of days the computer has been inactive. That runs in about the same time as the date filtered query from Get-ADComputer.
Thanks, JR! I don’t pretend to be an Powershell expert, so I appreciate the optimization help. This was one of the first useful scripts I had ever written and I’ve never gone back to refine it :)
Thanks for this great topic!
I used this:
Get-ADComputer -SearchBase “ou=Desktop,ou=Workstations,ou=Computers,ou=XXX,ou=XXX,dc=XXX,dc=XXX,dc=XXX,dc=XXX” -filter * -Properties LastLogonTimeStamp | Select-Object Name,@{Name=”Stamp”; Expression={[DateTime]::FromFileTime($_.lastLogonTimestamp)}}
i mod you powershell to this:
# Gets time stamps for all computers in the domain that have NOT logged in since after specified date
import-module activedirectory
$domain = “mydom.dom.com”
$DaysInactive = 90
$time = (Get-Date).Adddays(-($DaysInactive))
# Get all AD computers with lastLogonTimestamp less than our time
Get-ADComputer -Filter {LastLogonTimeStamp -lt $time} -Properties LastLogonTimeStamp |
# Output hostname and lastLogonTimestamp into CSV
select-object Name,@{Name=”Stamp”; Expression={[DateTime]::FromFileTime($_.lastLogonTimestamp)}} | export-csv OLD_Computer.csv -notypeinformation
and for old USER
import-module activedirectory
$domain = “mydom.dom.com”
$DaysInactive = 90
$time = (Get-Date).Adddays(-($DaysInactive))
# Get all AD computers with lastLogonTimestamp less than our time
Get-ADUser -Filter {LastLogonTimeStamp -lt $time -and enabled -eq $true} -Properties LastLogonTimeStamp |
# Output hostname and lastLogonTimestamp into CSV
select-object Name,@{Name=”Stamp”; Expression={[DateTime]::FromFileTime($_.lastLogonTimestamp)}} | export-csv OLD_User.csv -notypeinformation
can you provide the script for the last login with non-domain machines
Hi Matt,
I’ve just started looking at powershell and not too confident with scripting yet but, I need to find old machines across multiple domains and wondered if your scirpts could be modified to allow this? Thanks for explaining the original scripts so well!
Get-ADComputer -Filter {enabled -eq $true} -properties *|select Name, LastLogonDate, Enabled, Description| Where {[DateTime]::Now.AddDays(-90) -ge $_.LastLogonDate}
Excellent post, problem is that this will only get the last logon stamp from whatever DC you happen to be running it against. Here’s a challenge, modify this so that it looks at EVERY DC, then returns the most recent time stamp for xyz computer object. Make sense? I would LOVE to get that.
I am searching the same thing but with some modification. cant we run it against specific serverlist on my domain. i dont want to run it against all AD object . only want to extract from some of them. if this is possible, please help me. Thanks in Advance
Anjani,
you could pass a text file with the hostnames of the computers from the cmdlet “get-content” through to “get-adcomputer” using the name property as a filter to compare against the names in the file.
So the whole code would look like:
$time = get-date ($time)
$date = get-date ($time) -UFormat %d.%m.%y
get-content c:\computers.txt | % {
# Get AD computers from file c:\computers and with lastLogonTimestamp less than our time
Get-ADComputer -Filter {Name -like $_ -and LastLogonTimeStamp -lt $time} -Properties LastLogonTimeStamp} |
# Output hostname and lastLogonTimestamp into CSV
select-object Name,@{Name=”Stamp”; Expression={[DateTime]::FromFileTime($_.lastLogonTimestamp)}} | export-csv .all_old_computers_timestamps_older_than-$time.csv -notypeinformation
For some reason, NONE of this works. Run powershell as admin, then paste the script? I get a >> prompt and nothing. I run the text file saved as ps1, on my 2008 DC, nothing.
Please help
That usually means you’re missing a closing bracket “}” somewhere in your copy/paste. Try opening it with either Notepad++ or the PowerCLI ISE.
Genius work guys. I have successfully inventoried the entire farm of clients (3500 +), i have that as a CSV format, with filters to see which OS’ and SP they run, now i have also a CSV with the last logon, showing 1200+ devices that havent logged in since 2014 and going back. That is all well and good, but how am i then supposed to filter out which device types they are? I cant just go deleting all those machines, what if there are switches etc in there? If i want an accurate record of what OS’s we have, what version of IE they run etc then i need to clean up AD first otherwise i reporting on old data if the AD records of old machines are there
gonna need some more scripting work to be done here i think to ascertain exactly what is deletable! CLeaning up AD isnt easy as you can break things if you dont get it right! lol
I’ve been using you script for quite a while now (THANKS!!)
I want to move to the next level and add the ability to disable the inactive accounts.
I’ve searched and found many such scripts, but they either don’t disable any computers, or they disable CLEARLY active computers.
I’d like to restrict it to specific OS versions too. (don’t touch servers, disable XP machines)
Any pointers in that direction appreciated.
“.older_than-$time.csv” at the end of the second script should read “.older_than-$date.csv” since that $time variable is trying to output “:” in the file name, which isn’t allowed in Windows. $date has had -UFormat applied to it on line 5, so it works.
If you’re getting “The given path’s format is not supported” errors, this is why.
Nice.
I changed modified it to export OperatingSystem and DistinguishedName, so I can filter by OU and OS if needed,
# Get all AD computers with lastLogonTimestamp less than our time
Get-ADComputer -Filter {LastLogonTimeStamp -lt $time -or OperatingSystem -Like ‘*’} -Properties LastLogonTimeStamp, OperatingSystem |
# Output hostname and lastLogonTimestamp into CSV
select-object Enabled, OperatingSystem, Name,@{Name=”Stamp”; Expression={[DateTime]::FromFileTime($_.lastLogonTimestamp)}}, DistinguishedName| Export-Csv