Статья [Черновик] Переменные окружения в ОС Windows и командной строке

Dragokas

Very kind Developer
Команда форума
Супер-Модератор
Разработчик
Клуб переводчиков
Сообщения
6,378
Реакции
5,877
Баллы
718
5 лет назад я начинал писать статью по этой теме, но постоянно сдвигал сроки из-за новых вводных.
В итоге, большая часть статьи готова, но не имеет логического завершения в виде практических примеров.

Недавно один человек на киберфоруме задал очень близкий к тематике вопрос
и даже предельно грамотно расписал примеры с пояснениями:

Так что в той теме я всё же решился выложить статью как есть, а здесь продублирую в развёрнутом виде.
+ будут приложены все исходники и материалы.
Возможно, кто-то когда-нибудь захочет закончить эту статью в со-авторстве, т.к. я уже давно ушел из этой темы.
Не хочется, чтобы труды пропадали и пылились на полке.
 
Последнее редактирование:
  • Like
Реакции: E100

Dragokas

Very kind Developer
Команда форума
Супер-Модератор
Разработчик
Клуб переводчиков
Сообщения
6,378
Реакции
5,877
Баллы
718
Переменные окружения в ОС Windows и командной строке

vision_title_jpg.jpg



// TODO:
описать что вообще такое переменные окружения.
Описать функции для работы с переменными, как они храняться в памяти процесса, как работать с блоком переменных.
Проверить как система передаёт процессу блок переменных – из реестра, или из родительского.
Оглавление...

Здравствуйте, уважаемые читатели !!!

Сегодня мы поговорим о различных аспектах переменных окружения с точки зрения:
  • их классификации;
  • методики расчета их значения;
  • области видимости, действия и времени жизни;
  • особенностях их раскрытия под Wow64 и в реестре;
  • принципа наследования в среде командного интерпретатора CMD.
Также мы рассмотрим, как вызывать дочерние пакетные файлы и программы:
  • без или с передачей окружения;
  • с передачей окружения, его возвратом (или не возвратом) в экземпляр родительской среды.
Получим знания о:
  • локализации переменных с помощью команд SetLocal и EndLocal;
  • перебрасывании локализованной переменной за пределы локали.

Также я дам в конце статьи все это пощупать :)

Что это такое?

Переменная окружения (далее – EV (environment variable) – это ...


Зачем нужны переменные окружения?

...

Вместо предисловия:

Ко мне пришло такое письмо:
У нас на работе есть большой сценарий, построенный на разных тулах - jscript,
perl, msbuild и еще много чего, бат-файлы там тоже есть в достаточном количестве.
Время от времени приходится там кое-что подкручивать, добавлять новые операции.
Иногда получается так: задействовал какую-то переменную в бат-файле, а потом
вдруг узнаешь, что переменная с таким именем уже была задействована где-то
"выше по стеку", т.е. например, в другом бат-файле, из которого был вызван этот.

Так вот, суть вопроса: как в бат-файлах грамотно управлять областью видимости
переменных и реально ли это вообще ? Я бы, к примеру, хотел иметь возможность в
своем бат-файле объявить переменную "counter", чтобы она "замаскировала"
другую, созданную кем-то ранее переменную с таким же именем. И если я вызываю
другой батник, чтобы она не передавалась в него. Короче, подчистить хвосты
после завершения работы своего батника. Такое вообще реально ? Если да, то как ?
Для больших проектов с перекрестными вызовами скриптов проблема конфликтов
имен переменных действительно очень актуальна.

Итак, у нас есть пакетный файл (.bat или .cmd), который вызывает:
1) другие пакетные файлы
2) jscript / аналогично vbscript
3) perl
4) msbuild

Из всего этого, только пакетные файлы не имеют своих локальных переменных в привычном понимании языка высокого уровня, а хранят их в переменных окружения процесса cmd.exe со всеми вытекающими последствиями (иными словами: переменная в CMD = переменная окружения).

