<# .SYNOPSIS Identify inactive mailboxes based on LastLogonTime .DESCRIPTION Finds mailboxes with no logon activity within specified days. Useful for identifying stale accounts, cost optimization, and cleanup planning. .PARAMETER InactiveDays Number of days without logon to consider mailbox inactive (default: 90) .PARAMETER OutputFolder Destination folder for reports. Default: .\InactiveMailboxes- .PARAMETER IncludeShared Include shared mailboxes in the report (default: $false) .PARAMETER IncludeSize Include mailbox size information (slower, default: $true) .NOTES ⚠️ AI-GENERATED SCRIPT - UNTESTED This script was generated by Claude AI and has not been tested in production. Review and test thoroughly in a non-production environment before use. - Run in Exchange Management Shell with appropriate RBAC permissions - LastLogonTime may be null for never-accessed mailboxes - Size calculation can be slow with large mailbox counts .EXAMPLE .\Get-InactiveMailboxes.ps1 .EXAMPLE .\Get-InactiveMailboxes.ps1 -InactiveDays 180 -IncludeShared $true #> [CmdletBinding()] param( [int]$InactiveDays = 90, [string]$OutputFolder = (Join-Path -Path (Get-Location) -ChildPath ("InactiveMailboxes-" + (Get-Date -Format "yyyyMMdd-HHmm"))), [bool]$IncludeShared = $false, [bool]$IncludeSize = $true ) function NowTag { (Get-Date).ToString("yyyy-MM-dd HH:mm:ss") } Write-Host "[$(NowTag)] ⚠️ AI-GENERATED SCRIPT - UNTESTED" -ForegroundColor Yellow Write-Host "[$(NowTag)] Searching for mailboxes inactive for $InactiveDays+ days..." -ForegroundColor Green # Create output folder New-Item -ItemType Directory -Path $OutputFolder -Force | Out-Null $cutoffDate = (Get-Date).AddDays(-$InactiveDays) Write-Host "[$(NowTag)] Cutoff date: $($cutoffDate.ToString('yyyy-MM-dd'))" # Get mailboxes Write-Host "[$(NowTag)] Retrieving mailboxes..." $filter = if ($IncludeShared) { { RecipientTypeDetails -notmatch "^Remote" -and Database } } else { { RecipientTypeDetails -eq "UserMailbox" -and Database } } $mailboxes = Get-Mailbox -ResultSize Unlimited -ErrorAction SilentlyContinue | Where-Object $filter $mbCount = ($mailboxes | Measure-Object).Count Write-Host "[$(NowTag)] Found $mbCount mailboxes to check" # Get statistics per database to avoid prompts Write-Host "[$(NowTag)] Retrieving mailbox statistics..." $statsByDb = @{} $databases = ($mailboxes.Database | Sort-Object -Unique) $dbIdx = 0 foreach ($db in $databases) { $dbIdx++ Write-Progress -Activity "Collecting Mailbox Statistics" -Status "Database $db ($dbIdx/$($databases.Count))" -PercentComplete ([int](($dbIdx / $databases.Count) * 100)) try { $stats = Get-MailboxStatistics -Database $db -ErrorAction SilentlyContinue foreach ($stat in $stats) { $statsByDb[$stat.DisplayName] = $stat } } catch { Write-Host "[$(NowTag)] WARNING: Could not get statistics for database $db" -ForegroundColor Yellow } } Write-Progress -Activity "Collecting Mailbox Statistics" -Completed # Analyze for inactive mailboxes Write-Host "[$(NowTag)] Analyzing mailbox activity..." $inactiveMailboxes = @() $current = 0 foreach ($mb in $mailboxes) { $current++ $pct = [int](($current / $mbCount) * 100) Write-Progress -Activity "Analyzing Mailboxes" -Status "Processing $($mb.DisplayName) ($current/$mbCount)" -PercentComplete $pct $stat = $statsByDb[$mb.DisplayName] $lastLogon = if ($stat) { $stat.LastLogonTime } else { $null } $isInactive = $false $reason = "" if ($null -eq $lastLogon) { $isInactive = $true $reason = "Never logged on" } elseif ($lastLogon -lt $cutoffDate) { $isInactive = $true $reason = "Last logon: $($lastLogon.ToString('yyyy-MM-dd'))" } if ($isInactive) { $daysSinceLogon = if ($lastLogon) { [int]((Get-Date) - $lastLogon).TotalDays } else { "N/A" } $size = $null $itemCount = $null if ($IncludeSize -and $stat) { $size = $stat.TotalItemSize $itemCount = $stat.ItemCount } $inactiveMailboxes += [PSCustomObject]@{ DisplayName = $mb.DisplayName PrimarySmtpAddress = $mb.PrimarySmtpAddress RecipientTypeDetails = $mb.RecipientTypeDetails Database = $mb.Database LastLogonTime = $lastLogon DaysSinceLogon = $daysSinceLogon WhenCreated = $mb.WhenCreated TotalItemSize = $size ItemCount = $itemCount Reason = $reason } } } Write-Progress -Activity "Analyzing Mailboxes" -Completed # Export results Write-Host "[$(NowTag)] Found $($inactiveMailboxes.Count) inactive mailboxes" if ($inactiveMailboxes.Count -gt 0) { $csvFile = Join-Path $OutputFolder "Inactive-Mailboxes.csv" $inactiveMailboxes | Sort-Object DaysSinceLogon -Descending | Export-Csv -NoTypeInformation -Encoding UTF8 -Path $csvFile Write-Host "[$(NowTag)] Report exported: $csvFile" -ForegroundColor Green # Summary by type $byType = $inactiveMailboxes | Group-Object RecipientTypeDetails Write-Host "`nInactive Mailboxes by Type:" foreach ($type in $byType) { Write-Host " $($type.Name): $($type.Count)" } # Calculate potential storage savings if ($IncludeSize) { $totalBytes = 0 foreach ($mb in $inactiveMailboxes) { if ($mb.TotalItemSize) { try { $sizeStr = [string]$mb.TotalItemSize if ($sizeStr -match "([\d\.,]+)\s*(KB|MB|GB|TB)") { $num = [double]($matches[1] -replace ',', '.') $bytes = switch ($matches[2].ToUpper()) { "KB" { $num * 1KB } "MB" { $num * 1MB } "GB" { $num * 1GB } "TB" { $num * 1TB } } $totalBytes += $bytes } } catch {} } } $totalGB = [math]::Round($totalBytes / 1GB, 2) Write-Host "`nPotential storage savings: $totalGB GB" -ForegroundColor Cyan } } else { Write-Host "[$(NowTag)] No inactive mailboxes found!" -ForegroundColor Green } Write-Host "`n[$(NowTag)] Analysis complete! Output folder: $OutputFolder"