- Overview
- Variable conventions
- Step 1: Overview of WinSxS folder optimization
- Step 2: Overview of Temp folder cleanup
- Step 3: Overview of log file rotation
- Step 4: Overview of event log maintenance
- Step 5: Script design policy
- Step 6: Temp cleanup script
- Step 7: Log rotation script
- Step 8: WinSxS optimization script
- Step 9: Event log maintenance script
- Step 10: Task Scheduler registration examples
- Summary
Overview
This article classifies scheduled disk cleanup tasks on Windows Server into the following four categories, and explains how to automate each of them using dedicated PowerShell scripts:
- WinSxS optimization (reducing the component store)
- Temp folder cleanup (based on retention days)
- Log file rotation (multiple paths with per-path retention)
- Event log backup and clear
Variable conventions
| Variable | Example | Note |
|---|---|---|
<<ADMIN_USER>> |
Administrator |
User account that runs the scheduled tasks |
<<LOG_PATH>> |
C:\Maintenance\Logs |
Common log directory for all scripts |
<<BACKUP_PATH>> |
C:\Maintenance\Backups |
Directory for event log backups |
<<DAILY_TEMP_TASK_NAME>> |
DailyTempCleanup |
Task name for Temp cleanup |
<<DAILY_LOG_TASK_NAME>> |
DailyLogRotation |
Task name for log rotation |
<<MONTHLY_WINSXS_TASK_NAME>> |
MonthlyWinSxSCleanup |
Task name for WinSxS optimization |
<<MONTHLY_EVENTLOG_TASK_NAME>> |
MonthlyEventLogMaintenance |
Task name for event log maintenance |
Step 1: Overview of WinSxS folder optimization
C:\Windows\WinSxS is the component store that keeps the history of updates and feature components. It tends to grow over long-term operation. You can optimize it using the following DISM commands:
# Analyze the component store
Dism /Online /Cleanup-Image /AnalyzeComponentStore
# Clean up superseded components
Dism /Online /Cleanup-Image /StartComponentCleanup
# Completely remove old versions (no rollback possible)
Dism /Online /Cleanup-Image /StartComponentCleanup /ResetBase
Since /ResetBase makes it impossible to uninstall older updates, it is safer to run it only at planned times, such as once per month within a maintenance window.
Step 2: Overview of Temp folder cleanup
The directories $env:TEMP and C:\Windows\Temp accumulate temporary files created by installers and applications. Instead of deleting everything blindly, a realistic policy is:
“Delete files older than N days.”
Step 3: Overview of log file rotation
Log directories tend to increase with the number of applications, and long-term retention can heavily consume disk space. Since you often need different retention days per directory, it is better to manage them via a PSD1 configuration file.
Step 4: Overview of event log maintenance
Event logs are critical for troubleshooting and auditing, so they should not be cleared frequently without backup. A common approach is to back them up as .evtx and then clear them, for example about once per month.
# Basic pattern for backup and clear
wevtutil epl System C:\Logs\System_20250101.evtx
wevtutil cl System
Step 5: Script design policy
In this article, we prepare four separate scripts, one per purpose:
-
cleanup_temp.ps1
Deletes old files in Temp folders (retention days are parameterized). -
rotate_logs.ps1
Reads a PSD1 configuration file and deletes.logfiles under configured folders. -
optimize_winsxs.ps1
Runs/StartComponentCleanup /ResetBaseon WinSxS and records the reduction amount. -
maintain_eventlogs.ps1
Backs up specified event logs and then clears them.
With this structure, you can run each maintenance task independently from Task Scheduler, and flexibly adjust daily/monthly schedules or policies.
Example directory layout:
C:\
└─ Maintenance\
├─ cleanup_temp.ps1 # Temp folder cleanup (daily)
├─ rotate_logs.ps1 # Log rotation (daily)
├─ optimize_winsxs.ps1 # WinSxS optimization (monthly)
├─ maintain_eventlogs.ps1 # Event log backup + clear (monthly)
│
├─ log_rotation.psd1 # Log rotation config (multiple paths + retention days)
│
├─ Logs\ # Execution logs for each script
│ ├─ cleanup_temp_*.log
│ ├─ rotate_logs_*.log
│ ├─ optimize_winsxs_*.log
│ └─ eventlog_maint_*.log
│
└─ Backups\ # EVTX backups for event logs
├─ System_YYYYMMDD.evtx
├─ Application_YYYYMMDD.evtx
└─ Security_YYYYMMDD.evtx
Step 6: Temp cleanup script
This script recursively deletes files and folders older than DaysToKeep days under the folders specified in $TempPaths, and records the results to a log file.
You can override DaysToKeep and LogPath via parameters.
cleanup_temp.ps1
param(
[string[]]$TempPaths = @("$env:TEMP", "C:\Windows\Temp"),
[int]$DaysToKeep = 7,
[string]$LogPath = "<<LOG_PATH>>"
)
if (-not (Test-Path $LogPath)) {
New-Item $LogPath -ItemType Directory -Force | Out-Null
}
$timestamp = Get-Date -Format 'yyyyMMdd_HHmmss'
$logFile = Join-Path $LogPath "cleanup_temp_$timestamp.log"
function Write-Log($Message) {
"[{0}] {1}" -f (Get-Date -Format "yyyy-MM-dd HH:mm:ss"), $Message |
Tee-Object -FilePath $logFile -Append
}
Write-Log "===== Temp cleanup started ====="
Write-Log "DaysToKeep = $DaysToKeep"
$limitDate = (Get-Date).AddDays(-$DaysToKeep)
foreach ($path in $TempPaths) {
if (-not (Test-Path $path)) {
Write-Log "Skip: Not found -> $path"
continue
}
Write-Log "Processing: $path"
Get-ChildItem -Path $path -Recurse -Force -ErrorAction SilentlyContinue |
Where-Object { $_.LastWriteTime -lt $limitDate } |
ForEach-Object {
try {
Remove-Item -LiteralPath $_.FullName -Force -Recurse -ErrorAction Stop
Write-Log "Deleted: $($_.FullName)"
}
catch {
Write-Log "Failed: $($_.FullName) - $($_.Exception.Message)"
}
}
}
Write-Log "===== Temp cleanup finished ====="
Step 7: Log rotation script
Log rotation configuration file (PSD1)
This PSD1 file defines multiple directory + retention days pairs. To add a new log directory, just add an entry to this file.
log_rotation.psd1
@{
RotationTargets = @(
@{
Path = "C:\Logs\App1"
DaysToKeep = 7
},
@{
Path = "C:\Logs\App2"
DaysToKeep = 30
},
@{
Path = "<<LOG_PATH>>"
DaysToKeep = 7
}
)
}
Log rotation script
This script loads RotationTargets from the PSD1 file and, for each path, deletes .log files older than the configured retention. All operations are written to a log file.
rotate_logs.ps1
param(
[string]$ConfigPath = "C:\Maintenance\log_rotation.psd1",
[string]$LogPath = "<<LOG_PATH>>"
)
if (-not (Test-Path $LogPath)) {
New-Item $LogPath -ItemType Directory -Force | Out-Null
}
$timestamp = Get-Date -Format 'yyyyMMdd_HHmmss'
$logFile = Join-Path $LogPath "rotate_logs_$timestamp.log"
function Write-Log($Message) {
"[{0}] {1}" -f (Get-Date -Format "yyyy-MM-dd HH:mm:ss"), $Message |
Tee-Object -FilePath $logFile -Append
}
Write-Log "===== Log rotation started ====="
Write-Log "ConfigPath = $ConfigPath"
if (-not (Test-Path $ConfigPath)) {
Write-Log "Config file not found. Exit."
exit 1
}
try {
$config = Import-PowerShellDataFile -Path $ConfigPath
}
catch {
Write-Log "Config load failed: $($_.Exception.Message)"
exit 1
}
foreach ($target in $config.RotationTargets) {
$targetPath = $target.Path
$daysToKeep = [int]$target.DaysToKeep
if (-not (Test-Path $targetPath)) {
Write-Log "Skip (not found): $targetPath"
continue
}
$limitDate = (Get-Date).AddDays(-$daysToKeep)
Write-Log "Path=$targetPath DaysToKeep=$daysToKeep Limit=$limitDate"
Get-ChildItem -Path $targetPath -Recurse -Include *.log -ErrorAction SilentlyContinue |
Where-Object { $_.LastWriteTime -lt $limitDate } |
ForEach-Object {
try {
Remove-Item -LiteralPath $_.FullName -Force -ErrorAction Stop
Write-Log "Deleted: $($_.FullName)"
}
catch {
Write-Log "Failed: $($_.FullName) - $($_.Exception.Message)"
}
}
}
Write-Log "===== Log rotation finished ====="
Step 8: WinSxS optimization script
This script measures the size of the WinSxS directory before and after running /StartComponentCleanup /ResetBase, and logs the difference. This allows you to see how much disk space was actually reclaimed.
optimize_winsxs.ps1
param(
[string]$LogPath = "<<LOG_PATH>>"
)
if (-not (Test-Path $LogPath)) {
New-Item $LogPath -ItemType Directory -Force | Out-Null
}
$timestamp = Get-Date -Format 'yyyyMMdd_HHmmss'
$logFile = Join-Path $LogPath "optimize_winsxs_$timestamp.log"
function Write-Log($Message) {
"[{0}] {1}" -f (Get-Date -Format "yyyy-MM-dd HH:mm:ss"), $Message |
Tee-Object -FilePath $logFile -Append
}
Write-Log "===== WinSxS optimization started ====="
$winsxsPath = "C:\Windows\WinSxS"
Write-Log "Measuring WinSxS size before..."
$sizeBefore = (Get-ChildItem $winsxsPath -Recurse -Force -ErrorAction SilentlyContinue |
Measure-Object Length -Sum).Sum
$sizeBeforeGB = [math]::Round($sizeBefore / 1GB, 2)
Write-Log "Before: $sizeBeforeGB GB"
Dism /Online /Cleanup-Image /AnalyzeComponentStore |
Out-File (Join-Path $LogPath "dism_before_$timestamp.txt")
Write-Log "Running StartComponentCleanup /ResetBase..."
Dism /Online /Cleanup-Image /StartComponentCleanup /ResetBase |
Out-File (Join-Path $LogPath "dism_exec_$timestamp.txt")
Write-Log "Measuring WinSxS size after..."
$sizeAfter = (Get-ChildItem $winsxsPath -Recurse -Force -ErrorAction SilentlyContinue |
Measure-Object Length -Sum).Sum
$sizeAfterGB = [math]::Round($sizeAfter / 1GB, 2)
Write-Log "After: $sizeAfterGB GB"
Write-Log ("Reduced: {0} GB" -f ([math]::Round(($sizeBefore - $sizeAfter)/1GB,2)))
Dism /Online /Cleanup-Image /AnalyzeComponentStore |
Out-File (Join-Path $LogPath "dism_after_$timestamp.txt")
Write-Log "===== WinSxS optimization finished ====="
Step 9: Event log maintenance script
This script, for the specified logs (by default System / Application / Security), exports each one to the backup directory as .evtx and then clears the original log. All results are recorded in a detailed log file.
maintain_eventlogs.ps1
param(
[string]$LogPath = "<<LOG_PATH>>", # Destination for .log files
[string]$BackupPath = "<<BACKUP_PATH>>", # Destination for EVTX backups
[string[]]$EventLogs = @("System", "Application", "Security")
)
# Create log directory if missing
if (-not (Test-Path $LogPath)) {
New-Item $LogPath -ItemType Directory -Force | Out-Null
}
# Create backup directory if missing
if (-not (Test-Path $BackupPath)) {
New-Item $BackupPath -ItemType Directory -Force | Out-Null
}
$timestamp = Get-Date -Format 'yyyyMMdd_HHmmss'
$logFile = Join-Path $LogPath "eventlog_maint_$timestamp.log"
function Write-Log($Message) {
"[{0}] {1}" -f (Get-Date -Format "yyyy-MM-dd HH:mm:ss"), $Message |
Tee-Object -FilePath $logFile -Append
}
Write-Log "===== Event log maintenance started ====="
Write-Log "BackupPath = $BackupPath"
foreach ($name in $EventLogs) {
# Unique EVTX file per log
$destEvtx = Join-Path $BackupPath ("{0}_{1}.evtx" -f $name, $timestamp)
try {
Write-Log "Export: $name -> $destEvtx"
wevtutil epl $name $destEvtx
Write-Log "Clear: $name"
wevtutil cl $name
}
catch {
Write-Log "Failed: $name - $($_.Exception.Message)"
}
}
Write-Log "===== Event log maintenance finished ====="
Step 10: Task Scheduler registration examples
The following examples show how to register all maintenance scripts using schtasks.exe.
For a detailed explanation of options like /SC, /D, /ST, /RU, and typical patterns, see:
Managing Task Scheduler with schtasks.exe
Temp cleanup (daily task example)
Runs cleanup_temp.ps1 every day at 02:00, deleting Temp files older than 7 days.
The task runs under <<ADMIN_USER>> with highest privileges (/RL HIGHEST).
schtasks /Create `
/TN "<<DAILY_TEMP_TASK_NAME>>" `
/TR "powershell.exe -NoProfile -ExecutionPolicy Bypass -File C:\Maintenance\cleanup_temp.ps1 -DaysToKeep 7 -LogPath <<LOG_PATH>>" `
/SC DAILY `
/ST 02:00 `
/RU "<<ADMIN_USER>>" `
/RL HIGHEST `
/F
Log rotation (daily task example)
Runs rotate_logs.ps1 every day at 02:30, rotating .log files across multiple directories according to the PSD1 configuration.
Retention days are controlled only in log_rotation.psd1.
schtasks /Create `
/TN "<<DAILY_LOG_TASK_NAME>>" `
/TR "powershell.exe -NoProfile -ExecutionPolicy Bypass -File C:\Maintenance\rotate_logs.ps1 -ConfigPath C:\Maintenance\log_rotation.psd1 -LogPath <<LOG_PATH>>" `
/SC DAILY `
/ST 02:30 `
/RU "<<ADMIN_USER>>" `
/RL HIGHEST `
/F
WinSxS optimization (monthly task example)
Runs optimize_winsxs.ps1 on the 1st of every month at 03:00, executing StartComponentCleanup /ResetBase and measuring size before/after.
Because this includes a non-reversible operation, align the schedule with a maintenance window.
schtasks /Create `
/TN "<<MONTHLY_WINSXS_TASK_NAME>>" `
/TR "powershell.exe -NoProfile -ExecutionPolicy Bypass -File C:\Maintenance\optimize_winsxs.ps1 -LogPath <<LOG_PATH>>" `
/SC MONTHLY `
/D 1 `
/ST 03:00 `
/RU "<<ADMIN_USER>>" `
/RL HIGHEST `
/F
Event log maintenance (monthly task example)
Runs maintain_eventlogs.ps1 on the 1st of every month at 03:30, backing up event logs (System / Application / Security, etc.) to the backup directory and then clearing them.
Adjust -EventLogs and -BackupPath to match your audit requirements.
schtasks /Create `
/TN "<<MONTHLY_EVENTLOG_TASK_NAME>>" `
/TR "powershell.exe -NoProfile -ExecutionPolicy Bypass -File C:\Maintenance\maintain_eventlogs.ps1 -LogPath <<LOG_PATH>>" `
/SC MONTHLY `
/D 1 `
/ST 03:30 `
/RU "<<ADMIN_USER>>" `
/RL HIGHEST `
/F
Summary
This article showed how to separate Windows Server disk maintenance into purpose-specific scripts, and how to use a PSD1 configuration file to flexibly control log rotation targets and retention.
- Temp cleanup: daily, configurable retention days
- Log rotation: multiple paths + retention days managed via PSD1
- WinSxS optimization: monthly, with logged space reduction
- Event log maintenance: monthly, with EVTX backup before clear
This structure provides a reusable baseline for automated disk maintenance on Windows Server in production environments and can be extended with additional scripts and scheduled tasks as needed.
