Need help with logic for AD Powershell Audit <30 / 30-60 / >90 days inactive users

Posted on 2013-12-03
Last Modified: 2013-12-05
So I have this script I've been playing with... the goal is to do some basic AD auditing.  I'm using both Microsoft AD cmd-lets and the Quest cmd-lets (I find both have their place).

What I really need to make sure of is my logic for finding users that are inactive for 30 days, then inactive form 30-60 days, then inactive over 90 days.

The script will output:

- New accounts created in last seven days
- Groups modified in last seven days
- 30/30-60/90 and over inactive users
- Report user accounts that have never logged on to their account
- Send an email

Yeah, I know the attach as a text file is somewhat kludgy, but it works for these purposes.

Can someone take a look at the <30 / 30-60 / >90 logic strings and tell me what would work better, I'm not sure my results are accurate or my logic is correct.

# Prepare loading required modules

import-module activedirectory
Add-PSSnapin Quest.ActiveRoles.ADManagement -ErrorAction SilentlyContinue

# Prepare and output file for mailing

$OutputfilePath = "C:\ADUserAudit-"+([datetime]::Now).tostring("yyyyMMdd")+".txt"

# Accounts created this week

$When = ((Get-Date).AddDays(-7)).Date
$UsersCreatedLastWeek = (Get-ADUser -Filter {whenCreated -ge $When} -Properties whenCreated) | select samaccountname,name,distinguishedname
$UsersCreatedLastWeekTable = ($UsersCreatedLastWeek | format-table) 

# Groups Modified this week

$When = ((Get-Date).AddDays(-7)).Date
$GroupsModifiedLastWeek = (Get-ADGroup -Filter {whenChanged -ge $When} -Properties whenChanged) | select samaccountname,name,distinguishedname 
$GroupsModifiedLastWeekTable =  ($GroupsModifiedLastWeek | format-table)

# List all users in that have not logged on within 
# XXX days in "Active Directory" 
# Get the Current Date 
# Number of Days to check back.   
# Organizational Units to search 

$OUList = "OU=Users,OU=company,DC=com","OU=Users2,OU=company,DC=com"

# Prepare arrays for totals

$30Days = @()
$60Days = @()
$90Days = @()

ForEach ($OU in $OUList) 

    #LastLogon.AddDays() adds x number of days making the users lastlogon x number of days into the future (e.g. Sep 28th somes October 27th...)

    $30Days += (GET-QADUSER -SearchRoot $OU | where { $_.LastLogon.AddDays(30) -lt $CURRENTDATE}) | select samaccountname,Name,DN

    $60Days += (GET-QADUSER -SearchRoot $OU | where { ($_.LastLogon.AddDays(60) -lt $CURRENTDATE -and $_.LastLogon.AddDays(60) -lt ($CURRENTDATE.AddDays(-30))) }) | select samaccountname,Name,DN

    $90Days += (GET-QADUSER -SearchRoot $OU | where { ($_.LastLogon.AddDays(90) -lt $CURRENTDATE -and $_.LastLogon.AddDays(90) -lt ($CURRENTDATE.AddDays(-30))) }) | select samaccountname,Name,DN

$30DaysTable = ($30Days | format-table)
$60DaysTable = ($60Days | format-table)
$90DaysTable = ($90Days | format-table)

# Get users that have never logged in, but use Windows AD Powershell cmdlets as it should look at all domain controllers. Not restricted to OU:

$NeverBeenKissed = get-aduser -f {-not ( lastlogontimestamp -like "*") -and (enabled -eq $true)} | select samaccountname,name,distinguishedname

$NeverBeenKissedTable = $NeverBeenKissed | format-table
# Write to a file

Out-File $OutputfilePath

Add-Content -Path $OutputfilePath "Users Created in the Last Week:"
Add-Content -Path $OutputfilePath " "
$UsersCreatedLastWeekTable | Out-File $OutputfilePath -append
Add-Content -Path $OutputfilePath "Groups Modified in the Last Week:"
Add-Content -Path $OutputfilePath " "
$GroupsModifiedLastWeekTable | Out-File $OutputfilePath -append
Add-Content -Path $OutputfilePath "Users not logged in for 30 days:"
Add-Content -Path $OutputfilePath " "
$30DaysTable | Out-File $OutputfilePath -append
Add-Content -Path $OutputfilePath "Users not logged in for 60-90 days:"
Add-Content -Path $OutputfilePath " "
$60DaysTable | Out-File $OutputfilePath -append
Add-Content -Path $OutputfilePath "Users not logged in for over 90 days:"
Add-Content -Path $OutputfilePath " "
$90DaysTable | Out-File $OutputfilePath -append
Add-Content -Path $OutputfilePath "Users who have NEVER logged in!:"
Add-Content -Path $OutputfilePath " "
$NeverBeenKissedTable | Out-File $OutputfilePath -append

# Send it in email as an attachment, since it's not as simple to port it into an email yet.

$smtpServer = “”

Send-MailMessage -To "” -Subject “AD User Audit 30/60/90” -From “” -Body “Current AD User Audit is attached.” -Priority High -SmtpServer $smtpServer -Attachments $OutputfilePath

