# (c) Continuum Analytics, Inc. / http://continuum.io # All Rights Reserved # Copied from conda constructor at commit d91adfb1c49666768ef9fd625d02276af6ddb0c9 # This file is under the BSD license: # # Copyright (c) 2016, Continuum Analytics, Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of Continuum Analytics, Inc. nor the # names of its contributors may be used to endorse or promote products # derived from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL CONTINUUM ANALYTICS BE LIABLE FOR ANY # DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # # Helper script for adding and removing entries in the # Windows system path from the NSIS installer. __all__ = ['remove_from_system_path', 'add_to_system_path', 'broadcast_environment_settings_change'] import sys import os, ctypes from os import path from ctypes import wintypes if sys.version_info[0] >= 3: import winreg as reg else: import _winreg as reg HWND_BROADCAST = 0xffff WM_SETTINGCHANGE = 0x001A SMTO_ABORTIFHUNG = 0x0002 SendMessageTimeout = ctypes.windll.user32.SendMessageTimeoutW SendMessageTimeout.restype = None #wintypes.LRESULT SendMessageTimeout.argtypes = [wintypes.HWND, wintypes.UINT, wintypes.WPARAM, wintypes.LPCWSTR, wintypes.UINT, wintypes.UINT, ctypes.POINTER(wintypes.DWORD)] def sz_expand(value, value_type): if value_type == reg.REG_EXPAND_SZ: return reg.ExpandEnvironmentStrings(value) else: return value def remove_from_system_path(pathname, allusers=True, path_env_var='PATH'): """Removes all entries from the path which match the value in 'pathname' You must call broadcast_environment_settings_change() after you are finished manipulating the environment with this and other functions. For example, # Remove Anaconda from PATH remove_from_system_path(r'C:\Anaconda') broadcast_environment_settings_change() """ pathname = path.normcase(path.normpath(pathname)) envkeys = [(reg.HKEY_CURRENT_USER, r'Environment')] if allusers: envkeys.append((reg.HKEY_LOCAL_MACHINE, r'SYSTEM\CurrentControlSet\Control\Session Manager\Environment')) for root, keyname in envkeys: key = reg.OpenKey(root, keyname, 0, reg.KEY_QUERY_VALUE|reg.KEY_SET_VALUE) reg_value = None try: reg_value = reg.QueryValueEx(key, path_env_var) except WindowsError: # This will happen if we're a non-admin install and the user has # no PATH variable. reg.CloseKey(key) continue try: any_change = False results = [] for v in reg_value[0].split(os.pathsep): vexp = sz_expand(v, reg_value[1]) # Check if the expanded path matches the # requested path in a normalized way if path.normcase(path.normpath(vexp)) == pathname: any_change = True else: # Append the original unexpanded version to the results results.append(v) modified_path = os.pathsep.join(results) if any_change: reg.SetValueEx(key, path_env_var, 0, reg_value[1], modified_path) except: # If there's an error (e.g. when there is no PATH for the current # user), continue on to try the next root/keyname pair reg.CloseKey(key) def add_to_system_path(paths, allusers=True, path_env_var='PATH'): """Adds the requested paths to the system PATH variable. You must call broadcast_environment_settings_change() after you are finished manipulating the environment with this and other functions. """ # Make sure it's a list if not issubclass(type(paths), list): paths = [paths] # Ensure all the paths are valid before we start messing with the # registry. new_paths = None for p in paths: p = path.abspath(p) if not path.isdir(p): raise RuntimeError( 'Directory "%s" does not exist, ' 'cannot add it to the path' % p ) if new_paths: new_paths = new_paths + os.pathsep + p else: new_paths = p if allusers: # All Users root, keyname = (reg.HKEY_LOCAL_MACHINE, r'SYSTEM\CurrentControlSet\Control\Session Manager\Environment') else: # Just Me root, keyname = (reg.HKEY_CURRENT_USER, r'Environment') key = reg.OpenKey(root, keyname, 0, reg.KEY_QUERY_VALUE|reg.KEY_SET_VALUE) reg_type = None reg_value = None try: try: reg_value = reg.QueryValueEx(key, path_env_var) except WindowsError: # This will happen if we're a non-admin install and the user has # no PATH variable; in which case, we can write our new paths # directly. reg_type = reg.REG_EXPAND_SZ final_value = new_paths else: reg_type = reg_value[1] # If we're an admin install, put us at the end of PATH. If we're # a user install, throw caution to the wind and put us at the # start. (This ensures we're picked up as the default python out # of the box, regardless of whether or not the user has other # pythons lying around on their PATH, which would complicate # things. It's also the same behavior used on *NIX.) if allusers: final_value = reg_value[0] + os.pathsep + new_paths else: final_value = new_paths + os.pathsep + reg_value[0] reg.SetValueEx(key, path_env_var, 0, reg_type, final_value) finally: reg.CloseKey(key) def broadcast_environment_settings_change(): """Broadcasts to the system indicating that master environment variables have changed. This must be called after using the other functions in this module to manipulate environment variables. """ SendMessageTimeout(HWND_BROADCAST, WM_SETTINGCHANGE, 0, u'Environment', SMTO_ABORTIFHUNG, 5000, ctypes.pointer(wintypes.DWORD())) def main(): if sys.argv[1] == 'add': add_to_system_path(sys.argv[2]) broadcast_environment_settings_change() elif sys.argv[1] == 'remove': remove_from_system_path(sys.argv[2]) broadcast_environment_settings_change() if __name__ == '__main__': main()