Wes Ellis./ a personal notebook
Technology. Stories. Side projects.
A few things worth writing down.
← Back to Script Library

SCRIPT LIBRARY · POWERSHELL

Exporting Active Directory Computer Objects to CSV

A computer inventory straight out of AD, with OS build, last logon, password age and OU in one tidy CSV, ready for audits or a stale-machine cleanup.

AT A GLANCEExport-ADComputerInventory.ps1
What it does
Exports computer accounts from AD to CSV with OS and build, enabled state, last logon, days since last logon, machine password age, OU and description. Optionally resolves IP addresses too.
Requires
  • Windows PowerShell 5.1 or PowerShell 7 on Windows
  • ActiveDirectory module (RSAT)
Permissions
Any domain user can read these attributes by default.
Runs on
Windows 10/11 with RSAT, Windows Server 2016+
Tested
Parse-checked and dry-run with mocked AD cmdlets in PowerShell 7.4

Part 4 of the thread Keeping Active Directory tidy

Every environment has a few hundred computer accounts that nobody can vouch for. Some are real machines, some were reimaged under a new name two years ago, and at least one is a laptop sitting in a desk drawer with a dead battery.

This script gives you the list to start from. One row per computer, with the columns you actually end up sorting on: operating system and build, when it last logged on and how many days ago that was, how old its machine password is, which OU it's in, and whether it's enabled. It's the same export I'd reach for before a cleanup, an OS upgrade project, or an audit request.

Compared to the original version: the domain controller and output folder aren't hard-coded anymore, it adds the two "days since" columns that make stale machines jump out, and it drops a few attributes that were always blank on computers anyway. IP lookups are now optional, because they're slow and they come from DNS, not AD.

Export-ADComputerInventory.ps1Download
<#
.SYNOPSIS
    Exports Active Directory computer objects to CSV with the attributes you actually use.
.DESCRIPTION
    Pulls computer accounts from AD and flattens them into one row each: name, OS and build,
    enabled state, last logon, days since last logon, password age, OU and description.
    Handy for inventory, audits, and finding stale machines before a cleanup.
.PARAMETER SearchBase
    Only include computers under this OU. Defaults to the whole domain.
.PARAMETER CsvPath
    Output file. Defaults to ad-computers-<date>.csv in the current folder.
.PARAMETER EnabledOnly
    Leave disabled computer accounts out.
.PARAMETER IncludeIPAddress
    Resolve each computer's IPv4 address through DNS. Noticeably slower.
.PARAMETER Server
    Domain or domain controller to query.
.PARAMETER PassThru
    Return the rows as objects as well as writing the CSV.
.EXAMPLE
    .\Export-ADComputerInventory.ps1
.EXAMPLE
    .\Export-ADComputerInventory.ps1 -SearchBase 'OU=Workstations,DC=contoso,DC=com' -EnabledOnly -PassThru | Where-Object DaysSinceLogon -gt 90
#>
[CmdletBinding()]
param(
    [string]$SearchBase,
    [string]$CsvPath = (Join-Path (Get-Location) ('ad-computers-{0:yyyy-MM-dd}.csv' -f (Get-Date))),
    [switch]$EnabledOnly,
    [switch]$IncludeIPAddress,
    [string]$Server,
    [switch]$PassThru
)

$ErrorActionPreference = 'Stop'
Import-Module ActiveDirectory

$query = @{
    Filter     = if ($EnabledOnly) { 'Enabled -eq $true' } else { '*' }
    Properties = 'CanonicalName', 'Description', 'DNSHostName', 'LastLogonDate', 'OperatingSystem', 'OperatingSystemVersion', 'PasswordLastSet', 'whenCreated', 'ManagedBy'
}
if ($SearchBase) { $query.SearchBase = $SearchBase }
if ($Server)     { $query.Server = $Server }
if ($IncludeIPAddress) { $query.Properties += 'IPv4Address' }

$computers = Get-ADComputer @query
$now = Get-Date
Write-Verbose "Found $(@($computers).Count) computer objects"

$rows = foreach ($pc in $computers) {
    $row = [ordered]@{
        Name              = $pc.Name
        DNSHostName       = $pc.DNSHostName
        Enabled           = $pc.Enabled
        OperatingSystem   = $pc.OperatingSystem
        OSVersion         = $pc.OperatingSystemVersion
        LastLogonDate     = $pc.LastLogonDate
        DaysSinceLogon    = if ($pc.LastLogonDate) { [int]($now - $pc.LastLogonDate).TotalDays } else { $null }
        PasswordLastSet   = $pc.PasswordLastSet
        PasswordAgeDays   = if ($pc.PasswordLastSet) { [int]($now - $pc.PasswordLastSet).TotalDays } else { $null }
        Created           = $pc.whenCreated
        Description       = $pc.Description
        ManagedBy         = $pc.ManagedBy
        OU                = ($pc.CanonicalName -replace '/[^/]+$', '')
        DistinguishedName = $pc.DistinguishedName
    }
    if ($IncludeIPAddress) { $row.IPv4Address = $pc.IPv4Address }
    [pscustomobject]$row
}

