]> granicus.if.org Git - python/commitdiff
#2581: Vista UAC/elevation support for bdist_wininst
authorMark Hammond <mhammond@skippinet.com.au>
Fri, 2 May 2008 12:48:15 +0000 (12:48 +0000)
committerMark Hammond <mhammond@skippinet.com.au>
Fri, 2 May 2008 12:48:15 +0000 (12:48 +0000)
Doc/distutils/builtdist.rst
Lib/distutils/command/bdist_wininst.py
Lib/distutils/command/wininst-6.0.exe
Lib/distutils/command/wininst-7.1.exe
Lib/distutils/command/wininst-9.0-amd64.exe
Lib/distutils/command/wininst-9.0.exe
Lib/distutils/msvc9compiler.py
Misc/NEWS
PC/bdist_wininst/install.c
PC/bdist_wininst/wininst-7.1.vcproj
PC/bdist_wininst/wininst.dsp

index cd2bd817b423a2005d1d8c8f72b49f576ec2c764..c4b8dbf3f46f1d276c84c650f9060b571e6a2935 100644 (file)
@@ -426,6 +426,13 @@ built-in functions in the installation script.
    also the configuration.  For details refer to Microsoft's documentation of the
    :cfunc:`SHGetSpecialFolderPath` function.
 
+Vista User Access Control (UAC)
+===============================
+
+Starting with Python 2.6, bdist_wininst supports a :option:`--user-access-control`
+option.  The default is 'none' (meaning no UAC handling is done), and other
+valid values are 'auto' (meaning prompt for UAC elevation if Python was
+installed for all users) and 'force' (meaning always prompt for elevation)
 
 .. function:: create_shortcut(target, description, filename[, arguments[, workdir[, iconpath[, iconindex]]]])
 
@@ -437,5 +444,3 @@ built-in functions in the installation script.
    and *iconindex* is the index of the icon in the file *iconpath*.  Again, for
    details consult the Microsoft documentation for the :class:`IShellLink`
    interface.
-
-
index 02542afbd3fcd4141ddde9fb02962516399770ee..7c43e7459ec39f2b1ca8a9a9e7f1025c1d64ee18 100644 (file)
@@ -50,6 +50,10 @@ class bdist_wininst (Command):
                      "Fully qualified filename of a script to be run before "
                      "any files are installed.  This script need not be in the "
                      "distribution"),
