Skip to main content

Get All AD Devices and Last Login Dates

A PowerShell script to export every computer in Active Directory with its last logon date, operating system, and status — essential for cleanup, compliance, and licence management.

5 min readUpdated March 29, 2026

Overviewsection

Knowing which devices are active in your Active Directory and when they last signed in is critical for security hygiene, licence management, and compliance audits. Stale computer accounts are a security risk — attackers can impersonate them, and compliance frameworks like CMMC require you to maintain an accurate device inventory.

This script exports every computer in AD with its last logon timestamp, OS version, and status to a CSV file.

Prerequisitessection

  • Active Directory PowerShell module (RSAT-AD-PowerShell)
  • Read access to AD computer objects
  • PowerShell 5.1 or later (PowerShell 7.6 LTS recommended)

The Scriptsection

powershell
<#
.SYNOPSIS
    Export all AD computers with last logon date and OS info.
.DESCRIPTION
    Queries Active Directory for all computer objects and exports
    a report with last logon, OS version, enabled status, and
    staleness classification.
.PARAMETER DaysInactive
    Number of days since last logon to classify as inactive. Default: 90.
.PARAMETER SearchBase
    OU to search. Defaults to entire domain.
.PARAMETER OutputPath
    Directory for the CSV file. Defaults to current directory.
#>

param(
    [int]$DaysInactive = 90,
    [string]$SearchBase = "",
    [string]$OutputPath = "."
)

Import-Module ActiveDirectory -ErrorAction Stop

$cutoffDate = (Get-Date).AddDays(-$DaysInactive)
$timestamp = Get-Date -Format "yyyy-MM-dd_HHmm"

Write-Host "Querying Active Directory computers..." -ForegroundColor Yellow

# Build query parameters
$queryParams = @{
    Filter     = '*'
    Properties = @(
        'LastLogonTimestamp',
        'LastLogonDate',
        'OperatingSystem',
        'OperatingSystemVersion',
        'Enabled',
        'Created',
        'Description',
        'DistinguishedName',
        'IPv4Address',
        'PasswordLastSet'
    )
}

if ($SearchBase) {
    $queryParams['SearchBase'] = $SearchBase
}

$computers = Get-ADComputer @queryParams

Write-Host "Found $($computers.Count) computer objects." -ForegroundColor Cyan

$results = foreach ($pc in $computers) {
    # LastLogonTimestamp is replicated (slight delay but consistent across DCs)
    # Convert from file time if present
    $lastLogon = if ($pc.LastLogonTimestamp) {
        [DateTime]::FromFileTime($pc.LastLogonTimestamp)
    } elseif ($pc.LastLogonDate) {
        $pc.LastLogonDate
    } else {
        $null
    }

    $daysSinceLogon = if ($lastLogon) {
        [math]::Round(((Get-Date) - $lastLogon).TotalDays, 0)
    } else {
        -1
    }

    $status = if (-not $pc.Enabled) {
        "Disabled"
    } elseif ($null -eq $lastLogon) {
        "Never logged in"
    } elseif ($lastLogon -lt $cutoffDate) {
        "Inactive ($daysSinceLogon days)"
    } else {
        "Active"
    }

    [PSCustomObject]@{
        Name              = $pc.Name
        Enabled           = $pc.Enabled
        Status            = $status
        LastLogon         = $lastLogon
        DaysSinceLogon    = $daysSinceLogon
        OperatingSystem   = $pc.OperatingSystem
        OSVersion         = $pc.OperatingSystemVersion
        IPv4Address       = $pc.IPv4Address
        Created           = $pc.Created
        PasswordLastSet   = $pc.PasswordLastSet
        Description       = $pc.Description
        OU                = ($pc.DistinguishedName -replace '^CN=[^,]+,', '')
    }
}

# Sort by last logon (most stale first)
$sorted = $results | Sort-Object DaysSinceLogon -Descending

# Export
$filename = "AD-Devices_$timestamp.csv"
$filepath = Join-Path $OutputPath $filename
$sorted | Export-Csv -Path $filepath -NoTypeInformation -Encoding UTF8

