A process inherits its environment from its parent, and the consequences of this simple statement

A customer reported that they changed some system-level enviornment variables (include PATH). They also have a batch file that, among other things, runs a program that requires that the PATH be set a certain way. The batch file is set to run automatically every five minutes, and the customer observed that the batch file is not picking up the changes to the system-wide environment variables.

The customer developed two theories as to why this was happening.

Theory number one was that batch files inherit the environment from its parent process (unless the parent process passes an explicit environment to Create­Process). "Since we changed a global environment variable and the parent process is continually running, it does not pick up the new environment variables. Then it passes this outdated set of environment variables to the batch file."

Theory number two is that changes to global environment variables will eventually become applied to running processes, but the effect is not immediate; you have to wait a while for the change to propagate. "We started a new command prompt, and the new command prompt did not have the new environment variables, even though the new values did appear in the System control panel. After setting the environment variable a few more times, eventually the new values started showing up in the command prompt."

The first theory is correct. Processes inherit their initial environment from their parents. Of course, once the process is running, it is free to change its environment variables by calling Set­Environment­Variable, and those modified environment variables are passed to any child processes launched after the new variable is set. (A parent process can also pass a custom environment to the child process.)

Now, a child process inherits its initial environment from its parent, but it only gets a snapshot of that environment. If the parent subsequently modifies its environment, the child environment is not updated.

When the customer said that they "changed some global environment variables", that was a rather vague statement, because as we saw above, there is technically no such thing as a "global environment variable". Every environment variable is local to a process. What the customer actually did was make a chance to the template environment that is used to get the ball rolling.

When you log on, the system constructs an initial environment from the template specified in the Environment Variables control panel. That initial environment is then given to Explorer, where it becomes Explorer's initial environment, and from there, it becomes inherited by anything that gets launched by Explorer, and then anything that gets launched by anything that gets launched by Explorer, and so on. If you can trace your lineage back to Explorer, then your initial environment was based on the copy in Explorer.

Now, each process along the way may have edited its environment before spawning a child process, in which case those edited values are inherited by the children.

In order for the changes to the environment template to take effect in a process, that process needs to support the "Throw away all your environment variables and create a new set of environment variables based on the current template" message. That message is the WM_SETTING­CHANGED message, where the lParam points to the string "Environment".

The only program in common use that actually responds to that message is Explorer.

This means that when you change the template for the initial environment from the Environment Variables control panel, the only program that reacts immediately to those changes is Explorer. Any other programs that are already running will continue to operate with whatever environment variables they had when they started (or subsequently edited).

If you run a program from Explorer, it will get the updated environment because Explorer updated its environment in response to the message.

If you run a program from Task Manager or a command prompt or anything else, then it will not get the updated environment because Task Manager, the command prompt, and pretty much anything else does not update its environment in response to the message.

If you want to regenerate your environment in response to the WM_SETTING­CHANGED message with an lParam of Environment, you can use the Create­Environment­Block function. You can enumerate the contents of this environment block and copy it to your current environment.

But personally, I wouldn't bother, because pretty much nobody else bothers either.

