#2581: Vista UAC/elevation support for bdist_wininst

This commit is contained in:
Mark Hammond 2008-05-02 12:48:15 +00:00
parent f8cc64017c
commit 7c5c8e6823
11 changed files with 152 additions and 11 deletions

View 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.

View 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))

View 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)

View File

@ -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 '?'.

View 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);

View 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"

View 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