1 from distutils.errors import DistutilsSetupError
2 from setuptools import Command
5 class scriptsetup(Command):
6 action = (sys.platform == "win32"
7 and "set up .pyscript association and PATHEXT variable to run scripts"
8 or "this does nothing on non-Windows platforms")
12 'make changes for all users of this Windows installation (requires Administrator privileges)'),
14 boolean_options = ['allusers']
16 def initialize_options(self):
19 def finalize_options(self):
23 if sys.platform != "win32":
24 print "\n'scriptsetup' isn't needed on non-Windows platforms."
26 do_scriptsetup(self.allusers)
29 def do_scriptsetup(allusers=False):
30 print "\nSetting up environment to run scripts for %s..." % (allusers and "all users" or "the current user")
32 from _winreg import HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE, HKEY_CLASSES_ROOT, \
33 REG_SZ, REG_EXPAND_SZ, KEY_QUERY_VALUE, KEY_SET_VALUE, \
34 OpenKey, CreateKey, QueryValueEx, SetValueEx, FlushKey, CloseKey
36 USER_ENV = "Environment"
38 user_env = OpenKey(HKEY_CURRENT_USER, USER_ENV, 0, KEY_QUERY_VALUE)
39 except WindowsError, e:
40 raise DistutilsSetupError("I could not read the user environment from the registry.\n%r" % (e,))
42 SYSTEM_ENV = "SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment"
44 system_env = OpenKey(HKEY_LOCAL_MACHINE, SYSTEM_ENV, 0, KEY_QUERY_VALUE)
45 except WindowsError, e:
46 raise DistutilsSetupError("I could not read the system environment from the registry.\n%r" % (e,))
49 # HKEY_CLASSES_ROOT is a merged view that would only confuse us.
50 # <http://technet.microsoft.com/en-us/library/cc739822(WS.10).aspx>
52 USER_CLASSES = "SOFTWARE\\Classes"
54 user_classes = OpenKey(HKEY_CURRENT_USER, USER_CLASSES, 0, KEY_QUERY_VALUE)
55 except WindowsError, e:
56 raise DistutilsSetupError("I could not read the user filetype associations from the registry.\n%r" % (e,))
58 SYSTEM_CLASSES = "SOFTWARE\\Classes"
60 system_classes = OpenKey(HKEY_LOCAL_MACHINE, SYSTEM_CLASSES, 0, KEY_QUERY_VALUE)
61 except WindowsError, e:
62 raise DistutilsSetupError("I could not read the system filetype associations from the registry.\n%r" % (e,))
65 def query(key, subkey, what):
67 (value, type) = QueryValueEx(key, subkey)
68 except WindowsError, e:
69 if e.winerror == 2: # not found
71 raise DistutilsSetupError("I could not read %s from the registry.\n%r" % (what, e))
73 # It does not matter that we don't expand environment strings, in fact it's better not to.
75 if type != REG_SZ and type != REG_EXPAND_SZ:
76 raise DistutilsSetupError("I expected the registry entry for %s to have a string type (REG_SZ or REG_EXPAND_SZ), "
77 "and was flummoxed by it having type code %r." % (what, type))
81 def open_and_query(key, path, subkey, what):
83 read_key = OpenKey(key, path, 0, KEY_QUERY_VALUE)
84 except WindowsError, e:
85 if e.winerror == 2: # not found
87 raise DistutilsSetupError("I could not read %s from the registry because I could not open "
88 "the parent key.\n%r" % (what, e))
91 return query(read_key, subkey, what)
96 def update(key_name_path, subkey, desired_value, desired_type, goal, what):
97 (key, name, path) = key_name_path
99 (old_value, old_type) = open_and_query(key, path, subkey, what) or (None, None)
100 if (old_value, old_type) == (desired_value, desired_type):
101 print "Already done: %s." % (goal,)
105 update_key = OpenKey(key, path, 0, KEY_SET_VALUE|KEY_QUERY_VALUE)
106 except WindowsError, e:
108 raise DistutilsSetupError("I tried to %s, but was not successful because I could not open "
109 "the registry key %s\\%s for writing.\n%r"
110 % (goal, name, path, e))
112 update_key = CreateKey(key, path)
113 except WindowsError, e:
114 raise DistutilsSetupError("I tried to %s, but was not successful because the registry key %s\\%s "
115 "did not exist, and I was unable to create it.\n%r"
116 % (goal, name, path, e))
118 (new_value, new_type) = (None, None)
120 SetValueEx(update_key, subkey, 0, desired_type, desired_value)
121 except WindowsError, e:
122 raise DistutilsSetupError("I tried to %s, but was not able to set the subkey %r under %s\\%s to be %r.\n%r"
123 % (goal, subkey, name, path, desired_value))
125 (new_value, new_type) = query(update_key, subkey, what) or (None, None)
130 if (new_value, new_type) != (desired_value, desired_type):
131 raise DistutilsSetupError("I tried to %s by setting the subkey %r under %s\\%s to be %r, "
132 "and the call to SetValueEx succeeded, but the value ended up as "
133 "%r instead (it was previously %r). Maybe the update was unexpectedly virtualized?"
134 % (goal, subkey, name, path, desired_value, new_value, old_value))
136 print "Done: %s." % (goal,)
140 # Maintenance hazard: 'add_to_environment' and 'associate' use very similar, but not identical logic.
142 def add_to_environment(varname, addition, change_allusers):
144 what = "the %s environment variable %s" % (change_allusers and "system" or "user", varname)
145 goal = "add %s to %s" % (addition, what)
147 system_valueandtype = query(system_env, varname, "the system environment variable %s" % (varname,))
148 user_valueandtype = query(user_env, varname, "the user environment variable %s" % (varname,))
151 (value, type) = system_valueandtype or (u'', REG_SZ)
152 key_name_path = (HKEY_LOCAL_MACHINE, "HKEY_LOCAL_MACHINE", SYSTEM_ENV)
154 (value, type) = user_valueandtype or system_valueandtype or (u'', REG_SZ)
155 key_name_path = (HKEY_CURRENT_USER, "HKEY_CURRENT_USER", USER_ENV)
157 if addition.lower() in value.lower().split(u';'):
158 print "Already done: %s." % (goal,)
160 changed |= update(key_name_path, varname, value + u';' + addition, type, goal, what)
163 # Also change any overriding environment entry for the current user.
164 (user_value, user_type) = user_valueandtype or (u'', REG_SZ)
165 split_value = user_value.lower().split(u';')
167 if not (addition.lower() in split_value or u'%'+varname.lower()+u'%' in split_value):
168 now_what = "the overriding user environment variable %s" % (varname,)
169 changed |= update((HKEY_CURRENT_USER, "HKEY_CURRENT_USER", USER_ENV),
170 varname, user_value + u';' + addition, user_type,
171 "add %s to %s" % (addition, now_what), now_what)
176 def associate(ext, target, change_allusers):
178 what = "the %s association for %s" % (change_allusers and "system" or "user", ext)
179 goal = "associate the filetype %s with %s for %s" % (ext, target, change_allusers and "all users" or "the current user")
183 target_key = OpenKey(HKEY_LOCAL_MACHINE, "%s\\%s" % (SYSTEM_CLASSES, target), 0, KEY_QUERY_VALUE)
185 target_key = OpenKey(HKEY_CLASSES_ROOT, target, 0, KEY_QUERY_VALUE)
186 except WindowsError, e:
187 raise DistutilsSetupError("I was going to %s, but that won't work because the %s class does not exist in the registry, "
188 "as far as I can tell.\n%r" % (goal, target, e))
191 system_key_name_path = (HKEY_LOCAL_MACHINE, "HKEY_LOCAL_MACHINE", "%s\\%s" % (SYSTEM_CLASSES, ext))
192 user_key_name_path = (HKEY_CURRENT_USER, "HKEY_CURRENT_USER", "%s\\%s" % (USER_CLASSES, ext))
194 system_valueandtype = open_and_query(system_classes, ext, "", "the system association for %s" % (ext,))
195 user_valueandtype = open_and_query(user_classes, ext, "", "the user association for %s" % (ext,))
198 (value, type) = system_valueandtype or (u'', REG_SZ)
199 key_name_path = system_key_name_path
201 (value, type) = user_valueandtype or system_valueandtype or (u'', REG_SZ)
202 key_name_path = user_key_name_path
205 print "Already done: %s." % (goal,)
207 changed |= update(key_name_path, "", unicode(target), REG_SZ, goal, what)
210 # Also change any overriding association for the current user.
211 (user_value, user_type) = user_valueandtype or (u'', REG_SZ)
213 if user_value != target:
214 changed |= update(user_key_name_path, "", unicode(target), REG_SZ,
215 "associate the filetype %s with %s for the current user " \
216 "(because the system association is overridden)" % (ext, target),
217 "the overriding user association for %s" % (ext,))
222 def broadcast_settingchange(change_allusers):
223 print "Broadcasting that the environment has changed, please wait..."
225 # <http://support.microsoft.com/kb/104011/en-us>
226 # <http://msdn.microsoft.com/en-us/library/ms644952(VS.85).aspx>
227 # LRESULT WINAPI SendMessageTimeoutW(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam,
228 # UINT fuFlags, UINT uTimeout, PDWORD_PTR lpdwResult);
231 from ctypes import WINFUNCTYPE, POINTER, windll, addressof, c_wchar_p
232 from ctypes.wintypes import LONG, HWND, UINT, WPARAM, LPARAM, DWORD
234 SendMessageTimeout = WINFUNCTYPE(POINTER(LONG), HWND, UINT, WPARAM, LPARAM, UINT, UINT, POINTER(POINTER(DWORD))) \
235 (("SendMessageTimeoutW", windll.user32))
236 HWND_BROADCAST = 0xFFFF
237 WM_SETTINGCHANGE = 0x001A
238 SMTO_ABORTIFHUNG = 0x0002
239 SendMessageTimeout(HWND_BROADCAST, WM_SETTINGCHANGE, change_allusers and 1 or 0,
240 addressof(c_wchar_p(u"Environment")), SMTO_ABORTIFHUNG, 5000, None);
242 print "Warning: %r" % (e,)
245 changed_assoc = associate(".pyscript", "Python.File", allusers)
249 changed_env |= add_to_environment("PATHEXT", ".pyscript", allusers)
250 changed_env |= add_to_environment("PATHEXT", ".pyw", allusers)
255 if changed_assoc or changed_env:
256 broadcast_settingchange(allusers)
259 # whether logout is needed seems to randomly differ between installations
260 # of XP, but it is not needed in Vista or later.
263 need_logout = not re.search(r'^[6-9]|([1-9][0-9]+)\.', platform.version())
270 ***********************************************************************
271 Changes have been made to the persistent environment, but they may not
272 take effect in this Windows session. Running installed Python scripts
273 from a Command Prompt may only work after you have logged out and back
274 in again, or rebooted.
275 ***********************************************************************
279 ***********************************************************************
280 Changes have been made to the persistent environment, but not in this
281 Command Prompt. Running installed Python scripts will only work from
282 new Command Prompts opened from now on.
283 ***********************************************************************