# (c) Anaconda, Inc. / https://anaconda.com # All Rights Reserved # This file is under the BSD license # Helper script which is called from within the nsis install process # on Windows. The fact that we put this file into the standard library # directory is merely a convenience. This way, functionally can easily # be tested in an installation. import os import re import sys import traceback from os import environ from os.path import basename, exists, isfile, join from subprocess import STDOUT, CalledProcessError, check_output try: import winreg except ImportError: import _winreg as winreg ROOT_PREFIX = sys.prefix # Install an exception hook which pops up a message box. # Ideally, exceptions will get returned to NSIS and logged there, # etc, but this is a stopgap solution for now. old_excepthook = sys.excepthook # this sucks. It is copied from _nsis.py because it can't be a relative import. # _nsis.py must be standalone. def ensure_comspec_set(): if basename(environ.get("COMSPEC", "")).lower() != "cmd.exe": cmd_exe = join(environ.get('SystemRoot'), 'System32', 'cmd.exe') if not isfile(cmd_exe): cmd_exe = join(environ.get('windir'), 'System32', 'cmd.exe') if not isfile(cmd_exe): print("cmd.exe could not be found. " "Looked in SystemRoot and windir env vars.\n") else: environ['COMSPEC'] = cmd_exe def gui_excepthook(exctype, value, tb): try: import ctypes import traceback MB_ICONERROR = 0x00000010 title = u'Installation Error' msg = u''.join(traceback.format_exception(exctype, value, tb)) ctypes.windll.user32.MessageBoxW(0, msg, title, MB_ICONERROR) finally: # Also call the old exception hook to let it do # its thing too. old_excepthook(exctype, value, tb) sys.excepthook = gui_excepthook # If pythonw is being run, there may be no write function if sys.stdout and sys.stdout.write: out = sys.stdout.write err = sys.stderr.write else: import ctypes OutputDebugString = ctypes.windll.kernel32.OutputDebugStringW OutputDebugString.argtypes = [ctypes.c_wchar_p] def out(x): OutputDebugString('_nsis.py: ' + x) def err(x): OutputDebugString('_nsis.py: Error: ' + x) class NSISReg: def __init__(self, reg_path): self.reg_path = reg_path if exists(join(ROOT_PREFIX, '.nonadmin')): self.main_key = winreg.HKEY_CURRENT_USER else: self.main_key = winreg.HKEY_LOCAL_MACHINE def set(self, name, value): try: winreg.CreateKey(self.main_key, self.reg_path) registry_key = winreg.OpenKey(self.main_key, self.reg_path, 0, winreg.KEY_WRITE) winreg.SetValueEx(registry_key, name, 0, winreg.REG_SZ, value) winreg.CloseKey(registry_key) return True except WindowsError: return False def get(self, name): try: registry_key = winreg.OpenKey(self.main_key, self.reg_path, 0, winreg.KEY_READ) value, regtype = winreg.QueryValueEx(registry_key, name) winreg.CloseKey(registry_key) return value except WindowsError: return None def mk_menus(remove=False, prefix=None, pkg_names=None, root_prefix=None): try: import menuinst except (ImportError, OSError): return if prefix is None: prefix = sys.prefix if root_prefix is None: root_prefix = sys.prefix menu_dir = join(prefix, 'Menu') if not os.path.isdir(menu_dir): return for fn in os.listdir(menu_dir): if not fn.endswith('.json'): continue if pkg_names is not None and len(pkg_names) > 0 and fn[:-5] not in pkg_names: # skip when not in the list of menus to create # when installing, the pkg_names list is specified, otherwise not # and we don't skip to try to remove shortcuts continue shortcut = join(menu_dir, fn) try: menuinst.install(shortcut, remove, prefix=prefix, root_prefix=root_prefix) except Exception as e: out("Failed to process %s...\n" % shortcut) err("Error: %s\n" % str(e)) err("Traceback:\n%s\n" % traceback.format_exc(20)) else: out("Processed %s successfully.\n" % shortcut) def mk_dirs(): envs_dir = join(ROOT_PREFIX, 'envs') if not exists(envs_dir): os.mkdir(envs_dir) def get_conda_envs_from_python_api(): try: from conda.cli.python_api import Commands, run_command except (ImportError, OSError): return from json import loads c_stdout, c_stderr, return_code = run_command(Commands.INFO, "--json") json_conda_info = loads(c_stdout) return json_conda_info["envs"] get_conda_envs = get_conda_envs_from_python_api def rm_menus(prefix=None, root_prefix=None): try: import menuinst # noqa from conda.base.context import context except (ImportError, OSError): return try: envs = get_conda_envs() envs = list(envs) # make sure `envs` is iterable except Exception as e: out("Failed to get conda environments list\n") err("Error: %s\n" % str(e)) err("Traceback:\n%s\n" % traceback.format_exc(20)) if prefix is not None: out("Will only remove shortcuts created from '%s'" % prefix) mk_menus(remove=True, prefix=prefix, root_prefix=root_prefix) else: envs_dirs = list(context.envs_dirs) if prefix is not None: envs_dirs.append(prefix) for env in envs: env = str(env) # force `str` so that `os.path.join` doesn't fail for envs_dir in envs_dirs: # Make sure the environment is from one of the directory in # `envs_dirs` to avoid picking up environment from other # distributions. Not perfect but better than no checking if envs_dir in env: mk_menus(remove=True, prefix=env, root_prefix=root_prefix) def run_post_install(): """ call the post install script, if the file exists """ path = join(ROOT_PREFIX, 'pkgs', 'post_install.bat') if not isfile(path): return env = os.environ.copy() env.setdefault('PREFIX', str(ROOT_PREFIX)) cmd_exe = os.path.join(os.environ['SystemRoot'], 'System32', 'cmd.exe') if not os.path.isfile(cmd_exe): cmd_exe = os.path.join(os.environ['windir'], 'System32', 'cmd.exe') if not os.path.isfile(cmd_exe): err("Error: running %s failed. cmd.exe could not be found. " "Looked in SystemRoot and windir env vars.\n" % path) if os.environ.get("NSIS_SCRIPTS_RAISE_ERRORS"): sys.exit(1) args = [cmd_exe, '/d', '/c', path] import subprocess try: subprocess.check_call(args, env=env) except subprocess.CalledProcessError: err("Error: running %s failed\n" % path) if os.environ.get("NSIS_SCRIPTS_RAISE_ERRORS"): sys.exit(1) def run_pre_uninstall(): """ call the pre uninstall script, if the file exists """ path = join(ROOT_PREFIX, 'pre_uninstall.bat') if not isfile(path): return env = os.environ.copy() env.setdefault('PREFIX', str(ROOT_PREFIX)) cmd_exe = os.path.join(os.environ['SystemRoot'], 'System32', 'cmd.exe') if not os.path.isfile(cmd_exe): cmd_exe = os.path.join(os.environ['windir'], 'System32', 'cmd.exe') if not os.path.isfile(cmd_exe): err("Error: running %s failed. cmd.exe could not be found. " "Looked in SystemRoot and windir env vars.\n" % path) if os.environ.get("NSIS_SCRIPTS_RAISE_ERRORS"): sys.exit(1) args = [cmd_exe, '/d', '/c', path] import subprocess try: subprocess.check_call(args, env=env) except subprocess.CalledProcessError: err("Error: running %s failed\n" % path) if os.environ.get("NSIS_SCRIPTS_RAISE_ERRORS"): sys.exit(1) allusers = (not exists(join(ROOT_PREFIX, '.nonadmin'))) # out('allusers is %s\n' % allusers) # This must be the same as conda's binpath_from_arg() in conda/cli/activate.py PATH_SUFFIXES = ('', os.path.join('Library', 'mingw-w64', 'bin'), os.path.join('Library', 'usr', 'bin'), os.path.join('Library', 'bin'), 'Scripts') def remove_from_path(root_prefix=None): from _system_path import broadcast_environment_settings_change, remove_from_system_path if root_prefix is None: root_prefix = ROOT_PREFIX for path in [os.path.normpath(os.path.join(root_prefix, path_suffix)) for path_suffix in PATH_SUFFIXES]: remove_from_system_path(path, allusers) broadcast_environment_settings_change() def add_to_path(pyversion, arch): from _system_path import ( add_to_system_path, broadcast_environment_settings_change, get_previous_install_prefixes, ) # If a previous Anaconda install attempt to this location left remnants, # remove those. remove_from_path(ROOT_PREFIX) # If a previously registered Anaconda install left remnants, remove those. try: old_prefixes = get_previous_install_prefixes(pyversion, arch, allusers) except IOError: old_prefixes = [] for prefix in old_prefixes: out('Removing old installation at %s from PATH (if any entries get found)\n' % (prefix)) remove_from_path(prefix) # add Anaconda to the path add_to_system_path([os.path.normpath(os.path.join(ROOT_PREFIX, path_suffix)) for path_suffix in PATH_SUFFIXES], allusers) broadcast_environment_settings_change() def rm_regkeys(): cmdproc_reg_entry = NSISReg(r'Software\Microsoft\Command Processor') cmdproc_autorun_val = cmdproc_reg_entry.get('AutoRun') conda_hook_regex_pat = r'((\s+&\s+)?(if +exist)?(\s*?\"[^\"]*?conda[-_]hook\.bat\"))' if join(ROOT_PREFIX, 'condabin') in (cmdproc_autorun_val or ''): cmdproc_autorun_newval = re.sub(conda_hook_regex_pat, '', cmdproc_autorun_val) try: cmdproc_reg_entry.set('AutoRun', cmdproc_autorun_newval) except Exception: # Hey, at least we made an attempt to cleanup pass def win_del(dirname): # check_output uses comspec as the default shell when setting the parameter `shell=True` ensure_comspec_set() out = "unknown error (exception not caught)" # first, remove all files try: out = check_output('DEL /F/Q/S *.* > NUL', shell=True, stderr=STDOUT, cwd=dirname) except CalledProcessError as e: # error code 5 indicates a permission error. We ignore those, but raise for anything else if e.returncode != 5: print("Removing folder {} the fast way failed. " "Output was: {}".format(dirname, out)) raise else: print("removing dir contents the fast way failed. " "Output was: {}".format(out)) else: print("Unexpected error removing dirname {}. " "Uninstall was probably not successful".format(dirname)) # next, remove folder hierarchy try: out = check_output('RD /S /Q "{}" > NUL'.format(dirname), shell=True, stderr=STDOUT) except CalledProcessError as e: # error code 5 indicates a permission error. We ignore those, but raise for anything else if e.returncode != 5: print("Removing folder {} the fast way failed. " "Output was: {}".format(dirname, out)) raise else: print("Removing directory folders the fast way failed. " "Output was: {}".format(out)) else: print("Unexpected error removing dirname {}. " "Uninstall was probably not successful".format(dirname)) def main(): cmd = sys.argv[1].strip() if cmd == 'mkmenus': pkg_names = [s.strip() for s in sys.argv[2:]] mk_menus(remove=False, pkg_names=pkg_names) elif cmd == 'post_install': run_post_install() elif cmd == 'rmmenus': rm_menus() elif cmd == 'rmreg': rm_regkeys() elif cmd == 'mkdirs': mk_dirs() elif cmd == 'addpath': # These checks are probably overkill, but could be useful # if I forget to update something that uses this code. if len(sys.argv) > 2: pyver = sys.argv[2] else: pyver = '%s.%s.%s' % (sys.version_info.major, sys.version_info.minor, sys.version_info.micro) if len(sys.argv) > 3: arch = sys.argv[2] else: arch = '32-bit' if tuple.__itemsize__ == 4 else '64-bit' add_to_path(pyver, arch) elif cmd == 'rmpath': remove_from_path() elif cmd == 'pre_uninstall': run_pre_uninstall() elif cmd == 'del': assert len(sys.argv) == 3 win_del(sys.argv[2].strip()) else: sys.exit("ERROR: did not expect %r" % cmd) if __name__ == '__main__': main()