Published on

How to Find Inactive Computer Accounts in Active Directory (PowerShell Script)

Every AD environment accumulates computer accounts for machines that no longer exist — retired laptops, re-imaged servers that rejoined under a new name, VMs that got torn down without anyone running Remove-ADComputer first. They don't cause outages, so they don't get urgent attention, but they skew every inventory report and add dead weight to group policy processing.

The quick answer

Get-ADComputer -Filter {Enabled -eq $true} -Properties LastLogonDate |
    Where-Object { -not $_.LastLogonDate -or $_.LastLogonDate -lt (Get-Date).AddDays(-90) }

A more useful reporting script

#requires -Modules ActiveDirectory
<#
.SYNOPSIS
    Lists stale Active Directory computer accounts.

.DESCRIPTION
    Queries Active Directory for enabled computer accounts whose
    LastLogonDate is older than the given threshold (or that have never
    logged on), optionally scoped to an OU, and optionally exporting the
    results to CSV. These are typically decommissioned machines that were
    never cleaned up in AD, which bloat group policy processing and skew
    inventory counts. Uses the current logged-on user's credentials — run it
    from a domain-joined machine with RSAT installed.

.PARAMETER Days
    Flag computers that haven't logged on in at least this many days.
    Defaults to 90.

.PARAMETER SearchBase
    Distinguished name of the OU to search (e.g. "OU=Workstations,DC=contoso,DC=com").
    If omitted, searches the entire domain.

.PARAMETER OutputCsv
    Path to a CSV file to export the results to. If omitted, results are only
    written to the console.

.EXAMPLE
    .\Find-InactiveADComputers.ps1

    Lists enabled computer accounts that haven't logged on in 90+ days.

.EXAMPLE
    .\Find-InactiveADComputers.ps1 -Days 180 -OutputCsv .\stale-computers.csv

    Lists computer accounts inactive for 180+ days and exports to CSV.
#>

[CmdletBinding()]
param(
    [int]$Days = 90,

    [string]$SearchBase,

    [string]$OutputCsv
)

Import-Module ActiveDirectory -ErrorAction Stop

$cutoff = (Get-Date).AddDays(-$Days)

$params = @{
    Filter     = { Enabled -eq $true }
    Properties = @('LastLogonDate', 'OperatingSystem', 'DistinguishedName')
}
if ($SearchBase) {
    $params['SearchBase'] = $SearchBase
}

$inactiveComputers = Get-ADComputer @params |
    Where-Object { -not $_.LastLogonDate -or $_.LastLogonDate -lt $cutoff } |
    Select-Object Name, OperatingSystem, LastLogonDate, DistinguishedName |
    Sort-Object @{Expression = { $_.LastLogonDate } }

if (-not $inactiveComputers) {
    Write-Host "No inactive computer accounts found (threshold: $Days days)." -ForegroundColor Yellow
    return
}

Write-Host "Found $(@($inactiveComputers).Count) inactive computer account(s) (no logon in $Days+ days)." -ForegroundColor Cyan
$inactiveComputers | Format-Table Name, OperatingSystem, LastLogonDate -AutoSize

if ($OutputCsv) {
    $inactiveComputers | Export-Csv -Path $OutputCsv -NoTypeInformation
    Write-Host "Exported results to $OutputCsv" -ForegroundColor Green
}

Before you delete anything

A stale computer account isn't automatically safe to delete — some devices (kiosk machines, seasonal lab equipment, systems that get powered on quarterly for a specific job) are legitimately dormant on purpose. Treat this list as a review queue, not a deletion queue: confirm with the OU owner or asset inventory before disabling or removing anything it flags.

Where the manual approach runs out of road

A one-off script is fine for a single check. It starts to hurt once you actually need to run this regularly:

  • No scheduling. Cron/Task Scheduler can run the script, but now you own the scheduling, the credentials it runs as, and what happens when it silently fails.
  • No history. A CSV export is a snapshot. Was this the same five accounts as last month, or a growing list? A single export can't tell you.
  • No distribution. Getting the report to the right people (security, IT ops, compliance) on a schedule means building that plumbing yourself.
  • Multi-domain/multi-forest pain. Run it once per domain, reconcile the results yourself, and hope naming/OU conventions are consistent across all of them.
  • No alerting. If something changes unexpectedly between runs — an account re-enabled, a privileged group gaining a member — nothing tells you until you happen to run the script again.

None of that is a PowerShell problem. It's what turns a script into a product.

What SysFlint AD does instead

Our SysFlint AD runs the same kind of discovery shown above automatically, on a schedule, entirely inside your own network. No agents on domain controllers, no data leaving your environment. It's free, forever.

If you're currently doing this with a script on a scheduled task, here's the honest comparison between the two, or go straight to the download page.

Get this script, and the rest of them

The script above is part of awesome-it-scripts, SysFlint's free, open-source library of PowerShell scripts for Active Directory and other IT-admin tasks — no signup, no catch, MIT licensed. Grab this one directly from active-directory/Find-InactiveADComputers.ps1, or browse the whole thing.

Find every disabled/locked-out/stale/privileged-account script we've published (plus the FAQ that goes with each one) in the repo's active-directory folder. Star it if it's useful — new scripts land there regularly.