Welcome, guest | Sign In | My Account | Store | Cart

This script allows adding/modifying/removing environment variables persistently on Windows. It also allows adding entries to the PATH environment variable.

Python, 61 lines
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
from _winreg import *
import os, sys, win32gui, win32con

def queryValue(key, name):       
    value, type_id = QueryValueEx(key, name)
    return value

def show(key):
    for i in range(1024):                                           
        try:
            n,v,t = EnumValue(key, i)
            print '%s=%s' % (n, v)
        except EnvironmentError:
            break          

def main():
    try:
        path = r'SYSTEM\CurrentControlSet\Control\Session Manager\Environment'
        reg = ConnectRegistry(None, HKEY_LOCAL_MACHINE)
        key = OpenKey(reg, path, 0, KEY_ALL_ACCESS)
        
        if len(sys.argv) == 1:
            show(key)
        else:
            name, value = sys.argv[1].split('=')
            if name.upper() == 'PATH':
                value = queryValue(key, name) + ';' + value
            if value:
                SetValueEx(key, name, 0, REG_EXPAND_SZ, value)
            else:
                DeleteValue(key, name)
            
        win32gui.SendMessage(win32con.HWND_BROADCAST, win32con.WM_SETTINGCHANGE, 0, 'Environment')
                            
    except Exception, e:
        print e

    CloseKey(key)    
    CloseKey(reg)
    
if __name__=='__main__':    
    usage = \
"""
Usage:

Show all environment vsarisbles - enver
Add/Modify/Delete environment variable - enver <name>=[value]

If <name> is PATH enver will append the value prefixed with ;

If there is no value enver will delete the <name> environment variable

Note that the current command window will not be affected, 
only new command windows.
"""
    argc = len(sys.argv)
    if argc > 2 or (argc == 2 and sys.argv[1].find('=') == -1):
        print usage
        sys.exit()
        
    main()    

I often install programs that depend on environment variables for their proper operation and require that their executable directory will be add to the PATH. Doing it using the Windows GUI is error-prone and tedious. I have been burned many times. 'enver' is the result. The usage string explains how to use it.

Implementation highlights:

There aren't many :-). Enver parses the command line and works with the Windows registry using the _winreg module to modify the persistent environment. The registry key is SYSTEM\CurrentControlSet\Control\Session Manager\Environment. The treatment of PATH environment variable is special. Enver checks specifically if the env var name is PATH and only appends to it because that's what you usually do. Once everything is done enver broadcasts a WM_SETTINGCHANGE to all the windows in order to make the change visible.

Issues:

1) Note that at the moment enver doesn't modify the active environment, but only the registry so new command windows will see the changes. So, if you type: 'set' after running enver you won't see any changes. Setting os.environ wouldn't help because the script is executed in a different shell then the host window.

2) There is no way to replace the entire PATH, or to remove entries from the PATH, only to append entries to the PATH. Again, I don't need the other features, if it's important to someone go ahead and add the funtionality.

3) Works on the System environment only. doesn't bother with the User environment (System applies to all users of the machine, User applies to current user only).

4) Most applications (including active cmd.exe Windows) don't react to the broadcast message, so you will not see the cahnge until you restart them.

6 comments

Grig Gheorghiu 18 years, 9 months ago  # | flag

Note that if you modify the PATH value, you may also need to substitute variables such as %SystemRoot% with their actual value. If you look at the registry value for PATH, it may contain things such as:

%SystemRoot%;%SystemRoot%\system32;etc.

In this case, if you modify the PATH registry value by appending a directory and then you broadcast the change, you'll notice that subsequent command prompts will not have C:\WINNT and C:\WINNT\system32 in their PATH, but instead they'll still have %SystemRoot%;%SystemRoot%\system32. In consequence, they won't find the usual Windows binaries such as ping, ipconfig, etc. I'm not sure why this is happening, since you would think that the command prompt would substitute %SystemRoot% with its actual value automatically.

What I had to do was something like:

dirs = path.split(';')
try:
    systemroot = os.environ['SYSTEMROOT']
except KeyError:
    pass
else:
    dirs = [re.sub('%SystemRoot%', systemroot, dir) for dir in dirs]
path = ';'.join(dirs)
Ori Berger 18 years, 9 months ago  # | flag

The problem is REG_SZ. You mustn't make the path a REG_SZ entry - it should be a REG_EXPAND_SZ entry.

Grig Gheorghiu 18 years, 9 months ago  # | flag

Great! Thanks for the clarification, it worked just fine when I changed REG_SZ to REG_EXPAND_SZ.

ben w 18 years, 6 months ago  # | flag

Wow thanks! Exactly what I needed.

Stephen Hosking 17 years, 11 months ago  # | flag

There is a simpler way... I admit that I don't understand environment variables in NT/XP very well, however I did find that...

  1. The old "SET" command doesn't work as you expect it to.

  2. The "SETX" command does work as you expect it to. Almost! It doesn't apply the change in the current session, but it applies it in any new sessions.

Thus, one can simply do an os call to set an environment variable

>>> os.system("setx foo hello")

Then the environment variable "foo" will be set to "hello" IN SUBSEQUENT DOS sessions.

So after doing the above call start a new DOS session, restart python, and enter...

>>> os.system("set foo")

foo=hello

(note. we use "setx" to set the variable, but "set" to display it)

Anthon van der Neut 16 years, 4 months ago  # | flag

queryValue should be removed. The REG_EXPAND_SZ value is exactly the type_id that get thrown away in the queryValue function. You should remove that function and use all the output from QueryValueEx. This now breaks the registry if you pass something like: OS=Linux on the commandline (because the OS entry is a REG_SZ entry.

else:
    name, newval = sys.argv[1].split('=')
    value, type_id = QueryValueEx(key, name)
    if name.upper() == 'PATH':
        value  += ';' + newval
    if value:
        SetValueEx(key, name, 0, type_id, value)