123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119 |
- #-----------------------------------------------------------------------------
- # Copyright (c) 2013-2021, PyInstaller Development Team.
- #
- # Distributed under the terms of the GNU General Public License (version 2
- # or later) with exception for distributing the bootloader.
- #
- # The full license is in the file COPYING.txt, distributed with this software.
- #
- # SPDX-License-Identifier: (GPL-2.0-or-later WITH Bootloader-exception)
- #-----------------------------------------------------------------------------
- # Development notes kept for documentation purposes.
- #
- # Currently not implemented in the Manifest class:
- # * Validation (only very basic sanity checks are currently in place)
- # * comClass, typelib, comInterfaceProxyStub and windowClass child elements of
- # the file element
- # * comInterfaceExternalProxyStub and windowClass child elements of the
- # assembly element
- # * Application Configuration File and Multilanguage User Interface (MUI)
- # support when searching for assembly files
- #
- # Isolated Applications and Side-by-side Assemblies:
- # http://msdn.microsoft.com/en-us/library/dd408052%28VS.85%29.aspx
- #
- # Changelog:
- # 2009-12-17 fix: small glitch in toxml / toprettyxml methods (xml declaration
- # wasn't replaced when a different encodig than UTF-8 was used)
- # chg: catch xml.parsers.expat.ExpatError and re-raise as
- # ManifestXMLParseError
- # chg: support initialize option in parse method also
- #
- # 2009-12-13 fix: fixed os import
- # fix: skip invalid / empty dependent assemblies
- #
- # 2009-08-21 fix: Corrected assembly searching sequence for localized
- # assemblies
- # fix: Allow assemblies with no dependent files
- #
- # 2009-07-31 chg: Find private assemblies even if unversioned
- # add: Manifest.same_id method to check if two manifests have the
- # same assemblyIdentity
- #
- # 2009-07-30 fix: Potential failure in File.calc_hash method if hash
- # algorythm not supported
- # add: Publisher configuration (policy) support when searching for
- # assembly files
- # fix: Private assemblies are now actually found if present (and no
- # shared assembly exists)
- # add: Python 2.3 compatibility (oldest version supported by
- # pyinstaller)
- #
- # 2009-07-28 chg: Code cleanup, removed a bit of redundancy
- # add: silent mode (set silent attribute on module)
- # chg: Do not print messages in silent mode
- #
- # 2009-06-18 chg: Use glob instead of regular expression in Manifest.find_files
- #
- # 2009-05-04 fix: Don't fail if manifest has empty description
- # fix: Manifests created by the toxml, toprettyxml, writexml or
- # writeprettyxml methods are now correctly recognized by
- # Windows, which expects the XML declaration to be ordered
- # version-encoding-standalone (standalone being optional)
- # add: 'encoding' keyword argument in toxml, toprettyxml, writexml
- # and writeprettyxml methods
- # chg: UpdateManifestResourcesFromXML and
- # UpdateManifestResourcesFromXMLFile: set resource name
- # depending on file type ie. exe or dll
- # fix: typo in __main__: UpdateManifestResourcesFromDataFile
- # should have been UpdateManifestResourcesFromXMLFile
- #
- # 2009-03-21 First version
- """
- Create, parse and write MS Windows Manifest files.
- Find files which are part of an assembly, by searching shared and
- private assemblies.
- Update or add manifest resources in Win32 PE files.
- Commandline usage:
- winmanifest.py <dstpath> <xmlpath>
- Updates or adds manifest <xmlpath> as resource in Win32 PE file <dstpath>.
- """
- import os
- from glob import glob
- import hashlib
- import sys
- import xml
- from xml.dom import Node, minidom
- from xml.dom.minidom import Document, Element
- from PyInstaller import compat
- from PyInstaller.compat import string_types
- from PyInstaller import log as logging
- from PyInstaller.utils.win32 import winresource
- logger = logging.getLogger(__name__)
- LANGUAGE_NEUTRAL_NT5 = "x-ww"
- LANGUAGE_NEUTRAL_NT6 = "none"
- RT_MANIFEST = 24
- Document.aChild = Document.appendChild
- Document.cE = Document.createElement
- Document.cT = Document.createTextNode
- Document.getEByTN = Document.getElementsByTagName
- Element.aChild = Element.appendChild
- Element.getA = Element.getAttribute
- Element.getEByTN = Element.getElementsByTagName
- Element.remA = Element.removeAttribute
- Element.setA = Element.setAttribute
- def getChildElementsByTagName(self, tagName):
- """ Return child elements of type tagName if found, else [] """
- result = []
- for child in self.childNodes:
- if isinstance(child, Element):
- if child.tagName == tagName:
- result.append(child)
- return result
- def getFirstChildElementByTagName(self, tagName):
- """ Return the first element of type tagName if found, else None """
- for child in self.childNodes:
- if isinstance(child, Element):
- if child.tagName == tagName:
- return child
- return None
- Document.getCEByTN = getChildElementsByTagName
- Document.getFCEByTN = getFirstChildElementByTagName
- Element.getCEByTN = getChildElementsByTagName
- Element.getFCEByTN = getFirstChildElementByTagName
- class _Dummy:
- pass
- if winresource:
- _File = winresource.File
- else:
- _File = _Dummy
- class File(_File):
- """ A file referenced by an assembly inside a manifest. """
- def __init__(self, filename="", hashalg=None, hash=None, comClasses=None,
- typelibs=None, comInterfaceProxyStubs=None,
- windowClasses=None):
- if winresource:
- winresource.File.__init__(self, filename)
- else:
- self.filename = filename
- self.name = os.path.basename(filename)
- if hashalg:
- self.hashalg = hashalg.upper()
- else:
- self.hashalg = None
- if (os.path.isfile(filename) and hashalg and hashlib and
- hasattr(hashlib, hashalg.lower())):
- self.calc_hash()
- else:
- self.hash = hash
- self.comClasses = comClasses or [] # TO-DO: implement
- self.typelibs = typelibs or [] # TO-DO: implement
- self.comInterfaceProxyStubs = comInterfaceProxyStubs or [] # TO-DO: implement
- self.windowClasses = windowClasses or [] # TO-DO: implement
- def calc_hash(self, hashalg=None):
- """
- Calculate the hash of the file.
- Will be called automatically from the constructor if the file exists
- and hashalg is given (and supported), but may also be called manually
- e.g. to update the hash if the file has changed.
- """
- with open(self.filename, "rb") as fd:
- buf = fd.read()
- if hashalg:
- self.hashalg = hashalg.upper()
- self.hash = getattr(hashlib, self.hashalg.lower())(buf).hexdigest()
- def find(self, searchpath):
- logger.info("Searching for file %s", self.name)
- fn = os.path.join(searchpath, self.name)
- if os.path.isfile(fn):
- logger.info("Found file %s", fn)
- return fn
- else:
- logger.warning("No such file %s", fn)
- return None
- class InvalidManifestError(Exception):
- pass
- class ManifestXMLParseError(InvalidManifestError):
- pass
- class Manifest(object):
- # Manifests:
- # http://msdn.microsoft.com/en-us/library/aa375365%28VS.85%29.aspx
- """
- Manifest constructor.
- To build a basic manifest for your application:
- mf = Manifest(type='win32', name='YourAppName', language='*',
- processorArchitecture='x86', version=[1, 0, 0, 0])
- To write the XML to a manifest file:
- mf.writexml("YourAppName.exe.manifest")
- or
- mf.writeprettyxml("YourAppName.exe.manifest")
- """
- def __init__(self, manifestVersion=None, noInheritable=False,
- noInherit=False, type_=None, name=None, language=None,
- processorArchitecture=None, version=None,
- publicKeyToken=None, description=None,
- requestedExecutionLevel=None, uiAccess=None,
- dependentAssemblies=None, files=None,
- comInterfaceExternalProxyStubs=None):
- self.filename = None
- self.optional = None
- self.manifestType = "assembly"
- self.manifestVersion = manifestVersion or [1, 0]
- self.noInheritable = noInheritable
- self.noInherit = noInherit
- self.type = type_
- self.name = name
- self.language = language
- self.processorArchitecture = processorArchitecture
- self.version = version
- self.publicKeyToken = publicKeyToken
- # publicKeyToken:
- # A 16-character hexadecimal string that represents the last 8 bytes
- # of the SHA-1 hash of the public key under which the assembly is
- # signed. The public key used to sign the catalog must be 2048 bits
- # or greater. Required for all shared side-by-side assemblies.
- # http://msdn.microsoft.com/en-us/library/aa375692(VS.85).aspx
- self.applyPublisherPolicy = None
- self.description = None
- self.requestedExecutionLevel = requestedExecutionLevel
- self.uiAccess = uiAccess
- self.dependentAssemblies = dependentAssemblies or []
- self.bindingRedirects = []
- self.files = files or []
- self.comInterfaceExternalProxyStubs = comInterfaceExternalProxyStubs or [] # TO-DO: implement
- def __eq__(self, other):
- if isinstance(other, Manifest):
- return self.toxml() == other.toxml()
- if isinstance(other, string_types):
- return self.toxml() == other
- return False
- def __ne__(self, other):
- return not self.__eq__(other)
- def __repr__(self):
- return repr(self.toxml())
- def add_dependent_assembly(self, manifestVersion=None, noInheritable=False,
- noInherit=False, type_=None, name=None, language=None,
- processorArchitecture=None, version=None,
- publicKeyToken=None, description=None,
- requestedExecutionLevel=None, uiAccess=None,
- dependentAssemblies=None, files=None,
- comInterfaceExternalProxyStubs=None):
- """
- Shortcut for self.dependentAssemblies.append(Manifest(*args, **kwargs))
- """
- self.dependentAssemblies.append(Manifest(manifestVersion,
- noInheritable, noInherit, type_, name,
- language, processorArchitecture,
- version, publicKeyToken, description,
- requestedExecutionLevel, uiAccess,
- dependentAssemblies, files,
- comInterfaceExternalProxyStubs))
- if self.filename:
- # Enable search for private assembly by assigning bogus filename
- # (only the directory has to be correct)
- self.dependentAssemblies[-1].filename = ":".join((self.filename,
- name))
- def add_file(self, name="", hashalg="", hash="", comClasses=None,
- typelibs=None, comInterfaceProxyStubs=None,
- windowClasses=None):
- """ Shortcut for manifest.files.append """
- self.files.append(File(name, hashalg, hash, comClasses,
- typelibs, comInterfaceProxyStubs, windowClasses))
- @classmethod
- def get_winsxs_dir(cls):
- return os.path.join(compat.getenv("SystemRoot"), "WinSxS")
- @classmethod
- def get_manifest_dir(cls):
- winsxs = cls.get_winsxs_dir()
- if not os.path.isdir(winsxs):
- logger.warning("No such dir %s", winsxs)
- manifests = os.path.join(winsxs, "Manifests")
- if not os.path.isdir(manifests):
- logger.warning("No such dir %s", manifests)
- return manifests
- @classmethod
- def get_policy_dir(cls):
- winsxs = os.path.join(compat.getenv("SystemRoot"), "WinSxS")
- if sys.getwindowsversion() < (6, ):
- # Windows XP
- pcfiles = os.path.join(winsxs, "Policies")
- if not os.path.isdir(pcfiles):
- logger.warning("No such dir %s", pcfiles)
- else:
- # Vista or later
- pcfiles = cls.get_manifest_dir()
- return pcfiles
- def get_policy_redirect(self, language=None, version=None):
- # Publisher Configuration (aka policy)
- # A publisher configuration file globally redirects
- # applications and assemblies having a dependence on one
- # version of a side-by-side assembly to use another version of
- # the same assembly. This enables applications and assemblies
- # to use the updated assembly without having to rebuild all of
- # the affected applications.
- # http://msdn.microsoft.com/en-us/library/aa375680%28VS.85%29.aspx
- #
- # Under Windows XP and 2003, policies are stored as
- # <version>.policy files inside
- # %SystemRoot%\WinSxS\Policies\<name>
- # Under Vista and later, policies are stored as
- # <name>.manifest files inside %SystemRoot%\winsxs\Manifests
- redirected = False
- pcfiles = self.get_policy_dir()
- if version is None:
- version = self.version
- if language is None:
- language = self.language
- if os.path.isdir(pcfiles):
- logger.debug("Searching for publisher configuration %s ...",
- self.getpolicyid(True, language=language))
- if sys.getwindowsversion() < (6, ):
- # Windows XP
- policies = os.path.join(pcfiles,
- self.getpolicyid(True,
- language=language) +
- ".policy")
- else:
- # Vista or later
- policies = os.path.join(pcfiles,
- self.getpolicyid(True,
- language=language) +
- ".manifest")
- for manifestpth in glob(policies):
- if not os.path.isfile(manifestpth):
- logger.warning("Not a file %s", manifestpth)
- continue
- logger.info("Found %s", manifestpth)
- try:
- policy = ManifestFromXMLFile(manifestpth)
- except Exception:
- logger.error("Could not parse file %s",
- manifestpth, exc_info=1)
- else:
- logger.debug("Checking publisher policy for "
- "binding redirects")
- for assembly in policy.dependentAssemblies:
- if (not assembly.same_id(self, True) or
- assembly.optional):
- continue
- for redirect in assembly.bindingRedirects:
- old = "-".join([".".join([str(i)
- for i in
- part])
- for part in
- redirect[0]])
- new = ".".join([str(i)
- for i in
- redirect[1]])
- logger.debug("Found redirect for "
- "version(s) %s -> %s",
- old, new)
- if (version >= redirect[0][0] and
- version <= redirect[0][-1] and
- version != redirect[1]):
- logger.debug("Applying redirect "
- "%s -> %s",
- ".".join([str(i)
- for i in
- version]),
- new)
- version = redirect[1]
- redirected = True
- if not redirected:
- logger.debug("Publisher configuration not used")
- return version
- def find_files(self, ignore_policies=True):
- """ Search shared and private assemblies and return a list of files.
- If any files are not found, return an empty list.
- IMPORTANT NOTE: On some Windows systems, the dependency listed in the manifest
- will not actually be present, and finding its files will fail. This is because
- a newer version of the dependency is installed, and the manifest's dependency
- is being redirected to a newer version. To properly bundle the newer version of
- the assembly, you need to find the newer version by setting
- ignore_policies=False, and then either create a .config file for each bundled
- assembly, or modify each bundled assembly to point to the newer version.
- This is important because Python 2.7's app manifest depends on version 21022
- of the VC90 assembly, but the Python 2.7.9 installer will install version
- 30729 of the assembly along with a policy file that enacts the version redirect.
- """
- # Shared Assemblies:
- # http://msdn.microsoft.com/en-us/library/aa375996%28VS.85%29.aspx
- #
- # Private Assemblies:
- # http://msdn.microsoft.com/en-us/library/aa375674%28VS.85%29.aspx
- #
- # Assembly Searching Sequence:
- # http://msdn.microsoft.com/en-us/library/aa374224%28VS.85%29.aspx
- #
- # NOTE:
- # Multilanguage User Interface (MUI) support not yet implemented
- files = []
- languages = []
- if self.language not in (None, "", "*", "neutral"):
- languages.append(self.getlanguage())
- if "-" in self.language:
- # language-culture syntax, e.g. en-us
- # Add only the language part
- languages.append(self.language.split("-")[0])
- if self.language not in ("en-us", "en"):
- languages.append("en-us")
- if self.language != "en":
- languages.append("en")
- languages.append(self.getlanguage("*"))
- manifests = self.get_manifest_dir()
- winsxs = self.get_winsxs_dir()
- for language in languages:
- version = self.version
- # Search for publisher configuration
- if not ignore_policies and version:
- version = self.get_policy_redirect(language, version)
- # Search for assemblies according to assembly searching sequence
- paths = []
- if os.path.isdir(manifests):
- # Add winsxs search paths
- # Search for manifests in Windows\WinSxS\Manifests
- paths.extend(glob(os.path.join(manifests,
- self.getid(language=language,
- version=version) +
- "_*.manifest")))
- if self.filename:
- # Add private assembly search paths
- # Search for manifests inside assembly folders that are in
- # the same folder as the depending manifest.
- dirnm = os.path.dirname(self.filename)
- if language in (LANGUAGE_NEUTRAL_NT5,
- LANGUAGE_NEUTRAL_NT6):
- for ext in (".dll", ".manifest"):
- paths.extend(glob(os.path.join(dirnm, self.name + ext)))
- paths.extend(glob(os.path.join(dirnm, self.name,
- self.name + ext)))
- else:
- for ext in (".dll", ".manifest"):
- paths.extend(glob(os.path.join(dirnm, language,
- self.name + ext)))
- for ext in (".dll", ".manifest"):
- paths.extend(glob(os.path.join(dirnm, language,
- self.name,
- self.name + ext)))
- logger.info("Searching for assembly %s ...",
- self.getid(language=language, version=version))
- for manifestpth in paths:
- if not os.path.isfile(manifestpth):
- logger.warning("Not a file %s", manifestpth)
- continue
- assemblynm = os.path.basename(
- os.path.splitext(manifestpth)[0])
- try:
- if manifestpth.endswith(".dll"):
- logger.info("Found manifest in %s", manifestpth)
- manifest = ManifestFromResFile(manifestpth, [1])
- else:
- logger.info("Found manifest %s", manifestpth)
- manifest = ManifestFromXMLFile(manifestpth)
- except Exception:
- logger.error("Could not parse manifest %s",
- manifestpth, exc_info=1)
- else:
- if manifestpth.startswith(winsxs):
- # Manifest is in Windows\WinSxS\Manifests, so assembly
- # dir is in Windows\WinSxS
- assemblydir = os.path.join(winsxs, assemblynm)
- if not os.path.isdir(assemblydir):
- logger.warning("No such dir %s", assemblydir)
- logger.warning("Assembly incomplete")
- return []
- else:
- # Manifest is inside assembly dir.
- assemblydir = os.path.dirname(manifestpth)
- files.append(manifestpth)
- for file_ in self.files or manifest.files:
- fn = file_.find(assemblydir)
- if fn:
- files.append(fn)
- else:
- # If any of our files does not exist,
- # the assembly is incomplete
- logger.warning("Assembly incomplete")
- return []
- return files
- logger.warning("Assembly not found")
- return []
- def getid(self, language=None, version=None):
- """
- Return an identification string which uniquely names a manifest.
- This string is a combination of the manifest's processorArchitecture,
- name, publicKeyToken, version and language.
- Arguments:
- version (tuple or list of integers) - If version is given, use it
- instead of the manifest's
- version.
- """
- if not self.name:
- logger.warning("Assembly metadata incomplete")
- return ""
- id = []
- if self.processorArchitecture:
- id.append(self.processorArchitecture)
- id.append(self.name)
- if self.publicKeyToken:
- id.append(self.publicKeyToken)
- if version or self.version:
- id.append(".".join([str(i) for i in version or self.version]))
- if not language:
- language = self.getlanguage()
- if language:
- id.append(language)
- return "_".join(id)
- def getlanguage(self, language=None, windowsversion=None):
- """
- Get and return the manifest's language as string.
- Can be either language-culture e.g. 'en-us' or a string indicating
- language neutrality, e.g. 'x-ww' on Windows XP or 'none' on Vista
- and later.
- """
- if not language:
- language = self.language
- if language in (None, "", "*", "neutral"):
- return (LANGUAGE_NEUTRAL_NT5,
- LANGUAGE_NEUTRAL_NT6)[(windowsversion or
- sys.getwindowsversion()) >= (6, )]
- return language
- def getpolicyid(self, fuzzy=True, language=None, windowsversion=None):
- """
- Return an identification string which can be used to find a policy.
- This string is a combination of the manifest's processorArchitecture,
- major and minor version, name, publicKeyToken and language.
- Arguments:
- fuzzy (boolean) - If False, insert the full version in
- the id string. Default is True (omit).
- windowsversion - If not specified (or None), default to
- (tuple or list of integers) sys.getwindowsversion().
- """
- if not self.name:
- logger.warning("Assembly metadata incomplete")
- return ""
- id = []
- if self.processorArchitecture:
- id.append(self.processorArchitecture)
- name = []
- name.append("policy")
- if self.version:
- name.append(str(self.version[0]))
- name.append(str(self.version[1]))
- name.append(self.name)
- id.append(".".join(name))
- if self.publicKeyToken:
- id.append(self.publicKeyToken)
- if self.version and (windowsversion or sys.getwindowsversion()) >= (6, ):
- # Vista and later
- if fuzzy:
- id.append("*")
- else:
- id.append(".".join([str(i) for i in self.version]))
- if not language:
- language = self.getlanguage(windowsversion=windowsversion)
- if language:
- id.append(language)
- id.append("*")
- id = "_".join(id)
- if self.version and (windowsversion or sys.getwindowsversion()) < (6, ):
- # Windows XP
- if fuzzy:
- id = os.path.join(id, "*")
- else:
- id = os.path.join(id, ".".join([str(i) for i in self.version]))
- return id
- def load_dom(self, domtree, initialize=True):
- """
- Load manifest from DOM tree.
- If initialize is True (default), reset existing attributes first.
- """
- if domtree.nodeType == Node.DOCUMENT_NODE:
- rootElement = domtree.documentElement
- elif domtree.nodeType == Node.ELEMENT_NODE:
- rootElement = domtree
- else:
- raise InvalidManifestError("Invalid root element node type " +
- str(rootElement.nodeType) +
- " - has to be one of (DOCUMENT_NODE, "
- "ELEMENT_NODE)")
- allowed_names = ("assembly", "assemblyBinding", "configuration",
- "dependentAssembly")
- if rootElement.tagName not in allowed_names:
- raise InvalidManifestError(
- "Invalid root element <%s> - has to be one of <%s>" %
- (rootElement.tagName, ">, <".join(allowed_names)))
- # logger.info("loading manifest metadata from element <%s>", rootElement.tagName)
- if rootElement.tagName == "configuration":
- for windows in rootElement.getCEByTN("windows"):
- for assemblyBinding in windows.getCEByTN("assemblyBinding"):
- self.load_dom(assemblyBinding, initialize)
- else:
- if initialize:
- self.__init__()
- self.manifestType = rootElement.tagName
- self.manifestVersion = [int(i)
- for i in
- (rootElement.getA("manifestVersion") or
- "1.0").split(".")]
- self.noInheritable = bool(rootElement.getFCEByTN("noInheritable"))
- self.noInherit = bool(rootElement.getFCEByTN("noInherit"))
- for assemblyIdentity in rootElement.getCEByTN("assemblyIdentity"):
- self.type = assemblyIdentity.getA("type") or None
- self.name = assemblyIdentity.getA("name") or None
- self.language = assemblyIdentity.getA("language") or None
- self.processorArchitecture = assemblyIdentity.getA(
- "processorArchitecture") or None
- version = assemblyIdentity.getA("version")
- if version:
- self.version = tuple(int(i) for i in version.split("."))
- self.publicKeyToken = assemblyIdentity.getA("publicKeyToken") or None
- for publisherPolicy in rootElement.getCEByTN("publisherPolicy"):
- self.applyPublisherPolicy = (publisherPolicy.getA("apply") or
- "").lower() == "yes"
- for description in rootElement.getCEByTN("description"):
- if description.firstChild:
- self.description = description.firstChild.wholeText
- for trustInfo in rootElement.getCEByTN("trustInfo"):
- for security in trustInfo.getCEByTN("security"):
- for reqPriv in security.getCEByTN("requestedPrivileges"):
- for reqExeLev in reqPriv.getCEByTN("requestedExecutionLevel"):
- self.requestedExecutionLevel = reqExeLev.getA("level")
- self.uiAccess = (reqExeLev.getA("uiAccess") or
- "").lower() == "true"
- if rootElement.tagName == "assemblyBinding":
- dependencies = [rootElement]
- else:
- dependencies = rootElement.getCEByTN("dependency")
- for dependency in dependencies:
- for dependentAssembly in dependency.getCEByTN(
- "dependentAssembly"):
- manifest = ManifestFromDOM(dependentAssembly)
- if not manifest.name:
- # invalid, skip
- continue
- manifest.optional = (dependency.getA("optional") or
- "").lower() == "yes"
- self.dependentAssemblies.append(manifest)
- if self.filename:
- # Enable search for private assembly by assigning bogus
- # filename (only the directory has to be correct)
- self.dependentAssemblies[-1].filename = ":".join(
- (self.filename, manifest.name))
- for bindingRedirect in rootElement.getCEByTN("bindingRedirect"):
- oldVersion = tuple(tuple(int(i) for i in part.split("."))
- for part in
- bindingRedirect.getA("oldVersion").split("-"))
- newVersion = tuple(int(i)
- for i in
- bindingRedirect.getA("newVersion").split("."))
- self.bindingRedirects.append((oldVersion, newVersion))
- for file_ in rootElement.getCEByTN("file"):
- self.add_file(name=file_.getA("name"),
- hashalg=file_.getA("hashalg"),
- hash=file_.getA("hash"))
- def parse(self, filename_or_file, initialize=True):
- """ Load manifest from file or file object """
- if isinstance(filename_or_file, string_types):
- filename = filename_or_file
- else:
- filename = filename_or_file.name
- try:
- domtree = minidom.parse(filename_or_file)
- except xml.parsers.expat.ExpatError as e:
- args = ['\n File "%r"\n ' % filename, str(e.args[0])]
- raise ManifestXMLParseError(" ".join(args)) from e
- if initialize:
- self.__init__()
- self.filename = filename
- self.load_dom(domtree, False)
- def parse_string(self, xmlstr, initialize=True):
- """ Load manifest from XML string """
- try:
- domtree = minidom.parseString(xmlstr)
- except xml.parsers.expat.ExpatError as e:
- raise ManifestXMLParseError(e) from e
- self.load_dom(domtree, initialize)
- def same_id(self, manifest, skip_version_check=False):
- """
- Return a bool indicating if another manifest has the same identitiy.
- This is done by comparing language, name, processorArchitecture,
- publicKeyToken, type and version.
- """
- if skip_version_check:
- version_check = True
- else:
- version_check = self.version == manifest.version
- return (self.language == manifest.language and
- self.name == manifest.name and
- self.processorArchitecture == manifest.processorArchitecture and
- self.publicKeyToken == manifest.publicKeyToken and
- self.type == manifest.type and
- version_check)
- def todom(self):
- """ Return the manifest as DOM tree """
- doc = Document()
- docE = doc.cE(self.manifestType)
- if self.manifestType == "assemblyBinding":
- cfg = doc.cE("configuration")
- win = doc.cE("windows")
- win.aChild(docE)
- cfg.aChild(win)
- doc.aChild(cfg)
- else:
- doc.aChild(docE)
- if self.manifestType != "dependentAssembly":
- docE.setA("xmlns", "urn:schemas-microsoft-com:asm.v1")
- if self.manifestType != "assemblyBinding":
- docE.setA("manifestVersion",
- ".".join([str(i) for i in self.manifestVersion]))
- if self.noInheritable:
- docE.aChild(doc.cE("noInheritable"))
- if self.noInherit:
- docE.aChild(doc.cE("noInherit"))
- aId = doc.cE("assemblyIdentity")
- if self.type:
- aId.setAttribute("type", self.type)
- if self.name:
- aId.setAttribute("name", self.name)
- if self.language:
- aId.setAttribute("language", self.language)
- if self.processorArchitecture:
- aId.setAttribute("processorArchitecture",
- self.processorArchitecture)
- if self.version:
- aId.setAttribute("version",
- ".".join([str(i) for i in self.version]))
- if self.publicKeyToken:
- aId.setAttribute("publicKeyToken", self.publicKeyToken)
- if aId.hasAttributes():
- docE.aChild(aId)
- else:
- aId.unlink()
- if self.applyPublisherPolicy != None:
- ppE = doc.cE("publisherPolicy")
- if self.applyPublisherPolicy:
- ppE.setA("apply", "yes")
- else:
- ppE.setA("apply", "no")
- docE.aChild(ppE)
- if self.description:
- descE = doc.cE("description")
- descE.aChild(doc.cT(self.description))
- docE.aChild(descE)
- if self.requestedExecutionLevel in ("asInvoker", "highestAvailable",
- "requireAdministrator"):
- tE = doc.cE("trustInfo")
- tE.setA("xmlns", "urn:schemas-microsoft-com:asm.v3")
- sE = doc.cE("security")
- rpE = doc.cE("requestedPrivileges")
- relE = doc.cE("requestedExecutionLevel")
- relE.setA("level", self.requestedExecutionLevel)
- if self.uiAccess:
- relE.setA("uiAccess", "true")
- else:
- relE.setA("uiAccess", "false")
- rpE.aChild(relE)
- sE.aChild(rpE)
- tE.aChild(sE)
- docE.aChild(tE)
- if self.dependentAssemblies:
- for assembly in self.dependentAssemblies:
- if self.manifestType != "assemblyBinding":
- dE = doc.cE("dependency")
- if assembly.optional:
- dE.setAttribute("optional", "yes")
- daE = doc.cE("dependentAssembly")
- adom = assembly.todom()
- for child in adom.documentElement.childNodes:
- daE.aChild(child.cloneNode(False))
- adom.unlink()
- if self.manifestType != "assemblyBinding":
- dE.aChild(daE)
- docE.aChild(dE)
- else:
- docE.aChild(daE)
- if self.bindingRedirects:
- for bindingRedirect in self.bindingRedirects:
- brE = doc.cE("bindingRedirect")
- brE.setAttribute("oldVersion",
- "-".join([".".join([str(i)
- for i in
- part])
- for part in
- bindingRedirect[0]]))
- brE.setAttribute("newVersion",
- ".".join([str(i) for i in bindingRedirect[1]]))
- docE.aChild(brE)
- if self.files:
- for file_ in self.files:
- fE = doc.cE("file")
- for attr in ("name", "hashalg", "hash"):
- val = getattr(file_, attr)
- if val:
- fE.setA(attr, val)
- docE.aChild(fE)
- # Add compatibility section: http://stackoverflow.com/a/10158920
- cE = doc.cE("compatibility")
- cE.setAttribute("xmlns", "urn:schemas-microsoft-com:compatibility.v1")
- caE = doc.cE("application")
- supportedOS_guids = {"Vista":"{e2011457-1546-43c5-a5fe-008deee3d3f0}",
- "7" :"{35138b9a-5d96-4fbd-8e2d-a2440225f93a}",
- "8" :"{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}",
- "8.1" :"{1f676c76-80e1-4239-95bb-83d0f6d0da78}",
- "10" :"{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"}
- for guid in supportedOS_guids.values():
- sosE = doc.cE("supportedOS")
- sosE.setAttribute("Id", guid)
- caE.aChild(sosE)
- cE.aChild(caE)
- docE.aChild(cE)
- # Add application.windowsSettings section to enable longPathAware
- # option (issue #5423).
- if self.manifestType == "assembly":
- aE = doc.cE("application")
- aE.setAttribute("xmlns", "urn:schemas-microsoft-com:asm.v3")
- wsE = doc.cE("windowsSettings")
- lpaE = doc.cE("longPathAware")
- lpaE.setAttribute(
- "xmlns",
- "http://schemas.microsoft.com/SMI/2016/WindowsSettings"
- )
- lpaT = doc.cT("true")
- lpaE.aChild(lpaT)
- wsE.aChild(lpaE)
- aE.aChild(wsE)
- docE.aChild(aE)
- return doc
- def toprettyxml(self, indent=" ", newl=os.linesep, encoding="UTF-8"):
- """ Return the manifest as pretty-printed XML """
- domtree = self.todom()
- # WARNING: The XML declaration has to follow the order
- # version-encoding-standalone (standalone being optional), otherwise
- # if it is embedded in an exe the exe will fail to launch!
- # ('application configuration incorrect')
- xmlstr = domtree.toprettyxml(indent, newl, encoding)
- xmlstr = xmlstr.decode(encoding).strip(os.linesep).replace(
- '<?xml version="1.0" encoding="%s"?>' % encoding,
- '<?xml version="1.0" encoding="%s" standalone="yes"?>' %
- encoding)
- domtree.unlink()
- return xmlstr
- def toxml(self, encoding="UTF-8"):
- """ Return the manifest as XML """
- domtree = self.todom()
- # WARNING: The XML declaration has to follow the order
- # version-encoding-standalone (standalone being optional), otherwise
- # if it is embedded in an exe the exe will fail to launch!
- # ('application configuration incorrect')
- xmlstr = domtree.toxml(encoding).decode().replace(
- '<?xml version="1.0" encoding="%s"?>' % encoding,
- '<?xml version="1.0" encoding="%s" standalone="yes"?>' %
- encoding)
- domtree.unlink()
- return xmlstr
- def update_resources(self, dstpath, names=None, languages=None):
- """ Update or add manifest resource in dll/exe file dstpath """
- UpdateManifestResourcesFromXML(dstpath,
- self.toprettyxml().encode("UTF-8"),
- names, languages)
- def writeprettyxml(self, filename_or_file=None, indent=" ", newl=os.linesep,
- encoding="UTF-8"):
- """ Write the manifest as XML to a file or file object """
- if not filename_or_file:
- filename_or_file = self.filename
- if isinstance(filename_or_file, string_types):
- filename_or_file = open(filename_or_file, "wb")
- xmlstr = self.toprettyxml(indent, newl, encoding)
- with filename_or_file:
- filename_or_file.write(xmlstr.encode())
- def writexml(self, filename_or_file=None, indent=" ", newl=os.linesep,
- encoding="UTF-8"):
- """ Write the manifest as XML to a file or file object """
- if not filename_or_file:
- filename_or_file = self.filename
- if isinstance(filename_or_file, string_types):
- filename_or_file = open(filename_or_file, "wb")
- xmlstr = self.toxml(encoding)
- with filename_or_file:
- filename_or_file.write(xmlstr.encode())
- def ManifestFromResFile(filename, names=None, languages=None):
- """ Create and return manifest instance from resource in dll/exe file """
- res = GetManifestResources(filename, names, languages)
- pth = []
- if res and res[RT_MANIFEST]:
- while isinstance(res, dict) and res.keys():
- key, res = next(iter(res.items()))
- pth.append(str(key))
- if isinstance(res, dict):
- raise InvalidManifestError("No matching manifest resource found in '%s'" %
- filename)
- manifest = Manifest()
- manifest.filename = ":".join([filename] + pth)
- manifest.parse_string(res, False)
- return manifest
- def ManifestFromDOM(domtree):
- """ Create and return manifest instance from DOM tree """
- manifest = Manifest()
- manifest.load_dom(domtree)
- return manifest
- def ManifestFromXML(xmlstr):
- """ Create and return manifest instance from XML """
- manifest = Manifest()
- manifest.parse_string(xmlstr)
- return manifest
- def ManifestFromXMLFile(filename_or_file):
- """ Create and return manifest instance from file """
- manifest = Manifest()
- manifest.parse(filename_or_file)
- return manifest
- def GetManifestResources(filename, names=None, languages=None):
- """ Get manifest resources from file """
- return winresource.GetResources(filename, [RT_MANIFEST], names, languages)
- def UpdateManifestResourcesFromXML(dstpath, xmlstr, names=None,
- languages=None):
- """ Update or add manifest XML as resource in dstpath """
- logger.info("Updating manifest in %s", dstpath)
- if dstpath.lower().endswith(".exe"):
- name = 1
- else:
- name = 2
- winresource.UpdateResources(dstpath, xmlstr, RT_MANIFEST, names or [name],
- languages or [0, "*"])
- def UpdateManifestResourcesFromXMLFile(dstpath, srcpath, names=None,
- languages=None):
- """ Update or add manifest XML from srcpath as resource in dstpath """
- logger.info("Updating manifest from %s in %s", srcpath, dstpath)
- if dstpath.lower().endswith(".exe"):
- name = 1
- else:
- name = 2
- winresource.UpdateResourcesFromDataFile(dstpath, srcpath, RT_MANIFEST,
- names or [name],
- languages or [0, "*"])
- def create_manifest(filename, manifest, console, uac_admin=False, uac_uiaccess=False):
- """
- Create assembly manifest.
- """
- if not manifest:
- manifest = ManifestFromXMLFile(filename)
- # /path/NAME.exe.manifest - split extension twice to get NAME.
- name = os.path.basename(filename)
- manifest.name = os.path.splitext(os.path.splitext(name)[0])[0]
- elif isinstance(manifest, string_types) and "<" in manifest:
- # Assume XML string
- manifest = ManifestFromXML(manifest)
- elif not isinstance(manifest, Manifest):
- # Assume filename
- manifest = ManifestFromXMLFile(manifest)
- dep_names = set([dep.name for dep in manifest.dependentAssemblies])
- if manifest.filename != filename:
- # Update dependent assemblies
- depmanifest = ManifestFromXMLFile(filename)
- for assembly in depmanifest.dependentAssemblies:
- if not assembly.name in dep_names:
- manifest.dependentAssemblies.append(assembly)
- dep_names.add(assembly.name)
- if (not console and
- not "Microsoft.Windows.Common-Controls" in dep_names):
- # Add Microsoft.Windows.Common-Controls to dependent assemblies
- manifest.dependentAssemblies.append(
- Manifest(type_="win32",
- name="Microsoft.Windows.Common-Controls",
- language="*",
- processorArchitecture=processor_architecture(),
- version=(6, 0, 0, 0),
- publicKeyToken="6595b64144ccf1df")
- )
- if uac_admin:
- manifest.requestedExecutionLevel = 'requireAdministrator'
- else:
- manifest.requestedExecutionLevel = 'asInvoker'
- if uac_uiaccess:
- manifest.uiAccess = True
- # only write a new manifest if it is different from the old
- need_new = not os.path.exists(filename)
- if not need_new:
- old_xml = ManifestFromXMLFile(filename).toprettyxml().replace('\r', '')
- new_xml = manifest.toprettyxml().replace('\r', '')
- # this only works if PYTHONHASHSEED is set in environment
- need_new = (old_xml != new_xml)
- if need_new:
- manifest.writeprettyxml(filename)
- return manifest
- def processor_architecture():
- """
- Detect processor architecture for assembly manifest.
- According to:
- http://msdn.microsoft.com/en-us/library/windows/desktop/aa374219(v=vs.85).aspx
- item processorArchitecture in assembly manifest is
- 'x86' - 32bit Windows
- 'amd64' - 64bit Windows
- """
- if compat.architecture == '32bit':
- return 'x86'
- else:
- return 'amd64'
- if __name__ == "__main__":
- dstpath = sys.argv[1]
- srcpath = sys.argv[2]
- UpdateManifestResourcesFromXMLFile(dstpath, srcpath)
|