Skip to content

WinClean v2.21: self-update targeting and honest exit codes - #11

Merged
bivlked merged 3 commits into
mainfrom
feature/v2.21
Jul 22, 2026
Merged

WinClean v2.21: self-update targeting and honest exit codes#11
bivlked merged 3 commits into
mainfrom
feature/v2.21

Conversation

@bivlked

@bivlked bivlked commented Jul 22, 2026

Copy link
Copy Markdown
Owner

Что это

Релиз про честность на выходе. Две задачи, обе про то, что прогон сообщал состояние, которого не проверял.

MyAI-3y5i (P1): авто-обновление меняло не тот файл

Проверка спрашивала, есть ли копия из галереи где-нибудь на машине, а ответ использовался так, будто это ответ на вопрос «является ли выполняемый файл той самой копией». Оба условия штатно истинны одновременно: install.ps1 ставит копию в %ProgramFiles%\WinClean, куда смотрит ярлык, а старая копия из Install-Script остаётся в Documents. Update-Script обновлял копию в Documents, печатал «Update complete», выходил с кодом 0 - и ярлык продолжал запускать старый файл. Бесконечно, без единой ошибки.

Ревью вскрыло, что задача шире исходной:

  • совет для не-галерейных копий предлагал Install-Script, а он создаёт вторую установку - то есть сам строил конфигурацию, породившую дефект;
  • при двух установках Update-Script нацелить нечем (нет -Scope), поэтому авто-обновление теперь отключается с объяснением вместо мутации случайной копии;
  • весь путь был PowerShellGet-only: на машине только с PSResourceGet проверка падала, отказ читался как «обновлений нет», ветка молча не работала. Теперь оба провайдера, и ответивший при обнаружении получает приоритет при обновлении;
  • Get-PSResource по умолчанию видит только CurrentUser, поэтому копия AllUsers была невидима;
  • обновление проверяется по версии файла на диске; успешный путь пишет result JSON до выхода.

MyAI-iodp, MyAI-7iej (P3): код возврата

Код возврата считается только по ErrorsCount. Отсутствие winget, отсутствие сети и неудача самообновления перестали быть ошибками: это состояние окружения либо вспомогательная операция, а не отказ обслуживания. winget, который есть и не отработал, по-прежнему ошибка - как и галерея, заблокированная при рабочей сети.

Новое поле result JSON AppUpdatesStatus возвращает машиночитаемый признак, который убрало понижение уровня.

Проверки

  • 573 теста (было 452), линтер чист на 18 файлах
  • 21 мутация против новых защит, каждая поймана тестом
  • Релиз-гейт 10/10
  • Стенд Full на обеих VM: RU 1219 МБ, EN 2724 МБ, ErrorsCount: 0, версия 2.21 подтверждена из result JSON
  • Ревью: 16 раундов внешнего движка + 4 специализированных агента; последний раунд пустой

Документация приведена в соответствие: docs/troubleshooting.md, docs/result-json.md и оба README местами утверждали обратное тому, что делает код.

Ivan Bondarev added 3 commits July 22, 2026 23:32
… роняет прогон

MyAI-3y5i (P1). Проверка обновлений спрашивала "есть ли копия из галереи
где-нибудь на машине", а ответ использовался так, будто это был ответ на вопрос
"является ли выполняемый файл той самой копией". Оба условия штатно истинны
одновременно: install.ps1 (с 2.15) ставит копию в %ProgramFiles%\WinClean, куда
смотрит ярлык, а старая копия из Install-Script остаётся в Documents.
Update-Script обновлял копию в Documents, печатал "Update complete! Please run
WinClean again", выходил с кодом 0 - и ярлык продолжал запускать нетронутый
старый файл. Бесконечно, без единой ошибки.

Теперь выполняемый файл сравнивается с местом установки провайдера, и
авто-обновление предлагается только когда это один и тот же файл. Остальным
называется способ, который реально к ним применим.

Ревью (16 раундов Codex + 4 агента) вскрыло, что задача шире исходной:

- совет для не-галерейных копий предлагал Install-Script, а он СОЗДАЁТ вторую
  установку - то есть сам строил конфигурацию, породившую дефект;
- при двух установках Update-Script нельзя нацелить (нет -Scope), поэтому
  авто-обновление теперь отключается с объяснением вместо мутации случайной
  копии. Тот же принцип, что Select-StorageSenseTask в 2.20;
- весь путь был PowerShellGet-only: на машине только с PSResourceGet проверка
  падала, catch превращал это в "обновлений нет", ветка молча не работала.
  Теперь оба провайдера, и тот, что ОТВЕТИЛ при обнаружении, получает приоритет
  при обновлении;
- Get-PSResource по умолчанию видит только CurrentUser, поэтому AllUsers-копия
  была невидима - и получала совет, создающий вторую установку;
- обновление проверяется по версии в выполняемом файле; отказ проверки больше не
  неотличим от "у вас последняя версия";
- успешный путь пишет result JSON перед exit, иначе потребитель видел код 0 и
  отсутствующий артефакт.

MyAI-iodp (P3). Отсутствие winget было ERROR, а код возврата считается только по
ErrorsCount - значит машина без App Installer ВСЕГДА завершалась с кодом 1 при
девяти выполненных фазах. Теперь WARNING. ERROR остаётся за случаем, когда winget
есть и не отработал: провал самой проверки и необработанное исключение.
Добавлено поле result JSON AppUpdatesStatus - понижение убрало единственный
машиночитаемый признак "проверяли или нет".

Факты, установленные экспериментом, а не рассуждением: InstalledLocation у
скрипта это папка Scripts без версии; провайдеры видят установки друг друга;
Select-Object -Unique регистрозависим; GetFullPath не убирает завершающий
разделитель; [Version] 2.21 меньше 2.21.0; оба провайдера бросают и при
"не установлено", но Get-InstalledScript без -Name возвращает пустой список -
на этом и построено различение отказа от отсутствия копии.

452 -> 571 теста. Все защиты проверены мутациями (21 мутация, все пойманы).
Тот же класс, что отсутствие winget, и та же причина: код возврата считается
только по ErrorsCount, поэтому машина без сети ВСЕГДА завершалась с кодом 1 -
сколь бы полно ни отработала очистка. Ноутбук, обслуживаемый вне сети, рапортовал
провал на каждом запуске.

Обе половины фазы Updates (Windows Update и приложения) читают один мемоизованный
результат проверки связности, поэтому понижены обе - оставить одну ошибкой значило
бы сохранить код 1 и обессмыслить изменение. Состояние не исчезает из виду: WARNING
логируется и считается, а result JSON несёт AppUpdatesStatus 'skipped-offline',
который описывает всю фазу, включая половину Windows. Отдельное поле для Windows
не заводилось именно поэтому.

Осознанно НЕ тронуто: "PowerShell Gallery is unavailable" и соседние ветки
(NuGet-провайдер, установка модуля) остаются ERROR. Это не "нет сети": проверка
связности к тому моменту УЖЕ прошла, то есть сеть доказана рабочей, а не смогли
получить зависимость, которую WinClean сам решил ставить. Отсутствующая
предпосылка и неудавшаяся операция - разные вещи, и код возврата их различает.
Разграничение проверено сторонним ревью и явно описано в docs/troubleshooting.md,
где раздел про галерею теперь отсылает офлайн-читателя в раздел про связность.

Доки исправлены там, где сами утверждали обратное: docs/result-json.md и
docs/troubleshooting.md прямым текстом писали, что офлайн-ветка остаётся ошибкой.
Заодно везде уточнено, что Updates - ОДНА фаза с двумя половинами, а не две фазы:
формальные имена фаз попадают в result JSON, и прежняя формулировка читалась как
два элемента.

571 -> 573 теста. Помимо двух тестов на уровень для каждой половины добавлен
комбинированный: он готовит сам кеш связности, а не мокает проверку, потому что
именно общий кеш и есть предпосылка, позволяющая одному полю описывать обе
половины. Обе мутации (возврат любой ветки к ERROR) ловятся.
Версия поднята во всех местах: .VERSION, .RELEASENOTES, SYNOPSIS, NOTES с блоком
изменений, $script:Version, диаграммы Execution Flow в обоих README. Бейджи
динамические с 2.19 и правки не требуют.

CHANGELOG: раздел [Unreleased] закрыт как [2.21] с датой; добавлены записи про
документацию result JSON и про тесты (452 -> 573, 21 мутация).

Релиз-гейт 8/10: провалены только 'дерево чисто' и 'ветка синхронна с origin' -
оба про сам этот коммит. Версия 2.21 подтверждена из result JSON смоук-прогона.
@bivlked
bivlked merged commit 126d6f9 into main Jul 22, 2026
5 checks passed
@bivlked
bivlked deleted the feature/v2.21 branch July 22, 2026 21:38

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0712e6e33b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread WinClean.ps1
# either can be the one present, and the one that answered discovery goes first
switch (Select-UpdateCommand -Provider $UpdateInfo.Provider) {
'Update-Script' { Update-Script -Name WinClean -Force -ErrorAction Stop }
'Update-PSResource' { Update-PSResource -Name WinClean -Force -TrustRepository -ErrorAction Stop }

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Pass the matched scope to PSResourceGet updates

When the running Gallery copy is an AllUsers install and discovery selected PSResourceGet, this unscoped call does not target the file that Get-ScriptUpdateChannel just matched: Microsoft documents Update-PSResource as accepting -Scope and defaulting to CurrentUser (https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.psresourceget/update-psresource?view=powershellget-3.x#-scope). In that common admin install case, accepting the prompt either leaves the AllUsers script unchanged or creates/updates a CurrentUser copy, after which verification warns and the user still cannot self-update the running copy; carry the matched scope through detection or decline auto-update when it cannot be determined.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant