В одной консоли node --version показывает ожидаемую версию, в другой — старую, а русское сообщение инструмента превращается в набор знаков. Цена ошибки выше одной неудачной сборки: можно исправить не тот node.exe, сохранить повреждённый текстовый файл или навсегда засорить системный PATH ради разового опыта.
Разберём один вопрос: как Windows и PowerShell выбирают бинарник и почему рядом с этим выбором приходится проверять кодировку? Сначала увидим путь команды в текущем процессе, затем отдельно посмотрим кодовую страницу. Это две связанные проверки, но не одна «настройка окружения».
У процесса свой набор переменных
Переменная среды всегда строка, а дочерний процесс наследует её от родителя. На Windows значения могут существовать в системной, пользовательской и процессной области. Когда мы присваиваем $env:Path в текущем PowerShell, меняется только этот сеанс и программы, запущенные из него. Такой короткий опыт безопаснее постоянной правки: можно поставить папку с нужной версией первой, посмотреть результат и закрыть окно, если гипотеза не подтвердилась.
Из этого следует простой, но важный вывод. Снимок PATH из нового окна и снимок из уже открытой консоли могут различаться без ошибки Windows. Если правка сделана в настройках пользователя или системы, старое приложение не переписывает свой блок переменных само. Для проверки открываю новую консоль и фиксирую время снимка, а не ожидаю, что одна строка из реестра мгновенно объяснит чужой терминал.
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 запускает функцию | Физические файлы не описывают приоритет PowerShell | Get-Command node -All с колонкой CommandType | Проверить профиль и не менять PATH, пока функция не исключена |
| Русский вывод нечитаем только в одной консоли | Различается кодовая страница или настройка вывода | chcp и [Console]::OutputEncoding | Сравнить новую консоль и конкретный способ записи файла |
| После правки переменной результат прежний | Работает старый процесс с унаследованными значениями | Снимок из нового PowerShell без профиля | Открыть новый сеанс и повторить одну исходную команду |
| Сборка читает верный Node, но падает дальше | Причина не в поиске бинарника | Lock-файл, лог команды, права и конфигурация проекта | Не продолжать править PATH; расследовать следующий симптом |
Последовательность проверки
- Зафиксировать одну воспроизводимую команду проекта и её полный текст ошибки, не меняя настройки заранее.
- В той же консоли вывести
Get-Command -All,where.exeи--versionдля нужного инструмента. - Открыть новый PowerShell с
-NoProfileи повторить ровно те же три наблюдения. - Если расходится путь, добавить нужный каталог только в
$env:Pathтекущего сеанса и проверить исходную команду. - Если расходится текст, записать
chcp, настройку вывода и кодировку конкретного файла, не подменяя одну проверку другой. - Постоянное изменение делать лишь после того, как временный опыт дал понятный результат и его можно описать в README.
Границы такого объяснения
Механизм выбора команды не объясняет всё. Бинарники могут отличаться разрядностью, зависимыми DLL, правами запуска, содержимым каталога или настройками антивируса и прокси. Эти ограничения не повод отключать защиту и начинать с переустановки Windows. Они означают, что после совпадения пути и версии нужно смотреть следующий наблюдаемый факт — лог приложения, разрешения каталога или сетевой запрос.
Так же осторожно отношусь к кодировке. chcp нужен для проверки активной консоли, но не является рецептом «всегда ставить UTF-8». У старых программ и редакторов могут быть свои ожидания. Воспроизводимым считается результат, когда одна и та же программа из новой консоли выдаёт читаемый текст и открывает нужные файлы по документированному правилу.
Итог механизма
Имя команды не равно конкретному файлу. В Windows PowerShell между ними стоят процессное окружение, порядок PATH, PATHEXT и приоритет alias, функций, cmdlet и приложений. Рядом живёт отдельный слой отображения текста. Когда эти два пути проверены отдельными командами, «на моей машине другая версия» превращается в строку, которую можно сравнить и исправить без гадания.