diff --git a/Doc/distutils/builtdist.rst b/Doc/distutils/builtdist.rst index cd2bd817b42..c4b8dbf3f46 100644 --- a/Doc/distutils/builtdist.rst +++ b/Doc/distutils/builtdist.rst @@ -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. - - diff --git a/Lib/distutils/command/bdist_wininst.py b/Lib/distutils/command/bdist_wininst.py index 02542afbd3f..7c43e7459ec 100644 --- a/Lib/distutils/command/bdist_wininst.py +++ b/Lib/distutils/command/bdist_wininst.py @@ -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)) diff --git a/Lib/distutils/command/wininst-6.0.exe b/Lib/distutils/command/wininst-6.0.exe index bd715250e78..10c981993ba 100644 Binary files a/Lib/distutils/command/wininst-6.0.exe and b/Lib/distutils/command/wininst-6.0.exe differ diff --git a/Lib/distutils/command/wininst-7.1.exe b/Lib/distutils/command/wininst-7.1.exe index ee357139401..6779aa8d4c1 100644 Binary files a/Lib/distutils/command/wininst-7.1.exe and b/Lib/distutils/command/wininst-7.1.exe differ diff --git a/Lib/distutils/command/wininst-9.0-amd64.exe b/Lib/distutils/command/wininst-9.0-amd64.exe index c99ede4b3fc..b4cb062c391 100644 Binary files a/Lib/distutils/command/wininst-9.0-amd64.exe and b/Lib/distutils/command/wininst-9.0-amd64.exe differ diff --git a/Lib/distutils/command/wininst-9.0.exe b/Lib/distutils/command/wininst-9.0.exe index 5e0144c92b5..0d04a6678b9 100644 Binary files a/Lib/distutils/command/wininst-9.0.exe and b/Lib/distutils/command/wininst-9.0.exe differ diff --git a/Lib/distutils/msvc9compiler.py b/Lib/distutils/msvc9compiler.py index 8b1cf9a9109..c8d52c42375 100644 --- a/Lib/distutils/msvc9compiler.py +++ b/Lib/distutils/msvc9compiler.py @@ -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) diff --git a/Misc/NEWS b/Misc/NEWS index d94d6555cd6..19b80e25f32 100644 --- 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 '?'. diff --git a/PC/bdist_wininst/install.c b/PC/bdist_wininst/install.c index 0ce2371a771..a4f48654fa5 100644 --- a/PC/bdist_wininst/install.c +++ b/PC/bdist_wininst/install.c @@ -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); diff --git a/PC/bdist_wininst/wininst-7.1.vcproj b/PC/bdist_wininst/wininst-7.1.vcproj index b73cf34a8ed..1ce2bf1bb8e 100644 --- a/PC/bdist_wininst/wininst-7.1.vcproj +++ b/PC/bdist_wininst/wininst-7.1.vcproj @@ -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"/>