[article="Переменные в других языках"]
Напомню, что ЯВУ (например, C++, C#, VB6) также как и скриптовые JScript / VBscript оперируют своими внутренними переменными, которые не влияют ни на среду выполнения*, ни на вызываемые ними другие программы.

* если этого не делать специально. А такие возможность есть во всех названных языках.
[/article]Поэтому, читая эту статью, забудьте все, что знали о философии переменных в ЯВУ таких, как:
  • глобальные
  • локальные
  • статические
  • регистровые
    и прочне…
Здесь всё работает иначе.


Виды переменных окружения в операционной системе

Существуют такие (в порядке приоритета):

1) Уровня ядра (CORE) ………. «вшиты» в файл NTDLL.dll
2) системные (SYSTEM) ............ из ключа HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment
3) пользовательские (USER)* ... из ключа HKCU\Environment
4) летучие (VOLATILE) .............. из ключа HKCU\Volatile Environment
5) процесса (PROCESS), например, в процессе cmd.exe.

* Учитывайте, что каждый пользователь имеет свои собственные пользовательские (USER) переменные,
т.к. улей реестра HKCU монтируется в момент входа пользователя в систему из подраздела HKEY_USERS\<SID пользователя>.

Чтобы изменить переменную:
- уровня SYSTEM, необходимо обладать привилегиями Администратора;
- уровня VOLATILE, достаточно обладать правами обычного пользователя;
- уровня чужого процесса, необходимо иметь права не ниже тех, которые предоставлены чужому процессу.

Примечание: в системах версий Windows 9x, переменные окружения также можно было устанавливать через файл Autoexec.bat, расположенный в корне системного диска.


Как формируется значение переменной окружения в командной строке ?

Сначала берётся системная ветка и создаются переменные. Затем пользовательские...
При этом в одноимённых переменных пользовательские значения затирают системные.
Летучие затирают пользовательские и системные.
А значения переменных, созданные процессом затирают все остальные значения переменных.

Формула замены такая: SYSTEM <= USER <= VOLATILE <= PROCESS
Результат получим в виде значения переменной в командной строке командой echo %имя переменной%

Из этого правила есть исключение для переменной PATH.
Вероятно, в виду ее важности для системы алгоритм ее расчета другой:
Для ее формирования берётся значение из HKCU, которое объединяется со значением из HKLM (Smitis, 2013).
Это легко проверить. Откройте реестр и добавьте в конец значения параметра PATH системной ветки дописку ;c:\HKLM, а пользовательской ;c:\HKCU,
затем запустите командную строку от имени администратора и введите команду set.
В вывод попадут обе части.

Кроме этого, для конкретных процессов, запущенных функцией из библиотеки Shell32.dll (ShellExecute, ShellExecuteEx - например, Win + R)
к переменной окружения PATH добавляется еще и значение из веток реестра:
HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\{app.exe}\
HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\{app.exe}\
из параметра под именем "Path",
где {app.exe} - алиас (псевдоним) приложения, которое было запущено,
а значение по-умолчанию для этой ветки указывает на путь к приложению.

acro.jpg


Также, подобными свойствами (в Windows 9x) для консольных приложений
обладает файл %SystemRoot%\System32\autoexec.nt.

В целом, поиск исполняемого файла, для которого не указан полный путь
происходит в таком порядке:
  1. Текущая рабочая директория.
  2. Директория системы (%SystemRoot%).
  3. Директория %SystemRoot%\System32.
  4. Директории, перечисленные в переменной окружения PATH.
  5. Если в ветке реестра App Paths есть подраздел с именем исполняемого файла, то значение по-умолчанию данной ветки используется для поиска этого файла.
В отличие от ShellExecute, функция CreateProcess ищет исполняемый файл в другом порядке:
  1. Директория, из которой запущено приложение.
  2. Текущая директория родительского процесса.
  3. Cистемная директория (%SystemRoot%\System32)
  4. 16-битная системная директория (%SystemRoot%\System)
  5. Директория Windows (%SystemRoot%)
  6. Директории, перечисленные в переменной окружения PATH. Ветка реестра App Paths не задействуется.

