How to set global environment variables.

By: Toby Allen

Abstract: Article that shows how to update global environment settings and make sure they stay.

How to set global environment variables.

Using GetEnvironmentVariable and SetEnvironmentVariable and other related API calls you can get or set Environment Variables. However these changes only affect your own process, not others. How do you make these settings Global?

I had a problem recently with a tool I am working on. I needed to be able to put my applications install path onto the system path, so that users could use the start menu - Run command to access my application.

Ive been working with computers long enough to know how to do this in DOS or using an Autoexec.bat file. I could have written a batch file and spawned it as a separate process to set the path. However, not surprisingly, and due to the fact Im developing on Windows 2000, I really wanted a nice programming solution.

Next stop Windows API help.

I look to the WIN API help that comes with Delphi. I read about SetEnvironmentVariable and its related functions. The help kindly told me that I would be affecting the Environment variables for my process but no other. However it provided no clue as to how I should go about ensuring these settings were carried across the entire system.

Next Stop Google.

As with everything in life if you can't find it elsewhere go looking for it on Google. Quick search of the msdn.Microsoft.com site using google (site:msdn.Microsoft.com SetEnvironmentVariable ) which by the way I view as the best way of searching MSDN. I got some links.

After following a number of links around MSDN, the jist of what I read was that SetEnvironmentVariable was completely irrelevant for what I wanted to do. In order to achieve my ends all I needed to do was set the required value in the registry and send the WM_SETTINGCHANGE message to the system.

Following is some code to show how to do this and an explanation of interesting aspects.

The key with the particular settings I am interested in is HKEY_LOCAL_MACHINESYSTEMCurrentControlSetControlSession ManagerEnvironment and the Value to read is Path. Other folders within the Session Manager Key provide other values you can change. Also, if you wish to purely change a setting for a specific user (current user) use the path HKEY_CURRENT_USEREnvironment. Values may not already exist in CurrentUser, so you may need to create the value prior to setting it.

Define our Constants


const
    RegSection = 'SYSTEMCurrentControlSetControlSession ManagerEnvironment';
    ValueName  = 'Path';
implementation

In the source code in CodeCentral the source for the entire form can be downloaded, however since most of the code (reading and writing to the registry) is reasonably straightforward, I've left it out. The interesting part is the method that broadcasts the necessary message.


procedure TfrmPathSetting.BroadcastChange;
var
    lParam, wParam : Integer;   {Integers that indicate pointers to parameters}
    Buf     : Array[0..10] of Char; {Buffer used to indicate what setting we have changed.}
    aResult : Cardinal;         {Error Number returned from API Call}
begin
    {Now comes the interesting part.}
    {Environment is the section of global settings we want the system to update}
     Buf := 'Environment';
     wParam := 0;
     {This gives us a pointer to the Buffer for Windows to read.}
     lParam := Integer(@Buf[0]);

     {Here we make a call to SendMessageTimeout to Broadcast a message to the
     entire system telling every application (including explorer) to update
     its settings}
     SendMessageTimeout(HWND_BROADCAST ,
                        WM_SETTINGCHANGE ,
                        wParam,
                        lParam,
                        SMTO_NORMAL	,
                        4000,
                        aResult);

     {Display windows lasterror if the result is an error.}
     if aResult <> 0 then
     begin
         SysErrorMessage(aResult);
     end;
end;

I call SendMessageTimeout instead of Send or Postmessage because the MSDN document said this was necessary.


LRESULT SendMessageTimeout(

    HWND hWnd,	<I>// handle of destination window</I>
    UINT Msg,	<I>// message to send</I>
    WPARAM wParam,	// first message parameter
    LPARAM lParam,	// second message parameter
    UINT fuFlags,	// how to send the message
    UINT uTimeout,	// time-out duration
    LPDWORD lpdwResult 	// return value for synchronous call
   );

HWND_BROADCAST

Notice the use of HWND_BROADCAST for the handle. This according to the Win API help should be used when you want to broadcast a message to everything in the system. Which is exactly what we want to do.

LPARAM

Whenever I read stuff on Win API lots of things seem to be taken for granted,so if this next bit seems obvious to you bear with me. The type of lparam and wparam when you write then in Delphi is an Integer. However this is not really an integer, but a pointer to a memory address. Since Pointers and Integers are the same size and can be typecast to represent each other, this is how this works. What we really want is the first parameter wParam to be a Null. This is not a variant Null but a Null Pointer so we assign 0 to it. The second one, lparam, needs to be a pointer to a string indicating which part of the system we have changed, this string is "Environment". I achieve this by retrieving the pointer to the first char in the buf Array, which is essentially the pointer to the string.

Finally for the last three parameters, I use SMTO_NORMAL, which means my program can continue processing without waiting for the function to return. 4000 is the timeout in milliseconds that it should use, and I provide a Cardinal variable aResult to get any error value returned.

Finally

Even though hopefully I've presented this in a fairly straightforward manner, I spend quite a bit of time going backwards and forwards trying to get it all working right, using the wrong sendmessage functions, using the wrong wparam values etc. I hope this will prevent some of you from having to spend as much time if you need to do this.

Lastly, I've never been entirely happy with doing Win API calls, so if someone cleverer than me out there sees that I've got a huge memory leak going on Please let me know.

Download Sample Code from Code Central

By Toby Allen



Server Response from: ETNASC02