1#-----------------------------------------------------------------------------
2# Copyright (c) 2013-2019, PyInstaller Development Team.
3#
4# Distributed under the terms of the GNU General Public License with exception
5# for distributing bootloader.
6#
7# The full license is in the file COPYING.txt, distributed with this software.
8#-----------------------------------------------------------------------------
9
10
11# Development notes kept for documentation purposes.
12#
13# Currently not implemented in the Manifest class:
14# * Validation (only very basic sanity checks are currently in place)
15# * comClass, typelib, comInterfaceProxyStub and windowClass child elements of
16#   the file element
17# * comInterfaceExternalProxyStub and windowClass child elements of the
18#   assembly element
19# * Application Configuration File and Multilanguage User Interface (MUI)
20#   support when searching for assembly files
21#
22# Isolated Applications and Side-by-side Assemblies:
23# http://msdn.microsoft.com/en-us/library/dd408052%28VS.85%29.aspx
24#
25# Changelog:
26# 2009-12-17  fix: small glitch in toxml / toprettyxml methods (xml declaration
27#                  wasn't replaced when a different encodig than UTF-8 was used)
28#             chg: catch xml.parsers.expat.ExpatError and re-raise as
29#                  ManifestXMLParseError
30#             chg: support initialize option in parse method also
31#
32# 2009-12-13  fix: fixed os import
33#             fix: skip invalid / empty dependent assemblies
34#
35# 2009-08-21  fix: Corrected assembly searching sequence for localized
36#                  assemblies
37#             fix: Allow assemblies with no dependent files
38#
39# 2009-07-31  chg: Find private assemblies even if unversioned
40#             add: Manifest.same_id method to check if two manifests have the
41#                  same assemblyIdentity
42#
43# 2009-07-30  fix: Potential failure in File.calc_hash method if hash
44#                  algorythm not supported
45#             add: Publisher configuration (policy) support when searching for
46#                  assembly files
47#             fix: Private assemblies are now actually found if present (and no
48#                  shared assembly exists)
49#             add: Python 2.3 compatibility (oldest version supported by
50#                  pyinstaller)
51#
52# 2009-07-28  chg: Code cleanup, removed a bit of redundancy
53#             add: silent mode (set silent attribute on module)
54#             chg: Do not print messages in silent mode
55#
56# 2009-06-18  chg: Use glob instead of regular expression in Manifest.find_files
57#
58# 2009-05-04  fix: Don't fail if manifest has empty description
59#             fix: Manifests created by the toxml, toprettyxml, writexml or
60#                  writeprettyxml methods are now correctly recognized by
61#                  Windows, which expects the XML declaration to be ordered
62#                  version-encoding-standalone (standalone being optional)
63#             add: 'encoding' keyword argument in toxml, toprettyxml, writexml
64#                  and writeprettyxml methods
65#             chg: UpdateManifestResourcesFromXML and
66#                  UpdateManifestResourcesFromXMLFile: set resource name
67#                  depending on file type ie. exe or dll
68#             fix: typo in __main__: UpdateManifestResourcesFromDataFile
69#                  should have been UpdateManifestResourcesFromXMLFile
70#
71# 2009-03-21  First version
72
73
74"""
75Create, parse and write MS Windows Manifest files.
76Find files which are part of an assembly, by searching shared and
77private assemblies.
78Update or add manifest resources in Win32 PE files.
79
80Commandline usage:
81winmanifest.py <dstpath> <xmlpath>
82Updates or adds manifest <xmlpath> as resource in Win32 PE file <dstpath>.
83"""
84
85
86import os
87from glob import glob
88import hashlib
89import sys
90import xml
91from xml.dom import Node, minidom
92from xml.dom.minidom import Document, Element
93
94from PyInstaller import compat
95from PyInstaller.compat import string_types
96from PyInstaller import log as logging
97from PyInstaller.utils.win32 import winresource
98
99logger = logging.getLogger(__name__)
100
101LANGUAGE_NEUTRAL_NT5 = "x-ww"
102LANGUAGE_NEUTRAL_NT6 = "none"
103RT_MANIFEST = 24
104
105Document.aChild = Document.appendChild
106Document.cE = Document.createElement
107Document.cT = Document.createTextNode
108Document.getEByTN = Document.getElementsByTagName
109Element.aChild = Element.appendChild
110Element.getA = Element.getAttribute
111Element.getEByTN = Element.getElementsByTagName
112Element.remA = Element.removeAttribute
113Element.setA = Element.setAttribute
114
115
116def getChildElementsByTagName(self, tagName):
117    """ Return child elements of type tagName if found, else [] """
118    result = []
119    for child in self.childNodes:
120        if isinstance(child, Element):
121            if child.tagName == tagName:
122                result.append(child)
123    return result
124
125
126def getFirstChildElementByTagName(self, tagName):
127    """ Return the first element of type tagName if found, else None """
128    for child in self.childNodes:
129        if isinstance(child, Element):
130            if child.tagName == tagName:
131                return child
132    return None
133
134
135Document.getCEByTN = getChildElementsByTagName
136Document.getFCEByTN = getFirstChildElementByTagName
137Element.getCEByTN = getChildElementsByTagName
138Element.getFCEByTN = getFirstChildElementByTagName
139
140
141class _Dummy:
142    pass
143
144
145if winresource:
146    _File = winresource.File
147else:
148    _File = _Dummy
149
150
151class File(_File):
152
153    """ A file referenced by an assembly inside a manifest. """
154
155    def __init__(self, filename="", hashalg=None, hash=None, comClasses=None,
156                 typelibs=None, comInterfaceProxyStubs=None,
157                 windowClasses=None):
158        if winresource:
159            winresource.File.__init__(self, filename)
160        else:
161            self.filename = filename
162        self.name = os.path.basename(filename)
163        if hashalg:
164            self.hashalg = hashalg.upper()
165        else:
166            self.hashalg = None
167        if (os.path.isfile(filename) and hashalg and hashlib and
168            hasattr(hashlib, hashalg.lower())):
169            self.calc_hash()
170        else:
171            self.hash = hash
172        self.comClasses = comClasses or [] # TO-DO: implement
173        self.typelibs = typelibs or [] # TO-DO: implement
174        self.comInterfaceProxyStubs = comInterfaceProxyStubs or [] # TO-DO: implement
175        self.windowClasses = windowClasses or [] # TO-DO: implement
176
177    def calc_hash(self, hashalg=None):
178        """
179        Calculate the hash of the file.
180
181        Will be called automatically from the constructor if the file exists
182        and hashalg is given (and supported), but may also be called manually
183        e.g. to update the hash if the file has changed.
184
185        """
186        with open(self.filename, "rb") as fd:
187            buf = fd.read()
188        if hashalg:
189            self.hashalg = hashalg.upper()
190        self.hash = getattr(hashlib, self.hashalg.lower())(buf).hexdigest()
191
192    def find(self, searchpath):
193        logger.info("Searching for file %s", self.name)
194        fn = os.path.join(searchpath, self.name)
195        if os.path.isfile(fn):
196            logger.info("Found file %s", fn)
197            return fn
198        else:
199            logger.warning("No such file %s", fn)
200            return None
201
202
203class InvalidManifestError(Exception):
204    pass
205
206
207class ManifestXMLParseError(InvalidManifestError):
208    pass
209
210
211class Manifest(object):
212
213    # Manifests:
214    # http://msdn.microsoft.com/en-us/library/aa375365%28VS.85%29.aspx
215
216    """
217    Manifest constructor.
218
219    To build a basic manifest for your application:
220      mf = Manifest(type='win32', name='YourAppName', language='*',
221                    processorArchitecture='x86', version=[1, 0, 0, 0])
222
223    To write the XML to a manifest file:
224      mf.writexml("YourAppName.exe.manifest")
225    or
226      mf.writeprettyxml("YourAppName.exe.manifest")
227
228    """
229
230    def __init__(self, manifestVersion=None, noInheritable=False,
231                 noInherit=False, type_=None, name=None, language=None,
232                 processorArchitecture=None, version=None,
233                 publicKeyToken=None, description=None,
234                 requestedExecutionLevel=None, uiAccess=None,
235                 dependentAssemblies=None, files=None,
236                 comInterfaceExternalProxyStubs=None):
237        self.filename = None
238        self.optional = None
239        self.manifestType = "assembly"
240        self.manifestVersion = manifestVersion or [1, 0]
241        self.noInheritable = noInheritable
242        self.noInherit = noInherit
243        self.type = type_
244        self.name = name
245        self.language = language
246        self.processorArchitecture = processorArchitecture
247        self.version = version
248        self.publicKeyToken = publicKeyToken
249        # publicKeyToken:
250        # A 16-character hexadecimal string that represents the last 8 bytes
251        # of the SHA-1 hash of the public key under which the assembly is
252        # signed. The public key used to sign the catalog must be 2048 bits
253        # or greater. Required for all shared side-by-side assemblies.
254        # http://msdn.microsoft.com/en-us/library/aa375692(VS.85).aspx
255        self.applyPublisherPolicy = None
256        self.description = None
257        self.requestedExecutionLevel = requestedExecutionLevel
258        self.uiAccess = uiAccess
259        self.dependentAssemblies = dependentAssemblies or []
260        self.bindingRedirects = []
261        self.files = files or []
262        self.comInterfaceExternalProxyStubs = comInterfaceExternalProxyStubs or [] # TO-DO: implement
263
264    def __eq__(self, other):
265        if isinstance(other, Manifest):
266            return self.toxml() == other.toxml()
267        if isinstance(other, string_types):
268            return self.toxml() == other
269        return False
270
271    def __ne__(self, other):
272        return not self.__eq__(other)
273
274    def __repr__(self):
275        return repr(self.toxml())
276
277    def add_dependent_assembly(self, manifestVersion=None, noInheritable=False,
278                 noInherit=False, type_=None, name=None, language=None,
279                 processorArchitecture=None, version=None,
280                 publicKeyToken=None, description=None,
281                 requestedExecutionLevel=None, uiAccess=None,
282                 dependentAssemblies=None, files=None,
283                 comInterfaceExternalProxyStubs=None):
284        """
285        Shortcut for self.dependentAssemblies.append(Manifest(*args, **kwargs))
286        """
287        self.dependentAssemblies.append(Manifest(manifestVersion,
288                                        noInheritable, noInherit, type_, name,
289                                        language, processorArchitecture,
290                                        version, publicKeyToken, description,
291                                        requestedExecutionLevel, uiAccess,
292                                        dependentAssemblies, files,
293                                        comInterfaceExternalProxyStubs))
294        if self.filename:
295            # Enable search for private assembly by assigning bogus filename
296            # (only the directory has to be correct)
297            self.dependentAssemblies[-1].filename = ":".join((self.filename,
298                                                              name))
299
300    def add_file(self, name="", hashalg="", hash="", comClasses=None,
301                 typelibs=None, comInterfaceProxyStubs=None,
302                 windowClasses=None):
303        """ Shortcut for manifest.files.append """
304        self.files.append(File(name, hashalg, hash, comClasses,
305                          typelibs, comInterfaceProxyStubs, windowClasses))
306
307    @classmethod
308    def get_winsxs_dir(cls):
309        return os.path.join(compat.getenv("SystemRoot"), "WinSxS")
310
311    @classmethod
312    def get_manifest_dir(cls):
313        winsxs = cls.get_winsxs_dir()
314        if not os.path.isdir(winsxs):
315            logger.warning("No such dir %s", winsxs)
316        manifests = os.path.join(winsxs, "Manifests")
317        if not os.path.isdir(manifests):
318            logger.warning("No such dir %s", manifests)
319        return manifests
320
321    @classmethod
322    def get_policy_dir(cls):
323        winsxs = os.path.join(compat.getenv("SystemRoot"), "WinSxS")
324        if sys.getwindowsversion() < (6, ):
325            # Windows XP
326            pcfiles = os.path.join(winsxs, "Policies")
327            if not os.path.isdir(pcfiles):
328                logger.warning("No such dir %s", pcfiles)
329        else:
330            # Vista or later
331            pcfiles = cls.get_manifest_dir()
332        return pcfiles
333
334
335    def get_policy_redirect(self, language=None, version=None):
336        # Publisher Configuration (aka policy)
337        # A publisher configuration file globally redirects
338        # applications and assemblies having a dependence on one
339        # version of a side-by-side assembly to use another version of
340        # the same assembly. This enables applications and assemblies
341        # to use the updated assembly without having to rebuild all of
342        # the affected applications.
343        # http://msdn.microsoft.com/en-us/library/aa375680%28VS.85%29.aspx
344        #
345        # Under Windows XP and 2003, policies are stored as
346        # <version>.policy files inside
347        # %SystemRoot%\WinSxS\Policies\<name>
348        # Under Vista and later, policies are stored as
349        # <name>.manifest files inside %SystemRoot%\winsxs\Manifests
350        redirected = False
351        pcfiles = self.get_policy_dir()
352        if version is None:
353            version = self.version
354        if language is None:
355            language = self.language
356
357        if os.path.isdir(pcfiles):
358            logger.debug("Searching for publisher configuration %s ...",
359                         self.getpolicyid(True, language=language))
360            if sys.getwindowsversion() < (6, ):
361                # Windows XP
362                policies = os.path.join(pcfiles,
363                                        self.getpolicyid(True,
364                                                         language=language) +
365                                        ".policy")
366            else:
367                # Vista or later
368                policies = os.path.join(pcfiles,
369                                        self.getpolicyid(True,
370                                                         language=language) +
371                                        ".manifest")
372            for manifestpth in glob(policies):
373                if not os.path.isfile(manifestpth):
374                    logger.warning("Not a file %s", manifestpth)
375                    continue
376                logger.info("Found %s", manifestpth)
377                try:
378                    policy = ManifestFromXMLFile(manifestpth)
379                except Exception:
380                    logger.error("Could not parse file %s",
381                                 manifestpth, exc_info=1)
382                else:
383                    logger.debug("Checking publisher policy for "
384                                 "binding redirects")
385                    for assembly in policy.dependentAssemblies:
386                        if (not assembly.same_id(self, True) or
387                            assembly.optional):
388                            continue
389                        for redirect in assembly.bindingRedirects:
390                            old = "-".join([".".join([str(i)
391                                                      for i in
392                                                      part])
393                                            for part in
394                                            redirect[0]])
395                            new = ".".join([str(i)
396                                            for i in
397                                            redirect[1]])
398                            logger.debug("Found redirect for "
399                                         "version(s) %s -> %s",
400                                         old, new)
401                            if (version >= redirect[0][0] and
402                                version <= redirect[0][-1] and
403                                version != redirect[1]):
404                                logger.debug("Applying redirect "
405                                             "%s -> %s",
406                                             ".".join([str(i)
407                                                       for i in
408                                                       version]),
409                                             new)
410                                version = redirect[1]
411                                redirected = True
412            if not redirected:
413                logger.debug("Publisher configuration not used")
414
415        return version
416
417    def find_files(self, ignore_policies=True):
418        """ Search shared and private assemblies and return a list of files.
419
420        If any files are not found, return an empty list.
421
422        IMPORTANT NOTE: On some Windows systems, the dependency listed in the manifest
423        will not actually be present, and finding its files will fail. This is because
424        a newer version of the dependency is installed, and the manifest's dependency
425        is being redirected to a newer version. To properly bundle the newer version of
426        the assembly, you need to find the newer version by setting
427        ignore_policies=False, and then either create a .config file for each bundled
428        assembly, or modify each bundled assembly to point to the newer version.
429
430        This is important because Python 2.7's app manifest depends on version 21022
431        of the VC90 assembly, but the Python 2.7.9 installer will install version
432        30729 of the assembly along with a policy file that enacts the version redirect.
433
434        """
435
436        # Shared Assemblies:
437        # http://msdn.microsoft.com/en-us/library/aa375996%28VS.85%29.aspx
438        #
439        # Private Assemblies:
440        # http://msdn.microsoft.com/en-us/library/aa375674%28VS.85%29.aspx
441        #
442        # Assembly Searching Sequence:
443        # http://msdn.microsoft.com/en-us/library/aa374224%28VS.85%29.aspx
444        #
445        # NOTE:
446        # Multilanguage User Interface (MUI) support not yet implemented
447
448        files = []
449
450        languages = []
451        if self.language not in (None, "", "*", "neutral"):
452            languages.append(self.getlanguage())
453            if "-" in self.language:
454                # language-culture syntax, e.g. en-us
455                # Add only the language part
456                languages.append(self.language.split("-")[0])
457            if self.language not in ("en-us", "en"):
458                languages.append("en-us")
459            if self.language != "en":
460                languages.append("en")
461        languages.append(self.getlanguage("*"))
462
463        manifests = self.get_manifest_dir()
464        winsxs = self.get_winsxs_dir()
465
466        for language in languages:
467            version = self.version
468
469            # Search for publisher configuration
470            if not ignore_policies and version:
471                version = self.get_policy_redirect(language, version)
472
473            # Search for assemblies according to assembly searching sequence
474            paths = []
475            if os.path.isdir(manifests):
476                # Add winsxs search paths
477                # Search for manifests in Windows\WinSxS\Manifests
478                paths.extend(glob(os.path.join(manifests,
479                                               self.getid(language=language,
480                                                          version=version) +
481                                               "_*.manifest")))
482            if self.filename:
483                # Add private assembly search paths
484                # Search for manifests inside assembly folders that are in
485                # the same folder as the depending manifest.
486                dirnm = os.path.dirname(self.filename)
487                if language in (LANGUAGE_NEUTRAL_NT5,
488                                LANGUAGE_NEUTRAL_NT6):
489                    for ext in (".dll", ".manifest"):
490                        paths.extend(glob(os.path.join(dirnm, self.name + ext)))
491                        paths.extend(glob(os.path.join(dirnm, self.name,
492                                                       self.name + ext)))
493                else:
494                    for ext in (".dll", ".manifest"):
495                        paths.extend(glob(os.path.join(dirnm, language,
496                                                       self.name + ext)))
497                    for ext in (".dll", ".manifest"):
498                        paths.extend(glob(os.path.join(dirnm, language,
499                                                       self.name,
500                                                       self.name + ext)))
501            logger.info("Searching for assembly %s ...",
502                        self.getid(language=language, version=version))
503            for manifestpth in paths:
504                if not os.path.isfile(manifestpth):
505                    logger.warning("Not a file %s", manifestpth)
506                    continue
507                assemblynm = os.path.basename(
508                    os.path.splitext(manifestpth)[0])
509                try:
510                    if manifestpth.endswith(".dll"):
511                        logger.info("Found manifest in %s", manifestpth)
512                        manifest = ManifestFromResFile(manifestpth, [1])
513                    else:
514                        logger.info("Found manifest %s", manifestpth)
515                        manifest = ManifestFromXMLFile(manifestpth)
516                except Exception:
517                    logger.error("Could not parse manifest %s",
518                                 manifestpth, exc_info=1)
519                else:
520                    if manifestpth.startswith(winsxs):
521                        # Manifest is in Windows\WinSxS\Manifests, so assembly
522                        # dir is in Windows\WinSxS
523                        assemblydir = os.path.join(winsxs, assemblynm)
524                        if not os.path.isdir(assemblydir):
525                            logger.warning("No such dir %s", assemblydir)
526                            logger.warning("Assembly incomplete")
527                            return []
528                    else:
529                        # Manifest is inside assembly dir.
530                        assemblydir = os.path.dirname(manifestpth)
531                    files.append(manifestpth)
532                    for file_ in self.files or manifest.files:
533                        fn = file_.find(assemblydir)
534                        if fn:
535                            files.append(fn)
536                        else:
537                            # If any of our files does not exist,
538                            # the assembly is incomplete
539                            logger.warning("Assembly incomplete")
540                            return []
541                return files
542
543        logger.warning("Assembly not found")
544        return []
545
546    def getid(self, language=None, version=None):
547        """
548        Return an identification string which uniquely names a manifest.
549
550        This string is a combination of the manifest's processorArchitecture,
551        name, publicKeyToken, version and language.
552
553        Arguments:
554        version (tuple or list of integers) - If version is given, use it
555                                              instead of the manifest's
556                                              version.
557
558        """
559        if not self.name:
560            logger.warning("Assembly metadata incomplete")
561            return ""
562        id = []
563        if self.processorArchitecture:
564            id.append(self.processorArchitecture)
565        id.append(self.name)
566        if self.publicKeyToken:
567            id.append(self.publicKeyToken)
568        if version or self.version:
569            id.append(".".join([str(i) for i in version or self.version]))
570        if not language:
571            language = self.getlanguage()
572        if language:
573            id.append(language)
574        return "_".join(id)
575
576    def getlanguage(self, language=None, windowsversion=None):
577        """
578        Get and return the manifest's language as string.
579
580        Can be either language-culture e.g. 'en-us' or a string indicating
581        language neutrality, e.g. 'x-ww' on Windows XP or 'none' on Vista
582        and later.
583
584        """
585        if not language:
586            language = self.language
587        if language in (None, "", "*", "neutral"):
588            return (LANGUAGE_NEUTRAL_NT5,
589                    LANGUAGE_NEUTRAL_NT6)[(windowsversion or
590                                           sys.getwindowsversion()) >= (6, )]
591        return language
592
593    def getpolicyid(self, fuzzy=True, language=None, windowsversion=None):
594        """
595        Return an identification string which can be used to find a policy.
596
597        This string is a combination of the manifest's processorArchitecture,
598        major and minor version, name, publicKeyToken and language.
599
600        Arguments:
601        fuzzy (boolean)             - If False, insert the full version in
602                                      the id string. Default is True (omit).
603        windowsversion              - If not specified (or None), default to
604        (tuple or list of integers)   sys.getwindowsversion().
605
606        """
607        if not self.name:
608            logger.warning("Assembly metadata incomplete")
609            return ""
610        id = []
611        if self.processorArchitecture:
612            id.append(self.processorArchitecture)
613        name = []
614        name.append("policy")
615        if self.version:
616            name.append(str(self.version[0]))
617            name.append(str(self.version[1]))
618        name.append(self.name)
619        id.append(".".join(name))
620        if self.publicKeyToken:
621            id.append(self.publicKeyToken)
622        if self.version and (windowsversion or sys.getwindowsversion()) >= (6, ):
623            # Vista and later
624            if fuzzy:
625                id.append("*")
626            else:
627                id.append(".".join([str(i) for i in self.version]))
628        if not language:
629            language = self.getlanguage(windowsversion=windowsversion)
630        if language:
631            id.append(language)
632        id.append("*")
633        id = "_".join(id)
634        if self.version and (windowsversion or sys.getwindowsversion()) < (6, ):
635            # Windows XP
636            if fuzzy:
637                id = os.path.join(id, "*")
638            else:
639                id = os.path.join(id, ".".join([str(i) for i in self.version]))
640        return id
641
642    def load_dom(self, domtree, initialize=True):
643        """
644        Load manifest from DOM tree.
645
646        If initialize is True (default), reset existing attributes first.
647
648        """
649        if domtree.nodeType == Node.DOCUMENT_NODE:
650            rootElement = domtree.documentElement
651        elif domtree.nodeType == Node.ELEMENT_NODE:
652            rootElement = domtree
653        else:
654            raise InvalidManifestError("Invalid root element node type " +
655                                       str(rootElement.nodeType) +
656                                       " - has to be one of (DOCUMENT_NODE, "
657                                       "ELEMENT_NODE)")
658        allowed_names = ("assembly", "assemblyBinding", "configuration",
659                         "dependentAssembly")
660        if rootElement.tagName not in allowed_names:
661            raise InvalidManifestError(
662                "Invalid root element <%s> - has to be one of <%s>" %
663                (rootElement.tagName, ">, <".join(allowed_names)))
664        # logger.info("loading manifest metadata from element <%s>", rootElement.tagName)
665        if rootElement.tagName == "configuration":
666            for windows in rootElement.getCEByTN("windows"):
667                for assemblyBinding in windows.getCEByTN("assemblyBinding"):
668                    self.load_dom(assemblyBinding, initialize)
669        else:
670            if initialize:
671                self.__init__()
672            self.manifestType = rootElement.tagName
673            self.manifestVersion = [int(i)
674                                    for i in
675                                    (rootElement.getA("manifestVersion") or
676                                     "1.0").split(".")]
677            self.noInheritable = bool(rootElement.getFCEByTN("noInheritable"))
678            self.noInherit = bool(rootElement.getFCEByTN("noInherit"))
679            for assemblyIdentity in rootElement.getCEByTN("assemblyIdentity"):
680                self.type = assemblyIdentity.getA("type") or None
681                self.name = assemblyIdentity.getA("name") or None
682                self.language = assemblyIdentity.getA("language") or None
683                self.processorArchitecture = assemblyIdentity.getA(
684                    "processorArchitecture") or None
685                version = assemblyIdentity.getA("version")
686                if version:
687                    self.version = tuple(int(i) for i in version.split("."))
688                self.publicKeyToken = assemblyIdentity.getA("publicKeyToken") or None
689            for publisherPolicy in rootElement.getCEByTN("publisherPolicy"):
690                self.applyPublisherPolicy = (publisherPolicy.getA("apply") or
691                                             "").lower() == "yes"
692            for description in rootElement.getCEByTN("description"):
693                if description.firstChild:
694                    self.description = description.firstChild.wholeText
695            for trustInfo in rootElement.getCEByTN("trustInfo"):
696                for security in trustInfo.getCEByTN("security"):
697                    for reqPriv in security.getCEByTN("requestedPrivileges"):
698                        for reqExeLev in reqPriv.getCEByTN("requestedExecutionLevel"):
699                            self.requestedExecutionLevel = reqExeLev.getA("level")
700                            self.uiAccess = (reqExeLev.getA("uiAccess") or
701                                             "").lower() == "true"
702            if rootElement.tagName == "assemblyBinding":
703                dependencies = [rootElement]
704            else:
705                dependencies = rootElement.getCEByTN("dependency")
706            for dependency in dependencies:
707                for dependentAssembly in dependency.getCEByTN(
708                    "dependentAssembly"):
709                    manifest = ManifestFromDOM(dependentAssembly)
710                    if not manifest.name:
711                        # invalid, skip
712                        continue
713                    manifest.optional = (dependency.getA("optional") or
714                                         "").lower() == "yes"
715                    self.dependentAssemblies.append(manifest)
716                    if self.filename:
717                        # Enable search for private assembly by assigning bogus
718                        # filename (only the directory has to be correct)
719                        self.dependentAssemblies[-1].filename = ":".join(
720                            (self.filename, manifest.name))
721            for bindingRedirect in rootElement.getCEByTN("bindingRedirect"):
722                oldVersion = tuple(tuple(int(i) for i in part.split("."))
723                                   for part in
724                                   bindingRedirect.getA("oldVersion").split("-"))
725                newVersion = tuple(int(i)
726                                   for i in
727                                   bindingRedirect.getA("newVersion").split("."))
728                self.bindingRedirects.append((oldVersion, newVersion))
729            for file_ in rootElement.getCEByTN("file"):
730                self.add_file(name=file_.getA("name"),
731                              hashalg=file_.getA("hashalg"),
732                              hash=file_.getA("hash"))
733
734    def parse(self, filename_or_file, initialize=True):
735        """ Load manifest from file or file object """
736        if isinstance(filename_or_file, string_types):
737            filename = filename_or_file
738        else:
739            filename = filename_or_file.name
740        try:
741            domtree = minidom.parse(filename_or_file)
742        except xml.parsers.expat.ExpatError as e:
743            args = [e.args[0]]
744            # TODO Keep this for Python 2 - filename might be unicode and should be then converted to str.
745            # if isinstance(filename, unicode):
746                # filename = filename.encode(sys.getdefaultencoding(), "replace")
747            args.insert(0, '\n  File "%s"\n   ' % filename)
748            raise ManifestXMLParseError(" ".join([str(arg) for arg in args]))
749        if initialize:
750            self.__init__()
751        self.filename = filename
752        self.load_dom(domtree, False)
753
754    def parse_string(self, xmlstr, initialize=True):
755        """ Load manifest from XML string """
756        try:
757            domtree = minidom.parseString(xmlstr)
758        except xml.parsers.expat.ExpatError as e:
759            raise ManifestXMLParseError(e)
760        self.load_dom(domtree, initialize)
761
762    def same_id(self, manifest, skip_version_check=False):
763        """
764        Return a bool indicating if another manifest has the same identitiy.
765
766        This is done by comparing language, name, processorArchitecture,
767        publicKeyToken, type and version.
768
769        """
770        if skip_version_check:
771            version_check = True
772        else:
773            version_check = self.version == manifest.version
774        return (self.language == manifest.language and
775                self.name == manifest.name and
776                self.processorArchitecture == manifest.processorArchitecture and
777                self.publicKeyToken == manifest.publicKeyToken and
778                self.type == manifest.type and
779                version_check)
780
781    def todom(self):
782        """ Return the manifest as DOM tree """
783        doc = Document()
784        docE = doc.cE(self.manifestType)
785        if self.manifestType == "assemblyBinding":
786            cfg = doc.cE("configuration")
787            win = doc.cE("windows")
788            win.aChild(docE)
789            cfg.aChild(win)
790            doc.aChild(cfg)
791        else:
792            doc.aChild(docE)
793        if self.manifestType != "dependentAssembly":
794            docE.setA("xmlns", "urn:schemas-microsoft-com:asm.v1")
795            if self.manifestType != "assemblyBinding":
796                docE.setA("manifestVersion",
797                          ".".join([str(i) for i in self.manifestVersion]))
798        if self.noInheritable:
799            docE.aChild(doc.cE("noInheritable"))
800        if self.noInherit:
801            docE.aChild(doc.cE("noInherit"))
802        aId = doc.cE("assemblyIdentity")
803        if self.type:
804            aId.setAttribute("type", self.type)
805        if self.name:
806            aId.setAttribute("name", self.name)
807        if self.language:
808            aId.setAttribute("language", self.language)
809        if self.processorArchitecture:
810            aId.setAttribute("processorArchitecture",
811                             self.processorArchitecture)
812        if self.version:
813            aId.setAttribute("version",
814                             ".".join([str(i) for i in self.version]))
815        if self.publicKeyToken:
816            aId.setAttribute("publicKeyToken", self.publicKeyToken)
817        if aId.hasAttributes():
818            docE.aChild(aId)
819        else:
820            aId.unlink()
821        if self.applyPublisherPolicy != None:
822            ppE = doc.cE("publisherPolicy")
823            if self.applyPublisherPolicy:
824                ppE.setA("apply", "yes")
825            else:
826                ppE.setA("apply", "no")
827            docE.aChild(ppE)
828        if self.description:
829            descE = doc.cE("description")
830            descE.aChild(doc.cT(self.description))
831            docE.aChild(descE)
832        if self.requestedExecutionLevel in ("asInvoker", "highestAvailable",
833                                            "requireAdministrator"):
834            tE = doc.cE("trustInfo")
835            tE.setA("xmlns", "urn:schemas-microsoft-com:asm.v3")
836            sE = doc.cE("security")
837            rpE = doc.cE("requestedPrivileges")
838            relE = doc.cE("requestedExecutionLevel")
839            relE.setA("level", self.requestedExecutionLevel)
840            if self.uiAccess:
841                relE.setA("uiAccess", "true")
842            else:
843                relE.setA("uiAccess", "false")
844            rpE.aChild(relE)
845            sE.aChild(rpE)
846            tE.aChild(sE)
847            docE.aChild(tE)
848        if self.dependentAssemblies:
849            for assembly in self.dependentAssemblies:
850                if self.manifestType != "assemblyBinding":
851                    dE = doc.cE("dependency")
852                    if assembly.optional:
853                        dE.setAttribute("optional", "yes")
854                daE = doc.cE("dependentAssembly")
855                adom = assembly.todom()
856                for child in adom.documentElement.childNodes:
857                    daE.aChild(child.cloneNode(False))
858                adom.unlink()
859                if self.manifestType != "assemblyBinding":
860                    dE.aChild(daE)
861                    docE.aChild(dE)
862                else:
863                    docE.aChild(daE)
864        if self.bindingRedirects:
865            for bindingRedirect in self.bindingRedirects:
866                brE = doc.cE("bindingRedirect")
867                brE.setAttribute("oldVersion",
868                                 "-".join([".".join([str(i)
869                                                     for i in
870                                                     part])
871                                           for part in
872                                           bindingRedirect[0]]))
873                brE.setAttribute("newVersion",
874                                 ".".join([str(i) for i in bindingRedirect[1]]))
875                docE.aChild(brE)
876        if self.files:
877            for file_ in self.files:
878                fE = doc.cE("file")
879                for attr in ("name", "hashalg", "hash"):
880                    val = getattr(file_, attr)
881                    if val:
882                        fE.setA(attr, val)
883                docE.aChild(fE)
884
885        # Add compatibility section: http://stackoverflow.com/a/10158920
886        cE = doc.cE("compatibility")
887        cE.setAttribute("xmlns", "urn:schemas-microsoft-com:compatibility.v1")
888        caE = doc.cE("application")
889        supportedOS_guids = {"Vista":"{e2011457-1546-43c5-a5fe-008deee3d3f0}",
890                             "7"    :"{35138b9a-5d96-4fbd-8e2d-a2440225f93a}",
891                             "8"    :"{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}",
892                             "8.1"  :"{1f676c76-80e1-4239-95bb-83d0f6d0da78}",
893                             "10"   :"{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"}
894        for guid in supportedOS_guids.values():
895            sosE = doc.cE("supportedOS")
896            sosE.setAttribute("Id", guid)
897            caE.aChild(sosE)
898        cE.aChild(caE)
899        docE.aChild(cE)
900
901        return doc
902
903    def toprettyxml(self, indent="  ", newl=os.linesep, encoding="UTF-8"):
904        """ Return the manifest as pretty-printed XML """
905        domtree = self.todom()
906        # WARNING: The XML declaration has to follow the order
907        # version-encoding-standalone (standalone being optional), otherwise
908        # if it is embedded in an exe the exe will fail to launch!
909        # ('application configuration incorrect')
910        if sys.version_info >= (2,3):
911            xmlstr = domtree.toprettyxml(indent, newl, encoding)
912        else:
913            xmlstr = domtree.toprettyxml(indent, newl)
914        xmlstr = xmlstr.decode(encoding).strip(os.linesep).replace(
915                '<?xml version="1.0" encoding="%s"?>' % encoding,
916                '<?xml version="1.0" encoding="%s" standalone="yes"?>' %
917                encoding)
918        domtree.unlink()
919        return xmlstr
920
921    def toxml(self, encoding="UTF-8"):
922        """ Return the manifest as XML """
923        domtree = self.todom()
924        # WARNING: The XML declaration has to follow the order
925        # version-encoding-standalone (standalone being optional), otherwise
926        # if it is embedded in an exe the exe will fail to launch!
927        # ('application configuration incorrect')
928        xmlstr = domtree.toxml(encoding).decode().replace(
929                '<?xml version="1.0" encoding="%s"?>' % encoding,
930                '<?xml version="1.0" encoding="%s" standalone="yes"?>' %
931                encoding)
932        domtree.unlink()
933        return xmlstr
934
935    def update_resources(self, dstpath, names=None, languages=None):
936        """ Update or add manifest resource in dll/exe file dstpath """
937        UpdateManifestResourcesFromXML(dstpath,
938                                       self.toprettyxml().encode("UTF-8"),
939                                       names, languages)
940
941    def writeprettyxml(self, filename_or_file=None, indent="  ", newl=os.linesep,
942                       encoding="UTF-8"):
943        """ Write the manifest as XML to a file or file object """
944        if not filename_or_file:
945            filename_or_file = self.filename
946        if isinstance(filename_or_file, string_types):
947            filename_or_file = open(filename_or_file, "wb")
948        xmlstr = self.toprettyxml(indent, newl, encoding)
949        with filename_or_file:
950            filename_or_file.write(xmlstr.encode())
951
952    def writexml(self, filename_or_file=None, indent="  ", newl=os.linesep,
953                 encoding="UTF-8"):
954        """ Write the manifest as XML to a file or file object """
955        if not filename_or_file:
956            filename_or_file = self.filename
957        if isinstance(filename_or_file, string_types):
958            filename_or_file = open(filename_or_file, "wb")
959        xmlstr = self.toxml(encoding)
960        with filename_or_file:
961            filename_or_file.write(xmlstr.encode())
962
963
964def ManifestFromResFile(filename, names=None, languages=None):
965    """ Create and return manifest instance from resource in dll/exe file """
966    res = GetManifestResources(filename, names, languages)
967    pth = []
968    if res and res[RT_MANIFEST]:
969        while isinstance(res, dict) and res.keys():
970            key = res.keys()[0]
971            pth.append(str(key))
972            res = res[key]
973    if isinstance(res, dict):
974        raise InvalidManifestError("No matching manifest resource found in '%s'" %
975                                   filename)
976    manifest = Manifest()
977    manifest.filename = ":".join([filename] + pth)
978    manifest.parse_string(res, False)
979    return manifest
980
981
982def ManifestFromDOM(domtree):
983    """ Create and return manifest instance from DOM tree """
984    manifest = Manifest()
985    manifest.load_dom(domtree)
986    return manifest
987
988
989def ManifestFromXML(xmlstr):
990    """ Create and return manifest instance from XML """
991    manifest = Manifest()
992    manifest.parse_string(xmlstr)
993    return manifest
994
995
996def ManifestFromXMLFile(filename_or_file):
997    """ Create and return manifest instance from file """
998    manifest = Manifest()
999    manifest.parse(filename_or_file)
1000    return manifest
1001
1002
1003def GetManifestResources(filename, names=None, languages=None):
1004    """ Get manifest resources from file """
1005    return winresource.GetResources(filename, [RT_MANIFEST], names, languages)
1006
1007
1008def UpdateManifestResourcesFromXML(dstpath, xmlstr, names=None,
1009                                   languages=None):
1010    """ Update or add manifest XML as resource in dstpath """
1011    logger.info("Updating manifest in %s", dstpath)
1012    if dstpath.lower().endswith(".exe"):
1013        name = 1
1014    else:
1015        name = 2
1016    winresource.UpdateResources(dstpath, xmlstr, RT_MANIFEST, names or [name],
1017                             languages or [0, "*"])
1018
1019
1020def UpdateManifestResourcesFromXMLFile(dstpath, srcpath, names=None,
1021                                       languages=None):
1022    """ Update or add manifest XML from srcpath as resource in dstpath """
1023    logger.info("Updating manifest from %s in %s", srcpath, dstpath)
1024    if dstpath.lower().endswith(".exe"):
1025        name = 1
1026    else:
1027        name = 2
1028    winresource.UpdateResourcesFromDataFile(dstpath, srcpath, RT_MANIFEST,
1029                                         names or [name],
1030                                         languages or [0, "*"])
1031
1032
1033def create_manifest(filename, manifest, console, uac_admin=False, uac_uiaccess=False):
1034    """
1035    Create assembly manifest.
1036    """
1037    if not manifest:
1038        manifest = ManifestFromXMLFile(filename)
1039        # /path/NAME.exe.manifest - split extension twice to get NAME.
1040        name = os.path.basename(filename)
1041        manifest.name = os.path.splitext(os.path.splitext(name)[0])[0]
1042    elif isinstance(manifest, string_types) and "<" in manifest:
1043        # Assume XML string
1044        manifest = ManifestFromXML(manifest)
1045    elif not isinstance(manifest, Manifest):
1046        # Assume filename
1047        manifest = ManifestFromXMLFile(manifest)
1048    dep_names = set([dep.name for dep in manifest.dependentAssemblies])
1049    if manifest.filename != filename:
1050        # Update dependent assemblies
1051        depmanifest = ManifestFromXMLFile(filename)
1052        for assembly in depmanifest.dependentAssemblies:
1053            if not assembly.name in dep_names:
1054                manifest.dependentAssemblies.append(assembly)
1055                dep_names.add(assembly.name)
1056    if (not console and
1057        not "Microsoft.Windows.Common-Controls" in dep_names):
1058        # Add Microsoft.Windows.Common-Controls to dependent assemblies
1059        manifest.dependentAssemblies.append(
1060            Manifest(type_="win32",
1061                 name="Microsoft.Windows.Common-Controls",
1062                 language="*",
1063                 processorArchitecture=processor_architecture(),
1064                 version=(6, 0, 0, 0),
1065                 publicKeyToken="6595b64144ccf1df")
1066            )
1067    if uac_admin:
1068        manifest.requestedExecutionLevel = 'requireAdministrator'
1069    if uac_uiaccess:
1070        manifest.uiAccess = True
1071
1072    # only write a new manifest if it is different from the old
1073    need_new = not os.path.exists(filename)
1074    if not need_new:
1075        old_xml = ManifestFromXMLFile(filename).toprettyxml()
1076        new_xml = manifest.toprettyxml().replace('\r','')
1077
1078        # this only works if PYTHONHASHSEED is set in environment
1079        need_new = (old_xml != new_xml)
1080    if need_new:
1081        manifest.writeprettyxml(filename)
1082
1083    return manifest
1084
1085
1086def processor_architecture():
1087    """
1088    Detect processor architecture for assembly manifest.
1089
1090    According to:
1091    http://msdn.microsoft.com/en-us/library/windows/desktop/aa374219(v=vs.85).aspx
1092    item processorArchitecture in assembly manifest is
1093
1094    'x86' - 32bit Windows
1095    'amd64' - 64bit Windows
1096    """
1097    if compat.architecture == '32bit':
1098        return 'x86'
1099    else:
1100        return 'amd64'
1101
1102
1103if __name__ == "__main__":
1104    dstpath = sys.argv[1]
1105    srcpath = sys.argv[2]
1106    UpdateManifestResourcesFromXMLFile(dstpath, srcpath)
1107