Skip to content

Commit aa96c23

Browse files
author
mark.hammond
committed
#2581: Vista UAC/elevation support for bdist_wininst
git-svn-id: http://svn.python.org/projects/python/trunk@62636 6015fed2-1504-0410-9fe1-9d1591cc4771
1 parent 1e942b5 commit aa96c23

11 files changed

Lines changed: 152 additions & 11 deletions

File tree

Doc/distutils/builtdist.rst

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -426,6 +426,13 @@ built-in functions in the installation script.
426426
also the configuration. For details refer to Microsoft's documentation of the
427427
:cfunc:`SHGetSpecialFolderPath` function.
428428

429+
Vista User Access Control (UAC)
430+
===============================
431+
432+
Starting with Python 2.6, bdist_wininst supports a :option:`--user-access-control`
433+
option. The default is 'none' (meaning no UAC handling is done), and other
434+
valid values are 'auto' (meaning prompt for UAC elevation if Python was
435+
installed for all users) and 'force' (meaning always prompt for elevation)
429436

430437
.. function:: create_shortcut(target, description, filename[, arguments[, workdir[, iconpath[, iconindex]]]])
431438

@@ -437,5 +444,3 @@ built-in functions in the installation script.
437444
and *iconindex* is the index of the icon in the file *iconpath*. Again, for
438445
details consult the Microsoft documentation for the :class:`IShellLink`
439446
interface.
440-
441-

Lib/distutils/command/bdist_wininst.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,10 @@ class bdist_wininst (Command):
5050
"Fully qualified filename of a script to be run before "
5151
"any files are installed. This script need not be in the "
5252
"distribution"),
53+
('user-access-control=', None,
54+
"specify Vista's UAC handling - 'none'/default=no "
55+
"handling, 'auto'=use UAC if target Python installed for "
56+
"all users, 'force'=always use UAC"),
5357
]
5458

5559
boolean_options = ['keep-temp', 'no-target-compile', 'no-target-optimize',
@@ -68,6 +72,7 @@ def initialize_options (self):
6872
self.skip_build = 0
6973
self.install_script = None
7074
self.pre_install_script = None
75+
self.user_access_control = None
7176

7277
# initialize_options()
7378

@@ -220,6 +225,8 @@ def escape(s):
220225
lines.append("target_optimize=%d" % (not self.no_target_optimize))
221226
if self.target_version:
222227
lines.append("target_version=%s" % self.target_version)
228+
if self.user_access_control:
229+
lines.append("user_access_control=%s" % self.user_access_control)
223230

224231
title = self.title or self.distribution.get_fullname()
225232
lines.append("title=%s" % escape(title))
0 Bytes
Binary file not shown.
0 Bytes
Binary file not shown.
1 KB
Binary file not shown.
512 Bytes
Binary file not shown.

Lib/distutils/msvc9compiler.py

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -594,14 +594,25 @@ def link(self,
594594
# needed! Make sure they are generated in the temporary build
595595
# directory. Since they have different names for debug and release
596596
# builds, they can go into the same directory.
597+
build_temp = os.path.dirname(objects[0])
597598
if export_symbols is not None:
598599
(dll_name, dll_ext) = os.path.splitext(
599600
os.path.basename(output_filename))
600601
implib_file = os.path.join(
601-
os.path.dirname(objects[0]),
602+
build_temp,
602603
self.library_filename(dll_name))
603604
ld_args.append ('/IMPLIB:' + implib_file)
604605

606+
# Embedded manifests are recommended - see MSDN article titled
607+
# "How to: Embed a Manifest Inside a C/C++ Application"
608+
# (currently at http://msdn2.microsoft.com/en-us/library/ms235591(VS.80).aspx)
609+
# Ask the linker to generate the manifest in the temp dir, so
610+
# we can embed it later.
611+
temp_manifest = os.path.join(
612+
build_temp,
613+
os.path.basename(output_filename) + ".manifest")
614+
ld_args.append('/MANIFESTFILE:' + temp_manifest)
615+
605616
if extra_preargs:
606617
ld_args[:0] = extra_preargs
607618
if extra_postargs:
@@ -613,6 +624,18 @@ def link(self,
613624
except DistutilsExecError as msg:
614625
raise LinkError(msg)
615626

627+
# embed the manifest
628+
# XXX - this is somewhat fragile - if mt.exe fails, distutils
629+
# will still consider the DLL up-to-date, but it will not have a
630+
# manifest. Maybe we should link to a temp file? OTOH, that
631+
# implies a build environment error that shouldn't go undetected.
632+
mfid = 1 if target_desc == CCompiler.EXECUTABLE else 2
633+
out_arg = '-outputresource:%s;%s' % (output_filename, mfid)
634+
try:
635+
self.spawn(['mt.exe', '-nologo', '-manifest',
636+
temp_manifest, out_arg])
637+
except DistutilsExecError as msg:
638+
raise LinkError(msg)
616639
else:
617640
log.debug("skipping %s (up-to-date)", output_filename)
618641

Misc/NEWS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ Extension Modules
4747
Library
4848
-------
4949

50+
- Issue #2581: distutils: Vista UAC/elevation support for bdist_wininst
51+
5052
- Issue #2635: Fix bug in 'fix_sentence_endings' textwrap.fill option,
5153
where an extra space was added after a word containing (but not
5254
ending in) '.', '!' or '?'.

PC/bdist_wininst/install.c

Lines changed: 107 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ char meta_name[80]; /* package name without version like
133133
char install_script[MAX_PATH];
134134
char *pre_install_script; /* run before we install a single file */
135135

136+
char user_access_control[10]; // one of 'auto', 'force', otherwise none.
136137

137138
int py_major, py_minor; /* Python version selected for installation */
138139

@@ -344,8 +345,15 @@ struct PyMethodDef {
344345
};
345346
typedef struct PyMethodDef PyMethodDef;
346347

348+
// XXX - all of these are potentially fragile! We load and unload
349+
// the Python DLL multiple times - so storing functions pointers
350+
// is dangerous (although things *look* OK at present)
351+
// Better might be to roll prepare_script_environment() into
352+
// LoadPythonDll(), and create a new UnloadPythonDLL() which also
353+
// clears the global pointers.
347354
void *(*g_Py_BuildValue)(char *, ...);
348355
int (*g_PyArg_ParseTuple)(PyObject *, char *, ...);
356+
PyObject * (*g_PyLong_FromVoidPtr)(void *);
349357

350358
PyObject *g_PyExc_ValueError;
351359
PyObject *g_PyExc_OSError;
@@ -597,7 +605,7 @@ static PyObject *PyMessageBox(PyObject *self, PyObject *args)
597605

598606
static PyObject *GetRootHKey(PyObject *self)
599607
{
600-
return g_Py_BuildValue("l", hkey_root);
608+
return g_PyLong_FromVoidPtr(hkey_root);
601609
}
602610

603611
#define METH_VARARGS 0x0001
@@ -631,7 +639,9 @@ static HINSTANCE LoadPythonDll(char *fname)
631639
"SOFTWARE\\Python\\PythonCore\\%d.%d\\InstallPath",
632640
py_major, py_minor);
633641
if (ERROR_SUCCESS != RegQueryValue(HKEY_CURRENT_USER, subkey_name,
634-
fullpath, &size))
642+
fullpath, &size) &&
643+
ERROR_SUCCESS != RegQueryValue(HKEY_LOCAL_MACHINE, subkey_name,
644+
fullpath, &size))
635645
return NULL;
636646
strcat(fullpath, "\\");
637647
strcat(fullpath, fname);
@@ -648,6 +658,7 @@ static int prepare_script_environment(HINSTANCE hPython)
648658
DECLPROC(hPython, PyObject *, Py_BuildValue, (char *, ...));
649659
DECLPROC(hPython, int, PyArg_ParseTuple, (PyObject *, char *, ...));
650660
DECLPROC(hPython, PyObject *, PyErr_Format, (PyObject *, char *));
661+
DECLPROC(hPython, PyObject *, PyLong_FromVoidPtr, (void *));
651662
if (!PyImport_ImportModule || !PyObject_GetAttrString ||
652663
!PyObject_SetAttrString || !PyCFunction_New)
653664
return 1;
@@ -667,6 +678,7 @@ static int prepare_script_environment(HINSTANCE hPython)
667678
g_Py_BuildValue = Py_BuildValue;
668679
g_PyArg_ParseTuple = PyArg_ParseTuple;
669680
g_PyErr_Format = PyErr_Format;
681+
g_PyLong_FromVoidPtr = PyLong_FromVoidPtr;
670682

671683
return 0;
672684
}
@@ -777,7 +789,9 @@ static int run_simple_script(char *script)
777789

778790
hPython = LoadPythonDll(pythondll);
779791
if (!hPython) {
780-
set_failure_reason("Can't load Python for pre-install script");
792+
char reason[128];
793+
wsprintf(reason, "Can't load Python for pre-install script (%d)", GetLastError());
794+
set_failure_reason(reason);
781795
return -1;
782796
}
783797
rc = do_run_simple_script(hPython, script);
@@ -2073,6 +2087,71 @@ void RunWizard(HWND hwnd)
20732087
PropertySheet(&psh);
20742088
}
20752089

2090+
// subtly different from HasLocalMachinePrivs(), in that after executing
2091+
// an 'elevated' process, we expect this to return TRUE - but there is no
2092+
// such implication for HasLocalMachinePrivs
2093+
BOOL MyIsUserAnAdmin()
2094+
{
2095+
typedef BOOL (WINAPI *PFNIsUserAnAdmin)();
2096+
static PFNIsUserAnAdmin pfnIsUserAnAdmin = NULL;
2097+
HMODULE shell32;
2098+
// This function isn't guaranteed to be available (and it can't hurt
2099+
// to leave the library loaded)
2100+
if (0 == (shell32=LoadLibrary("shell32.dll")))
2101+
return FALSE;
2102+
if (0 == (pfnIsUserAnAdmin=(PFNIsUserAnAdmin)GetProcAddress(shell32, "IsUserAnAdmin")))
2103+
return FALSE;
2104+
return (*pfnIsUserAnAdmin)();
2105+
}
2106+
2107+
// Some magic for Vista's UAC. If there is a target_version, and
2108+
// if that target version is installed in the registry under
2109+
// HKLM, and we are not current administrator, then
2110+
// re-execute ourselves requesting elevation.
2111+
// Split into 2 functions - "should we elevate" and "spawn elevated"
2112+
2113+
// Returns TRUE if we should spawn an elevated child
2114+
BOOL NeedAutoUAC()
2115+
{
2116+
HKEY hk;
2117+
char key_name[80];
2118+
OSVERSIONINFO winverinfo;
2119+
winverinfo.dwOSVersionInfoSize = sizeof(winverinfo);
2120+
// If less than XP, then we can't do it (and its not necessary).
2121+
if (!GetVersionEx(&winverinfo) || winverinfo.dwMajorVersion < 5)
2122+
return FALSE;
2123+
// no Python version info == we can't know yet.
2124+
if (target_version[0] == '\0')
2125+
return FALSE;
2126+
// see how python is current installed
2127+
wsprintf(key_name,
2128+
"Software\\Python\\PythonCore\\%s\\InstallPath",
2129+
target_version);
2130+
if (ERROR_SUCCESS != RegOpenKeyEx(HKEY_LOCAL_MACHINE,
2131+
key_name, 0, KEY_READ, &hk))
2132+
return FALSE;
2133+
RegCloseKey(hk);
2134+
// Python is installed in HKLM - we must elevate.
2135+
return TRUE;
2136+
}
2137+
2138+
// Spawn ourself as an elevated application. On failure, a message is
2139+
// displayed to the user - but this app will always terminate, even
2140+
// on error.
2141+
void SpawnUAC()
2142+
{
2143+
// interesting failure scenario that has been seen: initial executable
2144+
// runs from a network drive - but once elevated, that network share
2145+
// isn't seen, and ShellExecute fails with SE_ERR_ACCESSDENIED.
2146+
int ret = (int)ShellExecute(0, "runas", modulename, "", NULL,
2147+
SW_SHOWNORMAL);
2148+
if (ret <= 32) {
2149+
char msg[128];
2150+
wsprintf(msg, "Failed to start elevated process (ShellExecute returned %d)", ret);
2151+
MessageBox(0, msg, "Setup", MB_OK | MB_ICONERROR);
2152+
}
2153+
}
2154+
20762155
int DoInstall(void)
20772156
{
20782157
char ini_buffer[4096];
@@ -2106,6 +2185,31 @@ int DoInstall(void)
21062185
install_script, sizeof(install_script),
21072186
ini_file);
21082187

2188+
GetPrivateProfileString("Setup", "user_access_control", "",
2189+
user_access_control, sizeof(user_access_control), ini_file);
2190+
2191+
// See if we need to do the Vista UAC magic.
2192+
if (strcmp(user_access_control, "force")==0) {
2193+
if (!MyIsUserAnAdmin()) {
2194+
SpawnUAC();
2195+
return 0;
2196+
}
2197+
// already admin - keep going
2198+
} else if (strcmp(user_access_control, "auto")==0) {
2199+
// Check if it looks like we need UAC control, based
2200+
// on how Python itself was installed.
2201+
if (!MyIsUserAnAdmin() && NeedAutoUAC()) {
2202+
SpawnUAC();
2203+
return 0;
2204+
}
2205+
} else {
2206+
// display a warning about unknown values - only the developer
2207+
// of the extension will see it (until they fix it!)
2208+
if (user_access_control[0] && strcmp(user_access_control, "none") != 0) {
2209+
MessageBox(GetFocus(), "Bad user_access_control value", "oops", MB_OK);
2210+
// nothing to do.
2211+
}
2212+
}
21092213

21102214
hwndMain = CreateBackground(title);
21112215

PC/bdist_wininst/wininst-7.1.vcproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
Name="VCCLCompilerTool"
2525
Optimization="1"
2626
InlineFunctionExpansion="1"
27-
AdditionalIncludeDirectories="..\..\Include,..\..\..\zlib-1.2.1"
27+
AdditionalIncludeDirectories="..\..\Include,..\..\..\zlib-1.2.3"
2828
PreprocessorDefinitions="WIN32;NDEBUG;_WINDOWS"
2929
StringPooling="TRUE"
3030
RuntimeLibrary="2"
@@ -41,7 +41,7 @@
4141
Name="VCCustomBuildTool"/>
4242
<Tool
4343
Name="VCLinkerTool"
44-
AdditionalDependencies="..\..\..\zlib-1.2.1\zlib.lib imagehlp.lib comctl32.lib"
44+
AdditionalDependencies="..\..\..\zlib-1.2.3\zlib.lib imagehlp.lib comctl32.lib"
4545
OutputFile="..\..\lib\distutils\command/wininst-7.1.exe"
4646
LinkIncremental="1"
4747
SuppressStartupBanner="TRUE"

0 commit comments

Comments
 (0)