磁盘清理与维护自动化

概述

本文将 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:TEMPC:\Windows\Temp 下会积累安装程序与应用生成的临时文件。与其全部删除,更合理的策略是:

“删除早于 N 天的文件”


Step 3: 日志文件轮换概述

随着应用数量增加,日志目录也会增多并长期占用磁盘空间。为了按目录单独设定“保留天数”,推荐使用 PSD1 配置文件 以便灵活管理。


Step 4: 事件日志维护概述

事件日志对于故障排查和审计非常重要,不应频繁直接清空。常见做法是先导出为 .evtx 备份,再执行清理,例如 每月一次

# 备份与清理的基本示例
wevtutil epl System C:\Logs\System_20250101.evtx
wevtutil cl System

Step 5: 脚本设计方针

本文按目的准备以下 4 个脚本:

  1. cleanup_temp.ps1
    清理 Temp 文件夹中的旧文件(可通过参数指定保留天数)

  2. rotate_logs.ps1
    根据 PSD1 配置文件,删除指定目录下的旧 .log 文件

  3. optimize_winsxs.ps1
    对 WinSxS 执行 /StartComponentCleanup /ResetBase 并记录节省空间

  4. 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 天的文件与文件夹,并将处理结果写入日志。
DaysToKeepLogPath 可在执行时通过参数覆盖。

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 等选项的详细说明与模式,请参考以下文章:

schtasks.exe 任务计划程序管理指南


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 磁盘维护自动化基线,可按需扩展新的脚本与计划任务。