$rows = @($rows | Sort-Object Name)
$rows | Export-Csv -Path $CsvPath -NoTypeInformation -Encoding UTF8
Write-Host "Exported $($rows.Count) computers to $CsvPath"
if ($PassThru) { $rows }

Parameters

ParameterTypeDefaultWhat it's for
-SearchBasestringwhole domainOnly include computers under this OU.
-CsvPathstring.\ad-computers-<date>.csvWhere to save the CSV.
-EnabledOnlyswitch—Skip disabled computer accounts.
-IncludeIPAddressswitch—Add an IPv4Address column. The AD module looks each one up in DNS, so this is a lot slower on big domains.
-Serverstring—Domain name or domain controller to query.
-PassThruswitch—Return the rows as objects as well as saving the CSV.

Run it

The whole domain, saved to a dated CSV in the current folder.

.\Export-ADComputerInventory.ps1

Just the workstations, enabled ones only.

.\Export-ADComputerInventory.ps1 -SearchBase 'OU=Workstations,DC=contoso,DC=com' -EnabledOnly

Machines that haven't logged on in 90 days. Your cleanup shortlist.

.\Export-ADComputerInventory.ps1 -EnabledOnly -PassThru | Where-Object DaysSinceLogon -gt 90 | Sort-Object DaysSinceLogon -Descending

How many of each OS build are still out there.

.\Export-ADComputerInventory.ps1 -PassThru | Group-Object OperatingSystem, OSVersion | Sort-Object Count -Descending | Select-Object Count, Name

What you'll see

Example outputvalues are illustrative
Exported 1,284 computers to C:\Reports\ad-computers-2026-09-29.csv

Name     OperatingSystem         OSVersion     LastLogonDate DaysSinceLogon PasswordAgeDays OU
----     ---------------         ---------     ------------- -------------- --------------- --
PC-0142  Windows 11 Enterprise   10.0 (26100)  9/26/2026                  3              20 contoso.com/Corp/Devices/Laptops
PC-0187  Windows 11 Enterprise   10.0 (22631)  9/12/2026                 17              11 contoso.com/Corp/Devices/Laptops
PC-0311  Windows 10 Enterprise   10.0 (19045)  2/3/2026                 238             238 contoso.com/Corp/Devices/Desktops
SRV-FS01 Windows Server 2022 ... 10.0 (20348)  9/28/2026                  1              14 contoso.com/Servers/File

How it works

  1. Build the query. Filter on enabled accounts if you asked for it, add a search base and server if you gave them, and request just the properties the report needs. Asking for specific properties instead of -Properties * keeps big exports fast.
  2. Shape each computer into a row. The script calculates DaysSinceLogon and PasswordAgeDays from the dates, and trims the computer's own name off its canonical name to get a readable OU path.
  3. Sort and save. Rows are sorted by name and written to CSV as UTF-8, which keeps non-English characters in descriptions intact.
  4. Hand the rows back if you want them. With -PassThru you can filter, group or pipe the results without opening the CSV at all.

Take it further

  • Clean up what you find. Once you've got a shortlist of stale machines, this script deletes or disables stale computer accounts with -WhatIf support and a CSV log.
  • Compare against Intune or your RMM. Join this CSV to an Intune device export on computer name to find machines that exist in one place and not the other.
  • Schedule it. Run it weekly from a scheduled task and keep the files. When someone asks when a machine disappeared, you'll have an answer.

Things that'll trip you up

  • Last logon is only accurate to about two weeks. LastLogonDate comes from lastLogonTimestamp, which AD deliberately updates only every 9 to 14 days to save on replication. Great for "has this been gone for months?", useless for "did it log on yesterday?"
  • The OS version is the real tell. The OperatingSystem attribute is whatever the machine last reported, and the build number in OperatingSystemVersion is how you tell Windows 11 24H2 (26100) from 23H2 (22631) or Windows 10 22H2 (19045).
  • Password age is a second opinion. Domain-joined Windows machines change their account password every 30 days by default. A password that's months old on an enabled machine means it hasn't talked to a DC in a long time, even if something else touched its logon timestamp.
  • Opening the CSV in Excel. Excel will happily reformat dates and drop leading zeros. If the columns look odd, use Data > From Text/CSV instead of double-clicking the file.