1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084 |
- #-----------------------------------------------------------------------------
- # 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 hashlib
- import os
- import sys
- import xml
- from glob import glob
- from xml.dom import Node, minidom
- from xml.dom.minidom import Document, Element
- from PyInstaller import compat
- from PyInstaller import log as logging
- from PyInstaller.compat import string_types
- 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 [] # TODO: implement
- self.typelibs = typelibs or [] # TODO: implement
- self.comInterfaceProxyStubs = comInterfaceProxyStubs or [] # TODO: implement
- self.windowClasses = windowClasses or [] # TODO: 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,
- manifestType="assembly",
- 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 = manifestType
- 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 [] # TODO: 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 redirect[0][0] <= 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 (tuple or list of integers or None):
- If not specified (or None), default to 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 %s - has to be one of (DOCUMENT_NODE, ELEMENT_NODE)" %
- rootElement.nodeType
- )
- 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 is not 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
- if self.manifestType == "assembly":
- 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 assembly.name not in dep_names:
- manifest.dependentAssemblies.append(assembly)
- dep_names.add(assembly.name)
- if "Microsoft.Windows.Common-Controls" not in dep_names:
- # Add Microsoft.Windows.Common-Controls to dependent assemblies.
- manifest.dependentAssemblies.append(
- Manifest(
- manifestType='dependentAssembly',
- 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)
|