Active Directory Reporting - Create a password age report

 

Security is becoming one of the bigger topics as of late in regards to Active Directory.  While working with other admins I am finding more and more Admins do not know what kind of state user account passwords are in the environment.  Here is a PowerShell script I use to help Admins find out password age.  I also include some other useful attributes to help determine if an account is used or not.

  • Enabled – is the account even used
  • PasswordExpired – if the password is older than my forest password age has it already expired
  • PwdLastset – is the last time the password was changed, I use this to create the password age in days
  • LastLogonTimeStamp – is the account being logged on with
  • Whenchanged & Whencreated – good to see if the account has changed since the account was created
  • ServicePrincipalName – not a perfect way to try to determine if an account is a service account. But it is the best we got.

Run this against one user.

 $user = ""
 get-aduser $user `
     -properties enabled,whencreated,whenchanged,lastlogontimestamp,PwdLastSet,PasswordExpired,DistinguishedName,servicePrincipalName |`
     SamAccountName,enabled,PasswordExpired,`
     @{Name="PwdLastSet";Expression={[datetime]::FromFileTime($_.PwdLastSet)}}, `
     @{Name="PwdAge";Expression={if($_.PwdLastSet -ne 0){(new-TimeSpan([datetime]::FromFileTimeUTC($_.PwdLastSet)) $(Get-Date)).days}else{0}}}, `
     @{Name="LastLogonTimeStamp";Expression={[datetime]::FromFileTime($_.LastLogonTimeStamp)}}, `
     whenchanged,whencreated,
     @{Name="HasServicePrincipal";Expression={if($_.servicePrincipalName){$True}else{$False}}}, `
     distinguishedname

Here is what the results looks like

image

Note - Only thing I would add to this is the useraccountcontrol to see if the account has a none expiring password or to see if the user can change their password.

Performing this against one user wont be as useful as running this against every user in every domain in the forest.  I'm going to add a loop to query all the users in each domain and will add the domain to the server parameter in get-aduser cmdlet.  I'm also going to have this not retrieve any of the criticalsystemobjects like KRBTGT and built-in administrator.  I could however see use for that information.

This will retrieve the password age for every user in a forest.

 import-module activedirectory
 $default_log = $env:userprofile + '\Documents\user_password_age.csv'
  
 #enumerate all the domain in a forest
 foreach($domain in (get-adforest).domains){
     #query all users except critical system objects
     get-aduser -LDAPFilter "(!(IsCriticalSystemObject=TRUE))" `
     -properties enabled,whencreated,whenchanged,lastlogontimestamp,PwdLastSet,PasswordExpired,DistinguishedName,servicePrincipalName `
     -server $domain |`
     select @{name='Domain';expression={$domain}},`
     SamAccountName,enabled,PasswordExpired,`
     @{Name="PwdLastSet";Expression={[datetime]::FromFileTime($_.PwdLastSet)}}, `
     @{Name="PwdAge";Expression={if($_.PwdLastSet -ne 0){(new-TimeSpan([datetime]::FromFileTimeUTC($_.PwdLastSet)) $(Get-Date)).days}else{0}}}, `
     @{Name="LastLogonTimeStamp";Expression={[datetime]::FromFileTime($_.LastLogonTimeStamp)}}, `
     whenchanged,whencreated,
     @{Name="HasServicePrincipal";Expression={if($_.servicePrincipalName){$True}else{$False}}}, `
     distinguishedname | export-csv $default_log -NoTypeInformation
 }

Whenever I write a script I like to know the path of the file with the results is in a place easy to find. Using the userprofilepath variable makes this a little more predictable.

Open the newly create csv in excel and lets take a look.

Here is a small sample of my data.

image

This isn't very useful, lets throw this into some graphs.

Select, Insert, PivotChart and PivotChart again.

image

In the PivotChart Fields make it look like this by dragging the PwdAge into the Axis and SamAccountName into the Value.

image

I now have this graph

image

Looking at this I have a lot of accounts that don’t have a password.  Not good but it’s a lab.

Add the domain as a slicer to be able to quickly view this data by domain. Do this by right clicking on Domain in the PivotChart Fields and select add as Slicer.

image

Move the Domain Slicer  from being on top of the graph. Then play around with the data to see what each domain looks like.

image

Add enabled, PasswordExpired, HasServicePrincipal as a slicer.  Can also drag each one of these into the Legend area to see the differences between the values of each one of the settings.

Make it look like this:

image

My lab doesn’t have the diversity of some of the values we grabbed unlike “real”  production environments.  Here is what this report looks like now.

image

This is all I have for today.  Play around with the graphs, use the data to determine if accounts are needed or work with your teams that are responsible for some of these accounts to get those password changed.  I hope you find this useful, and have a good day.

Chad