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.
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.
Select OK
Select Chart 1
View Stale Information By Domain
In the PivotChart Fields drag the fields to match the following
This will produce a nice graph grouped by domain that shows the number of computers that are / not stale.
View Stale Data Grouped by Parent OU
Drag the fields to match the following
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
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
Comments
- Anonymous
October 14, 2016
Great tip - Anonymous
October 01, 2017
error in: export-csv $defaultlog -append -NoTypeInformation- Anonymous
October 01, 2017
Sorry, powershell version problem
- Anonymous
- Anonymous
November 08, 2017
Thank you for an excellent script and documentation, this is very useful. - Anonymous
November 27, 2018
(The content was deleted per user request) - Anonymous
November 27, 2018
(The content was deleted per user request) - Anonymous
November 29, 2018
The comment has been removed - Anonymous
February 06, 2019
The comment has been removed