Programmatically changing environment variables in Windows

It’s not difficult to set environment variable in Windows. System level variables are stored in HKLM/System/CurrentControlSet/Control/Session Manager/Environment. User level variables are stored in HKCU/Environment. They are either REG_SZ or REG_EXPAND_SZ variables. REG_EXPAND_SZ values use other environment variables to get their ultimate value, while REG_SZ values are considered ‘final destination’ variables.

The issue arises when you programmatically change the value and want it reflected in new programs that are launched. You make your changes in the registry, but none of the newly launches applications notice the change. You need to inform all the running applications that the settings have been changed. To do this you send a WM_SETTINGCHANGE message to all the running applications.

The logic is to issue a SendMessage(HWND_BROADCAST, WM_SETTINGCHANGE, 0, (LPARAM)"Environment"). As the meerkat in the advertisement says ‘Seemples’. Unfortunately, I have a couple of applications with badly written message processing loops which don’t defer to DefWndProc if they don’t handle the message, which causes this function to hang.

The more sensible logic is to use a SendMessageTimeout call, which has 2 extra parameters, one of which is a flag and the other is a timeout in milliseconds. The timeout is a maximum per window, which means that if there are 10 windows causing timeouts and you’re issuing it with a 1000 milli-second (1 second) timeout, then you will be stalled for 10 seconds. You have been warned. Most applications should respond in < 100 milli-seconds, and typically there are only a few badly behaved applications.

This brings us to the code. It’s short, and it’s C and it doesn’t do anything fancy at all. Compiled using MinGW as gcc -mwindows settings.c -o settings.exe

#include <windows.h>

int APIENTRY WinMain(HINSTANCE hInstance,
  HINSTANCE hPrevInstance,
  LPSTR lpCmdLine,
  int nCmdShow)
{
    DWORD output;
    SendMessageTimeout(HWND_BROADCAST, WM_SETTINGCHANGE, 0,
      (LPARAM)"Environment", SMTO_BLOCK, 100, &output);
    return (0);
}

Set a variable in the registry. Pop up a cmd window and issue a set command and the change is not reflected in the window. Close the window, run the settings program compiled above, then launch another cmd window and it will now reflect the change to the environment you made in the registry.

The message causes Explorer to re-read the environment, which is why newly launched programs see the changes. You are launching your applications from explorer (the start menu, icons on the desktop, the run menu) for the most part.