适用于 PowerShell 5.1 及以上版本
每到年底,系统管理员面临着大量收尾工作:汇总全年的运维数据、归档陈旧日志、清理过期文件、检查系统健康状态,还要为来年的自动化计划做准备。这些任务如果逐一手动完成,往往要耗费数天时间。而通过 PowerShell 脚本,可以将这些重复性工作编排成可一键执行的自动化流程,大幅缩短收尾周期。
更重要的是,年度总结不仅是对过去一年工作的回顾,更是为来年制定计划的数据基础。通过脚本化汇总,可以确保数据的准确性和一致性——每年生成相同格式的报告,方便横向对比,发现趋势。将枯燥的数据整理交给脚本,管理员才能把精力集中在分析和决策上。
本文将从年度运维数据汇总、数据归档与清理、来年自动化准备三个维度,展示如何用 PowerShell 高效完成年末收尾工作。
年度运维数据汇总 年终总结的第一步是收集全年的关键运维指标。下面的脚本从 Windows 事件日志、系统运行时间等数据源中提取统计信息,生成一份结构化的年度运维报告。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 function Get-YearEndOpsSummary { param ( [int ]$Year = (Get-Date ).Year ) $startDate = Get-Date -Year $Year -Month 1 -Day 1 $endDate = Get-Date -Year $Year -Month 12 -Day 31 -Hour 23 -Minute 59 -Second 59 Write-Host "正在汇总 $Year 年度运维数据..." -ForegroundColor Cyan Write-Host ("=" * 50 ) Write-Host "`n[事件日志统计]" -ForegroundColor Yellow $logStats = @ {} $logNames = @ ('System' , 'Application' , 'Security' ) foreach ($logName in $logNames ) { try { $events = Get-WinEvent -LogName $logName | Where-Object { $_ .TimeCreated -ge $startDate -and $_ .TimeCreated -le $endDate } $logStats [$logName ] = [PSCustomObject ]@ { Total = $events .Count Critical = ($events | Where-Object Level -eq 1 ).Count Error = ($events | Where-Object Level -eq 2 ).Count Warning = ($events | Where-Object Level -eq 3 ).Count Information = ($events | Where-Object Level -eq 4 ).Count } Write-Host " $logName : $ ($logStats [$logName ].Total) 条" } catch { Write-Host " $logName : 无法读取或无记录" -ForegroundColor DarkGray } } Write-Host "`n[服务器可用性统计]" -ForegroundColor Yellow $reboots = Get-WinEvent -LogName System -ErrorAction SilentlyContinue | Where-Object { $_ .TimeCreated -ge $startDate -and $_ .TimeCreated -le $endDate -and $_ .Id -in 1074 , 6006 , 6008 , 41 } $uptimeSpan = (Get-Date ) - (Get-CimInstance Win32_OperatingSystem).LastBootUpTime $totalDaysInYear = ($endDate - $startDate ).TotalDays Write-Host "`n[磁盘使用情况]" -ForegroundColor Yellow $diskInfo = Get-CimInstance Win32_LogicalDisk -Filter 'DriveType=3' | ForEach-Object { $usedGB = [math ]::Round(($_ .Size - $_ .FreeSpace) / 1 GB, 2 ) $freeGB = [math ]::Round($_ .FreeSpace / 1 GB, 2 ) $totalGB = [math ]::Round($_ .Size / 1 GB, 2 ) $usedPct = [math ]::Round(($usedGB / $totalGB ) * 100 , 1 ) [PSCustomObject ]@ { Drive = $_ .DeviceID UsedGB = $usedGB FreeGB = $freeGB TotalGB = $totalGB UsedPct = "$usedPct %" } } $diskInfo | Format-Table -AutoSize $report = [PSCustomObject ]@ { Year = $Year GeneratedAt = Get-Date -Format 'yyyy-MM-dd HH:mm:ss' EventLogs = $logStats RebootCount = $reboots .Count CurrentUptime = "$ ([math]::Floor($uptimeSpan .TotalDays)) 天 $ ($uptimeSpan .Hours) 小时" DiskStatus = $diskInfo } $reportPath = Join-Path $env:USERPROFILE "Desktop\OpsSummary-$Year .json" $report | ConvertTo-Json -Depth 5 | Out-File $reportPath -Encoding UTF8 Write-Host "`n报告已保存至:$reportPath " -ForegroundColor Green return $report } Get-YearEndOpsSummary -Year 2025
执行结果示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 正在汇总 2025 年度运维数据... ================================================== [事件日志统计] System : 48256 条 Application : 31420 条 Security : 128750 条 [服务器可用性统计] [磁盘使用情况] Drive UsedGB FreeGB TotalGB UsedPct ----- ------ ------ ------- -------C: 186.42 63.58 250.00 74.6% D: 412.30 587.70 1000.00 41.2% 报告已保存至:C:\Users\admin\Desktop\OpsSummary-2025.json
数据归档与清理 年度数据归档是运维管理的重要环节。下面的脚本实现了日志压缩打包、过期文件自动清理以及备份完整性验证,帮助管理员在假期前完成数据整理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 function Invoke-YearEndArchive { param ( [string ]$ArchiveRoot = "D:\Archives\$ (Get-Date -Format 'yyyy')" , [int ]$RetentionDays = 90 , [string []]$LogPaths = @ ( "C:\Logs" , "D:\ApplicationLogs" , "$env:TEMP " ), [switch ]$WhatIf ) $timestamp = Get-Date -Format 'yyyyMMdd_HHmmss' $archiveDir = Join-Path $ArchiveRoot "Archive-$timestamp " if (-not $WhatIf ) { New-Item -ItemType Directory -Path $archiveDir -Force | Out-Null } Write-Host "=== 年度数据归档与清理 ===" -ForegroundColor Cyan Write-Host "归档目录:$archiveDir " Write-Host "保留天数:$RetentionDays 天" Write-Host "" Write-Host "[1/3] 压缩归档日志文件..." -ForegroundColor Yellow $archiveResults = @ () foreach ($logPath in $LogPaths ) { if (-not (Test-Path $logPath )) { Write-Host " 跳过(路径不存在):$logPath " -ForegroundColor DarkGray continue } $logFiles = Get-ChildItem $logPath -Recurse -File -ErrorAction SilentlyContinue | Where-Object { $_ .Extension -in '.log' , '.txt' , '.csv' , '.evtx' -and $_ .LastWriteTime -lt (Get-Date ).AddDays(-$RetentionDays ) } if ($logFiles ) { $totalSize = [math ]::Round(($logFiles | Measure-Object Length -Sum ).Sum / 1 MB, 2 ) $zipName = "$ ($logPath -replace '[\\:]', '_')_$timestamp .zip" $zipPath = Join-Path $archiveDir $zipName if (-not $WhatIf ) { $logFiles | Compress-Archive -DestinationPath $zipPath -CompressionLevel Optimal $zipSize = [math ]::Round((Get-Item $zipPath ).Length / 1 MB, 2 ) } else { $zipSize = "N/A (WhatIf)" } $archiveResults += [PSCustomObject ]@ { Source = $logPath FileCount = $logFiles .Count OrigMB = $totalSize ArchiveMB = $zipSize } Write-Host " $logPath : $ ($logFiles .Count) 个文件,$ {totalSize} MB -> $ {zipSize} MB" } else { Write-Host " $logPath : 无过期日志" -ForegroundColor DarkGray } } Write-Host "`n[2/3] 清理过期临时文件..." -ForegroundColor Yellow $cleanPatterns = @ ('*.tmp' , '*.temp' , '*.bak' , '~$*' ) $cleanPaths = @ ($env:TEMP , "C:\Windows\Temp" ) $cleanedCount = 0 $cleanedSize = 0 foreach ($path in $cleanPaths ) { foreach ($pattern in $cleanPatterns ) { $files = Get-ChildItem $path -Filter $pattern -Recurse -ErrorAction SilentlyContinue | Where-Object { $_ .LastWriteTime -lt (Get-Date ).AddDays(-7 ) } foreach ($file in $files ) { $cleanedSize += $file .Length if (-not $WhatIf ) { Remove-Item $file .FullName -Force -ErrorAction SilentlyContinue } $cleanedCount ++ } } } $cleanedSizeMB = [math ]::Round($cleanedSize / 1 MB, 2 ) Write-Host " 已清理 $cleanedCount 个临时文件,释放 $ {cleanedSizeMB} MB" Write-Host "`n[3/3] 验证归档完整性..." -ForegroundColor Yellow if (-not $WhatIf -and (Test-Path $archiveDir )) { $zips = Get-ChildItem $archiveDir -Filter '*.zip' foreach ($zip in $zips ) { try { Add-Type -AssemblyName System.IO.Compression.FileSystem $archive = [System.IO.Compression.ZipFile ]::OpenRead($zip .FullName) $entryCount = $archive .Entries.Count $archive .Dispose() Write-Host " $ ($zip .Name) : $entryCount 个文件 - 完整" -ForegroundColor Green } catch { Write-Host " $ ($zip .Name) : 验证失败 - $ ($_ .Exception.Message)" -ForegroundColor Red } } } Write-Host "`n归档完成!" -ForegroundColor Green } Invoke-YearEndArchive -WhatIf
执行结果示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 === 年度数据归档与清理 === 归档目录:D:\Archives\2025\Archive-20251224_080000 保留天数:90 天 [1/3] 压缩归档日志文件... C:\Logs : 156 个文件,2340.50 MB -> 412.30 MB D:\ApplicationLogs : 89 个文件,1580.75 MB -> 298.60 MB C:\Users\admin\AppData\Local\Temp : 42 个文件,86.20 MB -> 18.40 MB [2/3] 清理过期临时文件... 已清理 327 个临时文件,释放 456.80 MB [3/3] 验证归档完整性... _C_Logs_20251224_080000.zip : 156 个文件 - 完整 _D_ApplicationLogs_20251224_080000.zip : 89 个文件 - 完整 _C_Users_admin_AppData_Local_Temp_20251224_080000.zip : 42 个文件 - 完整 归档完成!
来年自动化准备 假期是审视和优化自动化体系的最佳时机。下面的脚本检查当前计划任务的健康状态、扫描即将到期的证书,并生成一份系统健康检查报告,为来年的运维规划提供数据支撑。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 function Invoke-NewYearPreparation { param ( [int ]$CertificateWarningDays = 60 , [string ]$ReportPath = "$env:USERPROFILE \Desktop\NewYear-HealthCheck-$ (Get-Date -Format 'yyyyMMdd').html" ) Write-Host "=== 来年自动化准备检查 ===" -ForegroundColor Cyan Write-Host "" Write-Host "[1/3] 检查计划任务..." -ForegroundColor Yellow $scheduledTasks = Get-ScheduledTask | Where-Object { $_ .TaskPath -notlike '\Microsoft\*' } | ForEach-Object { $info = $_ | Get-ScheduledTaskInfo -ErrorAction SilentlyContinue [PSCustomObject ]@ { Name = $_ .TaskName Path = $_ .TaskPath State = $_ .State LastRunTime = if ($info .LastRunTime -gt '1899-12-30' ) { $info .LastRunTime } else { '从未运行' } LastResult = $info .LastTaskResult NextRunTime = if ($info .NextRunTime -gt '1899-12-30' ) { $info .NextRunTime } else { '未计划' } } } $taskStats = @ { Total = $scheduledTasks .Count Running = ($scheduledTasks | Where-Object State -eq 'Running' ).Count Ready = ($scheduledTasks | Where-Object State -eq 'Ready' ).Count Disabled = ($scheduledTasks | Where-Object State -eq 'Disabled' ).Count Failed = ($scheduledTasks | Where-Object LastResult -ne '0' -and $_ .State -eq 'Ready' ).Count } Write-Host " 总计:$ ($taskStats .Total) 个自定义任务" Write-Host " 就绪:$ ($taskStats .Ready) | 运行中:$ ($taskStats .Running) | 已禁用:$ ($taskStats .Disabled)" $failedTasks = $scheduledTasks | Where-Object { $_ .LastResult -notin '0' , '' -and $_ .State -eq 'Ready' } if ($failedTasks ) { Write-Host "`n 上次执行失败的任务:" -ForegroundColor Red foreach ($task in $failedTasks ) { Write-Host " - $ ($task .Name) (错误码: $ ($task .LastResult))" -ForegroundColor Red } } Write-Host "`n[2/3] 检查证书到期情况..." -ForegroundColor Yellow $expiryThreshold = (Get-Date ).AddDays($CertificateWarningDays ) $certExpirations = Get-ChildItem Cert:\LocalMachine\My -ErrorAction SilentlyContinue | Where-Object { $_ .NotAfter -le $expiryThreshold } | Sort-Object NotAfter | ForEach-Object { $daysLeft = ($_ .NotAfter - (Get-Date )).Days $status = if ($daysLeft -le 0 ) { '已过期' } elseif ($daysLeft -le 30 ) { '紧急' } elseif ($daysLeft -le 60 ) { '警告' } else { '注意' } [PSCustomObject ]@ { Subject = $_ .Subject Thumbprint = $_ .Thumbprint.Substring(0 , 16 ) + '...' Expires = $_ .NotAfter.ToString('yyyy-MM-dd' ) DaysLeft = $daysLeft Status = $status } } if ($certExpirations ) { $certExpirations | Format-Table -AutoSize } else { Write-Host " 未发现即将到期的证书" -ForegroundColor Green } Write-Host "`n[3/3] 系统健康检查..." -ForegroundColor Yellow $os = Get-CimInstance Win32_OperatingSystem $cpu = Get-CimInstance Win32_Processor $memTotalGB = [math ]::Round($os .TotalVisibleMemorySize / 1 MB, 2 ) $memFreeGB = [math ]::Round($os .FreePhysicalMemory / 1 MB, 2 ) $memUsedPct = [math ]::Round(($memTotalGB - $memFreeGB ) / $memTotalGB * 100 , 1 ) $cpuLoad = $cpu .LoadPercentage $healthReport = [PSCustomObject ]@ { ComputerName = $env:COMPUTERNAME OSVersion = $os .Caption LastBootTime = $os .LastBootUpTime.ToString('yyyy-MM-dd HH:mm:ss' ) CPULoad = "$cpuLoad %" MemoryTotalGB = $memTotalGB MemoryFreeGB = $memFreeGB MemoryUsedPct = "$memUsedPct %" ServicesFailed = (Get-Service | Where-Object { $_ .Status -eq 'Stopped' -and $_ .StartType -eq 'Automatic' }).Count PendingReboot = (Test-Path 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootPending' ) } Write-Host " 主机名:$ ($healthReport .ComputerName)" Write-Host " 操作系统:$ ($healthReport .OSVersion)" Write-Host " CPU 负载:$ ($healthReport .CPULoad)" Write-Host " 内存使用:$ ($healthReport .MemoryUsedPct) ($ ($healthReport .MemoryFreeGB) GB 可用)" Write-Host " 已停止的自动启动服务:$ ($healthReport .ServicesFailed)" if ($healthReport .PendingReboot) { Write-Host " 待重启:是" -ForegroundColor Red } else { Write-Host " 待重启:否" -ForegroundColor Green } $htmlBody = @" <h1>新年系统健康检查报告</h1> <p>生成时间:$ (Get-Date -Format 'yyyy-MM-dd HH:mm:ss')</p> <h2>系统概况</h2> <table border="1" cellpadding="5" style="border-collapse:collapse"> <tr><td>主机名</td><td>$ ($healthReport .ComputerName)</td></tr> <tr><td>操作系统</td><td>$ ($healthReport .OSVersion)</td></tr> <tr><td>CPU 负载</td><td>$ ($healthReport .CPULoad)</td></tr> <tr><td>内存使用率</td><td>$ ($healthReport .MemoryUsedPct)</td></tr> <tr><td>待重启</td><td>$ ($healthReport .PendingReboot)</td></tr> </table> "@ $htmlBody | Out-File $ReportPath -Encoding UTF8 Write-Host "`nHTML 报告已保存至:$ReportPath " -ForegroundColor Green Write-Host "`n来年自动化准备检查完毕!" -ForegroundColor Green } Invoke-NewYearPreparation -CertificateWarningDays 60
执行结果示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 === 来年自动化准备检查 === [1/3] 检查计划任务... 总计:24 个自定义任务 就绪:18 | 运行中:2 | 已禁用:4 上次执行失败的任务: - DailyBackup (错误码: 0x1) - LogRotation (错误码: 0x2) [2/3] 检查证书到期情况... Subject Thumbprint Expires DaysLeft Status ------- ---------- ------- -------- ------CN=web.vichamp.com 3A2F8B1C9D0E4... 2026-01-15 22 紧急 CN=api.internal.local 7E6D5C4B3A2F1... 2026-02-20 58 警告 [3/3] 系统健康检查... 主机名:SRV-PROD-01 操作系统:Microsoft Windows Server 2022 Standard CPU 负载:23% 内存使用:67.5% (7.26 GB 可用) 已停止的自动启动服务:2 待重启:是 HTML 报告已保存至:C:\Users\admin\Desktop\NewYear-HealthCheck-20251224.html 来年自动化准备检查完毕!
注意事项
权限要求 :事件日志查询和计划任务管理需要管理员权限,建议以提升模式运行 PowerShell,或在脚本开头添加 #Requires -RunAsAdministrator 确保权限充足。
大日志处理 :Get-WinEvent 在处理整年事件日志时可能消耗大量内存。对于事件量超过十万条的日志源,建议按月份分批查询后合并统计,避免内存溢出。
归档前验证 :执行日志清理前务必先用 -WhatIf 参数预览操作范围,确认无误后再正式执行。已压缩的归档文件应存放到独立存储或异地备份,避免与原始数据在同一磁盘上。
证书到期监控 :建议将证书到期检查集成到日常监控流程中(如每周执行一次),而非仅在年底检查。可以在脚本中加入邮件通知逻辑,在证书到期前 30 天和 7 天分别发送提醒。
跨平台兼容 :本文部分示例使用了 Windows 特有的模块(如 ScheduledTask、Cert: 驱动器)。如果在 Linux 或 macOS 上运行 PowerShell 7,需要使用对应的平台命令替代,如 crontab -l 替代计划任务检查。
报告持续化 :年度报告建议统一存放并纳入版本管理。可以配合 Git 仓库或 SharePoint 文档库,每年追加新报告,形成连续的运维历史档案,便于长期趋势分析和审计回溯。