Open in new window

Question by:gerhardub
  • 3
  • 2
LVL 40

Expert Comment

ID: 39694056
You can try with Search-ADAccount to search the inactive accounts.. for example..

$30  = Search-ADAccount -UsersOnly -AccountInactive -TimeSpan 30
$3060 = Search-ADAccount -UsersOnly -AccountInactive -TimeSpan 60 | ?{($30 | Select -ExpandProperty Samaccountname) -notcontains $_.Samaccountname}
$90 = Search-ADAccount -UsersOnly -AccountInactive -TimeSpan 90

Open in new window

LVL 39

Accepted Solution

footech earned 500 total points
ID: 39694175
What I really need to make sure of is my logic for finding users that are inactive for 30 days, then inactive form 30-60 days, then inactive over 90 days
This says to me - find the users;
 - who haven't logged in in the last 30 days, so last logon date must be greater than 30
 - whose last logon was between 30 and 60 days ago
 - whose last logon was greater than 90 days ago

The first two are pretty much the same, the second just has an additional limit placed on it.  And what about last logon between 60 and 90 days ago?

Your script logic is pretty convoluted (and a little different than my understanding of what you want).  The first will get users whose last logon was greater then 30 days ago.  The second will get users whose last logon was between 30 and 60 days ago.  The third will get users whose last logon was between 30 (I think you meant 60 here) and 90 days ago.

In addition to Search-ADAccount you could also do something like the following:
$lt30 = @()
$30Days = @()
$60Days = @()
$90Days = @()

$now = Get-Date
ForEach ($OU in $OUList) 
        Get-ADUser -filter * -Properties LastLogonDate -SearchBase $OU | ForEach `
        If ( $_.LastLogonDate -gt $now.AddDays(-30) )
        { $lt30 += $_ | Select samaccountname,Name,DistinguishedName }
        ElseIf ( $_.LastLogonDate -lt $now.AddDays(-30) -and $_.LastLogonDate -gt $now.AddDays(-60) )
        { #another way to do the same thing
          $30Days += New-Object PsObject -Property @{ samaccountname = $_.samaccountname; Name = $; DN = $_.DistinguishedName }
        ElseIf ( $_.LastLogonDate -lt $now.AddDays(-60) -and $_.LastLogonDate -gt $now.AddDays(-90) )
        { $60Days += $_ | Select samaccountname,Name,DistinguishedName }
        ElseIf ( $_.LastLogonDate -lt $now.AddDays(-90) )
        { $90Days += $_ | Select samaccountname,Name,DistinguishedName }

Open in new window


Author Comment

ID: 39694411
Folks ,

You rock.... Let me plug these in and play in the AM... I'll get back to you tomorrow!
Windows Server 2016: All you need to know

Learn about Hyper-V features that increase functionality and usability of Microsoft Windows Server 2016. Also, throughout this eBook, you’ll find some basic PowerShell examples that will help you leverage the scripts in your environments!


Author Comment

ID: 39695312
Interestingly, the Search-ADAccount cmdlet gave me false results.  It was odd... $30 and $90 were exactly the same, $60 had zero results.... which makes sense in hindsight.

FooTech: Your understanding of what I really needed worked great.  The arrays literally break out the users from time as I needed, and then allowed me to break it out in a text based report that ended with 7/7/30/60/90 and "never been kissed." -grin-

Thanks for that... you've just saved me a ton of time, and I learned something from both of you.  I need to brush up on my Powershell logic... and revisit the cmdlets.

I'll open another ticket if either of your want 500 points toward figuring out how to format it in HTML and send that in an email! Let me know!


Author Closing Comment

ID: 39695317
Fantastic level of help... I can't thank you enough... and I'm a hardass.
LVL 39

Expert Comment

ID: 39699387
Glad I could help.

One thing to keep in mind if you want to explore the use of Search-ADAccount, when specifying a timespan to indicate days you should enclose the number in quotes.  So
Search-ADAccount -UsersOnly -AccountInactive -TimeSpan 30
would have a timespan of 30 ticks, and
Search-ADAccount -UsersOnly -AccountInactive -TimeSpan "30"
would have a timespan of 30 days.  You could also write it like
Search-ADAccount -UsersOnly -AccountInactive -TimeSpan 30.0:0
When written like this the quotes are optional.

Featured Post

Netscaler Common Configuration How To guides

If you use NetScaler you will want to see these guides. The NetScaler How To Guides show administrators how to get NetScaler up and configured by providing instructions for common scenarios and some not so common ones.

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

Utilizing an array to gracefully append to a list of EmailAddresses
A brief introduction to what I consider to be the best editor for PowerShell.
This tutorial will walk an individual through locating and launching the BEUtility application to properly change the service account username and\or password in situation where it may be necessary or where the password has been inadvertently change…
This tutorial will show how to configure a new Backup Exec 2012 server and move an existing database to that server with the use of the BEUtility. Install Backup Exec 2012 on the new server and apply all of the latest hotfixes and service packs. The…

911 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

17 Experts available now in Live!

Get 1:1 Help Now