+                    ('user-access-control=', None,
+                     "specify Vista's UAC handling - 'none'/default=no "
+                     "handling, 'auto'=use UAC if target Python installed for "
+                     "all users, 'force'=always use UAC"),
                    ]
 
     boolean_options = ['keep-temp', 'no-target-compile', 'no-target-optimize',
@@ -68,6 +72,7 @@ class bdist_wininst (Command):
         self.skip_build = 0
         self.install_script = None
         self.pre_install_script = None
+        self.user_access_control = None
 
     # initialize_options()
 
@@ -220,6 +225,8 @@ class bdist_wininst (Command):
         lines.append("target_optimize=%d" % (not self.no_target_optimize))
         if self.target_version:
             lines.append("target_version=%s" % self.target_version)
+        if self.user_access_control:
+            lines.append("user_access_control=%s" % self.user_access_control)
 
         title = self.title or self.distribution.get_fullname()
         lines.append("title=%s" % escape(title))
index bd715250e780b37e94996e33820070987bcc77fd..10c981993ba2c2e3a977db8ded201a46d529749d 100644 (file)
Binary files a/Lib/distutils/command/wininst-6.0.exe and b/Lib/distutils/command/wininst-6.0.exe differ
index ee35713940177c0eb8e3cf0435d7a6a9287a27fc..6779aa8d4c1b0d5fb37255f1eea211d92ea6942a 100644 (file)
Binary files a/Lib/distutils/command/wininst-7.1.exe and b/Lib/distutils/command/wininst-7.1.exe differ
index c99ede4b3fcebe39b1a1e33283307e12c42b7b2e..b4cb062c391d230dbf4a7fc3a16536aa3dbc5b42 100644 (file)
Binary files a/Lib/distutils/command/wininst-9.0-amd64.exe and b/Lib/distutils/command/wininst-9.0-amd64.exe differ
index 5e0144c92b58ea750211c540d15667e0b1d1a356..0d04a6678b94b8ecbdde10257396e8c0e85c038c 100644 (file)
Binary files a/Lib/distutils/command/wininst-9.0.exe and b/Lib/distutils/command/wininst-9.0.exe differ
index 8b1cf9a9109cd295760425a739d3c57e9d4c43f2..c8d52c423754d7ef6316085c49b310e54923e4f6 100644 (file)
@@ -594,14 +594,25 @@ class MSVCCompiler(CCompiler) :
             # needed! Make sure they are generated in the temporary build
             # directory. Since they have different names for debug and release
             # builds, they can go into the same directory.
+            build_temp = os.path.dirname(objects[0])
             if export_symbols is not None:
                 (dll_name, dll_ext) = os.path.splitext(
                     os.path.basename(output_filename))
                 implib_file = os.path.join(
-                    os.path.dirname(objects[0]),
+                    build_temp,
                     self.library_filename(dll_name))
                 ld_args.append ('/IMPLIB:' + implib_file)
 
+            # Embedded manifests are recommended - see MSDN article titled
+            # "How to: Embed a Manifest Inside a C/C++ Application"
+            # (currently at http://msdn2.microsoft.com/en-us/library/ms235591(VS.80).aspx)
+            # Ask the linker to generate the manifest in the temp dir, so
+            # we can embed it later.
+            temp_manifest = os.path.join(
+                    build_temp,
+                    os.path.basename(output_filename) + ".manifest")
+            ld_args.append('/MANIFESTFILE:' + temp_manifest)
+
             if extra_preargs:
                 ld_args[:0] = extra_preargs
             if extra_postargs:
@@ -613,6 +624,18 @@ class MSVCCompiler(CCompiler) :
             except DistutilsExecError as msg:
                 raise LinkError(msg)
 
+            # embed the manifest
+            # XXX - this is somewhat fragile - if mt.exe fails, distutils
+            # will still consider the DLL up-to-date, but it will not have a
+            # manifest.  Maybe we should link to a temp file?  OTOH, that
+            # implies a build environment error that shouldn't go undetected.
+            mfid = 1 if target_desc == CCompiler.EXECUTABLE else 2
+            out_arg = '-outputresource:%s;%s' % (output_filename, mfid)
+            try:
+                self.spawn(['mt.exe', '-nologo', '-manifest',
+                            temp_manifest, out_arg])
+            except DistutilsExecError as msg:
+                raise LinkError(msg)
         else:
             log.debug("skipping %s (up-to-date)", output_filename)
 
index d94d6555cd61b0472605db1635d14a28e280f304..19b80e25f3295ee629360861e27c7d63001880e7 100644 (file)
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -47,6 +47,8 @@ Extension Modules
 Library
 -------
 
+- Issue #2581: distutils: Vista UAC/elevation support for bdist_wininst
+
 - Issue #2635: Fix bug in 'fix_sentence_endings' textwrap.fill option,
   where an extra space was added after a word containing (but not
   ending in) '.', '!' or '?'.
index 0ce2371a771d8dedde2df300fd7828a2ac94233f..a4f48654fa57df035a82b992b0d5ec3f6664c0e6 100644 (file)
@@ -133,6 +133,7 @@ char meta_name[80];         /* package name without version like
 char install_script[MAX_PATH];
 char *pre_install_script; /* run before we install a single file */
 
+char user_access_control[10]; // one of 'auto', 'force', otherwise none.
 
 int py_major, py_minor;                /* Python version selected for installation */
 
@@ -344,8 +345,15 @@ struct PyMethodDef {
 };
 typedef struct PyMethodDef PyMethodDef;
 
+// XXX - all of these are potentially fragile!  We load and unload
+// the Python DLL multiple times - so storing functions pointers 
+// is dangerous (although things *look* OK at present)
+// Better might be to roll prepare_script_environment() into
+// LoadPythonDll(), and create a new UnloadPythonDLL() which also
+// clears the global pointers.
 void *(*g_Py_BuildValue)(char *, ...);
 int (*g_PyArg_ParseTuple)(PyObject *, char *, ...);
+PyObject * (*g_PyLong_FromVoidPtr)(void *);
 
 PyObject *g_PyExc_ValueError;
 PyObject *g_PyExc_OSError;
@@ -597,7 +605,7 @@ static PyObject *PyMessageBox(PyObject *self, PyObject *args)
 
 static PyObject *GetRootHKey(PyObject *self)
 {
-       return g_Py_BuildValue("l", hkey_root);
+       return g_PyLong_FromVoidPtr(hkey_root);
 }
 
 #define METH_VARARGS 0x0001
@@ -631,7 +639,9 @@ static HINSTANCE LoadPythonDll(char *fname)
                 "SOFTWARE\\Python\\PythonCore\\%d.%d\\InstallPath",
                 py_major, py_minor);
        if (ERROR_SUCCESS != RegQueryValue(HKEY_CURRENT_USER, subkey_name,
-                                          fullpath, &size))
+                                          fullpath, &size) &&
+           ERROR_SUCCESS != RegQueryValue(HKEY_LOCAL_MACHINE, subkey_name,
+                                          fullpath, &size))
                return NULL;
        strcat(fullpath, "\\");
        strcat(fullpath, fname);
