{ "index": 336, "slug": "editorial-2018-09-practice-windows-dev-env", "title": "Windows-окружение без гадания: как найти настоящий бинарник и сравнить запуск", "excerpt": "Если одна Windows-машина запускает проект, а другая не находит команду или выбирает другую версию, сначала снимите процессное окружение. Разбираем PATH, профиль PowerShell, кодовую страницу и обратимую проверку без глобальной переустановки.", "contentHtml": "

Симптом знаком: на одном компьютере npm run build проходит, а на другом команда не находится, запускает старый Node или печатает нечитаемый лог. Код и commit совпадают. Разработчик видит только имя node, но Windows выбирает конкретный файл из конкретного процесса. Цена ошибки — потерянные часы и испорченные доказательства. Если сразу переустановить Node или переписать системный PATH, изменятся несколько условий сразу. После этого трудно понять, что действительно помогло.

\n

Тезис простой: сравнивать нужно не список установленных программ, а путь запуска команды. В него входят процессные переменные, порядок каталогов в PATH, тип найденной команды, фактический путь к файлу, вывод версии и состояние консоли. Такой снимок не делает машины одинаковыми. Он сужает причину до наблюдаемого различия, которое можно проверить одним обратимым действием.

\n

Механизм: имя команды не равно файлу

\n

PowerShell ищет команды в текущей сессии. Результатом может быть alias, функция, cmdlet, скрипт или приложение. Для приложения поиск использует переменную PATH и расширения из PATHEXT. Первый найденный вариант становится тем, что запустит команда. Поэтому строка «Node установлен» не отвечает на вопрос «какой node.exe запустился сейчас?».

\n

Переменные среды имеют область процесса, пользователя и компьютера. Новая PowerShell-сессия получает значения от родительского процесса. Уже открытое окно не обязано увидеть изменение, сделанное в настройках Windows. Если добавить каталог в системный PATH, а затем повторить команду в старом окне, проверка может измерить старое состояние. Для диагностики сначала используйте новый процесс, а постоянную настройку не меняйте.

\n

Кодовая страница — отдельный слой. Она влияет на то, как консоль показывает текст, но не выбирает бинарник. Если путь и версия совпадают, нечитаемый вывод не доказывает проблему PATH. Аналогично, совпадающий node --version не доказывает, что проект получает одинаковые права, сертификаты, зависимости или настройки редактора.

\n
\"Схема
Снимок фиксирует путь запуска команды. Он не является копией компьютера и не должен содержать секреты.
\n

Минимальный снимок

\n

Снимок должен отвечать на четыре вопроса: какая PowerShell выполняет команду; какие каталоги участвуют в поиске; какие кандидаты возвращает Get-Command -All; что сообщает сам инструмент. Отдельно запишите кодовую страницу и кодировку вывода. Не собирайте весь Env:: в нём могут оказаться токены, прокси, внутренние адреса и персональные пути.

\n
Что фиксировать при сравнении двух запусков
ПолеПроверкаЧто объясняетОграничение
Версия и редакция PowerShell$PSVersionTable.PSVersion, $PSVersionTable.PSEditionДоступные команды и различия поведения оболочкиНе доказывает версию проекта
Порядок PATH$env:Path -split ';'Какая папка может дать первый бинарникСам по себе путь не говорит, что файл исправен
Кандидаты командыGet-Command node -AllAlias, функцию и приложения по одному имениНе проверяет зависимости приложения
Фактическая версияnode --versionЧто ответил запущенный инструментНе заменяет lock-файл и требования проекта
Состояние консолиcmd /c chcp, [Console]::OutputEncoding.WebNameПричину различий в отображении текстаНе меняет кодировку файлов и редактора
\n

Пример сбора отчёта

\n

Ниже — ограниченный учебный скрипт для PowerShell 5.1. Он собирает выбранные поля и версии четырёх команд. Пример не устанавливает инструменты, не исправляет PATH и не создаёт production-данные. Замените список команд на требования конкретного проекта. Если проект использует только PHP, отсутствие Node в отчёте нормально.

\n
# Capture-Environment.ps1\n$ErrorActionPreference = 'Stop'\n$toolChecks = @(\n  @{ name = 'node'; arguments = @('--version') },\n  @{ name = 'npm'; arguments = @('--version') },\n  @{ name = 'php'; arguments = @('--version') },\n  @{ name = 'git'; arguments = @('--version') }\n)\n\nfunction Get-Candidates($name) {\n  @(Get-Command -Name $name -All -ErrorAction SilentlyContinue |\n    ForEach-Object {\n      [ordered]@{\n        type = $_.CommandType.ToString()\n        name = $_.Name\n        definition = $_.Definition\n        source = $_.Source\n        version = if ($_.Version) { $_.Version.ToString() } else { $null }\n      }\n    })\n}\n\nfunction Get-VersionOutput($name, $arguments) {\n  if (-not (Get-Command $name -ErrorAction SilentlyContinue)) { return @('NOT FOUND') }\n  try {\n    $lines = @(& $name @arguments 2>&1 | Select-Object -First 3 |\n      ForEach-Object { $_.ToString() })\n    return @($lines + ('exitCode=' + $LASTEXITCODE))\n  } catch {\n    return @('FAILED: ' + $_.Exception.Message)\n  }\n}\n\n$report = [ordered]@{\n  formatVersion = 1\n  capturedAt = (Get-Date).ToString('o')\n  powershell = [ordered]@{\n    version = $PSVersionTable.PSVersion.ToString()\n    edition = $PSVersionTable.PSEdition\n  }\n  console = [ordered]@{\n    codePage = ((cmd.exe /d /c chcp) -join ' ').Trim()\n    outputEncoding = [Console]::OutputEncoding.WebName\n  }\n  environment = [ordered]@{\n    pathEntries = @($env:Path -split ';' | Where-Object { $_ })\n    pathext = $env:PATHEXT\n  }\n  commands = @($toolChecks | ForEach-Object {\n    [ordered]@{\n      name = $_.name\n      candidates = Get-Candidates $_.name\n      versionOutput = Get-VersionOutput $_.name $_.arguments\n    }\n  })\n}\n\n$report | ConvertTo-Json -Depth 6 | Set-Content -LiteralPath '.\\environment-snapshot.json' -Encoding UTF8
\n

Запускайте его из каталога проекта без профиля, чтобы пользовательская функция или alias не скрыли реальную картину:

\n
powershell.exe -NoProfile -File .\\tools\\Capture-Environment.ps1\nGet-Content .\\environment-snapshot.json -Raw | ConvertFrom-Json | Format-List\nGet-Command node -All | Select-Object CommandType, Name, Version, Definition
\n

Перед отправкой JSON удалите имя пользователя, внутренние каталоги, URL прокси и любые значения, которые относятся к доступу. Сохраните исходный файл только там, где это разрешено. Если отчёт нужен для сравнения, полезнее заменить часть пути на <USER>, чем публиковать личные данные. Не добавляйте снимок в репозиторий, пока не проверили его содержимое.

\n

Симптом → причина → проверка → действие

\n
Диагностическая карта для Windows-окружения
СимптомПричинаПроверкаДействие
Команда не найденаКаталог отсутствует в процессном PATH или программа не установлена$env:Path -split ';', Get-Command name -AllСверить требование проекта и проверить одобренную установку; не дописывать путь наугад
Запускается не та версияСтарая папка стоит раньше в PATHСравнить первый Definition, весь список кандидатов и name --versionВ новом окне временно проверить документированный каталог в начале процессного $env:Path
Вместо приложения найдена функция или aliasПрофиль PowerShell подменяет имяСравнить обычный запуск с powershell.exe -NoProfileИспользовать явный путь или исправить профиль после подтверждения причины
После изменения ничего не поменялосьКоманда выполняется в старом процессеСнять отчёт из новой сессии без профиляПовторить одну исходную команду; не делать вывод о неработающем исправлении по старому окну
Лог нечитаемРазличаются кодовая страница или кодировка выводаchcp, OutputEncoding и способ записи файлаПроверить консоль и файл раздельно; не менять PATH
Путь и версия совпадают, сборка всё ещё падаетПричина находится в зависимостях, правах, сети или конфигурацииLock-файл, полный лог, права каталога и следующий симптомПрекратить правку окружения и продолжить расследование по новому факту
\n

Сравнение двух машин

\n

Сначала сделайте входы сравнимыми: один commit, одна команда, один рабочий каталог и одинаковый lock-файл. На обеих машинах сохраните очищенные снимки. Затем сравните только поля, относящиеся к запуску. В учебном примере машина A первой находит C:\\Tools\\node-6\\node.exe, а машина B — C:\\Program Files\\nodejs\\node.exe. Это доказывает различие поиска, но не доказывает, какая версия нужна проекту. Требование должно прийти из README, lock-файла или официальной документации проекта.

\n
function Get-SnapshotLines($path) {\n  $s = Get-Content $path -Raw | ConvertFrom-Json\n  $lines = @(\n    'powershell=' + $s.powershell.version\n    'codePage=' + $s.console.codePage\n    'outputEncoding=' + $s.console.outputEncoding\n    'pathext=' + $s.environment.pathext\n  )\n  foreach ($entry in $s.environment.pathEntries) { $lines += 'path=' + $entry }\n  foreach ($tool in $s.commands) {\n    $first = @($tool.candidates | Select-Object -First 1)\n    if ($first.Count -eq 0) { $lines += $tool.name + '=NOT FOUND'; continue }\n    $lines += ('{0}={1}|{2}|{3}' -f $tool.name, $first[0].type, $first[0].definition, $first[0].version)\n  }\n  return $lines\n}\nCompare-Object (Get-SnapshotLines '.\\machine-a.json') (Get-SnapshotLines '.\\machine-b.json')
\n

Compare-Object показывает различающиеся строки. Он не ставит диагноз. Если отличаются десять каталогов, выберите тот, который связан с исходной ошибкой, и проверьте его отдельно. Сначала сравните первый кандидат и версию. Только затем смотрите кодовую страницу или второстепенные различия.

\n

Обратимая проверка PATH

\n

Допустим, документация проекта требует Node из конкретного каталога, а снимок показывает старый бинарник. В новом PowerShell добавьте проверенный каталог только в процессную переменную. Учебный путь ниже условный. Его нельзя копировать в рабочую машину без подтверждения версии и расположения файла.

\n
$requiredNode = 'C:\\Tools\\node-8'\nif (-not (Test-Path (Join-Path $requiredNode 'node.exe'))) {\n  throw 'node.exe not found: ' + $requiredNode\n}\n$env:Path = $requiredNode + ';' + $env:Path\nGet-Command node -All | Select-Object CommandType, Definition\nnode --version\nnpm run build
\n

Если версия изменилась и исходная ошибка исчезла, гипотеза о порядке PATH получила подтверждение в этой сессии. Это ещё не разрешение менять системные настройки. Сначала зафиксируйте требование проекта и способ установки. Если результат не изменился, верните внимание к логу. Не оставляйте случайный каталог в постоянном PATH и не скачивайте исполняемый файл из непроверенного источника.

\n

Порядок действий

\n
  1. Запишите commit, рабочий каталог, команду и полный текст ошибки до любых изменений.
  2. Назовите инструменты, которые действительно нужны проекту, и их требуемые версии.
  3. Снимите выбранные поля из обычной PowerShell-сессии.
  4. Повторите снимок в новом окне с -NoProfile, если есть подозрение на alias, функцию или профиль.
  5. Сравните порядок PATH, PATHEXT, кандидатов команды, путь первого приложения и результат --version.
  6. Выберите одно отличие, которое прямо связано с ошибкой. Не исправляйте остальные различия одновременно.
  7. Проверьте гипотезу в новом процессе через временный $env:Path или другой обратимый шаг.
  8. Повторите исходную команду и сохраните результат вместе с новым снимком.
  9. Только после подтверждения обновите README или согласованный установочный механизм. Закройте временную сессию и убедитесь, что глобальные настройки не изменились.
\n

Ограничения и отрицательный путь

\n

Одинаковый снимок не гарантирует одинаковый запуск. На результат влияют разрядность, DLL, права каталога, антивирус, прокси, сертификаты, кеши, редактор, локальные файлы и сетевой доступ. Снимок не видит все эти причины. Он только помогает исключить подмену команды и ошибку поиска.

\n

Если команда не найдена, не добавляйте в PATH первую папку из загрузок. Сначала проверьте официальную инструкцию проекта, наличие нужного файла и архитектуру. Если путь и версия совпали, не продолжайте менять PATH ради другого симптома. Если отчёт содержит секрет, удалите его из копии до публикации и сообщите владельцу доступа по принятому каналу.

\n

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

\n

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

\n

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

\n

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

\n" }