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


36 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