Ревью и рефакторинг PowerShell-скрипта
Разбирает существующий скрипт PowerShell: что сломается на реальных данных, где ошибки глушатся вместо обработки и что переписать в первую очередь, а что оставить.
Пример ответа
Что скрипт делает на самом деле
Заявлено: «синхронизирует файлы из рабочей папки в резервную». Фактически скрипт копирует файлы в одну сторону и удаляет в приёмнике всё, чего нет в источнике. Это не синхронизация, а зеркалирование, и разница критична: файл, случайно удалённый в источнике, при следующем запуске исчезает и в «резервной копии». То есть резервной копией это не является — от самого частого сценария потери данных такая схема не защищает.
Это главная находка, и она не про код. Прежде чем править строки, стоит решить, нужно ли вам зеркало или всё-таки резервная копия.
Находки по важности
1. Критично: удаление без пробного режима и без подтверждения
Скрипт удаляет файлы в приёмнике сразу, при любом запуске. Одна опечатка в пути источника — и приёмник вычищается целиком, потому что «в источнике этих файлов нет».
# Было:
Get-ChildItem $Dest | Where-Object { $_.Name -notin $sourceNames } | Remove-Item -Force -Recurse
# Стало:
$toRemove = Get-ChildItem -LiteralPath $Dest |
Where-Object { $_.Name -notin $sourceNames }
# Предохранитель: массовое удаление почти всегда означает ошибку в пути,
# а не реальное исчезновение всех файлов разом.
$total = (Get-ChildItem -LiteralPath $Dest).Count
if ($total -gt 0 -and $toRemove.Count / $total -gt 0.5) {
throw "Под удаление попало $($toRemove.Count) из $total. Похоже на ошибку в пути источника. Проверьте -Source."
}
foreach ($item in $toRemove) {
if ($Execute) { Remove-Item -LiteralPath $item.FullName -Force -Recurse }
else { Write-Host "[пробный прогон] удалил бы: $($item.FullName)" }
}
2. Критично: ошибки подавлены, скрипт всегда рапортует об успехе
В теле копирования стоит подавление ошибок, и результат нигде не проверяется. Если копирование не удалось — нет прав, нет места, файл занят, — скрипт молча идёт дальше и в конце пишет, что всё прошло успешно. Дальше он удаляет в приёмнике «лишнее», не зная, что нужное туда не доехало.
# Было:
Copy-Item $file.FullName $target -Force -ErrorAction SilentlyContinue
# Стало:
try {
Copy-Item -LiteralPath $file.FullName -Destination $target -Force -ErrorAction Stop
$copied++
}
catch {
# Файл не доехал — фиксируем и НЕ удаляем ничего в приёмнике на этом прогоне.
Write-Warning "Не скопирован: $($file.FullName) — $($_.Exception.Message)"
$failed++
}
И в конце — обязательная развязка: if ($failed -gt 0) { exit 1 }. Сейчас скрипт возвращает ноль всегда, поэтому планировщик считает удачными и полностью проваленные прогоны.
3. Важно: удаление выполняется даже при ошибках копирования
Следствие предыдущего пункта, но стоит отдельно, потому что именно эта комбинация приводит к потере данных. Правило: если хоть один файл не скопировался — фаза удаления не выполняется вообще. Лучше оставить лишнее в приёмнике, чем удалить нужное.
4. Важно: одиночный файл ломает логику
Когда Get-ChildItem находит ровно один файл, возвращается не массив, а объект. Обращение к .Count даёт пустоту, и проверка «если файлов нет — выходим» не срабатывает. Лечится обёрткой: @(Get-ChildItem ...). Классическая ловушка PowerShell, ловится редко, потому что на тестовых данных файлов обычно много.
5. Средне: сравнение только по имени
Файл считается «уже скопированным», если совпало имя. Изменившийся файл того же имени не обновится. Сравнивайте по размеру и дате изменения, а при повышенных требованиях — по хешу.
6. Средне: -Path вместо -LiteralPath
Файлы с квадратными скобками в имени тихо не обрабатываются: -Path трактует их как шаблон. Ошибки нет, файла в приёмнике тоже нет.
Крайние случаи
- Источник недоступен: выборка вернёт пустоту, и скрипт удалит в приёмнике всё. Проверка доступности источника обязательна до фазы удаления — это тот же класс проблемы, что и находка 1.
- Повторный запуск после обрыва: сейчас безопасен, потому что копирование идёт с перезаписью. После правок сохраните это свойство.
- Длинные пути: при глубокой вложенности возможно превышение лимита длины пути. Проверьте, включена ли поддержка длинных путей в вашем окружении.
- Юникод в именах: сам по себе проблем не создаёт, но логирование пишется без указания кодировки — в логе получится нечитаемо. Добавьте
-Encoding UTF8.
Что оставить как есть
- Структура с param и валидацией на входе — сделана правильно, не трогайте.
- Использование Write-Host для прогресса — в интерактивном сценарии это нормально, менять на другие потоки ради чистоты не нужно.
- Отсутствие распараллеливания. На ваших объёмах последовательный обход укладывается в окно, а параллельное копирование добавит гонок и усложнит обработку ошибок. Не усложняйте без нужды.
- Именование переменных. Не идеальное, но читаемое. Переписывать ради стиля — риск без выгоды.
Порядок работ
- Решить вопрос зеркало или резервная копия — от этого зависит, нужна ли фаза удаления вообще.
- Находки 2 и 3: обработка ошибок и блокировка удаления при сбоях копирования. Самая дешёвая правка с наибольшим эффектом.
- Находка 1: пробный режим и предохранитель на массовое удаление.
- Находка 4: обёртка выборок в массив.
- Находки 5 и 6 — по остаточному принципу.
Похожие промпты
Скрипт PowerShell под задачу
Пишет скрипт PowerShell с обработкой ошибок, логированием и режимом пробного прогона — так, чтобы его можно было запустить на рабочей машине и не бояться последствий.
Помощник по настройке WireGuard и OpenVPN
Пошагово разворачивает собственный VPN-сервер на WireGuard или OpenVPN: готовые команды и конфиги, firewall, клиентские профили и проверка утечек.
Аудит безопасности малого бизнеса и домашней сети
Аудит безопасности малого бизнеса или домашней сети: чек-лист по областям, оценка рисков через сценарии атак и план усиления защиты на 30 дней.
Консультант по выбору VPN-решения
Помогает выбрать VPN под задачу: сравнивает коммерческие сервисы и self-hosted решения с учётом страны, устройств, модели угроз и бюджета.