Принцип наследования переменных процессами

Как мы ранее говорили, есть несколько уровней переменных окружения:
SYSTEM (1), USER (2), VOLATILE (3), PROCESS (4)

Когда загружается операционная система, читаются ключи реестра, где хранятся переменные. Затем один процесс создает другой процесс, передавая переменные.
Родительский процесс может передавать дочернему любой блок переменных (Environment Block), если он подготовит его заранее, иначе дочерний процесс унаследует блок переменных родительского процесса.

1) Если Вы изменили переменную окружения (1,2,3), то также должны предупредить об этом другие уже запущенные процессы, чтобы они заново прочитали ключи реестра.
C++:
BOOL result = SendMessageTimeout(HWND_BROADCAST, WM_SETTINGCHANGE, 0, (LPARAM) "Environment", SMTO_ABORTIFHUNG, 5000, &dwReturnValue);

Примечание. В отличие от API SendMessage, функция SendMessageTimeout с флагом SMTO_ABORTIFHUNG заставляет проверить, не завис ли поток-приемник, и, если да, немедленно вернуть управление вызывающему потоку. По-умолчанию, операционная система считает поток зависшим, если он прекращает обработку сообщений более чем на 5 секунд (Рихтер, 2001).

Для консоли есть специальная команда setx (Vista и выше), которая одновременно делает и изменение переменных, и оповещение об этом других процессов:
Код:
:: изменить системную переменную окружения MACHINE
SETX MACHINE "COMPAQ COMPUTER" /M
:: изменить пользовательскую переменную окружения MACHINE
SETX MACHINE "COMPAQ COMPUTER"
В этом случае процессы, создаваемые explorer.exe, будут гарантированно получать новые значения переменных.

При этом другие программы не обязаны отзываться на такое оповещение и можно только надеятся, что они подхватят новые переменные.
Например, процесс cmd.exe (как и большинство консольных приложений) не обновит свои переменные без явного перезапуска.
Правда, можно сделать трюк, создав файл env.cmd:
Код:
for /f "tokens=1* delims==" %%a in ('set') do set "%%a=%%b"
и вызвав его из текущего cmd.exe командой:
Код:
call env.cmd
Следует понимать, что нет простого способа обновить переменные окружения всех процессов, не перезагружая систему.

2) Если изменилась переменная процесса (4) CMD.exe,
то она может повлиять только на процессы, запущенные из-под этого конкретного CMD.exe.

Подчеркну слово "может". Т.к. можно отключить наследование через ключи команды Start, тогда создаваемый процесс унаследует переменные родительского процесса, а не текущего.
Примечание: при запуске процесса через меню ПУСК, Выполнить (Win + R), он наследует переменные процесса explorer.exe.


Управление наследованием переменных окружения

Есть как минимум 3 способа управлять наследованием:
1) командой SetLocal и EndLocal (это локализация переменной)
2) командой Call
3) командой Start

Разберем на примерах.

1) Локализация переменной в CMD не имеет ничего общего с локальными переменными ЯВУ.

Действует это так:



Максимальная глубина вложенности SetLocal друга в друга = 31 (первая + 31 вложенных).



1) Передача своего окружения в новый пакетный файл
и возврат уже нового окружения назад в исходный (родительский) пакетный файл.


