PowerShell Script to Find and Clean Up Orphaned/Inactive Computer Objects in Active Directory


2 views

Orphaned computer objects are machine accounts that exist in Active Directory but no longer correspond to actual physical or virtual machines in your environment. These typically occur when:

  • Machines are decommissioned without proper AD cleanup
  • OS reinstalls create new computer objects while old ones remain
  • VMs are deleted without removing their AD accounts

Here's a comprehensive PowerShell script that identifies computers inactive for specified days:


# Parameters
$DaysInactive = 90
$SearchBase = "OU=Computers,DC=domain,DC=com"
$ReportPath = "C:\ADReports\InactiveComputers.csv"

# Calculate cutoff date
$CutoffDate = (Get-Date).AddDays(-$DaysInactive)

# Get all computer objects
$Computers = Get-ADComputer -Filter * -Properties LastLogonDate,OperatingSystem,Description -SearchBase $SearchBase

# Filter inactive computers
$InactiveComputers = $Computers | Where-Object {
    $_.LastLogonDate -lt $CutoffDate -or $_.LastLogonDate -eq $null
}

# Export results
$InactiveComputers | Select-Object Name,DistinguishedName,LastLogonDate,OperatingSystem | 
Export-Csv $ReportPath -NoTypeInformation

To ensure we don't accidentally target still-active computers:


# Ping verification
$VerifiedInactive = @()
foreach ($Computer in $InactiveComputers) {
    if (-not (Test-Connection -ComputerName $Computer.Name -Count 1 -Quiet)) {
        $VerifiedInactive += $Computer
    }
}

For more complex environments:


# Multi-domain forest search
$AllDomains = (Get-ADForest).Domains
$AllInactive = @()

foreach ($Domain in $AllDomains) {
    $Inactive = Get-ADComputer -Server $Domain -Filter {
        LastLogonDate -lt $CutoffDate -or 
        LastLogonDate -notlike "*"
    } -Properties *
    $AllInactive += $Inactive
}

Before deleting, consider these safety measures:


# Move to quarantine OU first
$QuarantineOU = "OU=DisabledComputers,DC=domain,DC=com"
$InactiveComputers | ForEach-Object {
    Move-ADObject -Identity $_.DistinguishedName -TargetPath $QuarantineOU
    Disable-ADAccount -Identity $_.DistinguishedName
}

# Optional: Delete after verification
Start-Sleep -Days 14 # Wait period
Get-ADComputer -Filter * -SearchBase $QuarantineOU | Remove-ADObject -Confirm:$false

Create a scheduled task for regular maintenance:


$Trigger = New-ScheduledTaskTrigger -Weekly -DaysOfWeek Sunday -At 2am
$Action = New-ScheduledTaskAction -Execute "PowerShell.exe" -Argument "-File C:\Scripts\CleanInactiveComputers.ps1"
Register-ScheduledTask -TaskName "AD Computer Cleanup" -Trigger $Trigger -Action $Action -RunLevel Highest

Orphaned computer objects in Active Directory are those that no longer have a corresponding physical machine in your network. These can accumulate over time and clutter your AD. Here's how to find them using PowerShell:


# Import Active Directory module
Import-Module ActiveDirectory

# Find computer objects that haven't logged on in 90 days
$inactiveDays = 90
$cutoffDate = (Get-Date).AddDays(-$inactiveDays)

# Get all computer objects that haven't logged on since cutoff date
$orphanedComputers = Get-ADComputer -Filter {LastLogonTimeStamp -lt $cutoffDate} -Properties LastLogonTimeStamp, OperatingSystem, Enabled |
    Where-Object { $_.Enabled -eq $true } |
    Select-Object Name, DistinguishedName, LastLogonTimeStamp, OperatingSystem

# Output results
$orphanedComputers | Format-Table -AutoSize

The LastLogonTimeStamp attribute isn't replicated immediately across all domain controllers. For more accurate results, you might want to query all DCs:


function Get-ADComputerLastLogon {
    param(
        [string]$ComputerName,
        [int]$DaysInactive = 90
    )
    
    $cutoffDate = (Get-Date).AddDays(-$DaysInactive)
    $domainControllers = Get-ADDomainController -Filter *
    
    $lastLogons = foreach ($dc in $domainControllers) {
        try {
            Get-ADComputer -Identity $ComputerName -Server $dc.HostName -Properties LastLogon |
                Select-Object @{Name='DC';Expression={$dc.HostName}}, LastLogon
        }
        catch {
            Write-Warning "Failed to query $($dc.HostName) for $ComputerName"
        }
    }
    
    $mostRecent = $lastLogons | Sort-Object LastLogon -Descending | Select-Object -First 1
    
    if ($mostRecent.LastLogon -lt $cutoffDate) {
        return $true
    }
    else {
        return $false
    }
}

You can extend the basic query to include additional criteria:


# Find computers inactive for 180 days that are not in specific OUs
$excludedOUs = @(
    "OU=Servers,DC=domain,DC=com",
    "OU=Workstations,DC=domain,DC=com"
)

$inactiveComputers = Get-ADComputer -Filter {LastLogonTimeStamp -lt $cutoffDate} -Properties * |
    Where-Object {
        $_.Enabled -eq $true -and
        $excludedOUs -notcontains $_.DistinguishedName -and
        $_.OperatingSystem -notlike "*Server*"
    } |
    Select-Object Name, DistinguishedName, LastLogonTimeStamp, OperatingSystem, Enabled

For larger environments, you'll want to export the results:


# Export to CSV with timestamp
$exportPath = "C:\ADReports\InactiveComputers_$(Get-Date -Format 'yyyyMMdd').csv"
$inactiveComputers | Export-Csv -Path $exportPath -NoTypeInformation

# Optional: Email the report
$emailParams = @{
    From = "adadmin@domain.com"
    To = "it-team@domain.com"
    Subject = "Inactive Computer Objects Report"
    Body = "Attached is the report of computer objects inactive for $inactiveDays days."
    SmtpServer = "smtp.domain.com"
    Attachments = $exportPath
}

Send-MailMessage @emailParams

After verifying the results, you can disable or delete inactive objects:


# Disable inactive computers
$inactiveComputers | ForEach-Object {
    Disable-ADAccount -Identity $_.DistinguishedName
    Write-Host "Disabled computer: $($_.Name)"
}

# Or move to a quarantine OU first
$quarantineOU = "OU=Disabled Computers,DC=domain,DC=com"
$inactiveComputers | ForEach-Object {
    Move-ADObject -Identity $_.DistinguishedName -TargetPath $quarantineOU
}
  • Always test queries in a non-production environment first
  • Consider creating a staging OU before deletion
  • Maintain documentation of your cleanup process
  • Schedule regular reports rather than immediate deletion
  • Check for associated DNS records and other dependencies