February 27, 2026 powershell
Remove Old User Profiles
PowerShell script to remove stale user profiles from Windows machines.
Remove Old User Profiles
This script removes user profiles older than a specified number of days from Windows machines.
Usage
.\Remove-OldProfiles.ps1 -DaysOld 90 -WhatIf
Parameters
- DaysOld: Number of days since last logon (default: 90)
- WhatIf: Preview what would be deleted without actually deleting
- Force: Skip confirmation prompts
Script
<#
.SYNOPSIS
Remove old user profiles from Windows machines.
.DESCRIPTION
This script removes user profiles that haven't been logged into for a specified number of days.
Supports -WhatIf mode for safe testing.
.PARAMETER DaysOld
Number of days since last logon (default: 90)
.PARAMETER WhatIf
Preview what would be deleted without actually deleting
.PARAMETER Force
Skip confirmation prompts
.EXAMPLE
.\Remove-OldProfiles.ps1 -DaysOld 90 -WhatIf
.EXAMPLE
.\Remove-OldProfiles.ps1 -DaysOld 60 -Force
#>
[CmdletBinding(SupportsShouldProcess)]
param(
[Parameter(Mandatory=$false)]
[int]$DaysOld = 90,
[Parameter(Mandatory=$false)]
[switch]$Force
)
$LogFile = "$env:TEMP\ProfileCleanup_$(Get-Date -Format 'yyyyMMdd_HHmmss').log"
function Write-Log {
param([string]$Message)
$Timestamp = Get-Date -Format 'yyyy-MM-dd HH:mm:ss'
$LogMessage = "[$Timestamp] $Message"
Write-Host $LogMessage
Add-Content -Path $LogFile -Value $LogMessage
}
Write-Log "Starting profile cleanup. DaysOld: $DaysOld"
# Get all user profiles
$Profiles = Get-CimInstance -ClassName Win32_UserProfile |
Where-Object { $_.LocalPath -notmatch '^(C:|D:|E:|F:)\\Users\\(Public|Default|Administrator|System)' }
$CutoffDate = (Get-Date).AddDays(-$DaysOld)
$ToDelete = @()
foreach ($Profile in $Profiles) {
$LastUse = $Profile.LastUseTime
$Username = $Profile.LocalPath -replace '.*\\', ''
if ($LastUse -and $LastUse -lt $CutoffDate) {
$ToDelete += [PSCustomObject]@{
Username = $Username
LastUsed = $LastUse
Path = $Profile.LocalPath
DaysSince = ((Get-Date) - $LastUse).Days
}
}
}
if ($ToDelete.Count -eq 0) {
Write-Log "No profiles older than $DaysOld days found."
exit 0
}
Write-Log "Found $($ToDelete.Count) profiles to remove:"
$ToDelete | ForEach-Object { Write-Log " - $($_.Username) (last used: $($_.LastUsed), $($_.DaysSince) days ago)" }
if ($WhatIfPreference -or $PSCmdlet.ShouldProcess("Delete $($ToDelete.Count) profiles")) {
if ($Force -or $PSCmdlet.ShouldContinue("Delete $($ToDelete.Count) old profiles?", "Confirm Deletion")) {
foreach ($Profile in $ToDelete) {
try {
# Remove the profile using WMI
$Profile | Remove-CimInstance -ErrorAction Stop
Write-Log "DELETED: $($Profile.Username)"
}
catch {
Write-Log "ERROR deleting $($Profile.Username): $_"
}
}
}
}
Write-Log "Profile cleanup complete. Log saved to: $LogFile"
Important Notes
- Always run with -WhatIf first to see what will be deleted
- Excluded accounts: Public, Default, Administrator, System profiles are always preserved
- Requires admin rights to delete profiles
- Logged actions are saved to a log file in TEMP
Automation
To run as a scheduled task:
$Action = New-ScheduledTaskAction -Execute 'Powershell.exe' -Argument '-NoProfile -ExecutionPolicy Bypass -File C:\Scripts\Remove-OldProfiles.ps1 -DaysOld 90 -Force'
$Trigger = New-ScheduledTaskTrigger -Weekly -DaysOfWeek Sunday -At 3AM
Register-ScheduledTask -TaskName 'Cleanup Old Profiles' -Action $Action -Trigger $Trigger -RunLevel Highest