Call "Пакетный файл.cmd"
Вызовет новый пакетный файл в этой же среде.​
Поэтому переменные окружения для обеих скриптов являются общими.​
[fieldset=Level1.cmd]
Код:
[/INDENT][/INDENT][/INDENT][/INDENT][/INDENT][/INDENT]
[INDENT][INDENT][INDENT][INDENT][INDENT][INDENT]@echo off[/INDENT][/INDENT][/INDENT][/INDENT][/INDENT][/INDENT]
[INDENT][INDENT][INDENT][INDENT][INDENT][INDENT]set temp=L1[/INDENT][/INDENT][/INDENT][/INDENT][/INDENT][/INDENT]
[INDENT][INDENT][INDENT][INDENT][INDENT][INDENT]call Level2.cmd[/INDENT][/INDENT][/INDENT][/INDENT][/INDENT][/INDENT]
[INDENT][INDENT][INDENT][INDENT][INDENT][INDENT]echo Батник 1 temp (after) = %temp%[/INDENT][/INDENT][/INDENT][/INDENT][/INDENT][/INDENT]
[INDENT][INDENT][INDENT][INDENT][INDENT][INDENT]pause[/INDENT][/INDENT][/INDENT][/INDENT][/INDENT][/INDENT]
[INDENT][INDENT][INDENT][INDENT][INDENT][INDENT]
[/fieldset][fieldset=Level2.cmd]
Код:
[/INDENT][/INDENT][/INDENT][/INDENT][/INDENT][/INDENT]
[INDENT][INDENT][INDENT][INDENT][INDENT][INDENT]@echo off[/INDENT][/INDENT][/INDENT][/INDENT][/INDENT][/INDENT]
[INDENT][INDENT][INDENT][INDENT][INDENT][INDENT]echo Батник 2 temp (before) = %temp%[/INDENT][/INDENT][/INDENT][/INDENT][/INDENT][/INDENT]
[INDENT][INDENT][INDENT][INDENT][INDENT][INDENT]set temp=L2[/INDENT][/INDENT][/INDENT][/INDENT][/INDENT][/INDENT]
[INDENT][INDENT][INDENT][INDENT][INDENT][INDENT]
[/fieldset]Level2.cmd будет выполнен в том же окне и в той же среде, что и Level.cmd
затем передаст управление дальше по коду файла Level1.cmd
Результат в окне консоли:​
Код:
Батник 2 temp (before) = L1[/INDENT][/INDENT][/INDENT][/INDENT][/INDENT][/INDENT]
[INDENT][INDENT][INDENT][INDENT][INDENT][INDENT]Батник 1 temp (after) = L2[/INDENT][/INDENT][/INDENT][/INDENT][/INDENT][/INDENT]
[INDENT][INDENT][INDENT][INDENT][INDENT][INDENT]
Как видим батник № 2 наследовал переменную батника № 1.​
Затем наоборот батник № 1 наследовал переменную батника № 2.​


2) Передача своего окружения в новый пакетный файл
без возврата окружения обратно в родительский.


Call + SetLocal*


* Дочерний процесс будет наследовать среду родительского в зависимости от того,
как построена команда вызова нового процесса.


Например, такой командой можно вызвать пакетный файл в изолированной среде:​
[fieldset=Level1.cmd]
Код:
start "" /i cmd /c Isolated.cmd
[/fieldset]​
Новой средой для Isolated.cmd будет среда, переданная текущей cmd.exe​
перед началом выполнения в ней пакетного файла Level1.cmd
(по умолчанию, от процесса explorer.exe).​
Эта команда создает новое окно и выполняет в нем Isolated.cmd без ожидания его завершения*.​
Чтобы не создавать новое окно + ждать завершения работы Isolated.cmd,​
команда должна выглядеть так:​
Код:
start "" /i /b /wait cmd /c Isolated.cmd
/i - запуск в "изолированной" среде (авт.)​
/b - не создавать новое окно​
/w или /wait - ожидать, пока не завершится процесс​

** Среда CMD также может наследовать переменные, измененные в дочернем пакетном файле (среда останется той же).

Для этого выполним команду:​


Использована литература:
Smitis (cyberforum.ru). Заметки о переменной окружения PATH.
Lih Wern Wong. Forensic Analysis of the Windows Registry.
Рихтер Дж. Windows для профессионалов: создание эффективных Win32 приложений с учетом специфики 64-разрядной версии Windows, 2001.
MSDN. Application Registration.
MSDN. CreateProcess function.
MSDN. Changing Environment Variables.
MSDN. Как переносить переменных среды в систему.

Инструментарий: CMD, Sysinternals Process Monitor, редактор реестра Windows, Visual Basic 6 IDE.

