views:

947

answers:

2

I'm writing a Win32 DLL with a function that adds a directory to the Windows PATH environment variable (to be used in an installer).

Looking at the environment variables in Regedit or the Control Panel after the DLL has run shows me that my DLL has succeeded in adding the path to *HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Environment* and *HKEY_CURRENT_USER\Environment*.

But when I start up a new Command Prompt (after running the DLL), the directory I added does not show up in the output of "echo %PATH%" and I can not access the executable that lives in that directory by typing its name.

I think my program is not doing a good job of notifying the system that the PATH has changed, or maybe it is notifying them before the change has fully taken effect. I read an article by Microsoft that says to broadcast the WM_SETTINGCHANGE message after changing an environment variable, and I am doing that with this code:

DWORD result2 = 0;
LRESULT result = SendMessageTimeout(HWND_BROADCAST, WM_SETTINGCHANGE, 0,
    (LPARAM)"Environment", SMTO_ABORTIFHUNG, 5000, &result2);
if (result == 0){ /* ... Display error message to user ... */ }

The order of my calls is: RegCreateKeyEx, RegSetValueEx, RegCloseKey, SendMessageTimeout

If I press "OK" in the Control Panel "Environment Variables" window, the changes made by my DLL to the PATH show up in newly-created command prompts, so there is something that the Control Panel is doing to propagate PATH changes; I want to figure out what it is and do the same thing.

Does anyone know what I should do?

I'm running 64-bit Windows Vista but I want this to work on all Windows XP, Vista and Windows 7 operating systems.

Update: The problem with the code I posted above is that I did not put the L prefix on the "Environment" string. Although it does not say it explicitly anywhere in the Microsoft documentation that I can find, the LPARAM needs to be a pointer to a WCHAR string (2-byte characters) as opposed to a CHAR string, which is what Visual Studio's compiler generates by default when I write a string literal. The solution to my problem was to change "Environment" to L"Environment". (I thought I already tried that before posting this question, but apparently I didn't try it correctly!) But anyone who wants a complete C++ solution for this task should look at Dan Moulding's answer.

A: 

I have a program which calls the same Win32 API to yours to update the environment, and it works fine.

One thing to be careful of is how you are opening up the command prompt.

If you open up the command prompt by doing this:

Start -> Run -> cmd.exe

then the environment in the prompt shows that the new variable is set.

However, I also have a programmable function key on my keyboard which I have set to run the cmd.exe process. If I open a command prompt via that function key and then type env, it doesn't show the variable as being set.

I'm not sure why it works differently, but it must have something to do with the way the cmd.exe process is launched (although both are running under my user name, not SYSTEM).

How are you opening up the command prompt?

LeopardSkinPillBoxHat
The program that manages your programmable function keys is starting CMD.EXE with its own copy of the environment, which hasn't been updated if you didn't restart it after changing the path.
Todd
Interesting. I was starting my command prompt by clicking on "Command Prompt" in my start menu. This is a shortcut that came with Windows, in the Accessories menu, which I "pinned" to the start menu. The target is %SystemRoot%\system32\cmd.exe
David Grayson
@Todd - yep that sounds right. Good analysis!@David - Any difference if you run cmd.exe from Start->Run?
LeopardSkinPillBoxHat
On my system, running it from the start menu shortcut versus using Start->Run makes no difference. Which makes sense, because the main "shell" (explorer.exe) updates its environment as a result of the WM_SETTINGCHANGE broadcast. Any processes it spawns (whether via Start->Run or otherwise) should inherit that updated environment.
Dan Moulding
@LeopardSkinPillBoxHat, I don't know if there was a difference; I already solved the problem though and it works fine now. Thanks for the suggestion.
David Grayson
+3  A: 

It turns out there really isn't anything new under the sun. This has already been done before, at least once. By me. I created a DLL very much like what you describe for exactly the same purpose (for use in modifying the path from an NSIS installer). It gets used by the Visual Leak Detector installer.

The DLL is called editenv.dll. The source is available at github. I just tested the installer and it updated the system PATH environment variable, no problem. Based on what you've written, I don't see anything that stands out as being wrong. I also don't see anything obvious that's missing. But it may be worth a look at the editenv.dll source (you'd be most interested in EnvVar::set() in EnvVar.cpp, and possibly the pathAdd() and pathRemove() C APIs in editenv.cpp).

Dan Moulding
Thanks for the code Dan! Even thought I could not find my specific solution by looking at your code, your answer will be immensely helpful to future readers of this question.
David Grayson