Updated 9/12/2017 - My Guidance on Identifying Stale Computers Objects in Active Directory using Powershell

 

This is a very common discussion, and a simple search using your favorite search engine provides multiple results from both the community and my Microsoft Peers.  This is my take on the topic and the guidance I usually provide.

These are common questions I get:

What AD attribute should be used pwdlastset or Lastlogontimestamp to determine if a computer object is stale?    I like this guidance “One-Liner: My Take On Finding Stale User and Computer Accounts”. Ian recommends using both attributes as a way to determine when an object is stale.   Since it is using the get-adcomputer powershell cmdlet, I am going to add to that recommendation and include the ipv4address attribute equals null, along with looking for cluster related spn entries (Cluster and Stale Computer Accounts).   Create a report and review the results to see if this guidance works.

UPDATE: this script is going to use hash tables. Click here for more info.

Create a Report

This script example is to identify the impact based on the criteria.  Run the PowerShell cmdlets below and review the findings.

 param($defaultlog = "$($env:userprofile)\Documents\computer_report.csv",
     $staledate = 90)
  
  
 #format date
 $stale_date = [DateTime]::Today.AddDays(-$staledate)
 #delete results if already exist
 If ($(Try { Test-Path $defaultlog} Catch { $false })){Remove-Item $defaultlog -force}
  
 #region create hashtables
  
 #this is hashtable used to populate a calculated property to determine if the account is stale
 $hash_isComputerStale = @{Name="Stale";
     Expression={if(($_.LastLogonTimeStamp -lt $stale_date.ToFileTimeUTC() -or $_.LastLogonTimeStamp -notlike "*") `
         -and ($_.pwdlastset -lt $stale_date.ToFileTimeUTC() -or $_.pwdlastset -eq 0) `
         -and ($_.enabled -eq $true) -and ($_.whencreated -lt $stale_date) `
         -and ($_.IPv4Address -eq $null) -and ($_.OperatingSystem -like "Windows*") `
         -and (!($_.serviceprincipalnames -like "*MSClusterVirtualServer*"))){$True}else{$False}}}
  
 #this hashtable is used to create a calculated property that converts pwdlastset
 $hash_pwdLastSet = @{Name="pwdLastSet";
     Expression={([datetime]::FromFileTime($_.pwdLastSet))}}
  
 #this hashtable is used to create a calculated property that converts lastlogontimestamp
 $hash_lastLogonTimestamp = @{Name="LastLogonTimeStamp";
     Expression={([datetime]::FromFileTime($_.LastLogonTimeStamp))}}
  
 #this hashtable is used to create a calculated property to display domain of the computer
 $hash_domain = @{Name="Domain";
     Expression={$domain}}
  
 #endregion
  
 foreach($domain in (get-adforest).domains){
     
     get-adcomputer -filter {isCriticalSystemObject -eq $False} `
         -properties PwdLastSet,whencreated,SamAccountName,LastLogonTimeStamp,
             Enabled,IPv4Address,operatingsystem,serviceprincipalnames `
         -server $domain | `
             select $hash_domain,SamAccountName,enabled,operatingsystem,IPv4Address,`
                 $hash_isComputerStale,$hash_pwdLastSet,$hash_lastLogonTimestamp | `
             export-csv $defaultlog -append -NoTypeInformation
 }
  
 $results = import-csv $defaultlog
 $stale =  $results | group-object stale | select name, count
 $disabled = $results | group-object enabled | select name, count
  
  
 Write-Host "Found $(($stale | where name -eq $true).count) stale computers"
 Write-Host "Found $(($disabled | where name -eq $false).count) disabled computers"
 Write-Host "Found $($results.count) total computers"

Reviewing the Results

Open stale_computer_report.csv in Excel, look over the results.  Check to make sure the objects with ip addresses aren't showing as True in the Stale column.

image

Following this blog theme, by looking at the data with charts and tables creates an easier way to review and tell a story about the data,

To do this in excel, select insert, pivot chart, and then pivot chart again.

image

Select OK

image

Select Chart 1

image

View Stale Information By Domain

In the PivotChart Fields drag the fields to match the following

image

This will produce a nice graph grouped by domain that shows the number of computers that are / not stale.

image

View Stale Data Grouped by Parent OU

Drag the fields to match the following

 image

 

image

View Stale Data Grouped by Operating System

Now that you have the new data you can also chart/graph it by operating system. Drag the fields to match the following

image

 

image

 

Gather just the Computers that are stale

 param($defaultlog = "$($env:userprofile)\Documents\stale_computer_report.csv",
     $staledate = 90)
  
 #dates
 $stale_date = [DateTime]::Today.AddDays(-$staledate)
 $utc_stale_date = $stale_date.ToFileTimeUTC()
  
 #delete existing log
 If ($(Try { Test-Path $defaultlog} Catch { $false })){Remove-Item $defaultlog -force}
  
 #region create hashtables
  
 #this is hashtable used to populate a calculated property to determine if the account is stale
 $hash_isComputerStale = @{Name="Stale";
     Expression={if(($_.LastLogonTimeStamp -lt $stale_date.ToFileTimeUTC() -or $_.LastLogonTimeStamp -notlike "*") `
         -and ($_.pwdlastset -lt $stale_date.ToFileTimeUTC() -or $_.pwdlastset -eq 0) `
         -and ($_.enabled -eq $true) -and ($_.whencreated -lt $stale_date) `
         -and ($_.IPv4Address -eq $null) -and ($_.OperatingSystem -like "Windows*") `
         -and (!($_.serviceprincipalname -like "*MSClusterVirtualServer*"))){$True}else{$False}}}
  
 #this hashtable is used to create a calculated property that converts pwdlastset
 $hash_pwdLastSet = @{Name="pwdLastSet";
     Expression={([datetime]::FromFileTime($_.pwdLastSet))}}
  
 #this hashtable is used to create a calculated property that converts lastlogontimestamp
 $hash_lastLogonTimestamp = @{Name="LastLogonTimeStamp";
     Expression={([datetime]::FromFileTime($_.LastLogonTimeStamp))}}
  
 #this hashtable is used to create a calculated property to display domain of the computer
 $hash_domain = @{Name="Domain";
     Expression={$domain}}
  
 $hash_ParentOU = @{Name="ParentOU";
     Expression={$_.distinguishedname.Substring($_.samaccountname.Length + 3)}}
  
 #endregion
  
 foreach($domain in (get-adforest).domains){
     
     get-adcomputer -filter {(LastLogonTimeStamp -lt $utc_stale_date -or LastLogonTimeStamp -notlike "*")
             -and (pwdlastset -lt $utc_stale_date -or pwdlastset -eq 0) -and (enabled -eq $true)
             -and (iscriticalsystemobject -notlike $true) -and (OperatingSystem -like 'Windows*')
             -and ((ServicePrincipalName -notlike "*") -or (ServicePrincipalName -notlike "*MSClusterVirtualServer*"))} `
         -properties PwdLastSet,whencreated,SamAccountName,name,LastLogonTimeStamp,
             Enabled,IPv4Address,operatingsystem,serviceprincipalname `
         -server $domain  |  `
             select $hash_domain,name,enabled,operatingsystem,IPv4Address,whencreated,`
                 $hash_isComputerStale,$hash_pwdLastSet,$hash_lastLogonTimestamp,$hash_ParentOU | `
                 export-csv $defaultlog -append -NoTypeInformation
 }
  
  
 Write-Host "Found $((import-csv $defaultlog | group-object stale | select name, count | where name -eq $true).count) stale windows computers"
 write-host "Results are here: $defaultlog"

Determine if the report is good, if so start disabling the computers.

 import-csv $defaultlog | foreach{get-adcomputer $_.samaccountname -server $_.domain | disable-adaccount -confirm:$false -whatif}

Conclusion

There are multiple ways to do this.  Hopefully, you will leverage some of this to discover what is going on with computer objects in your environment and use it to help with Active Directory object hygiene.

Thank you for reading and have a good day.

-Chad