Скрипт PowerShell под задачу
Пишет скрипт PowerShell с обработкой ошибок, логированием и режимом пробного прогона — так, чтобы его можно было запустить на рабочей машине и не бояться последствий.
Пример ответа
Как я понял задачу
Нужно удалять из сетевой папки файлы старше заданного числа дней, запуск по расписанию раз в сутки, при этом нельзя трогать подпапку с архивом.
Крайние случаи, которые вижу:
- Сетевая папка недоступна — это ожидаемая ситуация для сетевого пути, не сбой скрипта. Логируем и выходим с ненулевым кодом, чтобы мониторинг заметил.
- Файл занят другим процессом — при удалении по расписанию это норма. Пропускаем конкретный файл, продолжаем работу, в конце сообщаем счёт пропущенных.
- Файлов под условие не попало — не ошибка. Пишем в лог и выходим с нулём.
- Символические ссылки — рекурсивный обход по ним может увести за пределы папки. Исключаю их явно.
Неоднозначность: «старше N дней» может считаться по дате создания или по дате последнего изменения. Для задач очистки практически всегда имеют в виду последнее изменение — беру его, но параметром это переключается.
#Requires -Version 5.1
<#
.SYNOPSIS
Удаляет файлы старше заданного возраста, по умолчанию — в режиме пробного прогона.
.DESCRIPTION
Обходит каталог рекурсивно, исключая указанные подпапки и символические ссылки.
Без ключа -Execute ничего не удаляет, только показывает список кандидатов.
.EXAMPLE
.\Clear-OldFiles.ps1 -Path \\server\share -Days 30
Пробный прогон: показывает, что было бы удалено.
.EXAMPLE
.\Clear-OldFiles.ps1 -Path \\server\share -Days 30 -ExcludeFolder 'archive' -Execute
Реальное удаление.
#>
[CmdletBinding()]
param(
[Parameter(Mandatory)]
[string]$Path,
[Parameter(Mandatory)]
[ValidateRange(1, 3650)]
[int]$Days,
[string[]]$ExcludeFolder = @(),
[ValidateSet('LastWrite', 'Creation')]
[string]$AgeBy = 'LastWrite',
[string]$LogPath = "$PSScriptRoot\Clear-OldFiles.log",
[switch]$Execute
)
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'
function Write-Log {
param([string]$Message, [ValidateSet('INFO','WARN','ERROR')][string]$Level = 'INFO')
$line = '{0:yyyy-MM-dd HH:mm:ss} [{1}] {2}' -f (Get-Date), $Level, $Message
Write-Host $line
Add-Content -LiteralPath $LogPath -Value $line -Encoding UTF8
}
$skipped = 0
$removed = 0
$freed = 0L
try {
Write-Log "Запуск. Путь: $Path, порог: $Days дн., режим: $(if ($Execute) { 'УДАЛЕНИЕ' } else { 'пробный прогон' })"
# Ожидаемая ситуация для сетевого пути, а не сбой скрипта:
# выходим с ненулевым кодом, чтобы это увидел мониторинг.
if (-not (Test-Path -LiteralPath $Path)) {
Write-Log "Путь недоступен: $Path" -Level ERROR
exit 2
}
$threshold = (Get-Date).AddDays(-$Days)
$property = if ($AgeBy -eq 'Creation') { 'CreationTime' } else { 'LastWriteTime' }
# -Force показывает скрытые файлы; ReparsePoint отсекает симлинки,
# иначе рекурсия способна уйти за пределы каталога.
$candidates = Get-ChildItem -LiteralPath $Path -File -Recurse -Force |
Where-Object {
$_.$property -lt $threshold -and
-not $_.Attributes.HasFlag([IO.FileAttributes]::ReparsePoint)
}
if ($ExcludeFolder.Count -gt 0) {
$candidates = $candidates | Where-Object {
$relative = $_.FullName.Substring($Path.TrimEnd('\').Length)
$segments = $relative.Split('\', [StringSplitOptions]::RemoveEmptyEntries)
-not ($segments | Where-Object { $ExcludeFolder -contains $_ })
}
}
Write-Log "Под условие попало файлов: $($candidates.Count)"
foreach ($file in $candidates) {
if (-not $Execute) {
Write-Log "[пробный прогон] удалил бы: $($file.FullName) ($($file.$property))"
continue
}
try {
$size = $file.Length
Remove-Item -LiteralPath $file.FullName -Force -ErrorAction Stop
$removed++
$freed += $size
}
catch [System.IO.IOException] {
# Занятый файл при плановой очистке — рабочая ситуация, не сбой.
Write-Log "Пропущен (занят): $($file.FullName)" -Level WARN
$skipped++
}
catch [System.UnauthorizedAccessException] {
Write-Log "Пропущен (нет прав): $($file.FullName)" -Level WARN
$skipped++
}
}
$mb = [math]::Round($freed / 1MB, 2)
Write-Log "Готово. Удалено: $removed, пропущено: $skipped, освобождено: $mb МБ"
if (-not $Execute) {
Write-Log 'Это был пробный прогон. Для реального удаления добавьте -Execute.'
}
exit 0
}
catch {
Write-Log "Непредвиденная ошибка: $($_.Exception.Message)" -Level ERROR
Write-Log $_.ScriptStackTrace -Level ERROR
exit 1
}
Ключевые решения
- Пробный прогон по умолчанию. Ключ называется -Execute, а не -WhatIf наоборот: чтобы удалить, нужно осознанное действие. Забытый флаг приводит к безобидному отчёту, а не к потере данных.
- Разные catch на разные исключения. Занятый файл и отсутствие прав — ожидаемые ситуации, они логируются и не прерывают обход. Всё остальное всплывает во внешний catch и останавливает скрипт: непонятная ошибка при массовом удалении — повод прекратить, а не продолжать вслепую.
- Set-StrictMode и ErrorActionPreference = Stop. Без них PowerShell молча продолжает работу после ошибок, и скрипт удаления «успешно» отрабатывает, ничего не сделав, — худший из возможных исходов.
- Коды возврата 0 / 1 / 2. Планировщик видит только код. Двойка отдельно выделена для «путь недоступен»: эта ситуация чаще всего означает проблему сети, а не скрипта, и по коду её удобно отличать в мониторинге.
- LiteralPath везде. Обычный -Path интерпретирует квадратные скобки как шаблон, и файлы с ними в имени тихо не обрабатываются.
- Исключение по сегментам пути, а не подстрокой: проверка вхождения подстроки ошибочно исключила бы файл из папки «archive-2024».
Что необратимо и что проверить
- Необратимо: Remove-Item удаляет мимо корзины. Восстановление — только из резервной копии.
- Права: нужен доступ на чтение и удаление в целевой папке. Под учётной записью планировщика проверьте отдельно — она часто отличается от вашей, и это выясняется в первый же ночной запуск.
- Перед первым боевым запуском: прогоните без -Execute и прочитайте лог целиком. Убедитесь, что исключения сработали и в списке нет ничего из архивной папки.
- Проверьте политику выполнения в целевом окружении — иначе скрипт не запустится по расписанию.
- Лог не ротируется. При ежедневном запуске он будет расти; заложите ротацию или чистку, иначе через год получите файл на несколько сотен мегабайт.
Похожие промпты
Ревью и рефакторинг PowerShell-скрипта
Разбирает существующий скрипт PowerShell: что сломается на реальных данных, где ошибки глушатся вместо обработки и что переписать в первую очередь, а что оставить.
Помощник по настройке WireGuard и OpenVPN
Пошагово разворачивает собственный VPN-сервер на WireGuard или OpenVPN: готовые команды и конфиги, firewall, клиентские профили и проверка утечек.
Аудит безопасности малого бизнеса и домашней сети
Аудит безопасности малого бизнеса или домашней сети: чек-лист по областям, оценка рисков через сценарии атак и план усиления защиты на 30 дней.
Консультант по выбору VPN-решения
Помогает выбрать VPN под задачу: сравнивает коммерческие сервисы и self-hosted решения с учётом страны, устройств, модели угроз и бюджета.