winmanifest.py 46 KB

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