Saturday 26 March 2016

Environment Variables

Notice that this post applies only to Windows environment Variables. Until a couple of years ago I had rarely made any use of environment variables other than adding paths to PATH. However, in one of the projects that I have been working on these last years they are used quite a lot (for passing basic data between parent and child processes, for global data rather than using the Registry...) I don't like this way of working, but it's not up to me to change it, and indeed this "env var culture" in this project is due to some external tools also following the same principle. The thing is that because of it, I have learnt a few things about environment variables, and I will share them here. I'm not any sort of expert, and some of the information below (mainly regarding Windows startup) could be inaccurate, so be warned and bear with me.

We all know that there are System Env Vars and User Env Vars. When a user logs on to a machine an Environment Block is created for him, containing a combination of the System and User env vars. If the same variable exists for System and User, the User one will be used, save for PATH, that will be a combination of both. Then, each process runs with an Environment Block associated to it. How is this Environment Block associated to a process? Well, when an application starts another application, via the different Win32 functions: CreateProcess, CreateProcessAsUser... it can either pass it an Environment Block to be used for the process, or pass nothing, in which case the Environment Block of the Parent process will be used. This second case is the most common, it's what happens when you launch a program via Windows Explorer or the command line. So most times your new program will take the (it's a copy, not a reference) environment block of explorer.exe. When you start a program from the command line, unless you have started it with runas or have done some set on it, it will be as starting it from Explorer. I think the explorer.exe in your session is started by winlogon.exe (which in turn was started by wininit.exe) which is running under the System account, so I assume it starts Explorer with a call to CreateProcessAsUser and passing it the environment block for your user. You can read more about the Windows startup process here and here

Windows Services set to run under an account other than System get the environment block of that user, so I assume services.exe (that runs as System) is also creating the process by calling into CreateProcessAsUser with the environment block of that user.

When User1 start a process with runas User2, by default it will use the Environment Block of User2, unless you call it with the /env switch, in which case it will run with the environment block of User1.

The command line:

Apart from setting environment variables via the Windows UI you can also do it in the command line, but this tends to cause confusion. When you use set it's acting on the environment variables of the current process (I.e. cmd.exe). So if you add/change something there it will have an effect on programs that you start from that command line, but not in the ones that you start by any other means (explorer, another cmd). This is like when in .net you use Environment.SetEnvironmentVariable/GetEnvironmentVariable with the EnvironmentVariableTarget.Process parameter.

If you want to set env vars permanently from the command line you have to use setx. Using the /M switch it will set it as a System env var, otherwise as a user env var. So it's like using EnvironmentVariableTarget.Machine or EnvironmentVariableTarget.User in .Net. If you wonder why they use Machine rather than System, it's because environment variables are stored in the Registry, and System ones do it in HKEY_LOCAL_MACHINE hive.

Reactive to change

When you add/change an env var via the UI, new processes started from Explorer will get the new value. This is because when an env var is changed Windows broadcasts a WM_SETTINGCHANGE Windows Message. Any application (like Explorer) that is listening for this kind of message can then update its environment block. I had copypasted on my notes these 2 paragraphs from somewhere:

"To effect these changes without having to log off, broadcast a WM_SETTINGCHANGE message to all windows in the system, so that any interested applications (such as Windows Explorer, Program Manager, Task Manager, Control Panel, and so forth) can perform an update."

"If you're changing the environment through the My Computer -> Properties -> Environment Variables, leaving that dialog broadcasts a global window message that Explorer.exe and a few other processes listen to, informing them that the persistent environment (stored in the registry) has been updated, and those processes proceed to reload their environment from the registry."

No comments:

Post a Comment