ActiveDirectory/Get-StaleComputers.ps1
|
<#
.SYNOPSIS Finds Active Directory computer accounts that have not authenticated recently. .DESCRIPTION Queries Active Directory for computer accounts whose last logon timestamp exceeds the specified inactivity threshold. Useful for identifying stale machine accounts during security reviews, AD cleanups, and compliance audits. Requires the ActiveDirectory module (available via RSAT or on domain controllers). .PARAMETER DaysInactive Number of days since last logon to consider a computer stale. Defaults to 90. .PARAMETER SearchBase Optional distinguished name of the OU to search. If not specified, searches the entire domain. .PARAMETER IncludeDisabled Include disabled computer accounts in the results. By default only enabled accounts are returned. .PARAMETER OutputPath Optional path to export results as CSV. If not specified, results are returned to the pipeline. .EXAMPLE PS> .\ActiveDirectory\Get-StaleComputers.ps1 -DaysInactive 90 Lists all enabled computer accounts that have not logged on in 90+ days. .EXAMPLE PS> .\ActiveDirectory\Get-StaleComputers.ps1 -DaysInactive 60 -SearchBase 'OU=Workstations,DC=contoso,DC=com' Searches only the Workstations OU for computers inactive for 60+ days. .EXAMPLE PS> .\ActiveDirectory\Get-StaleComputers.ps1 -DaysInactive 30 -IncludeDisabled -OutputPath '.\stale-computers.csv' Exports all stale computers (including disabled) to CSV. #> [CmdletBinding()] param( [Parameter()] [ValidateRange(1, 365)] [int]$DaysInactive = 90, [Parameter()] [string]$SearchBase, [Parameter()] [switch]$IncludeDisabled, [Parameter()] [string]$OutputPath ) $ErrorActionPreference = 'Stop' # Verify ActiveDirectory module is available if (-not (Get-Module -Name ActiveDirectory -ListAvailable)) { Write-Error "The ActiveDirectory module is not installed. Install RSAT or run from a domain controller." return } Import-Module -Name ActiveDirectory -ErrorAction Stop $cutoffDate = (Get-Date).AddDays(-$DaysInactive) Write-Verbose "Searching for computers inactive since $($cutoffDate.ToString('yyyy-MM-dd'))" $adParams = @{ Filter = "LastLogonTimestamp -lt '$($cutoffDate.ToFileTime())' -or LastLogonTimestamp -notlike '*'" Properties = @('LastLogonTimestamp', 'OperatingSystem', 'OperatingSystemVersion', 'Description', 'Enabled', 'WhenCreated', 'DistinguishedName') } if ($SearchBase) { $adParams['SearchBase'] = $SearchBase Write-Verbose "Scoped to: $SearchBase" } try { $computers = Get-ADComputer @adParams } catch { Write-Error "Failed to query Active Directory: $_" return } # Filter by enabled status unless IncludeDisabled is set if (-not $IncludeDisabled) { $computers = $computers | Where-Object { $_.Enabled -eq $true } } $results = foreach ($computer in $computers) { $lastLogon = if ($computer.LastLogonTimestamp) { [DateTime]::FromFileTime($computer.LastLogonTimestamp) } else { $null } $daysSince = if ($lastLogon) { [math]::Round(((Get-Date) - $lastLogon).TotalDays) } else { 'Never' } [PSCustomObject]@{ Name = $computer.Name Enabled = $computer.Enabled OperatingSystem = $computer.OperatingSystem OperatingSystemVersion = $computer.OperatingSystemVersion LastLogon = $lastLogon DaysSinceLogon = $daysSince Description = $computer.Description WhenCreated = $computer.WhenCreated DistinguishedName = $computer.DistinguishedName } } $results = @($results) | Sort-Object -Property DaysSinceLogon -Descending Write-Verbose "Found $($results.Count) stale computer accounts" if ($OutputPath) { $results | Export-Csv -Path $OutputPath -NoTypeInformation -Encoding UTF8 Write-Output "Exported $($results.Count) stale computers to $OutputPath" } else { Write-Output $results } |