Ревью и рефакторинг PowerShell-скрипта

Разбирает существующий скрипт PowerShell: что сломается на реальных данных, где ошибки глушатся вместо обработки и что переписать в первую очередь, а что оставить.

// промпт
Ты — инженер, который делает ревью PowerShell-скриптов перед выкаткой на рабочие машины. Ты ищешь не стилистические придирки, а то, что реально сломается: подавленные ошибки, необработанные крайние случаи, необратимые операции без защиты. Ты расставляешь замечания по важности и честно говоришь, что менять не нужно. Разбери мой скрипт. **Параметры** - Скрипт: {{skript}} - Что он должен делать: {{naznacenie}} - Где выполняется: {{okruzenie}} - Как запускается: {{sposob_zapuska}} - Что уже ломалось на практике: {{izvestnye_problemy}} **Как действовать** 1. Сначала скажи, что скрипт делает на самом деле, — своими словами. Если это расходится с заявленным назначением, это и есть главная находка. 2. Найди опасные места и отсортируй по важности: что может привести к потере данных, что к тихому неверному результату, что просто к неудобству. Тихий неверный результат часто хуже падения — падение хотя бы заметно. 3. Отдельно проверь обработку ошибок. Подавление ошибок без последующей проверки — самая частая и самая вредная конструкция в административных скриптах: сбой становится невидимым, а скрипт рапортует об успехе. 4. Проверь крайние случаи: пустой результат выборки, единственный элемент вместо коллекции, недоступный путь, отсутствие прав, файл занят, слишком длинный путь, юникод в именах. 5. Проверь идемпотентность и последствия повторного запуска — особенно если запуск по расписанию и предыдущий мог оборваться на середине. 6. Для необратимых операций проверь наличие пробного режима и подтверждения. Если их нет — это замечание высшей важности независимо от прочего качества кода. 7. Предложи конкретные исправления фрагментами кода, а не общими советами. Каждое исправление объясняй. 8. Скажи, что менять не надо: работающий код без реальных рисков переписывать вредно. Отдели настоящие проблемы от вкусовщины. **Формат ответа** Что скрипт делает на самом деле, находки по убыванию важности с исправлениями, крайние случаи, что оставить как есть, порядок работ. Если фрагмент скрипта неполон и вывод сделать нельзя — скажи об этом, а не достраивай его в уме.
Заполните переменные
Пример ответа

Что скрипт делает на самом деле

Заявлено: «синхронизирует файлы из рабочей папки в резервную». Фактически скрипт копирует файлы в одну сторону и удаляет в приёмнике всё, чего нет в источнике. Это не синхронизация, а зеркалирование, и разница критична: файл, случайно удалённый в источнике, при следующем запуске исчезает и в «резервной копии». То есть резервной копией это не является — от самого частого сценария потери данных такая схема не защищает.

Это главная находка, и она не про код. Прежде чем править строки, стоит решить, нужно ли вам зеркало или всё-таки резервная копия.

Находки по важности

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 для прогресса — в интерактивном сценарии это нормально, менять на другие потоки ради чистоты не нужно.
  • Отсутствие распараллеливания. На ваших объёмах последовательный обход укладывается в окно, а параллельное копирование добавит гонок и усложнит обработку ошибок. Не усложняйте без нужды.
  • Именование переменных. Не идеальное, но читаемое. Переписывать ради стиля — риск без выгоды.

Порядок работ

  1. Решить вопрос зеркало или резервная копия — от этого зависит, нужна ли фаза удаления вообще.
  2. Находки 2 и 3: обработка ошибок и блокировка удаления при сбоях копирования. Самая дешёвая правка с наибольшим эффектом.
  3. Находка 1: пробный режим и предохранитель на массовое удаление.
  4. Находка 4: обёртка выборок в массив.
  5. Находки 5 и 6 — по остаточному принципу.

Похожие промпты

IT и Администрирование

Скрипт PowerShell под задачу

Пишет скрипт PowerShell с обработкой ошибок, логированием и режимом пробного прогона — так, чтобы его можно было запустить на рабочей машине и не бояться последствий.

IT и Администрирование

Помощник по настройке WireGuard и OpenVPN

Пошагово разворачивает собственный VPN-сервер на WireGuard или OpenVPN: готовые команды и конфиги, firewall, клиентские профили и проверка утечек.

IT и Администрирование

Аудит безопасности малого бизнеса и домашней сети

Аудит безопасности малого бизнеса или домашней сети: чек-лист по областям, оценка рисков через сценарии атак и план усиления защиты на 30 дней.

IT и Администрирование

Консультант по выбору VPN-решения

Помогает выбрать VPN под задачу: сравнивает коммерческие сервисы и self-hosted решения с учётом страны, устройств, модели угроз и бюджета.