4

I need to set an environment variable programmatically.

Microsoft provides documentation for that here. You just need to create a new value in the registry under HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Environment. This part works fine.

The problem is that these changes only come into effect after logging out and logging in again.

To circument this they propose to execute this little piece of code:

if (!SendMessageTimeout(HWND_BROADCAST, WM_SETTINGCHANGE, 0,
    (LPARAM) "Environment", SMTO_ABORTIFHUNG,
    5000, &dwReturnValue))
{
  ... take action in case of failure
}

I did exactly this, SendMessageTimeout returns TRUE, but at least under Windows 10 it has no effect. A newly opened command prompt window still won't show the newly created variable.

I also tried to run this piece of code in an elevated process, but the result is still the same.

But when I use system applet for changing environment variables, my newly created variable shows up and when I click OK on the applet and when I open another command prompt, then the variable is there.

Any thoughts?

Jabberwocky
  • 48,281
  • 17
  • 65
  • 115
  • 1
    Just curious: "*at least under Windows 10 it has no effect.*" under earlier version(s) "it works"? – alk Feb 22 '18 at 12:59
  • @alk Good question, I'm not able to check this right now, but I suspect this has worked in earlier versions of Windows and because it is kind of a hack it doesnn't work anymore – Jabberwocky Feb 22 '18 at 13:02
  • Do you run the app calling the code snippet in an elevated mode? – ryyker Feb 22 '18 at 13:03
  • @ryyker question edited: elevated or not, the result is the same – Jabberwocky Feb 22 '18 at 13:06
  • This has to be a dupe... – zett42 Feb 22 '18 at 13:11
  • 1
    FYI, there is a similar ask _[here](https://stackoverflow.com/questions/531998/is-there-a-way-to-set-the-environment-path-programmatically-in-c-on-windows)_, discussing machine, user and application levels of modifying environment variables. Also, not sure if it will make a difference, but should `(LPARAM) "Environment"` be `(LPARAM)L"Environment"`? – ryyker Feb 22 '18 at 13:14
  • I think we need a [mcve]. – zett42 Feb 22 '18 at 13:20
  • 1
    I am also reading others have had similar results using SendMessageTimeout. I have no experience with it, but there is a suggestion _[here](https://www.codeproject.com/Questions/138079/using-SendMessageTimeout-cannot-refresh-all-window)_ using _[SHChangeNotify()](https://msdn.microsoft.com/en-us/library/windows/desktop/bb762118(v=vs.85).aspx)_. – ryyker Feb 22 '18 at 13:26
  • Are you trying to set an environment variable for the current environment or for all environments? – UKMonkey Feb 22 '18 at 13:33
  • @UKMonkey for all environnments, the same as you would do with the "System environment variables" in the control panel. – Jabberwocky Feb 22 '18 at 13:35
  • @MichaelWalz Didn't know about that call - interesting. Glad you solved it! (Back to sleep I go) – UKMonkey Feb 22 '18 at 13:37
  • 1
    @ryyker the `L` `(LPARAM)L"Environment"` in your comment raised a red flag in by brain, thanks. – Jabberwocky Feb 22 '18 at 13:40
  • In Windows 10 the applet you speak of is probably written using C# methods. Sure, there is likely a `winapi` approach as well, but it will be hidden somewhere in Microsoft's maze of obfuscation. – ryyker Feb 22 '18 at 13:41
  • 1
    @ryyker there _is_ one, see my own anser below. – Jabberwocky Feb 22 '18 at 13:42

2 Answers2

6

The problem was solved by calling explicitly the wide version of SendMessageTimeout and sending the "Environment" as wide string:

SendMessageTimeoutW(HWND_BROADCAST, WM_SETTINGCHANGE, 0, 
                   (LPARAM)L"Environment", SMTO_ABORTIFHUNG, 5000, &dwReturnValue);
Jabberwocky
  • 48,281
  • 17
  • 65
  • 115
  • I have looked around several sites that were trying to solve the same thing. This answer is the one approach I have seen that actually has the teeth to address the issue. – ryyker Feb 22 '18 at 13:47
  • @zett Actually I think that's not the case. He's targeting Unicode otherwise the code in the question works. – David Heffernan Feb 22 '18 at 19:53
2

As Michael found out, the string width needs to match the A/W function type. WM_SETTINGCHANGE is in the < WM_USER range and will be marshaled by the window manager.

You can use the TEXT macro to create code that works for everyone everywhere if you don't want to hardcode the function name suffix:

SendMessageTimeout(
  HWND_BROADCAST,
  WM_SETTINGCHANGE, 
  0, 
  (LPARAM) TEXT("Environment"),
  SMTO_ABORTIFHUNG,
  5000,
  &dwReturnValue
);
Anders
  • 97,548
  • 12
  • 110
  • 164
  • Well, that's exactly *not* what Michael's answer implies. Your code either calls `SendMessageTimeoutA` or `SendMessageTimeoutW`, though according to Michael one has to always call `SendMessageTimeoutW`, even for MBCS builds. – zett42 Feb 22 '18 at 15:42
  • @zett42 No, his original code just uses SendMessageTimeout but is really SendMessageTimeoutW when UNICODE is defined but is passed a ANSI string and the compiler cannot detect the mismatch because you have to cast. In c++ the compiler can detect it if you do `(LPARAM) static_cast("Environment")`. – Anders Feb 22 '18 at 17:04
  • This is correct, but I wouldn't use these macros from the 1990s. They only existed to support Windows 9x. Just use Unicode explicitly -- `SendMessageTimeOutW` with `L"Environment"`. – Eryk Sun Feb 22 '18 at 17:18
  • 1
    _"The W is SendMessageTimeoutW is mandatory if the program is compiled as non UNICODE version (MBCS)."_ – zett42 Feb 22 '18 at 17:36
  • 1
    @zett42 And I believe that statement is incorrect, W is mandatory if your string is L"..." because the string has to match the function. If SendMessageTimeoutA with a "..." ANSI string does not work then all programs written for 95/98/ME are broken. – Anders Feb 22 '18 at 17:43
  • Sounds reasonable. If @MichaelWalz could clarify. – zett42 Feb 22 '18 at 17:53
  • What's to clarify? Anders is correct without doubt. Internally Windows will decode an ANSI string for this message. It takes less than 5 minutes to test this. Build a simple program that calls `SendMessageTimeoutA` with `"Environment"`. Manually modify a value in "HKCU\Environment". Run your program, and check Explorer's environment in Process Explorer. Great. Now everyone please stop using ANSI. – Eryk Sun Feb 22 '18 at 18:08
  • Can you please fake-edit your post so I can undo my downvote? xD – zett42 Feb 23 '18 at 09:55
  • @zett42 Sure :) – Anders Feb 23 '18 at 12:48
  • Should be "character size" though ;-) – zett42 Feb 23 '18 at 14:25