10

Following on from my previous question, is it possible to make a Python script which persistently changes a Windows environment variable?

Changes to os.environ do not persist once the python interpreter terminates. If I were scripting this on UNIX, I might do something like:

set foo=`myscript.py`

But alas, cmd.exe does not have anything that works like sh's back-tick behavior. I have seen a very long-winded solution... it 'aint pretty so surely we can improve on this:

for /f "tokens=1* delims=" %%a in ('python  ..\myscript.py') do set path=%path%;%%a

Surely the minds at Microsoft have a better solution than this!

Note: exact duplicate of this question.

Community
  • 1
  • 1
Salim Fadhley
  • 22,020
  • 23
  • 75
  • 102

5 Answers5

5

You might want to try Python Win32 Extensions, developed by Mark Hammond, which is included in the ActivePython (or can be installed separately). You can learn how to perform many Windows related tasks in Hammond's and Robinson's book.

Using PyWin32 to access windows COM objects, a Python program can use the Environment Property of the WScript.Shell object - a collection of environment variables.

Sridhar Ratnakumar
  • 81,433
  • 63
  • 146
  • 187
gimel
  • 83,368
  • 10
  • 76
  • 104
3

Windows sets Environment variables from values stored in the Registry for each process independently.

However, there is a tool in the Windows XP Service Pack 2 Support Tools named setx.exe that allows you to change global Environment variables from the command line.

Powerlord
  • 87,612
  • 17
  • 125
  • 175
  • 4
    setx.exe is installed with Vista as well. However, I get the impression that the original poster actually wanted to find out how to modify the parent process's environment, not make environment variable values persist across reboots. – bk1e Jan 29 '09 at 06:49
3

My solution using win32api:

import os, sys, win32api, win32con
'''Usage: appendenv.py envvar data_to_append'''
def getenv_system(varname, default=None):
    '''
    Author: Denis Barmenkov <barmenkov at bpc.ru>

    Copyright: this code is free, but if you want to use it, 
               please keep this multiline comment along with function source. 
               Thank you.

    2006-01-28 15:30
    '''
    v = default
    try:
        rkey = win32api.RegOpenKey(win32con.HKEY_LOCAL_MACHINE, 'SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment')
        try:
            v = str(win32api.RegQueryValueEx(rkey, varname)[0])
            v = win32api.ExpandEnvironmentStrings(v)
        except:
            pass
    finally:
        win32api.RegCloseKey(rkey)
    return v

#My set function
def setenv_system(varname, value):
    try:
        rkey = win32api.RegOpenKeyEx(win32con.HKEY_LOCAL_MACHINE, 'SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment',0 ,win32con.KEY_WRITE)
        try:
            win32api.RegSetValueEx(rkey, varname, 0, win32con.REG_SZ, value)
            return True
        except Exception, (error):
            pass
    finally:
        win32api.RegCloseKey(rkey)
    return False

if len(sys.argv) == 3:
    value = getenv_system(sys.argv[1])
    if value:
        setenv_system(sys.argv[1],value + ";" + sys.argv[2])
        print "OK! %s = %s" % (sys.argv[1], getenv_system(sys.argv[1]))
    else:
        print "ERROR: No such environment variable. (%s)" % sys.argv[1]
else:
    print "Usage: appendenv.py envvar data_to_append"
Maiku Mori
  • 7,419
  • 2
  • 40
  • 52
  • 1
    Is it voluntary that the "get" uses the CurrentControlSet and that the set uses ControlSet001? If so, could you explain why? – SuperOli Oct 06 '11 at 13:26
  • @SuperOli, they're equal in almost all cases, but you're right it would be safer to use CurrentControlSet in 'set'. I don't recall why I wrote it like that back then. Going to edit the answer. (More info: http://support.microsoft.com/kb/100010) – Maiku Mori Oct 08 '11 at 16:49
2

This link provides a solution that uses the built-in winreg library.

(copypasta)

import sys
from subprocess import check_call
if sys.hexversion > 0x03000000:
    import winreg
else:
    import _winreg as winreg

class Win32Environment:
    """Utility class to get/set windows environment variable"""

    def __init__(self, scope):
        assert scope in ('user', 'system')
        self.scope = scope
        if scope == 'user':
            self.root = winreg.HKEY_CURRENT_USER
            self.subkey = 'Environment'
        else:
            self.root = winreg.HKEY_LOCAL_MACHINE
            self.subkey = r'SYSTEM\CurrentControlSet\Control\Session Manager\Environment'

    def getenv(self, name):
        key = winreg.OpenKey(self.root, self.subkey, 0, winreg.KEY_READ)
        try:
            value, _ = winreg.QueryValueEx(key, name)
        except WindowsError:
            value = ''
        return value

    def setenv(self, name, value):
        # Note: for 'system' scope, you must run this as Administrator
        key = winreg.OpenKey(self.root, self.subkey, 0, winreg.KEY_ALL_ACCESS)
        winreg.SetValueEx(key, name, 0, winreg.REG_EXPAND_SZ, value)
        winreg.CloseKey(key)
        # For some strange reason, calling SendMessage from the current process
        # doesn't propagate environment changes at all.
        # TODO: handle CalledProcessError (for assert)
        check_call('''\
"%s" -c "import win32api, win32con; assert win32api.SendMessage(win32con.HWND_BROADCAST, win32con.WM_SETTINGCHANGE, 0, 'Environment')"''' % sys.executable)
Jace Browning
  • 11,699
  • 10
  • 66
  • 90
1

Your long-winded solution is probably the best idea; I don't believe this is possible from Python directly. This article suggests another way, using a temporary batch file:

http://code.activestate.com/recipes/159462/

DNS
  • 37,249
  • 18
  • 95
  • 132
  • 5
    That doesn't change the environment persistently. It merely changes a copy of the environment variables. – rtn Jul 05 '10 at 13:20
  • 2
    @Salim Fadhley If that's what you want why do you ask for something different in your question? By accepting the answer which doesn't answer the question you mislead people looking for information... – Piotr Dobrogost Sep 07 '11 at 21:57
  • 2
    It's true that the question is ambiguous, but it's not outright incorrect. He asks for a way to set the variable persistently, *not* globally. His requirement was that the variable still be set after the interpreter exits, which is the case with this solution. – DNS Sep 07 '11 at 22:33
  • 2
    @DNS **Persistent** change to environment variable has always meant the same thing - to change it so the new value is visible to **all** other processes not only chosen ones. – Piotr Dobrogost Sep 08 '11 at 19:54