Repository: spyder-ide/spyder

Issue:
can't run win_post_install from pip
please add this snippet to spyder_win_post_install.py to mimic bdist_wininst. I cribbed it from pywin32_post_install.py. Thanks!

```
try:
    # When this script is run from inside the bdist_wininst installer,
    # file_created() and directory_created() are additional builtin
    # functions which write lines to Python23\pywin32-install.log. This is
    # a list of actions for the uninstaller, the format is inspired by what
    # the Wise installer also creates.
    file_created
    is_bdist_wininst = True
except NameError:
    is_bdist_wininst = False # we know what it is not - but not what it is :)
    def file_created(file):
        pass
    def directory_created(directory):
        pass
    def get_root_hkey():
        try:
            winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE,
                           root_key_name, 0, winreg.KEY_CREATE_SUB_KEY)
            return winreg.HKEY_LOCAL_MACHINE
        except OSError, details:
            # Either not exist, or no permissions to create subkey means
            # must be HKCU
            return winreg.HKEY_CURRENT_USER

try:
    create_shortcut
except NameError:
    # Create a function with the same signature as create_shortcut provided
    # by bdist_wininst
    def create_shortcut(path, description, filename,
                        arguments="", workdir="", iconpath="", iconindex=0):
        import pythoncom
        from win32com.shell import shell, shellcon

        ilink = pythoncom.CoCreateInstance(shell.CLSID_ShellLink, None,
                                           pythoncom.CLSCTX_INPROC_SERVER,
                                           shell.IID_IShellLink)
        ilink.SetPath(path)
        ilink.SetDescription(description)
        if arguments:
            ilink.SetArguments(arguments)
        if workdir:
            ilink.SetWorkingDirectory(workdir)
        if iconpath or iconindex:
            ilink.SetIconLocation(iconpath, iconindex)
        # now save it.
        ipf = ilink.QueryInterface(pythoncom.IID_IPersistFile)
        ipf.Save(filename, 0)

    # Support the same list of "path names" as bdist_wininst.
    def get_special_folder_path(path_name):
        import pythoncom
        from win32com.shell import shell, shellcon

        for maybe in """
            CSIDL_COMMON_STARTMENU CSIDL_STARTMENU CSIDL_COMMON_APPDATA
            CSIDL_LOCAL_APPDATA CSIDL_APPDATA CSIDL_COMMON_DESKTOPDIRECTORY
            CSIDL_DESKTOPDIRECTORY CSIDL_COMMON_STARTUP CSIDL_STARTUP
            CSIDL_COMMON_PROGRAMS CSIDL_PROGRAMS CSIDL_PROGRAM_FILES_COMMON
            CSIDL_PROGRAM_FILES CSIDL_FONTS""".split():
            if maybe == path_name:
                csidl = getattr(shellcon, maybe)
                return shell.SHGetSpecialFolderPath(0, csidl, False)
        raise ValueError("%s is an unknown path ID" % (path_name,))
```

can't run win_post_install from pip
please add this snippet to spyder_win_post_install.py to mimic bdist_wininst. I cribbed it from pywin32_post_install.py. Thanks!

```
try:
    # When this script is run from inside the bdist_wininst installer,
    # file_created() and directory_created() are additional builtin
    # functions which write lines to Python23\pywin32-install.log. This is
    # a list of actions for the uninstaller, the format is inspired by what
    # the Wise installer also creates.
    file_created
    is_bdist_wininst = True
except NameError:
    is_bdist_wininst = False # we know what it is not - but not what it is :)
    def file_created(file):
        pass
    def directory_created(directory):
        pass
    def get_root_hkey():
        try:
            winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE,
                           root_key_name, 0, winreg.KEY_CREATE_SUB_KEY)
            return winreg.HKEY_LOCAL_MACHINE
        except OSError, details:
            # Either not exist, or no permissions to create subkey means
            # must be HKCU
            return winreg.HKEY_CURRENT_USER

try:
    create_shortcut
except NameError:
    # Create a function with the same signature as create_shortcut provided
    # by bdist_wininst
    def create_shortcut(path, description, filename,
                        arguments="", workdir="", iconpath="", iconindex=0):
        import pythoncom
        from win32com.shell import shell, shellcon

        ilink = pythoncom.CoCreateInstance(shell.CLSID_ShellLink, None,
                                           pythoncom.CLSCTX_INPROC_SERVER,
                                           shell.IID_IShellLink)
        ilink.SetPath(path)
        ilink.SetDescription(description)
        if arguments:
            ilink.SetArguments(arguments)
        if workdir:
            ilink.SetWorkingDirectory(workdir)
        if iconpath or iconindex:
            ilink.SetIconLocation(iconpath, iconindex)
        # now save it.
        ipf = ilink.QueryInterface(pythoncom.IID_IPersistFile)
        ipf.Save(filename, 0)

    # Support the same list of "path names" as bdist_wininst.
    def get_special_folder_path(path_name):
        import pythoncom
        from win32com.shell import shell, shellcon

        for maybe in """
            CSIDL_COMMON_STARTMENU CSIDL_STARTMENU CSIDL_COMMON_APPDATA
            CSIDL_LOCAL_APPDATA CSIDL_APPDATA CSIDL_COMMON_DESKTOPDIRECTORY
            CSIDL_DESKTOPDIRECTORY CSIDL_COMMON_STARTUP CSIDL_STARTUP
            CSIDL_COMMON_PROGRAMS CSIDL_PROGRAMS CSIDL_PROGRAM_FILES_COMMON
            CSIDL_PROGRAM_FILES CSIDL_FONTS""".split():
            if maybe == path_name:
                csidl = getattr(shellcon, maybe)
                return shell.SHGetSpecialFolderPath(0, csidl, False)
        raise ValueError("%s is an unknown path ID" % (path_name,))
```



Discussion:
I'm afraid you're going to need to create a pull request, because this _snippet_ is too big and some things seem to be repeated with our code.

This snippet checks if spyder is being installed from a bdist_wininst .exe archive on a Windows machine or something from else, eg pip. It checks for several attributes that are available when using bdist_wininst ie `file_created`, `create_shortcut()` and `get_special_folder_path()` and if it doesn't find them, creates dummy functions that do the same thing.

I understand now, but please do a pull request for this.

Could you also comment what's the exact problem that your snippet solves? Is it that you can't install Spyder on Windows using pip because of the lack of it? 

On Windows, if user installs spyder using pip, then tries to run the win_post_install script, to add the start menu shortcuts which are quite nice, especially for less savvy, terminal-phobic users which spyder is a good choice for (ie: MATLAB converts), the user will get a nasty traceback because some of the module level attributes are only available when this script is part of a bdist_wininst

