Jump to content
Nathan Wild

Setting Environment Variables

Recommended Posts

This can not possibly be as complicated as it seems...  I want to set an environment variable from within my application that persists after it terminates.  I have tried using SetEnvironmentVariable() and then SendMessage() to broadcast the change, to no avail...  I found an old post with the following function which seems like it SHOULD do what I want - but no matter what my environment is not touched.

 

function SetGlobalEnvironment(const Name, Value: string; const User: Boolean = True): Boolean;
resourcestring
  REG_MACHINE_LOCATION = 'System\CurrentControlSet\Control\Session Manager\Environment';
  REG_USER_LOCATION = 'Environment';

begin
  with TRegistry.Create do
    try
      if User then { User Environment Variable }
        Result := OpenKey(REG_USER_LOCATION, True)
      else { System Environment Variable }
      begin
        RootKey := HKEY_LOCAL_MACHINE;
        Result := OpenKey(REG_MACHINE_LOCATION, True);
      end;
      if Result then
      begin
        WriteString(Name, Value); { Write Registry for Global Environment }
        { Update Current Process Environment Variable }
        SetEnvironmentVariable(PChar(Name), PChar(Value));
        { Send Message To All Top Window for Refresh }
        SendMessage(HWND_BROADCAST, WM_SETTINGCHANGE, 0, Integer(PChar('Environment')));
      end;
    finally
      Free;
    end;
end;

Any help appreciated!

Share this post


Link to post

You code runs the risk of hanging when it broadcasts. But the basic concept is correct albeit poorly implemented. Have you done any debugging? 

Share this post


Link to post

Stéphane Wierzbicki: That post was helpful, but seems to be pretty much what I am doing?  I changed my code to use SendMessageTimeOut(), which seems to make all the top level windows that I have open flash a little, but seems to leave my live environment otherwise untouched 😞

 

Dave Nottage: That too was very helpful, although all it really told me is that I am fighting a losing battle 😉

Share this post


Link to post

Also please note that in Windows several environmental variables requires a logoff-logon for the changes to take affect (like %PATH%).

Share this post


Link to post
5 hours ago, aehimself said:

Also please note that in Windows several environmental variables requires a logoff-logon for the changes to take affect (like %PATH%).

Don't think that's true. That's the point of the broadcast message. The shell updates its environment and uses that when creating new processes. 

Share this post


Link to post
3 hours ago, David Heffernan said:

Don't think that's true. That's the point of the broadcast message. The shell updates its environment and uses that when creating new processes. 

This is correct...  If I run my process to set the variable, then spawn a new shell it works...  So it seems like it's just the broadcasting part of this process that doesn't work?

Share this post


Link to post
On 9/11/2019 at 12:17 PM, Nathan Wild said:

I have tried using SetEnvironmentVariable() and then SendMessage() to broadcast the change, to no avail...  I found an old post with the following function which seems like it SHOULD do what I want - but no matter what my environment is not touched.

New values are not actually saved in the Registry until the key is closed, which you are not doing until AFTER sending the broadcast.  And the TRegistry.LazyWrite property is true by default, making TRegistry.CloseKey() essentially asynchronous, so new values are saved in the background while control returns to your app.  It may take a few seconds for the new values to actually get written, flushed to disk, and cached, thus other apps are likely to read old values while processing the broadcast.

 

You should set LazyWrite=false to ensure the new value is fully saved when closing the key, and you should close the key before sending the broadcast.

 

Try this:

function SetGlobalEnvironment(const Name, Value: string; const User: Boolean = True): Boolean;
const
  REG_MACHINE_LOCATION = 'System\CurrentControlSet\Control\Session Manager\Environment';
  REG_USER_LOCATION = 'Environment';
var
  dwReturnValue: DWORD_PTR;
begin
  with TRegistry.Create do
  try
    LazyWrite := false;
    if User then
    begin
      { User Environment Variable }
      RootKey := HKEY_CURRENT_USER;
      Result := OpenKey(REG_USER_LOCATION, True);
    end else
    begin
      { System Environment Variable }
      RootKey := HKEY_LOCAL_MACHINE;
      Result := OpenKey(REG_MACHINE_LOCATION, True);
    end;
    if not Result then Exit;
    WriteString(Name, Value); { Write Registry for Global Environment }
  finally
    Free;
  end;
  { Update Current Process Environment Variable }
  SetEnvironmentVariable(PChar(Name), PChar(Value));
  { Send Message To All Top Windows for Refresh }
  //SendMessage(HWND_BROADCAST, WM_SETTINGCHANGE, 0, LPARAM(PChar('Environment')));
  SendMessageTimeout(HWND_BROADCAST, WM_SETTINGCHANGE, 0, LPARAM(PChar('Environment')), SMTO_ABORTIFHUNG, 5000, @dwReturnValue);
end;

 

Share this post


Link to post
On 9/12/2019 at 1:31 PM, David Heffernan said:

Don't think that's true. That's the point of the broadcast message. The shell updates its environment and uses that when creating new processes. 

I just checked my old codes and yes - they do take effect after the broadcast was sent. Must have mistaken it with setting it somewhere else in Windows...?

Share this post


Link to post

Remy: Thanks very much!  With your code, I can see all top level windows flicker a little when the broadcast call is issued, but my shell still does not recognize the new variable even after the process terminates.  Closing and opening a new shell does work, but this is not what I'm after...  Starting to thing that this is not possible with Windows?!

Share this post


Link to post

What you are trying to do is possible. I guess there is just a mistake somewhere, or some extra detail that we don't know of yet. 

  • Like 1

Share this post


Link to post
14 hours ago, Nathan Wild said:

With your code, I can see all top level windows flicker a little when the broadcast call is issued, but my shell still does not recognize the new variable even after the process terminates.

Which specific environment variable are you trying to set?

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×