@@ -648,6 +658,7 @@ static int prepare_script_environment(HINSTANCE hPython)
        DECLPROC(hPython, PyObject *, Py_BuildValue, (char *, ...));
        DECLPROC(hPython, int, PyArg_ParseTuple, (PyObject *, char *, ...));
        DECLPROC(hPython, PyObject *, PyErr_Format, (PyObject *, char *));
+       DECLPROC(hPython, PyObject *, PyLong_FromVoidPtr, (void *));
        if (!PyImport_ImportModule || !PyObject_GetAttrString || 
            !PyObject_SetAttrString || !PyCFunction_New)
                return 1;
@@ -667,6 +678,7 @@ static int prepare_script_environment(HINSTANCE hPython)
        g_Py_BuildValue = Py_BuildValue;
        g_PyArg_ParseTuple = PyArg_ParseTuple;
        g_PyErr_Format = PyErr_Format;
+       g_PyLong_FromVoidPtr = PyLong_FromVoidPtr;
 
        return 0;
 }
@@ -777,7 +789,9 @@ static int run_simple_script(char *script)
 
        hPython = LoadPythonDll(pythondll);
        if (!hPython) {
-               set_failure_reason("Can't load Python for pre-install script");
+               char reason[128];
+               wsprintf(reason, "Can't load Python for pre-install script (%d)", GetLastError());
+               set_failure_reason(reason);
                return -1;
        }
        rc = do_run_simple_script(hPython, script);
@@ -2073,6 +2087,71 @@ void RunWizard(HWND hwnd)
                PropertySheet(&psh);
 }
 