Comments (30)
  1. Damien says:

    Ah, the classic case of "If you build it, everyone will ignore it"

  2. Joshua says:

    I agree. The only time I'd consider implementing it is writing a shell replacement.

  3. Nicholas says:

    It is interesting in that the environment regeneration feature is pretty much designed for Explorer only (virtually speaking).  It would suck if Explorer had to be power cycled (logout/login, reboot, ?, etc.) so that it can obtain the updated environment.  In this case this feature makes sense.

    On the other hand, I would not want such sophistication in my programs.  My programs get the environment they first get and that's it.  I think only confusion and bugs can be introduced by having such programs utilize that feature.  Imagine a compiler writing object files to C:Foo and then changes mid compilation to writing object files to C:Bar.  I would not want to be the guy that has to investigate that problem.

    If it was really necessary then I would have a watchdog process that utilizes that feature.  When the environment changes the watchdog can launch a new instance of the process.  Depending on the nature of the program, the watchdog may first gracefully close the current instance before launching a new instance (using the example above, so as to let our compiler finish writing files to C:Foo).

  4. Kelden says:

    I have reported this "bug" to Mr. Ghisler who writes a great file manager and he fixed it a long time ago.

    Now when you open a new command prompt in his file manager, you get the new environment.

  5. Ben Voigt says:

    As Nicholas points out, changing environment mid-task is usually not a good idea.  And because environment is process-wide state, there's just no good way to update it in a multi-threaded program.  On the other hand, many launchers don't have any customized environment to pass.  Ideally, there would be a variation on CreateProcess and the spawn* family of functions that implements "initialize environment for child process from the current environment template" instead of "initialize from parent's environment".  That option would probably cover 98% of all use cases.

    For the other 2%, some way of merging the change to the template with the current environment would be needed.  Some IDE might like to be able to pick up environment changes and pass those to build steps or when launching the debug target… but not at the expense of environment settings made by a batch file used to launch the IDE.  Unfortunately, WM_SETTINGSCHANGE doesn't lead to an easy way to discover which particular variables were changed.

    [I already provided the information to do that yourself; Call Create­Environment­Block and pass it as the environment parameter to Create­Process. -Raymond]
  6. Medinoc says:

    @Joshua: "I agree. The only time I'd consider implementing it is writing a shell replacement."

    Well ideally I'd try supporting it on a program that's supposed to launch other programs (task scheduler, possibly also an Integrated Development Environment)…

  7. Yukkuri says:

    @Kelden It is indeed a great file manager. First thing I install on any new Windows box.

  8. EclecticMonk says:

    I'm intrigued by this statement from the customer:

    "We started a new command prompt, and the new command prompt did not have the new environment variables, even though the new values did appear in the System control panel. After setting the environment variable a few more times, eventually the new values started showing up in the command prompt."

    The second sentence seems impossible, unless they quit and restarted the command prompt (from Explorer) at some point and forgot to mention it.

  9. Goran says:

    I am under impression that services behave the same, also when restarted. This is quite inconvenient, because an environment change requires a system not service, restart.

  10. Martin Ba. _ says:

    @Goran – only services running as SYSTEM (for whatever reason) will not pick up environment changes on svc restart – need a system restart.

    Services running under a normal user account will pick up "global" environment changes if you restart the service only.

  11. Joshua says:

    @Medinoc: IDEs need to load or inherit the environment declaration for their compilers so they end up depending on differing environments. Even with platform libraries and compilers you can't get away from this (cross compiling).

  12. Gabe says:

    EclecticMonk: Remember that you are responding to an anecdote, so you have to assume that some important parts are left out or inaccurate (misremembered). If you don't already understand how the system works, you might not realize there would be a material difference between running "start cmd" from an existing command prompt, and pressing the Start button and entering "cmd".

    Odds are the user was changing the environment variables and then launching a new command prompt from a process that doesn't automatically update them, then later launched a new command prompt from Explorer and noticed that the new values were picked up. Rather than recognizing that it was the manner of launch, they assumed it was the time difference that mattered.

  13. @Goran, Martin Ba.: I suspect it's because the services are being run as part of svchost.exe, which can run multiple services under the same process.  As long as that svchost.exe process doesn't exit, you won't pick up the environment changes.

    That being said, one way of being able to support running child processes with the latest environment variables while avoiding having them change from under you is to create your own snapshot of the variables after startup.  The only downside to this is that any other Win32 API methods that use the environment block would use the potentially modified variables instead of your snapshot, so you'd have to make sure to write your own.

  14. Myria says:

    Where does the environment come from if the process doing the CreateProcessW call isn't the parent process?  I.e., if PROC_THREAD_ATTRIBUTE_PARENT_PROCESS is applied.

  15. John says:

    Came here to say the same thing, this is probably running in a Service which won't pick up the environment variable changes until a restart. Knew it after reading the first few sentences.

  16. DWalker says:

    "Theory number two is that changes to global environment variables will eventually become applied to running processes, but the effect is not immediate; you have to wait a while for the change to propagate."

    That is a weird theory.  I wouldn't even EXPECT that to happen.  

    Also, "After setting the environment variable a few more times, …"  The customer thought that SETTING the variable a few times would have some effect different from setting it once?  Is this the same theory that makes people push the "elevator close" button six times after they get into an elevator?  I wonder what the customer thinks is the threshold number of repeated changes, before Explorer decides that you really, really want the updated values to be propagated everywhere?

    Also, did they think about the effect of forcibly changing some values that are BEING USED BY currently running processes?  Such as Homedrive, Homepath, ProgramData, etc?  

    Yes, they thought there was a global environment that could be changed.  I suppose they are just learning, and I was there once too….

  17. ChuckOp says:

    I think it's arguable that Task Manager should respond to WM_SETTINGSCHANGE, since launching new tasks is one piece of it's functionality.  I don't think cmd.exe and/or conhost.exe should, because I want the ability to modify the environment before launching a new process through functionality provided.

  18. @ChuckOp: Sure, if none of its functionality is dependent on the values in the environment variables aside from launching processes.

  19. Henri Hein says:

    @DWalker: Don't knock it! Banging the "close door" button repeatedly absolutely works.  Maybe only in the sense of perceived time, but when I'm standing there and nothing is happening, that is really the measure that matters.

  20. Ben Voigt says:

    Raymond, I'm not sure if you're suggesting that as a reply to part #1 (the "98%") or part #2.  For part #1, it's a matter of convenience and discoverability.  If there were a flag to CreateProcess, people would know about and think about which environment to propagate.  As a separate function, no one thinks about it.  Instead, you have to get a token for the current user, pass that to Create­Environment­Block, later call DestroyEnvironmentBlock, release the user token, and why do the docs say you can only use it with CreateProcessAsUser?

    For part #2 (merging), it looks like CreateEnvironmentBlock's third parameter kinda maybe causes some merging.  But the documentation on that parameter is seriously unclear.

  21. How do you respond to WM_SETTING­CHANGED if you have a console application? I guess you could always create a hidden background window.

  22. Richard says:

    @DWalker:  "That is a weird theory.  I wouldn't even EXPECT that to happen."

    Most Windows (Active Directory) administrators would probably assume this behavior.  For example, Group Policy mostly works this way.

  23. cheong00 says:

    @Richard: No. Any Active Directory Administrators who worths their salt shouldn't assume the environment variables would change on the already running process.

    Active Directory Administrators should be familiar with "setx" command and know how it works. And the remarks explicitly says:

    "Setx writes variables to the master environment in the registry. Variables set with setx variables are available in future command windows only, not in the current command window."

    So this command do not even affects current common prompt instance, let alone other command prompt instances.

  24. Killer{R} says:

    If only there was special CreateProcess argument that when used – caused process environment to be re-initialized from template, but not inherited from parent.. That could be another bit in the dwCreationFlags or lpEnvironment special pointer value or pointer to special string. Or, if I'd be a designer of CreateProcess – I'd either made bInheritHandles as another bit-flags arg, that deals with inheritance (handles, environment, some staff that currently controlled by dwCreationFlags) and called it dwInheritanceFlags either put that all into dwCreationFlags.

    And yes, I understand that Create­Environment­Block does this, but with flag such question would never have been asked.

    [But such a flag would introduce a layering violation. A low-level component (kernel) now has a dependency on a higher-level component (profile management). -Raymond]
  25. Killer{R} says:

    Its already depends on (IMHO) higher level user32 (desktop), and current user's template environment block not such big profile thing.. Anyway I agree with such point. Also may be ShellExecute(Ex) would be more correct place for that but.. somewhy it doesn't have environment-specific options at all. Probably it considered too low-level for it :)

  26. cheong00 says:

    I wonder if the launching (parent) process exited, will the current process get the Environment update or not.

    [I think you kind of missed the main point of the article. Once the environment is passed to the child, what happens to the parent in the future is irrelevant. "a child process inherits its initial environment from its parent, but it only gets a snapshot of that environment." -Raymond]
  27. Harry Johnston says:

    In my experience, one of the most common causes of "we started a new command window and it didn't have the new environment variables" is the "Command Prompt Here" right-click menu in Explorer.  I'm not sure where that menu comes from (it doesn't exist on my machine) or exactly why it doesn't work, but I've seen plenty of people complaining about this particular behaviour.

    Of course, environment variables should be considered legacy technology anyway, and avoided whenever possible.  (This is only one of the reasons.)

  28. Gabe says:

    Harry Johnston: The "Open command window here" item that pops up in menus with a Shift+Right-click should give you a command prompt with the new environment variables when it comes from a window belonging to Explorer.

    However, any other program that shows a file explorer (say, via a common Open or Save dialog) would inherit the environment of the process whose window you clicked in. And as we all know that process won't have updated its environment the way Explorer would.

  29. Alex Cohn says:

    @cheong00: So what happens when I use `start cmd` on a command line prompt? The environment will not be updated.

  30. meh says:

    @Harry Johnston. Re: "Command Prompt Here". Maybe it ran a separate helper process that launched the command prompt, and that separate helper process didn't pick up the environment change. MS had the Command Prompt Here power toy for Windows XP that came with its own installer program. Not sure if it just added keys to the registry or actually contained a helper exe. Other sites have similar toys which do appear to have helper exe's (http://code.kliu.org/cmdopen). Never looked at their code, but maybe they start up on login and don't pickup environment variable changes.

Comments are closed.

Skip to main content