From: <mha...@us...> - 2008-08-28 03:56:02
|
Revision: 670 http://py2exe.svn.sourceforge.net/py2exe/?rev=670&view=rev Author: mhammond Date: 2008-08-28 03:56:11 +0000 (Thu, 28 Aug 2008) Log Message: ----------- [ 2038411 ] copy manifest to target executables and allow user-specified UAC settings for the target. Modified Paths: -------------- trunk/py2exe/ChangeLog trunk/py2exe/py2exe/build_exe.py trunk/py2exe/source/py2exe_util.c Added Paths: ----------- trunk/py2exe/py2exe/samples/user_access_control/ trunk/py2exe/py2exe/samples/user_access_control/README.txt trunk/py2exe/py2exe/samples/user_access_control/hello.py trunk/py2exe/py2exe/samples/user_access_control/setup.py Modified: trunk/py2exe/ChangeLog =================================================================== --- trunk/py2exe/ChangeLog 2008-08-05 05:15:40 UTC (rev 669) +++ trunk/py2exe/ChangeLog 2008-08-28 03:56:11 UTC (rev 670) @@ -1,3 +1,13 @@ +2008-08-28 Mark Hammond <mha...@sk...> + + * Copy the manifest, if any, from the 'template' into the targets + to ensure embedded assembly references, as required for python 2.6 based + apps, are copied. + + * Allow each target to specify Vista User Access Control flags. For + example, specifying 'uac_execution_info="requireAdministrator"' would + force elevation for the final executable. + 2008-05-19 Jimmy Retzlaff <ji...@re...> * Bump version number to 0.6.8. Modified: trunk/py2exe/py2exe/build_exe.py =================================================================== --- trunk/py2exe/py2exe/build_exe.py 2008-08-05 05:15:40 UTC (rev 669) +++ trunk/py2exe/py2exe/build_exe.py 2008-08-28 03:56:11 UTC (rev 670) @@ -16,6 +16,7 @@ import sets import tempfile import struct +import re is_win64 = struct.calcsize("P") == 8 @@ -34,7 +35,12 @@ # resource constants RT_BITMAP=2 +RT_MANIFEST=24 +# Pattern for modifying the 'requestedExecutionLevel' in the manifest. Groups +# are setup so all text *except* for the values is matched. +pat_manifest_uac = re.compile(r'(^.*<requestedExecutionLevel level=")([^"])*(" uiAccess=")([^"])*(".*$)') + # note: we cannot use the list from imp.get_suffixes() because we want # .pyc and .pyo, independent of the optimize flag. _py_suffixes = ['.py', '.pyo', '.pyc', '.pyw'] @@ -85,6 +91,15 @@ # A very loosely defined "target". We assume either a "script" or "modules" # attribute. Some attributes will be target specific. class Target: + # A custom requestedExecutionLevel for the User Access Control portion + # of the manifest for the target. May be a string, which will be used for + # the 'requestedExecutionLevel' portion and False for 'uiAccess', or a tuple + # of (string, bool) which specifies both values. If specified and the + # target's 'template' executable has no manifest (ie, python 2.5 and + # earlier), then a default manifest is created, otherwise the manifest from + # the template is copied then updated. + uac_info = None + def __init__(self, **kw): self.__dict__.update(kw) # If modules is a simple string, assume they meant list @@ -719,6 +734,58 @@ } return self.build_executable(target, template, arcname, None, vars) + def build_manifest(self, target, template): + # Optionally return a manifest to be included in the target executable. + # Note for Python 2.6 and later, its *necessary* to include a manifest + # which correctly references the CRT. For earlier versions, a manifest + # is optional, and only necessary to customize things like + # Vista's User Access Control 'requestedExecutionLevel' setting, etc. + default_manifest = """ + <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"> + <trustInfo xmlns="urn:schemas-microsoft-com:asm.v3"> + <security> + <requestedPrivileges> + <requestedExecutionLevel level="asInvoker" uiAccess="false"></requestedExecutionLevel> + </requestedPrivileges> + </security> + </trustInfo> + </assembly> + """ + from py2exe_util import load_resource + if os.path.splitext(template)[1]==".exe": + rid = 1 + else: + rid = 2 + try: + # Manfiests have resource type of 24, and ID of either 1 or 2. + mfest = load_resource(ensure_unicode(template), RT_MANIFEST, rid) + # we consider the manifest 'changed' as we know we clobber all + # resources including the existing manifest - so this manifest must + # get written even if we make no other changes. + changed = True + except RuntimeError: + mfest = default_manifest + # in this case the template had no existing manifest, so its + # not considered 'changed' unless we make further changes later. + changed = False + # update the manifest according to our options. + # for now a regex will do. + if target.uac_info: + changed = True + if isinstance(target.uac_info, tuple): + exec_level, ui = target.uac_info + else: + exec_level = target.uac_info + ui = False + new_lines = [] + for line in mfest.splitlines(): + repl = r'\1%s\3%s\5' % (exec_level, ui) + new_lines.append(re.sub(pat_manifest_uac, repl, line)) + mfest = "".join(new_lines) + if not changed: + return None, None + return mfest, rid + def build_executable(self, target, template, arcname, script, vars={}): # Build an executable for the target # template is the exe-stub to use, and arcname is the zipfile @@ -827,6 +894,13 @@ if not self.dry_run: add_icon(ensure_unicode(exe_path), ensure_unicode(ico_filename), ico_id) + # a manifest + mfest, mfest_id = self.build_manifest(target, src) + if mfest: + self.announce("add manifest, %d bytes" % len(mfest)) + if not self.dry_run: + add_resource(ensure_unicode(exe_path), mfest, RT_MANIFEST, mfest_id, False) + for res_type, res_id, data in getattr(target, "other_resources", []): if not self.dry_run: if isinstance(res_type, basestring): @@ -960,13 +1034,17 @@ images = dlls + templates self.announce("Resolving binary dependencies:") + excludes_use = dll_excludes[:] + # The MSVCRT modules are never found when using VS2008+ + if sys.version_info > (2,6): + excludes_use.append("msvcr90.dll") # we add python.exe (aka sys.executable) to the list of images # to scan for dependencies, but remove it later again from the # results list. In this way pythonXY.dll is collected, and # also the libraries it depends on. alldlls, warnings, other_depends = \ - bin_depends(loadpath, images + [sys.executable], dll_excludes) + bin_depends(loadpath, images + [sys.executable], excludes_use) alldlls.remove(sys.executable) for dll in alldlls: self.announce(" %s" % dll) Added: trunk/py2exe/py2exe/samples/user_access_control/README.txt =================================================================== --- trunk/py2exe/py2exe/samples/user_access_control/README.txt (rev 0) +++ trunk/py2exe/py2exe/samples/user_access_control/README.txt 2008-08-28 03:56:11 UTC (rev 670) @@ -0,0 +1,54 @@ +This is a sample for how to control Vista's user-access-control for your +py2exe created programs. + +Execute 'python setup.py py2exe' to create various executables, all with +different manifest values. Each of these may behave slightly differently +when executed under Vista. + +Important: +---------- + +There is an important difference between Python 2.6 versus +earlier versions. + +Python 2.5 and before created executables will not have a manifest +by default, meaning a backwards compatibility virtualization mode +may be used by default. However, if you specify any value for 'uac_info' +this virtualization will be disabled as it will cause a manifest to be +created for the executable. + +To demonstrate on Vista: + +* Using an elevated process, create a temp directory under "C:\Program Files" + and copy the generated 'dist' directory to that temp dir. + +* From a non-elevated command-prompt, execute: + c:\Program Files\temp\not_specified.exe -e foo + + You will see an error dialog due to 'foo' being written to stderr. The + error dialog will tell you the log file was written to the + "C:\Program Files\temp" directory, but that log file will not exist + in that directory - instead, it will be in the + "C:\Users\{username}\AppData\Local\VirtualStore\Program Files (x86)\temp" + directory - Windows has virtualized the file-system. Similar things will + be able to be demonstrated with the registry. + +* From a non-elevated command-prompt, execute: + c:\Program Files\temp\as_invoker.exe -e foo + + You will see an error dialog, but it will be slightly different - it + will relate to a failure to open the log file in the "c:\Program Files\temp" + directory. In this case, Windows has *not* virtualized the file-system but + your process does not have permission to write in that directory, so it + fails to write a log file at all. + +* From a non-elevated command-prompt, execute: + c:\Program Files\temp\admin_required.exe -e foo + + You will be prompted to elevate the process, then a log file will be + written to "C:\Program Files\temp" as the permissions exists in the + elevated process. + +NOTE: For Python 2.6 and later there is always a manifest, so virtualization +is always disabled. In other words, in Python 2.6, not_specified.exe will +work exactly like as_invoker.exe does for all versions. Added: trunk/py2exe/py2exe/samples/user_access_control/hello.py =================================================================== --- trunk/py2exe/py2exe/samples/user_access_control/hello.py (rev 0) +++ trunk/py2exe/py2exe/samples/user_access_control/hello.py 2008-08-28 03:56:11 UTC (rev 670) @@ -0,0 +1,28 @@ +import sys +import optparse + +def main(): + parser = optparse.OptionParser() + # Main point here is showing that writing to stderr + # currently forces py2exe to write a .log file next + # to the executable, but that might not always work. + # README.txt relies on that to demonstrate different + # behaviour for the different manifest values, so + # if this changes, be sure to also ensure README.txt + # stays accurate. + parser.add_option("-e", "--write-err", + action="store", + help="a message to write to stderr.") + + parser.add_option("-o", "--write-out", + action="store", + help="a message to write to stdout.") + + opts, args = parser.parse_args() + if opts.write_err: + sys.stderr.write(opts.write_err + "\n") + if opts.write_out: + sys.stderr.write(opts.write_out+ "\n") + +if __name__=='__main__': + main() Added: trunk/py2exe/py2exe/samples/user_access_control/setup.py =================================================================== --- trunk/py2exe/py2exe/samples/user_access_control/setup.py (rev 0) +++ trunk/py2exe/py2exe/samples/user_access_control/setup.py 2008-08-28 03:56:11 UTC (rev 670) @@ -0,0 +1,46 @@ +# A simple setup script to create various executables with +# different User Access Control flags in the manifest. + +# Run the build process by entering 'setup.py py2exe' or +# 'python setup.py py2exe' in a console prompt. +# +# If everything works well, you should find a subdirectory named 'dist' +# containing lots of executables + +from distutils.core import setup +import py2exe + +# The targets to build + +# create a target that says nothing about UAC - On Python 2.6+, this +# should be identical to "asInvoker" below. However, for 2.5 and +# earlier it will force the app into compatibility mode (as no +# manifest will exist at all in the target.) +t1 = dict(script="hello.py", + dest_base="not_specified") +# targets with different values for requestedExecutionLevel +t2 = dict(script="hello.py", + dest_base="as_invoker", + uac_info="asInvoker") +t3 = dict(script="hello.py", + dest_base="highest_available", + uac_info="highestAvailable") +t4 = dict(script="hello.py", + dest_base="require_admin", + uac_info="requireAdministrator") +console = [t1, t2, t3, t4] + +# hack to make windows copies of them all too, but +# with '_w' on the tail of the executable. +windows = [t1.copy(), t2.copy(), t3.copy(), t4.copy()] +for t in windows: + t['dest_base'] += "_w" + +setup( + version = "0.5.0", + description = "py2exe user-access-control sample script", + name = "py2exe samples", + # targets to build + windows = windows, + console = console, + ) Modified: trunk/py2exe/source/py2exe_util.c =================================================================== --- trunk/py2exe/source/py2exe_util.c 2008-08-05 05:15:40 UTC (rev 669) +++ trunk/py2exe/source/py2exe_util.c 2008-08-28 03:56:11 UTC (rev 670) @@ -357,6 +357,62 @@ return NULL; } +static PyObject *load_resource(PyObject *self, PyObject *args) +{ + PyObject *ret = NULL; + Py_UNICODE *exename; + HANDLE hMod = NULL; + HRSRC hrsrc = NULL; + HGLOBAL hglob = 0; + LPVOID res_ptr = NULL; + DWORD res_size = 0; + PyObject *py_res_type, *py_res_id; + Py_UNICODE *res_type; + Py_UNICODE *res_id; + WORD wLanguage = MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL); + + if (!PyArg_ParseTuple(args, "uOO|H", + &exename, &py_res_type, &py_res_id, &wLanguage)) + return NULL; + + res_type = PyObject_AsRESOURCEID(py_res_type); + if (res_type==NULL) + return NULL; + + res_id = PyObject_AsRESOURCEID(py_res_id); + if (res_id==NULL) + return NULL; + + // Note lack of function pointers - this was added after win9x support was + // dropped. + if (!(hMod = LoadLibraryExW(exename, NULL, LOAD_LIBRARY_AS_DATAFILE))) + return SystemError(GetLastError(), "LoadLibraryExW"); + + // from here. take care to exit via 'done' + if (!(hrsrc = FindResourceExW(hMod, res_type, res_id, wLanguage))) { + SystemError(GetLastError(), "FindResourceEx"); + goto done; + } + if (0 == (res_size = SizeofResource(hMod, hrsrc))) { + SystemError(GetLastError(), "SizeofResource"); + goto done; + } + if (0 == (hglob = LoadResource(hMod, hrsrc))) { + SystemError(GetLastError(), "LoadResource"); + goto done; + } + if (0 == (res_ptr = LockResource(hglob))) { + SystemError(GetLastError(), "LockResource"); + goto done; + } + ret = PyString_FromStringAndSize((char *)res_ptr, res_size); +done: + if (hMod) + FreeLibrary(hMod); + // nothing else to clean up (no Unlock/UnloadResource exist) + return ret; +} + /*********************************************************************************** * * Dependency tracker @@ -510,6 +566,9 @@ "Return a list containing the dlls needed to run 'executable'.\n" "The dlls are searched along 'loadpath'\n" "or windows default loadpath", }, + { "load_resource", load_resource, METH_VARARGS, + "load_resource(executable, res_type, res_id[, language]) -> string\n\n" + "Load the specified resource from 'executable'", }, { NULL, NULL }, /* Sentinel */ }; This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |