10

How do I permanently add the path to system's environment variable "PATH"?

I want to only add the path if it does not already exist.

Also I want to remove all paths that contain a folder name such as \myprogram whether it be:

C:\path\to\myprogram\dist; or D:\another\path\to\myprogram\dist;

martineau
  • 119,623
  • 25
  • 170
  • 301
jshbrntt
  • 5,134
  • 6
  • 31
  • 60

5 Answers5

13

Here's something that does what you want which is similar to code in the jaraco.windows project. And like it, only uses built-in Python modules—so doesn't require first downloading and installing the pywin32 extensions. Plus it's Python 2.6+ and 3.x compatible and supports Unicode environment variables and values (directory paths in this case).

Note that Windows administrator rights are required to change the permanent system-level environment variables.

import ctypes
from ctypes.wintypes import HWND, UINT, WPARAM, LPARAM, LPVOID
LRESULT = LPARAM  # synonymous
import os
import sys
try:
    import winreg
    unicode = str
except ImportError:
    import _winreg as winreg  # Python 2.x

class Environment(object):
    path = r'SYSTEM\CurrentControlSet\Control\Session Manager\Environment'
    hklm = winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE)
    key = winreg.OpenKey(hklm, path, 0, winreg.KEY_READ | winreg.KEY_WRITE)
    SendMessage = ctypes.windll.user32.SendMessageW
    SendMessage.argtypes = HWND, UINT, WPARAM, LPVOID
    SendMessage.restype = LRESULT
    HWND_BROADCAST = 0xFFFF
    WM_SETTINGCHANGE = 0x1A
    NO_DEFAULT_PROVIDED = object()

    def get(self, name, default=NO_DEFAULT_PROVIDED):
        try:
            value = winreg.QueryValueEx(self.key, name)[0]
        except WindowsError:
            if default is self.NO_DEFAULT_PROVIDED:
                raise ValueError("No such registry key", name)
            value = default
        return value

    def set(self, name, value):
        if value:
            winreg.SetValueEx(self.key, name, 0, winreg.REG_EXPAND_SZ, value)
        else:
            winreg.DeleteValue(self.key, name)
        self.notify()

    def notify(self):
        self.SendMessage(self.HWND_BROADCAST, self.WM_SETTINGCHANGE, 0, u'Environment')

Environment = Environment()  # singletion - create instance

PATH_VAR = 'PATH'

def append_path_envvar(addpath):
    def canonical(path):
        path = unicode(path.upper().rstrip(os.sep))
        return winreg.ExpandEnvironmentStrings(path)  # Requires Python 2.6+
    canpath = canonical(addpath)
    curpath = Environment.get(PATH_VAR, '')
    if not any(canpath == subpath
                for subpath in canonical(curpath).split(os.pathsep)):
        Environment.set(PATH_VAR, os.pathsep.join((curpath, addpath)))

def remove_envvar_path(folder):
    """ Remove *all* paths in PATH_VAR that contain the folder path. """
    curpath = Environment.get(PATH_VAR, '')
    folder = folder.upper()
    keepers = [subpath for subpath in curpath.split(os.pathsep)
                if folder not in subpath.upper()]
    Environment.set(PATH_VAR, os.pathsep.join(keepers))

Sample usage:

print(Environment.get('path'))
append_path_envvar(r'C:\path\to\myprogram\dist')
append_path_envvar(r'D:\another\path\to\myprogram\dist')
print(Environment.get('path'))
remove_envvar_path(r'\myprogram')  # remove *both* added paths
print(Environment.get('path'))
martineau
  • 119,623
  • 25
  • 170
  • 301
  • 1
    Brilliant! It really answers the question the best way. Saved me 2 hours. – Vasily Ryabov Jan 20 '17 at 10:16
  • @Vasily: You're welcome. Note I just made a few minor improvements, if you're interested. – martineau Jan 20 '17 at 20:39
  • That is great. But what about user environment variables? You code can only remove env var from system, not user's ones. – Gauthier Buttez Jun 08 '20 at 12:10
  • @Gauthier: That's because the question is about modifying the system-level environment variables. I would assume the user's variables are simply stored somewhere else in the registry — probably `HKEY_CURRENT_USER\Environment`. – martineau Jun 08 '20 at 13:48
11
import _winreg as reg
import win32gui
import win32con


# read the value
key = reg.OpenKey(reg.HKEY_CURRENT_USER, 'Environment', 0, reg.KEY_ALL_ACCESS)
# use this if you need to modify the system variable and if you have admin privileges
#key = reg.OpenKey(reg.HKEY_LOCAL_MACHINE, r'SYSTEM\CurrentControlSet\Control\Session Manager\Environment', 0, reg.KEY_ALL_ACCESS) 
try
    value, _ = reg.QueryValueEx(key, 'PATH')
except WindowsError:
    # in case the PATH variable is undefined
    value = ''

# modify it
value = ';'.join([s for s in value.split(';') if not r'\myprogram' in s])

# write it back
reg.SetValueEx(key, 'PATH', 0, reg.REG_EXPAND_SZ, value)
reg.CloseKey(key)

# notify the system about the changes
win32gui.SendMessage(win32con.HWND_BROADCAST, win32con.WM_SETTINGCHANGE, 0, 'Environment')
utapyngo
  • 6,946
  • 3
  • 44
  • 65
  • Note that `win32gui` and `win32con` are contained in the package `pywin32`. At least in version 301 of pywin32, you need to do `import win32.win32gui as win32gui` instead of `import win32gui`. – golden96371 Jun 16 '21 at 07:10
1

There is a python package pathtub (I am the author), which can do the job. You can read the code and docs on GitHub.

Installing

pip install pathtub

Example usage

Reading Path

from pathtub import get_path

# Getting path (can be 'process' (default), 'user' or 'machine')
user_path = get_path('user')

print(user_path)
# C:\Program Files\Java\jdk-13.0.1\bin;C:\Programs;C:\Programs\apache-maven-3.6.2\bin;C:\Programs\cloc;C:\Programs\fciv;C:\Python\Python37;C:\Python\Python37\lib\site-packages\pywin32_system32;C:\Python\Python37\Scripts;C:\texlive\2018\bin\win32;C:\Users\USER\AppData\Local\Microsoft\WindowsApps;C:\Users\USER\AppData\Local\Programs\Microsoft VS Code\bin;C:\Users\USER\AppData\Roaming\npm

Adding to path

from pathtub import add_to_path
added = add_to_path(r'C:\Add this folder\to user path', mode='user')

print(added)
#True

# Adding duplicate entries is prevented
added = add_to_path(r'C:\Add this folder\to user path', mode='user')

print(added)
#False

Removing from Path

removed = remove_from_path(r'C:\Add this folder\to user path', mode='user')

print(removed)
#True

# Removing non-existing folder just returns False
removed = remove_from_path(r'C:\Add this folder\to user path', mode='user')

print(removed)
#False

Requirements

Current implementation (v.1.1.2) uses Powershell for the permanent Path modifications. I have only tested this with newer (Python 3.7 and 3.8), but I guess it should work also on some older versions.

Niko Föhr
  • 28,336
  • 10
  • 93
  • 96
0

I am inferring from the paths in your question that your are interested in doing this on the Windows platform.

The documentation describes the process:

To programmatically add or modify system environment variables, add them to the HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\Environment registry key, then broadcast a WM_SETTINGCHANGE message with lParam set to the string "Environment". This allows applications, such as the shell, to pick up your updates.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
-1

You should check os.environ. It's a dictionary that can be manipulated directly or via os.putenv:

Set the environment variable named varname to the string value. Such changes to the environment affect subprocesses started with os.system(), popen() or fork() and execv().

Hence:

>>> import os
>>> os.environ["PATH"] =  path_old + ":/tmp/hallo" 
>>> os.environ["PATH"] 
'/usr/local/sbin:/usr/local/bin:/usr/bin:/usr/bin/vendor_perl:/usr/bin/core_perl:/tmp/hallo'

[update]

according to this answer you can make them persistent via windows registry

Community
  • 1
  • 1
mkind
  • 2,015
  • 2
  • 20
  • 25
  • You cannot "magically" make the changes to `os.environ` persistent using windows registry. You must write a concrete value there. You do not need to modify `os.environ` in order to do this. Moreover, you don't need to read it: the value of `os.environ["PATH"]` is composed of two values: user `PATH` and system `PATH`, which are stored in different registry keys. You should read a corresponding registry value instead. – utapyngo Jan 31 '14 at 17:05