<# .SYNOPSIS Finds and removes Outlook .ost files that haven't been written to in a while. .DESCRIPTION Looks in the Outlook data folder for the current user, or every local profile with -AllProfiles, and removes .ost files whose last write is older than -Days. Any file that's open (Outlook running, a search indexer holding it) is skipped rather than fought with. Returns one object per file, so you can review before you commit. Supports -WhatIf, and you should use it first. .PARAMETER Days Minimum age, in days since the file was last written. Default: 60. .PARAMETER AllProfiles Check every local user profile, not just yours. Needs to run elevated. .PARAMETER MinimumSizeMB Ignore files smaller than this. Handy when you only care about the big ones. Default: 0. .EXAMPLE .\Remove-StaleOstFile.ps1 -WhatIf .EXAMPLE .\Remove-StaleOstFile.ps1 -AllProfiles -Days 90 -MinimumSizeMB 500 #> [CmdletBinding(SupportsShouldProcess)] param( [ValidateRange(7, 3650)] [int]$Days = 60, [switch]$AllProfiles, [ValidateRange(0, 1048576)] [int]$MinimumSizeMB = 0 ) $cutoff = (Get-Date).AddDays(-$Days) $outlookFolders = if ($AllProfiles) { Get-CimInstance -ClassName Win32_UserProfile -Filter 'Special = FALSE' | ForEach-Object { Join-Path $_.LocalPath 'AppData\Local\Microsoft\Outlook' } } else { Join-Path $env:LOCALAPPDATA 'Microsoft\Outlook' } foreach ($folder in $outlookFolders) { if (-not (Test-Path -LiteralPath $folder)) { continue } # LastWriteTime, not LastAccessTime. Access times are often not updated on modern # Windows, and an antivirus scan can bump them anyway. $candidates = Get-ChildItem -LiteralPath $folder -Filter '*.ost' -File -Force -ErrorAction SilentlyContinue | Where-Object { $_.LastWriteTime -lt $cutoff -and $_.Length -ge ($MinimumSizeMB * 1MB) } foreach ($file in $candidates) { $row = [pscustomobject]@{ Path = $file.FullName SizeMB = [math]::Round($file.Length / 1MB, 1) LastWriteTime = $file.LastWriteTime Action = $null } # If we can't get an exclusive handle, something is using it. Leave it be. try { $stream = [System.IO.File]::Open($file.FullName, 'Open', 'ReadWrite', 'None') $stream.Dispose() } catch { $row.Action = 'SkippedInUse' $row continue } if ($PSCmdlet.ShouldProcess($file.FullName, "Delete OST ($($row.SizeMB) MB, last written $($file.LastWriteTime.ToString('yyyy-MM-dd')))")) { try { Remove-Item -LiteralPath $file.FullName -Force -Confirm:$false -ErrorAction Stop $row.Action = 'Removed' } catch { $row.Action = "Failed: $($_.Exception.Message)" } } else { $row.Action = if ($WhatIfPreference) { 'WouldRemove' } else { 'Skipped' } } $row } }