To delete files older than 30 days with PowerShell, list the files with Get-ChildItem, keep the ones whose LastWriteTime is before a cutoff date, and pipe them to Remove-Item. Run it with -WhatIf first:
$cutoff = ([datetime]'2026-09-28').AddDays(-30)
Get-ChildItem -Path C:\psfaqs\Cleanup -File -Recurse |
Where-Object LastWriteTime -LT $cutoff |
Remove-Item -WhatIf
Output:
What if: Performing the operation "Remove File" on target "C:\psfaqs\Cleanup\app-2026-07-01.log".
What if: Performing the operation "Remove File" on target "C:\psfaqs\Cleanup\app-2026-08-15.log".
What if: Performing the operation "Remove File" on target "C:\psfaqs\Cleanup\export-2026-07-20.csv".
What if: Performing the operation "Remove File" on target "C:\psfaqs\Cleanup\Archive\2026-06\old.zip".

In a real script, set the cutoff with (Get-Date).AddDays(-30). I used a fixed date and a sample folder so the output stays the same, and I ran every example in PowerShell 7.6 and Windows PowerShell 5.1.
Delete the files and report what was freed
Once the preview looks right, remove -WhatIf. Measuring the files first lets you log how much space you got back:
$cutoff = ([datetime]'2026-09-28').AddDays(-30)
$old = Get-ChildItem -Path C:\psfaqs\Cleanup -File -Recurse -Force | Where-Object LastWriteTime -LT $cutoff
$freed = ($old | Measure-Object -Property Length -Sum).Sum
$old | Remove-Item -Force -ErrorAction Continue -ErrorVariable failed
"Deleted $($old.Count - $failed.Count) files, freed {0:N1} MB, errors: $($failed.Count)" -f ($freed / 1MB)
"Left behind: $((Get-ChildItem -Path C:\psfaqs\Cleanup -File -Recurse -Force).Name -join ', ')"
Output:
Deleted 5 files, freed 4.0 MB, errors: 0
Left behind: app-2026-09-25.log, today.log
-ErrorVariable collects files that couldn’t be deleted, like ones another program has open, while the rest still go. Writing to a log file shows how to keep a record.
Hidden and read-only files need -Force
Two things quietly leave old files behind. Get-ChildItem skips hidden files, and Remove-Item refuses read-only ones:
$cutoff = ([datetime]'2026-09-28').AddDays(-30)
"Without -Force, Get-ChildItem sees: $((Get-ChildItem -Path C:\psfaqs\Cleanup -File | Where-Object LastWriteTime -LT $cutoff).Name -join ', ')"
try { Remove-Item -Path C:\psfaqs\Cleanup\export-2026-07-20.csv -ErrorAction Stop }
catch { 'Remove-Item without -Force failed on the read-only export-2026-07-20.csv' }
Output:
Without -Force, Get-ChildItem sees: app-2026-07-01.log, app-2026-08-15.log, export-2026-07-20.csv
Remove-Item without -Force failed on the read-only export-2026-07-20.csv

Add -Force to both commands, as in the delete example above. Remove-Item covers -Force and -Recurse in detail.
Delete only certain file types
Use -Filter to limit the cleanup to one extension, like old log files:
$cutoff = ([datetime]'2026-09-28').AddDays(-30)
Get-ChildItem -Path C:\psfaqs\Cleanup -Filter *.log -File -Recurse |
Where-Object LastWriteTime -LT $cutoff |
ForEach-Object { "Would delete $($_.Name)" }
Output:
Would delete app-2026-07-01.log
Would delete app-2026-08-15.log
-Filter is faster than -Include and works without extra wildcards in the path.
Remove empty folders afterward
Deleting files can leave empty folders. Remove them deepest first, so a parent becomes empty before you check it:
$cutoff = ([datetime]'2026-09-28').AddDays(-30)
Get-ChildItem -Path C:\psfaqs\Cleanup -File -Recurse -Force | Where-Object LastWriteTime -LT $cutoff | Remove-Item -Force
Get-ChildItem -Path C:\psfaqs\Cleanup -Directory -Recurse |
Sort-Object -Property { $_.FullName.Length } -Descending |
Where-Object { -not (Get-ChildItem -Path $_.FullName -Force) } |
ForEach-Object { Remove-Item -Path $_.FullName; "Removed empty folder $($_.FullName.Replace('C:\psfaqs\Cleanup\', ''))" }
Output:
Removed empty folder Archive\2026-06
Removed empty folder Archive

Don’t delete folders by their own date. A folder’s LastWriteTime only changes when files are added or removed, so an old date doesn’t mean old contents.
A reusable cleanup function
Wrap the steps in a function with parameters, and SupportsShouldProcess gives it -WhatIf for free:
function Remove-OldFile {
[CmdletBinding(SupportsShouldProcess)]
param(
[Parameter(Mandatory)] [string]$Path,
[int]$Days = 30,
[string]$Filter = '*'
)
$cutoff = (Get-Date).Date.AddDays(-$Days)
Get-ChildItem -Path $Path -Filter $Filter -File -Recurse -Force |
Where-Object LastWriteTime -LT $cutoff |
Remove-Item -Force
}
Remove-OldFile -Path C:\psfaqs\Cleanup -Days 60 -Filter *.log -WhatIf
Output (run on September 28, 2026):
What if: Performing the operation "Remove File" on target "C:\psfaqs\Cleanup\app-2026-07-01.log".
Finding files by date covers the date filters in more depth.
Run the cleanup on a schedule
Save the function and a call to it as C:\Scripts\cleanup.ps1, then register a daily task from an elevated PowerShell window:
$action = New-ScheduledTaskAction -Execute 'powershell.exe' -Argument '-NoProfile -ExecutionPolicy Bypass -File C:\Scripts\cleanup.ps1'
$trigger = New-ScheduledTaskTrigger -Daily -At '2:00 AM'
Register-ScheduledTask -TaskName 'Delete old logs' -Action $action -Trigger $trigger -User 'NT AUTHORITY\SYSTEM' -RunLevel Highest
Test the script by hand with -WhatIf before you schedule it. Microsoft’s Register-ScheduledTask documentation covers other triggers, and the Remove-Item reference lists every parameter.