+// subtly different from HasLocalMachinePrivs(), in that after executing
+// an 'elevated' process, we expect this to return TRUE - but there is no
+// such implication for HasLocalMachinePrivs
+BOOL MyIsUserAnAdmin()
+{
+       typedef BOOL (WINAPI *PFNIsUserAnAdmin)();
+       static PFNIsUserAnAdmin pfnIsUserAnAdmin = NULL;
+       HMODULE shell32;
+       // This function isn't guaranteed to be available (and it can't hurt 
+       // to leave the library loaded)
+       if (0 == (shell32=LoadLibrary("shell32.dll")))
+               return FALSE;
+       if (0 == (pfnIsUserAnAdmin=(PFNIsUserAnAdmin)GetProcAddress(shell32, "IsUserAnAdmin")))
+               return FALSE;
+       return (*pfnIsUserAnAdmin)();
+}
+
+// Some magic for Vista's UAC.  If there is a target_version, and
+// if that target version is installed in the registry under
+// HKLM, and we are not current administrator, then
+// re-execute ourselves requesting elevation.
+// Split into 2 functions - "should we elevate" and "spawn elevated"
+
+// Returns TRUE if we should spawn an elevated child
+BOOL NeedAutoUAC()
+{
+       HKEY hk;
+       char key_name[80];
+       OSVERSIONINFO winverinfo;
+       winverinfo.dwOSVersionInfoSize = sizeof(winverinfo);
+       // If less than XP, then we can't do it (and its not necessary).
+       if (!GetVersionEx(&winverinfo) || winverinfo.dwMajorVersion < 5)
+               return FALSE;
+       // no Python version info == we can't know yet.
+       if (target_version[0] == '\0')
+               return FALSE;
+       // see how python is current installed
+       wsprintf(key_name,
+                        "Software\\Python\\PythonCore\\%s\\InstallPath",
+                        target_version);
+       if (ERROR_SUCCESS != RegOpenKeyEx(HKEY_LOCAL_MACHINE, 
+                                         key_name, 0, KEY_READ, &hk))
+               return FALSE;
+       RegCloseKey(hk);
+       // Python is installed in HKLM - we must elevate.
+       return TRUE;
+}
+
+// Spawn ourself as an elevated application.  On failure, a message is
+// displayed to the user - but this app will always terminate, even
+// on error.
+void SpawnUAC()
+{
+       // interesting failure scenario that has been seen: initial executable
+       // runs from a network drive - but once elevated, that network share
+       // isn't seen, and ShellExecute fails with SE_ERR_ACCESSDENIED.
+       int ret = (int)ShellExecute(0, "runas", modulename, "", NULL, 
+                                   SW_SHOWNORMAL);
+       if (ret <= 32) {
+               char msg[128];
+               wsprintf(msg, "Failed to start elevated process (ShellExecute returned %d)", ret);
+               MessageBox(0, msg, "Setup", MB_OK | MB_ICONERROR);
+       }
+}
+
 int DoInstall(void)
 {
        char ini_buffer[4096];
@@ -2106,6 +2185,31 @@ int DoInstall(void)
                                 install_script, sizeof(install_script),
                                 ini_file);
 
+       GetPrivateProfileString("Setup", "user_access_control", "",
+                                user_access_control, sizeof(user_access_control), ini_file);
+
+       // See if we need to do the Vista UAC magic.
+       if (strcmp(user_access_control, "force")==0) {
+               if (!MyIsUserAnAdmin()) {
+                       SpawnUAC();
+                       return 0;
+               }
+               // already admin - keep going
+       } else if (strcmp(user_access_control, "auto")==0) {
+               // Check if it looks like we need UAC control, based
+               // on how Python itself was installed.
+               if (!MyIsUserAnAdmin() && NeedAutoUAC()) {
+                       SpawnUAC();
+                       return 0;
+               }
+       } else {
+               // display a warning about unknown values - only the developer
+               // of the extension will see it (until they fix it!)
+               if (user_access_control[0] && strcmp(user_access_control, "none") != 0) {
+                       MessageBox(GetFocus(), "Bad user_access_control value", "oops", MB_OK);
+               // nothing to do.
+               }
+       }
 
        hwndMain = CreateBackground(title);
 
index b73cf34a8ed863f9f3dd527d7e89a59f73a4be71..1ce2bf1bb8e8f6d67b454f223ec4bb75e1a32b0e 100644 (file)
@@ -24,7 +24,7 @@
                                Name="VCCLCompilerTool"
                                Optimization="1"
                                InlineFunctionExpansion="1"