_______________________________________________________________________
Во 2-й части статьи, которую я готовлю, Вы узнаете о том:
  • какие свойства имеет переменная окружения;
  • как хранятся переменные окружения в памяти процесса;
  • как создавать процессы с особыми настройками слоев совместимости;
  • ознакомитесь с правилами именования;
  • научитесь просматривать переменные чужого процесса;
  • авто-протоколирование переменных, которые изменились;
  • поиск и решение конфликтов при использовании одинаковых переменных;
_______________________________________________________________________
Спасибо. Надеюсь, Вам понравилось :)

Дополнения.

Переменные окружения в реестре Windows


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

Для этого нужно установить тип этого параметра в REG_EXPAND_SZ.

reg.png


Здесь, HKCU\Environment => Temp будет раскрыта сразу в, например:
C:\Users\Alex\AppData\Local\Temp
Примечание: консольная утилита REG.exe никогда не разыменовывает значение.

Если Ваша задача – наоборот, прочитать оригинальное значение параметра, имеющего тип REG_EXPAND_SZ, вы можете передать флаг RRF_NOEXPAND в функцию RegGetValue (поддерживается, начиная с Windows XP x64).

%Temp% и REG_EXPAND_SZ

Я встречал случаи, когда сторонняя программа перезаписала тип данных, изменив REG_EXPAND_SZ на REG_SZ. Это нарушило работу целого ряда программ. Вот пример.
Если вы столкнётесь с ошибками отсутствия доступа на записи временных файлов, проверьте тип параметра:
Не допускайте подобных ошибок в своей програмне. Если Вы пишите в параметр, который заранее существует, проверьте его тип. Если он - REG_EXPAND_SZ, не меняйте его без надобности.

Переменные окружения в среде Wow64

Следует быть осторожным при раскрытии переменных под Wow64 (когда вы работаете в 32-битном процессе на 64-битной ОС).

При раскрытии некоторых переменных будет ощущаться разница:

var.png


Вы можете оказаться в затруднительном положении, если к примеру, прочитаете из реестра путь к файлу: “%ProgramFiles%\file.txt” и затем попытаетесь его открыть из-под Wow64, не учтя поправку. Полученный путь «C:\Program Files (x86)\file.txt» окажется неверным.

Учтите также, что папка Program Files не подпадает под действие механизма File System Redirector.

Другие черновые материалы
Набор стандартных переменных окружения процесса

Category:Hidden variables - Environment Variables
Windows Environment Variables | Windows CMD | SS64.com
https://support.microsoft.com/en-us/kb/100843
https://support.microsoft.com/en-us/kb/286705
Не понимаю, зачем нужны переменные среды и командная строка - C++ и WinAPI - CyberForum.ru
[Undocumented] Variables __CD__ & __APPDIR__ - DosTips.com
Batch files - The SET command: Windows NT 4..Windows 7 Syntax
Why can't I access a variable named __CD__ on Windows 7?
Windows Environment Variables
[Undocumented] Input redirection check DPATH (Page 1) / Windows CMD Shell / SS64 Forum
Windows Environment Variables | Windows CMD | SS64.com


//TODO:
Исследовать проблему с незапуском
X64 Debuggers And Tools-10.0.10240.9_en-us
из-под Total Commander Kit-PPP
и оценить влияние на это фактора стороннего блока переменных окружения, переданного от процесса Total Commander-a.
(при успехе - упомянуть об этом в конце статьи)

PATH variable different when cmd was run through the context menu

Упомянуть про "переадресацию" переменных на х64 ОС для х32-битных процессов:
Как узнать объект ярлыка в 64-битной ОС - Страница 2 - Visual Basic - CyberForum.ru
File System Redirector (Windows)
(учесть также что для функций shell другой механизм. Они оперируют PIDL)
Упомянуть про раскрытие переменных в реестре, REG_EXPAND_SZ, а так то что, есть флаг для API который может из нее вытянуть путь без раскрытия переменной.

