概述
本文将 Windows Server 上定期执行的磁盘清理任务划分为以下 4 类,并说明如何为每一类编写独立的 PowerShell 脚本实现自动化:
- WinSxS 优化(压缩组件存储)
- Temp 文件夹清理(按保留天数)
- 日志文件轮换(多路径 + 独立保留天数)
- 事件日志备份 + 清理
变量说明
| 变量名 | 示例值 | 说明 |
|---|---|---|
<<ADMIN_USER>> |
Administrator |
用于运行计划任务的帐户 |
<<LOG_PATH>> |
C:\Maintenance\Logs |
脚本通用日志目录 |
<<BACKUP_PATH>> |
C:\Maintenance\Backups |
事件日志备份目录 |
<<DAILY_TEMP_TASK_NAME>> |
DailyTempCleanup |
Temp 清理任务名称 |
<<DAILY_LOG_TASK_NAME>> |
DailyLogRotation |
日志轮换任务名称 |
<<MONTHLY_WINSXS_TASK_NAME>> |
MonthlyWinSxSCleanup |
WinSxS 优化任务名称 |
<<MONTHLY_EVENTLOG_TASK_NAME>> |
MonthlyEventLogMaintenance |
事件日志维护任务名称 |
Step 1: WinSxS 文件夹优化概述
C:\Windows\WinSxS 是组件存储区(Component Store),用于保存更新程序和功能组件的历史版本。长期运行后容易膨胀,可通过以下 DISM 命令进行优化:
# 分析组件存储
Dism /Online /Cleanup-Image /AnalyzeComponentStore
# 清理旧组件
Dism /Online /Cleanup-Image /StartComponentCleanup
# 完全删除旧版本(无法回滚)
Dism /Online /Cleanup-Image /StartComponentCleanup /ResetBase
由于 /ResetBase 会使旧更新无法卸载,建议仅在每月一次等计划维护窗口中执行。
Step 2: Temp 文件夹清理概述
$env:TEMP 和 C:\Windows\Temp 下会积累安装程序与应用生成的临时文件。与其全部删除,更合理的策略是:
“删除早于 N 天的文件”。
Step 3: 日志文件轮换概述
随着应用数量增加,日志目录也会增多并长期占用磁盘空间。为了按目录单独设定“保留天数”,推荐使用 PSD1 配置文件 以便灵活管理。
Step 4: 事件日志维护概述
事件日志对于故障排查和审计非常重要,不应频繁直接清空。常见做法是先导出为 .evtx 备份,再执行清理,例如 每月一次:
# 备份与清理的基本示例
wevtutil epl System C:\Logs\System_20250101.evtx
wevtutil cl System
Step 5: 脚本设计方针
本文按目的准备以下 4 个脚本:
-
cleanup_temp.ps1
清理 Temp 文件夹中的旧文件(可通过参数指定保留天数) -
rotate_logs.ps1
根据 PSD1 配置文件,删除指定目录下的旧.log文件 -
optimize_winsxs.ps1
对 WinSxS 执行/StartComponentCleanup /ResetBase并记录节省空间 -
maintain_eventlogs.ps1
备份指定事件日志并清空原日志
通过这种结构,可以在任务计划程序中 按用途分别调度脚本,轻松组合日常 / 每月计划以及策略变更。
参考目录结构示例:
C:\
└─ Maintenance\
├─ cleanup_temp.ps1 # Temp 文件夹清理(每日)
├─ rotate_logs.ps1 # 日志轮换(每日)
├─ optimize_winsxs.ps1 # WinSxS 优化(每月)
├─ maintain_eventlogs.ps1 # 事件日志备份 + 清理(每月)
│
├─ log_rotation.psd1 # 日志轮换配置文件(多路径 + 保留天数)
│
├─ Logs\ # 各脚本的执行日志
│ ├─ cleanup_temp_*.log
│ ├─ rotate_logs_*.log
│ ├─ optimize_winsxs_*.log
│ └─ eventlog_maint_*.log
│
└─ Backups\ # 事件日志(EVTX)备份
├─ System_YYYYMMDD.evtx
├─ Application_YYYYMMDD.evtx
└─ Security_YYYYMMDD.evtx
Step 6: Temp 清理脚本
此脚本会在 $TempPaths 指定的目录下,递归删除早于 DaysToKeep 天的文件与文件夹,并将处理结果写入日志。
DaysToKeep 与 LogPath 可在执行时通过参数覆盖。
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: 日志轮换脚本
日志轮换配置文件(PSD1)
该 PSD1 文件用于定义多个 “日志目录 + 保留天数” 对。新增日志目录时,只需在此文件中添加一个条目。
log_rotation.psd1
@{
RotationTargets = @(
@{
Path = "C:\Logs\App1"
DaysToKeep = 7
},
@{
Path = "C:\Logs\App2"
DaysToKeep = 30
},
@{
Path = "<<LOG_PATH>>"
DaysToKeep = 7
}
)
}
日志轮换脚本
该脚本从 PSD1 中读取 RotationTargets,依次处理每个目录,删除超出保留天数的 .log 文件,并将操作记录到日志中。
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 优化脚本
此脚本在执行 /StartComponentCleanup /ResetBase 的同时,分别测量 WinSxS 目录前后大小并记录到日志,便于定量评估释放空间情况。
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: 事件日志维护脚本
该脚本会将指定日志(默认包括 System / Application / Security)导出为 .evtx 至备份目录,然后清空原日志,并将每一步的结果写入日志文件。
maintain_eventlogs.ps1
param(
[string]$LogPath = "<<LOG_PATH>>", # 日志(.log)保存目录
[string]$BackupPath = "<<BACKUP_PATH>>", # EVTX 备份目录
[string[]]$EventLogs = @("System", "Application", "Security")
)
# 创建日志目录
if (-not (Test-Path $LogPath)) {
New-Item $LogPath -ItemType Directory -Force | Out-Null
}
# 创建备份目录
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) {
# 为每个日志生成唯一 EVTX 名称
$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: 任务计划程序注册示例
本节示例展示如何使用 schtasks.exe 为上述维护脚本创建计划任务。
关于 /SC、/D、/ST、/RU 等选项的详细说明与模式,请参考以下文章:
Temp 清理(每日任务示例)
每天 2:00 运行 cleanup_temp.ps1,删除早于 7 天的 Temp 文件。
任务在 <<ADMIN_USER>> 帐户下以 最高权限(/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
日志轮换(每日任务示例)
每天 2:30 运行 rotate_logs.ps1,根据 PSD1 配置,对多个目录中的 .log 文件进行轮换。
具体保留天数全部在 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 优化(每月任务示例)
每月 1 日 3:00 运行 optimize_winsxs.ps1,执行 StartComponentCleanup /ResetBase 并记录前后大小。
由于操作不可回滚,应放在维护窗口中执行。
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
事件日志维护(每月任务示例)
每月 1 日 3:30 运行 maintain_eventlogs.ps1,将 System / Application / Security 等事件日志备份到目录后清空。
可根据审计要求调整 -EventLogs 与 -BackupPath 参数。
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
总结
本文通过将 Windows Server 的磁盘维护 按目的拆分为独立脚本,并借助 PSD1 配置文件 灵活管理日志路径与保留天数,构建了一个可扩展的自动化方案:
- Temp 清理:每日执行,可按参数指定保留天数
- 日志轮换:通过 PSD1 管理多个路径与保留策略
- WinSxS 优化:每月执行,记录释放空间大小,便于评估效果
- 事件日志维护:每月执行,先备份为 EVTX 再清理,满足审计要求
该结构适用于生产环境下的 Windows Server 磁盘维护自动化基线,可按需扩展新的脚本与计划任务。
