_system_path.py 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  1. # (c) Continuum Analytics, Inc. / http://continuum.io
  2. # All Rights Reserved
  3. # Copied from conda constructor at commit d91adfb1c49666768ef9fd625d02276af6ddb0c9
  4. # This file is under the BSD license:
  5. #
  6. # Copyright (c) 2016, Continuum Analytics, Inc.
  7. # All rights reserved.
  8. #
  9. # Redistribution and use in source and binary forms, with or without
  10. # modification, are permitted provided that the following conditions are met:
  11. # * Redistributions of source code must retain the above copyright
  12. # notice, this list of conditions and the following disclaimer.
  13. # * Redistributions in binary form must reproduce the above copyright
  14. # notice, this list of conditions and the following disclaimer in the
  15. # documentation and/or other materials provided with the distribution.
  16. # * Neither the name of Continuum Analytics, Inc. nor the
  17. # names of its contributors may be used to endorse or promote products
  18. # derived from this software without specific prior written permission.
  19. #
  20. # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
  21. # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  22. # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  23. # DISCLAIMED. IN NO EVENT SHALL CONTINUUM ANALYTICS BE LIABLE FOR ANY
  24. # DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  25. # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  26. # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  27. # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  28. # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  29. # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  30. #
  31. # Helper script for adding and removing entries in the
  32. # Windows system path from the NSIS installer.
  33. __all__ = ['remove_from_system_path', 'add_to_system_path', 'broadcast_environment_settings_change']
  34. import sys
  35. import os, ctypes
  36. from os import path
  37. from ctypes import wintypes
  38. if sys.version_info[0] >= 3:
  39. import winreg as reg
  40. else:
  41. import _winreg as reg
  42. HWND_BROADCAST = 0xffff
  43. WM_SETTINGCHANGE = 0x001A
  44. SMTO_ABORTIFHUNG = 0x0002
  45. SendMessageTimeout = ctypes.windll.user32.SendMessageTimeoutW
  46. SendMessageTimeout.restype = None #wintypes.LRESULT
  47. SendMessageTimeout.argtypes = [wintypes.HWND, wintypes.UINT, wintypes.WPARAM,
  48. wintypes.LPCWSTR, wintypes.UINT, wintypes.UINT, ctypes.POINTER(wintypes.DWORD)]
  49. def sz_expand(value, value_type):
  50. if value_type == reg.REG_EXPAND_SZ:
  51. return reg.ExpandEnvironmentStrings(value)
  52. else:
  53. return value
  54. def remove_from_system_path(pathname, allusers=True, path_env_var='PATH'):
  55. """Removes all entries from the path which match the value in 'pathname'
  56. You must call broadcast_environment_settings_change() after you are finished
  57. manipulating the environment with this and other functions.
  58. For example,
  59. # Remove Anaconda from PATH
  60. remove_from_system_path(r'C:\Anaconda')
  61. broadcast_environment_settings_change()
  62. """
  63. pathname = path.normcase(path.normpath(pathname))
  64. envkeys = [(reg.HKEY_CURRENT_USER, r'Environment')]
  65. if allusers:
  66. envkeys.append((reg.HKEY_LOCAL_MACHINE,
  67. r'SYSTEM\CurrentControlSet\Control\Session Manager\Environment'))
  68. for root, keyname in envkeys:
  69. key = reg.OpenKey(root, keyname, 0,
  70. reg.KEY_QUERY_VALUE|reg.KEY_SET_VALUE)
  71. reg_value = None
  72. try:
  73. reg_value = reg.QueryValueEx(key, path_env_var)
  74. except WindowsError:
  75. # This will happen if we're a non-admin install and the user has
  76. # no PATH variable.
  77. reg.CloseKey(key)
  78. continue
  79. try:
  80. any_change = False
  81. results = []
  82. for v in reg_value[0].split(os.pathsep):
  83. vexp = sz_expand(v, reg_value[1])
  84. # Check if the expanded path matches the
  85. # requested path in a normalized way
  86. if path.normcase(path.normpath(vexp)) == pathname:
  87. any_change = True
  88. else:
  89. # Append the original unexpanded version to the results
  90. results.append(v)
  91. modified_path = os.pathsep.join(results)
  92. if any_change:
  93. reg.SetValueEx(key, path_env_var, 0, reg_value[1], modified_path)
  94. except:
  95. # If there's an error (e.g. when there is no PATH for the current
  96. # user), continue on to try the next root/keyname pair
  97. reg.CloseKey(key)
  98. def add_to_system_path(paths, allusers=True, path_env_var='PATH'):
  99. """Adds the requested paths to the system PATH variable.
  100. You must call broadcast_environment_settings_change() after you are finished
  101. manipulating the environment with this and other functions.
  102. """
  103. # Make sure it's a list
  104. if not issubclass(type(paths), list):
  105. paths = [paths]
  106. # Ensure all the paths are valid before we start messing with the
  107. # registry.
  108. new_paths = None
  109. for p in paths:
  110. p = path.abspath(p)
  111. if not path.isdir(p):
  112. raise RuntimeError(
  113. 'Directory "%s" does not exist, '
  114. 'cannot add it to the path' % p
  115. )
  116. if new_paths:
  117. new_paths = new_paths + os.pathsep + p
  118. else:
  119. new_paths = p
  120. if allusers:
  121. # All Users
  122. root, keyname = (reg.HKEY_LOCAL_MACHINE,
  123. r'SYSTEM\CurrentControlSet\Control\Session Manager\Environment')
  124. else:
  125. # Just Me
  126. root, keyname = (reg.HKEY_CURRENT_USER, r'Environment')
  127. key = reg.OpenKey(root, keyname, 0,
  128. reg.KEY_QUERY_VALUE|reg.KEY_SET_VALUE)
  129. reg_type = None
  130. reg_value = None
  131. try:
  132. try:
  133. reg_value = reg.QueryValueEx(key, path_env_var)
  134. except WindowsError:
  135. # This will happen if we're a non-admin install and the user has
  136. # no PATH variable; in which case, we can write our new paths
  137. # directly.
  138. reg_type = reg.REG_EXPAND_SZ
  139. final_value = new_paths
  140. else:
  141. reg_type = reg_value[1]
  142. # If we're an admin install, put us at the end of PATH. If we're
  143. # a user install, throw caution to the wind and put us at the
  144. # start. (This ensures we're picked up as the default python out
  145. # of the box, regardless of whether or not the user has other
  146. # pythons lying around on their PATH, which would complicate
  147. # things. It's also the same behavior used on *NIX.)
  148. if allusers:
  149. final_value = reg_value[0] + os.pathsep + new_paths
  150. else:
  151. final_value = new_paths + os.pathsep + reg_value[0]
  152. reg.SetValueEx(key, path_env_var, 0, reg_type, final_value)
  153. finally:
  154. reg.CloseKey(key)
  155. def broadcast_environment_settings_change():
  156. """Broadcasts to the system indicating that master environment variables have changed.
  157. This must be called after using the other functions in this module to
  158. manipulate environment variables.
  159. """
  160. SendMessageTimeout(HWND_BROADCAST, WM_SETTINGCHANGE, 0, u'Environment',
  161. SMTO_ABORTIFHUNG, 5000, ctypes.pointer(wintypes.DWORD()))
  162. def main():
  163. if sys.argv[1] == 'add':
  164. add_to_system_path(sys.argv[2])
  165. broadcast_environment_settings_change()
  166. elif sys.argv[1] == 'remove':
  167. remove_from_system_path(sys.argv[2])
  168. broadcast_environment_settings_change()
  169. if __name__ == '__main__':
  170. main()