DarkRiDDeR11 мин

Windows. Почему PATH, кодировка и версия бинарника меняют запуск проекта

WindowsИнструменты

В одной консоли node --version показывает ожидаемую версию, в другой — старую, а русское сообщение инструмента превращается в набор знаков. Цена ошибки выше одной неудачной сборки: можно исправить не тот node.exe, сохранить повреждённый текстовый файл или навсегда засорить системный PATH ради разового опыта.

Разберём один вопрос: как Windows и PowerShell выбирают бинарник и почему рядом с этим выбором приходится проверять кодировку? Сначала увидим путь команды в текущем процессе, затем отдельно посмотрим кодовую страницу. Это две связанные проверки, но не одна «настройка окружения».

У процесса свой набор переменных

Переменная среды всегда строка, а дочерний процесс наследует её от родителя. На Windows значения могут существовать в системной, пользовательской и процессной области. Когда мы присваиваем $env:Path в текущем PowerShell, меняется только этот сеанс и программы, запущенные из него. Такой короткий опыт безопаснее постоянной правки: можно поставить папку с нужной версией первой, посмотреть результат и закрыть окно, если гипотеза не подтвердилась.

Из этого следует простой, но важный вывод. Снимок PATH из нового окна и снимок из уже открытой консоли могут различаться без ошибки Windows. Если правка сделана в настройках пользователя или системы, старое приложение не переписывает свой блок переменных само. Для проверки открываю новую консоль и фиксирую время снимка, а не ожидаю, что одна строка из реестра мгновенно объяснит чужой терминал.

Схема выбора команды в Windows PowerShell: процесс получает PATH и PATHEXT, Get-Command -All показывает функции, alias и приложения в порядке выбора, where.exe ищет файлы, а кодовая страница проверяется отдельной веткой.
Путь к бинарнику и способ отображения его сообщения надо проверять отдельно: один отвечает за запуск, второй — за читаемость вывода.

PATH и PATHEXT отвечают только за часть выбора

PATH — это список каталогов, где система ищет исполняемые файлы. В Windows его элементы разделены точкой с запятой. PATHEXT задаёт расширения, которые считаются исполняемыми, поэтому имя tool может привести к tool.exe или tool.cmd. Но для PowerShell этого мало: перед приложением может оказаться alias, функция, cmdlet или внешний скрипт с тем же именем.

Поэтому where.exe node полезен, но не является окончательным ответом. Он ищет файлы в текущем каталоге и папках PATH; он не покажет функцию node, определённую в профиле PowerShell. Get-Command node -All выводит все варианты в порядке, который PowerShell использует при выборе. В диагностике я запускаю обе команды: первая хорошо показывает физические файлы, вторая — фактическую команду оболочки.

$names = @("node", "npm", "php", "git")

foreach ($name in $names) {
  Write-Host ""
  Write-Host ("== " + $name + " ==")
  Get-Command -Name $name -All -ErrorAction SilentlyContinue |
    Select-Object CommandType, Name, Version, Source, Definition |
    Format-Table -AutoSize

  Write-Host "-- files found by where.exe --"
  where.exe $name 2>$null

  if (Get-Command -Name $name -ErrorAction SilentlyContinue) {
    & $name --version
    Write-Host ("exitCode=" + $LASTEXITCODE)
  }
}

У этой проверки есть ограничение: аргумент --version подходит не каждой утилите. Для старого проекта список аргументов лучше хранить рядом с проектом, а не использовать универсальный запуск. Если команда является функцией, её вывод ещё не доказывает путь к приложению. В таком случае сначала называю тип кандидата, затем проверяю его определение и решаю, является ли это частью проекта или случайной надстройкой профиля.

Кодовая страница — отдельный слой

chcp показывает активную кодовую страницу консоли и умеет её менять. Документация Windows отдельно оговаривает, что программы, запущенные после смены страницы, используют новое значение, а уже запущенные программы обычно сохраняют прежнее. Это объясняет частый ложный вывод «я поменял кодировку, но ошибка осталась»: проверка сделана в процессе, который стартовал раньше.

Для разбора не надо немедленно переключать консоль на 65001 или менять системный язык. Сначала записываю три наблюдения: строку chcp, [Console]::OutputEncoding.WebName и байты проблемного файла, если проблема именно в файле. Кодовая страница cmd.exe, настройка вывода .NET и кодировка файла — разные вещи. Совпадение двух из них ничего не гарантирует, а изменение одного не лечит остальные.

# Наблюдение, без изменения глобальных настроек.
& cmd.exe /d /c chcp
[Console]::OutputEncoding.WebName
[Text.Encoding]::Default.WebName

# Короткий опыт только в текущем PowerShell.
$projectTools = Join-Path $PSScriptRoot "tools"
$env:Path = $projectTools + ";" + $env:Path

Get-Command node -All |
  Select-Object CommandType, Name, Version, Definition |
  Format-Table -AutoSize
node --version

Последние четыре строки намеренно меняют только процессную область. Если нужный node.exe оказался первым и проект прошёл документированную команду, гипотеза подтверждена. Если нет, закрываю окно — постоянного следа не осталось. Лишь после такой проверки имеет смысл обсуждать установщик, путь в пользовательском PATH или явный путь в скрипте проекта.

Как не спутать три похожих симптома

НаблюдениеЧто оно означаетЧем проверитьСледующее действие
node --version даёт старую версиюВыбран другой кандидат или путь в PATH стоит раньшеGet-Command node -All и where.exe nodeВременно поставить нужную папку первой в текущем сеансе и повторить версию
where.exe показывает два файла, а PowerShell запускает функциюФизические файлы не описывают приоритет PowerShellGet-Command node -All с колонкой CommandTypeПроверить профиль и не менять PATH, пока функция не исключена
Русский вывод нечитаем только в одной консолиРазличается кодовая страница или настройка выводаchcp и [Console]::OutputEncodingСравнить новую консоль и конкретный способ записи файла
После правки переменной результат прежнийРаботает старый процесс с унаследованными значениямиСнимок из нового PowerShell без профиляОткрыть новый сеанс и повторить одну исходную команду
Сборка читает верный Node, но падает дальшеПричина не в поиске бинарникаLock-файл, лог команды, права и конфигурация проектаНе продолжать править PATH; расследовать следующий симптом

Последовательность проверки

  1. Зафиксировать одну воспроизводимую команду проекта и её полный текст ошибки, не меняя настройки заранее.
  2. В той же консоли вывести Get-Command -All, where.exe и --version для нужного инструмента.
  3. Открыть новый PowerShell с -NoProfile и повторить ровно те же три наблюдения.
  4. Если расходится путь, добавить нужный каталог только в $env:Path текущего сеанса и проверить исходную команду.
  5. Если расходится текст, записать chcp, настройку вывода и кодировку конкретного файла, не подменяя одну проверку другой.
  6. Постоянное изменение делать лишь после того, как временный опыт дал понятный результат и его можно описать в README.

Границы такого объяснения

Механизм выбора команды не объясняет всё. Бинарники могут отличаться разрядностью, зависимыми DLL, правами запуска, содержимым каталога или настройками антивируса и прокси. Эти ограничения не повод отключать защиту и начинать с переустановки Windows. Они означают, что после совпадения пути и версии нужно смотреть следующий наблюдаемый факт — лог приложения, разрешения каталога или сетевой запрос.

Так же осторожно отношусь к кодировке. chcp нужен для проверки активной консоли, но не является рецептом «всегда ставить UTF-8». У старых программ и редакторов могут быть свои ожидания. Воспроизводимым считается результат, когда одна и та же программа из новой консоли выдаёт читаемый текст и открывает нужные файлы по документированному правилу.

Итог механизма

Имя команды не равно конкретному файлу. В Windows PowerShell между ними стоят процессное окружение, порядок PATH, PATHEXT и приоритет alias, функций, cmdlet и приложений. Рядом живёт отдельный слой отображения текста. Когда эти два пути проверены отдельными командами, «на моей машине другая версия» превращается в строку, которую можно сравнить и исправить без гадания.

Проверяемые источники