src
Код (C++):
if (g_fRunningOnNT)
{
// NT has a control panel applet that allows users to change the
// environment with-which to spawn new applications. On NT, we need
// to pick up that environment change so that anything we spawn in
// the future will pick up those updated environment values.
//
if (lParam && (lstrcmpi((LPTSTR)lParam, TEXT("Environment")) == 0))
{
void *pv;
RegenerateUserEnvironment(&pv, TRUE);
}
}
SystemRoot vs WinDir

Пример простейшего драйвера.
Как подгрузить драйвер.

RtlQueryEnvironmentVariable_U

Newly created with $env: environment variables in PowerShell are of Process type. For variable to display in the Advanced System Settings it has be the Machine or User level variable. To create variable with specific level use Environment.SetEnvironmentVariable method:
[Environment]::SetEnvironmentVariable('MyPath', 'c:\mypath', 'User')

[Environment]::SetEnvironmentVariable('MyPath', 'c:\mypath', 'Machine')

Что-то про алфавитный порядок:
Reuse of environment variables in Path

Двойное раскрытие переменных в реестре.

Net. Working with Env.variabels: https://msdn.microsoft.com/en-us/library/system.environmentvariabletarget.aspx

Проверить раскрывается ли %ProgramFiles% в Path: 1. Динамически, 2. После перезагрузки.
1. Для User 2. для System.

Разница между TMP и TEMP

Предупредить, что хранить один REG_EXPAND_SZ внутри другого – плохо.


setx VAR1 "CONTENT OF VAR1"
Later I've found those limitations:
1. If the %PATH% is too long then I get following warning and the %PATH% variable is truncated:
WARNING: The data being saved is truncated to 1024 characters.
2. As the help for setx command says:
When you use Setx.exe to clear an environment variable value, the environment variable name is not affected
In other words when I run setx -m OCVLIBDIR "" then the OCVLIBDIR will not be deleted but rather empty.


Как удалить прееменную окружения.

О слоях совместимости

Как получить * на PEB

Какие есть фнукции для работы с EV

Сказать что %EV% можно указывать в разных окнах shell32

https://technet.microsoft.com/en-us/library/cc951699.aspx

https://msdn.microsoft.com/en-us/library/windows/desktop/aa373347(v=vs.85).aspx - см. в комментариях

gentilkiwi/mimikatz
http://www.nosuchcon.org/talks/2014/D3_05_Alex_ionescu_Breaking_protected_processes.pdf

Добавить про CMDEXTVERSION (т.е. внутренние переменные CMD)

Семейство Windows NT[править | править вики-текст]
В операционных системах семейства Windows NT AUTOEXEC.BAT обрабатывается при входе пользователя в систему, и, как и в Windows ME, в нём игнорируются все команды, кроме команд установки переменных окружения (PATH, PROMPT и SET).[17] После обработки переменные из AUTOEXEC.BAT добавляются к переменным, заданным в реестре (в том числе, содержимое переменной PATH дописывается к содержимому, сформированному Windows). Обработку AUTOEXEC.BAT можно отменить, установив в 0 значение ключа реестра HKCU\Software\Microsoft\Windows NT\CurrentVersion\Winlogon\ParseAutoexec.[18]


Другие автостартующие пакетные файлы системы[править | править вики-текст]
Файл autoexec.nt[править | править вики-текст]
В операционных системах семейства Windows NT файл AUTOEXEC.BAT используется только для чтения переменных окружения. При старте DOS-сессий (для запуска в режиме эмуляции приложений, написанных для DOS) вместо него исполняется файл autoexec.nt, расположенный в %systemroot%\System32. Синтаксис этого файла похож на синтаксис AUTOEXEC.BAT, но исполняется он без вывода сообщений о программах и командах на консоль (если только в файле config.nt не дана команда echoconfig[20]). Помимо этого, в свойствах ярлыка (pif-файла) для DOS-приложения можно задать собственные файлы config.nt и autoexec.nt (англ. Custom MS-DOS initialization files).

 

Вложения

Последнее редактирование:
Сверху Снизу