-                               AdditionalIncludeDirectories="..\..\Include,..\..\..\zlib-1.2.1"
+                               AdditionalIncludeDirectories="..\..\Include,..\..\..\zlib-1.2.3"
                                PreprocessorDefinitions="WIN32;NDEBUG;_WINDOWS"
                                StringPooling="TRUE"
                                RuntimeLibrary="2"
@@ -41,7 +41,7 @@
                                Name="VCCustomBuildTool"/>
                        <Tool
                                Name="VCLinkerTool"
-                               AdditionalDependencies="..\..\..\zlib-1.2.1\zlib.lib imagehlp.lib comctl32.lib"
+                               AdditionalDependencies="..\..\..\zlib-1.2.3\zlib.lib imagehlp.lib comctl32.lib"
                                OutputFile="..\..\lib\distutils\command/wininst-7.1.exe"
                                LinkIncremental="1"
                                SuppressStartupBanner="TRUE"
index 23a6591d4099542cd0fb181bda0e68a0f1ddc69e..38be55a9f697cc9b220dcac42ccc4fa802824491 100644 (file)
@@ -43,7 +43,7 @@ RSC=rc.exe
 # PROP Ignore_Export_Lib 0
 # PROP Target_Dir ""
 # ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /YX /FD /c
-# ADD CPP /nologo /MD /W3 /O1 /I "..\..\Include" /I "..\..\..\zlib-1.2.1" /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /YX /FD /c
+# ADD CPP /nologo /MD /W3 /O1 /I "..\..\Include" /I "..\..\..\zlib-1.2.3" /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /YX /FD /c
 # ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32
 # ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
 # ADD BASE RSC /l 0x407 /d "NDEBUG"
@@ -53,7 +53,7 @@ BSC32=bscmake.exe
 # ADD BSC32 /nologo
 LINK32=link.exe
 # ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /machine:I386
-# ADD LINK32 ..\..\..\zlib-1.2.1\zlib.lib imagehlp.lib comdlg32.lib ole32.lib comctl32.lib kernel32.lib user32.lib gdi32.lib advapi32.lib shell32.lib /nologo /subsystem:windows /machine:I386 /nodefaultlib:"LIBC" /out:"..\..\lib\distutils\command/wininst-6.exe"
+# ADD LINK32 ..\..\..\zlib-1.2.3\zlib.lib imagehlp.lib comdlg32.lib ole32.lib comctl32.lib kernel32.lib user32.lib gdi32.lib advapi32.lib shell32.lib /nologo /subsystem:windows /machine:I386 /nodefaultlib:"LIBC" /out:"..\..\lib\distutils\command/wininst-6.0.exe"
 
 !ELSEIF  "$(CFG)" == "wininst - Win32 Debug"
 
@@ -79,7 +79,7 @@ BSC32=bscmake.exe
 # ADD BSC32 /nologo
 LINK32=link.exe
 # ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /debug /machine:I386 /pdbtype:sept
-# ADD LINK32 ..\..\..\zlib-1.2.1\zlib.lib imagehlp.lib comdlg32.lib ole32.lib comctl32.lib kernel32.lib user32.lib gdi32.lib advapi32.lib shell32.lib /nologo /subsystem:windows /pdb:none /debug /machine:I386 /nodefaultlib:"LIBC" /out:"..\..\lib\distutils\command/wininst-6_d.exe"
+# ADD LINK32 ..\..\..\zlib-1.2.3\zlib.lib imagehlp.lib comdlg32.lib ole32.lib comctl32.lib kernel32.lib user32.lib gdi32.lib advapi32.lib shell32.lib /nologo /subsystem:windows /pdb:none /debug /machine:I386 /nodefaultlib:"LIBC" /out:"..\..\lib\distutils\command/wininst-6.0_d.exe"
 
 !ENDIF