# Summary
$total = $sorted.Count
$active = ($sorted | Where-Object { $_.Status -eq "Active" }).Count
$inactive = ($sorted | Where-Object { $_.Status -like "Inactive*" }).Count
$disabled = ($sorted | Where-Object { $_.Status -eq "Disabled" }).Count
$neverLoggedIn = ($sorted | Where-Object { $_.Status -eq "Never logged in" }).Count

Write-Host "`n--- Summary ---" -ForegroundColor Cyan
Write-Host "Total:           $total"
Write-Host "Active:          $active" -ForegroundColor Green
Write-Host "Inactive (>$DaysInactive days): $inactive" -ForegroundColor Yellow
Write-Host "Disabled:        $disabled" -ForegroundColor Gray
Write-Host "Never logged in: $neverLoggedIn" -ForegroundColor Red
Write-Host "`nExported to: $filepath" -ForegroundColor Green

# Also output the top 20 most stale active computers
$staleActive = $sorted | Where-Object { $_.Enabled -and $_.DaysSinceLogon -gt $DaysInactive } | Select-Object -First 20
if ($staleActive) {
    Write-Host "`n--- Top 20 Stale Active Computers (candidates for cleanup) ---" -ForegroundColor Yellow
    $staleActive | Format-Table Name, DaysSinceLogon, OperatingSystem, OU -AutoSize
}

Usagesection

Basic export (entire domain, 90-day threshold)section

powershell
.\Get-ADDeviceReport.ps1

Custom inactivity thresholdsection

powershell
.\Get-ADDeviceReport.ps1 -DaysInactive 60

Scan a specific OUsection

powershell
.\Get-ADDeviceReport.ps1 -SearchBase "OU=Workstations,DC=corp,DC=example,DC=com"

Export to a network sharesection

powershell
.\Get-ADDeviceReport.ps1 -OutputPath "\\server\reports"

Output Examplesection

NameEnabledStatusLastLogonDaysSinceLogonOperatingSystem
OLD-PC01TrueInactive (342 days)2025-04-22342Windows 10 Pro
CONF-ROOMTrueNever logged in-1Windows 11 Enterprise
WS-ABC123TrueActive2026-03-281Windows 11 Pro
LT-JSMITHFalseDisabled2025-12-15104Windows 11 Pro

Automating Cleanupsection

Disable stale computers (preview first)section

powershell
# Preview — shows what would be disabled
$stale = Get-ADComputer -Filter {LastLogonTimestamp -lt $cutoffDate -and Enabled -eq $true} -Properties LastLogonTimestamp
$stale | Select-Object Name, @{N='LastLogon';E={[DateTime]::FromFileTime($_.LastLogonTimestamp)}}

# Disable (uncomment when ready)
# $stale | Disable-ADAccount
# $stale | Set-ADComputer -Description "Disabled by cleanup script $(Get-Date -Format 'yyyy-MM-dd')"
Warning

Always preview before disabling. Some computers (conference room displays, kiosks, rarely-used lab machines) may have legitimate long gaps between sign-ins. Move disabled accounts to a "Disabled" OU and wait 30 days before deleting.

Schedule monthly reportssection

powershell
$action = New-ScheduledTaskAction -Execute "powershell.exe" -Argument "-ExecutionPolicy Bypass -File C:\Scripts\Get-ADDeviceReport.ps1 -OutputPath C:\Reports"
$trigger = New-ScheduledTaskTrigger -Monthly -DaysOfMonth 1 -At 7am
Register-ScheduledTask -TaskName "Monthly AD Device Report" -Action $action -Trigger $trigger -RunLevel Highest

Why This Matterssection

Use CaseWhat the Report Tells You
SecurityStale accounts can be exploited for lateral movement
ComplianceCMMC, NIST, and CIS require an accurate asset inventory
LicensingInactive devices may hold Windows or M365 licences unnecessarily
CleanupReducing AD clutter improves Group Policy processing time
AuditingProvides evidence of asset management for auditors
PowerShellActive DirectoryReportingAutomation

Was this article helpful?