winmanifest.py 43 KB

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