Would it be okay to make [pywin32](http://sourceforge.net/projects/pywin32/) a depndency on Windows systems? maybe something else already depends on pywin32?

I'm afraid you're going to need to create a pull request, because this _snippet_ is too big and some things seem to be repeated with our code.

This snippet checks if spyder is being installed from a bdist_wininst .exe archive on a Windows machine or something from else, eg pip. It checks for several attributes that are available when using bdist_wininst ie `file_created`, `create_shortcut()` and `get_special_folder_path()` and if it doesn't find them, creates dummy functions that do the same thing.

I understand now, but please do a pull request for this.

Could you also comment what's the exact problem that your snippet solves? Is it that you can't install Spyder on Windows using pip because of the lack of it? 

On Windows, if user installs spyder using pip, then tries to run the win_post_install script, to add the start menu shortcuts which are quite nice, especially for less savvy, terminal-phobic users which spyder is a good choice for (ie: MATLAB converts), the user will get a nasty traceback because some of the module level attributes are only available when this script is part of a bdist_wininst

Would it be okay to make [pywin32](http://sourceforge.net/projects/pywin32/) a depndency on Windows systems? maybe something else already depends on pywin32?


Resolving patch:
diff --git a/scripts/spyder_win_post_install.py b/scripts/spyder_win_post_install.py
--- a/scripts/spyder_win_post_install.py
+++ b/scripts/spyder_win_post_install.py
@@ -21,17 +21,103 @@
 KEY_C1 = KEY_C0 + r"\command"
 
 
+# ability to run spyder-win-post-install outside of bdist_wininst installer
+# copied from pywin32-win-post-install.py
+# http://pywin32.hg.sourceforge.net/hgweb/pywin32/pywin32/file/default/pywin32_postinstall.py
+ver_string = "%d.%d" % (sys.version_info[0], sys.version_info[1])
+root_key_name = "Software\\Python\\PythonCore\\" + ver_string
+
+try:
+    # When this script is run from inside the bdist_wininst installer,
+    # file_created() and directory_created() are additional builtin
+    # functions which write lines to Python23\pywin32-install.log. This is
+    # a list of actions for the uninstaller, the format is inspired by what
+    # the Wise installer also creates.
+    # https://docs.python.org/2/distutils/builtdist.html#the-postinstallation-script
+    file_created
+    is_bdist_wininst = True
+except NameError:
+    is_bdist_wininst = False # we know what it is not - but not what it is :)
+    # file_created() and directory_created() functions do nothing if post install script
+    # isn't run from bdist_wininst installer, instead if shortcuts and start menu directory
+    # exist, they are removed when the post install script is called with the -remote option
+    def file_created(file):
+        pass
+    def directory_created(directory):
+        pass
+    def get_root_hkey():
+        try:
+            winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE,
+                           root_key_name, 0, winreg.KEY_CREATE_SUB_KEY)
+            return winreg.HKEY_LOCAL_MACHINE
+        except OSError, details:
+            # Either not exist, or no permissions to create subkey means
+            # must be HKCU
+            return winreg.HKEY_CURRENT_USER
+try:
+    create_shortcut
+except NameError:
+    # Create a function with the same signature as create_shortcut provided
+    # by bdist_wininst
+    def create_shortcut(path, description, filename,
+                        arguments="", workdir="", iconpath="", iconindex=0):
+        try:
+            import pythoncom
+        except ImportError:
+            print("PyWin32 is required to run windows postinstall manually.",
+                  file=sys.stderr)
+            sys.exit(1)  # pywin32 required
+        from win32com.shell import shell, shellcon
+
+        ilink = pythoncom.CoCreateInstance(shell.CLSID_ShellLink, None,
+                                           pythoncom.CLSCTX_INPROC_SERVER,
+                                           shell.IID_IShellLink)
+        ilink.SetPath(path)
+        ilink.SetDescription(description)
+        if arguments:
+            ilink.SetArguments(arguments)
+        if workdir:
+            ilink.SetWorkingDirectory(workdir)
+        if iconpath or iconindex:
+            ilink.SetIconLocation(iconpath, iconindex)
+        # now save it.
+        ipf = ilink.QueryInterface(pythoncom.IID_IPersistFile)
+        ipf.Save(filename, 0)
+
+    # Support the same list of "path names" as bdist_wininst.
+    def get_special_folder_path(path_name):
+        import pythoncom
+        from win32com.shell import shell, shellcon
+
+        for maybe in """
+            CSIDL_COMMON_STARTMENU CSIDL_STARTMENU CSIDL_COMMON_APPDATA
+            CSIDL_LOCAL_APPDATA CSIDL_APPDATA CSIDL_COMMON_DESKTOPDIRECTORY
+            CSIDL_DESKTOPDIRECTORY CSIDL_COMMON_STARTUP CSIDL_STARTUP
+            CSIDL_COMMON_PROGRAMS CSIDL_PROGRAMS CSIDL_PROGRAM_FILES_COMMON
+            CSIDL_PROGRAM_FILES CSIDL_FONTS""".split():
+            if maybe == path_name:
+                csidl = getattr(shellcon, maybe)
+                return shell.SHGetSpecialFolderPath(0, csidl, False)
+        raise ValueError("%s is an unknown path ID" % (path_name,))
+
+
 def install():
     """Function executed when running the script with the -install switch"""
     # Create Spyder start menu folder
-    start_menu = osp.join(get_special_folder_path('CSIDL_COMMON_PROGRAMS'),
+    # Don't use CSIDL_COMMON_PROGRAMS because it requres admin rights
+    # This is consistent with use of CSIDL_DESKTOPDIRECTORY below
+    # CSIDL_COMMON_PROGRAMS =
+    # C:\ProgramData\Microsoft\Windows\Start Menu\Programs
+    # CSIDL_PROGRAMS =
+    # C:\Users\<username>\AppData\Roaming\Microsoft\Windows\Start Menu\Programs
+    start_menu = osp.join(get_special_folder_path('CSIDL_PROGRAMS'),
                           'Spyder (Py%i.%i %i bit)' % (sys.version_info[0],
                                                        sys.version_info[1],
                                                        struct.calcsize('P')*8))
     if not osp.isdir(start_menu):
         os.mkdir(start_menu)
         directory_created(start_menu)
-    
+
     # Create Spyder start menu entries
     python = osp.abspath(osp.join(sys.prefix, 'python.exe'))
     pythonw = osp.abspath(osp.join(sys.prefix, 'pythonw.exe'))
@@ -40,6 +126,9 @@ def install():
     import distutils.sysconfig
     lib_dir = distutils.sysconfig.get_python_lib(plat_specific=1)
     ico_dir = osp.join(lib_dir, 'spyderlib', 'windows')
+    # if user is running -install manually then icons are in Scripts/
+    if not osp.isdir(ico_dir):
+        ico_dir = osp.dirname(osp.abspath(__file__))
 
     desc = 'Scientific Python Development EnvironmEnt, an alternative to IDLE'
     fname = osp.join(start_menu, 'Spyder (full).lnk')
@@ -67,7 +156,7 @@ def install():
     winreg.SetValueEx(winreg.CreateKey(root, KEY_C1 % ("NoCon", EWS)),
                       "", 0, winreg.REG_SZ,
                       '"%s" "%s\Scripts\spyder" "%%1"' % (pythonw, sys.prefix))
-    
+
     # Create desktop shortcut file
     desktop_folder = get_special_folder_path("CSIDL_DESKTOPDIRECTORY")
     fname = osp.join(desktop_folder, 'Spyder.lnk')
@@ -87,6 +176,46 @@ def remove():
             winreg.DeleteKey(root, key)
         except WindowsError:
             pass
+        else:
+            if not is_bdist_wininst:
+                print("Successfully removed Spyder shortcuts from Windows "\
+                      "Explorer context menu.", file=sys.stdout)
+    if not is_bdist_wininst:
+        # clean up desktop
+        desktop_folder = get_special_folder_path("CSIDL_DESKTOPDIRECTORY")
+        fname = osp.join(desktop_folder, 'Spyder.lnk')
+        if osp.isfile(fname):
+            try:
+                os.remove(fname)
+            except OSError:
+                print("Failed to remove %s; you may be able to remove it "\
+                      "manually." % fname, file=sys.stderr)
+            else:
+                print("Successfully removed Spyder shortcuts from your desktop.",
+                      file=sys.stdout)
+        # clean up startmenu
+        start_menu = osp.join(get_special_folder_path('CSIDL_PROGRAMS'),
+                              'Spyder (Py%i.%i %i bit)' % (sys.version_info[0],
+                                                           sys.version_info[1],
+                                                           struct.calcsize('P')*8))
+        if osp.isdir(start_menu):
+            for fname in os.listdir(start_menu):
+                try:
+                    os.remove(osp.join(start_menu,fname))
+                except OSError:
+                    print("Failed to remove %s; you may be able to remove it "\
+                          "manually." % fname, file=sys.stderr)
+                else:
+                    print("Successfully removed Spyder shortcuts from your "\
+                          " start menu.", file=sys.stdout)
+            try:
+                os.rmdir(start_menu)
+            except OSError:
+                print("Failed to remove %s; you may be able to remove it "\
+                      "manually." % fname, file=sys.stderr)
+            else:
+                print("Successfully removed Spyder shortcut folder from your "\
+                      " start menu.", file=sys.stdout)
 
 
 if __name__=='__main__':
@@ -95,8 +224,7 @@ def remove():
             try:
                 install()
             except OSError:
-                print("Failed to create Start Menu items, try running "\
-                      "installer as administrator.", file=sys.stderr)
+                print("Failed to create Start Menu items.", file=sys.stderr)
         elif sys.argv[1] == '-remove':
             remove()
         else:


Test patch: