3

I am running into an issue with a script I've built into an executable using PyInstaller. The script works fine when run as a Python script, but when run as a PyInstaller application, it fails when it encounters a file whose path is longer than 260 characters.

I understand this is due to a limitation of Windows and support for longer paths must be opted into both in the registry and using an application manifest which enables the longPathAware setting. Incidentally, the reason this works in Python itself is because at Python 3.6 the developers enabled this setting for python.exe and pythonw.exe.

So far I've done all of this, and indeed it works if I place the following manifest file alongside the built PyInstaller application (using --onefile mode):

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
  <application xmlns="urn:schemas-microsoft-com:asm.v3">
    <windowsSettings>
      <longPathAware xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">true</longPathAware>
    </windowsSettings>
  </application>
</assembly>

However, to keep the application self-contained and portable for end users, I am trying to avoid having to use an external manifest file and instead get PyInstaller to embed this custom manifest into the executable.

The --manifest option purportedly does this -- at least since PyInstaller 3.5, per the changelog and PR #3746:

  • (Windows) Fix UAC in one-file mode by embedding the manifest. (#1729, #3746)

But when specified, the custom manifest file seems to be ignored, as the application continues to fail on long paths without the external manifest file in place, and inspecting the bundled manifest file in --onedir mode, it just looks like it's ignored the custom one and created this one instead:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
  <dependency>
    <dependentAssembly>
      <assemblyIdentity language="*" name="Microsoft.Windows.Common-Controls" processorArchitecture="*" publicKeyToken="6595b64144ccf1df" type="win32" version="6.0.0.0"/>
      <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1"/>
    </dependentAssembly>
  </dependency>
  <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
    <application>
      <supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/>
      <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
      <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
      <supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
      <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
    </application>
  </compatibility>
</assembly>

Am I doing or understanding something wrong? Does the --manifest option not do what I am thinking it should? Is this a bug?

This answer describes modifying the PyInstaller source code to override the embedded manifest creation. Is this still necessary?

I also came across a project on GitHub that seems to be having the same problem; the author of this PR states:

Note that PyInstaller does not understand the longPathAware setting yet and strips it out of the manifest.

I don't know if that is true, but I think it does lend some weight to this being a bug.

I am using PyInstaller 3.6, Python 3.7.2, and Windows 10 version 1809.

blah238
  • 1,796
  • 2
  • 18
  • 51
  • Is your program really only supposed to work correctly with long paths in Windows 10 systems that have long DOS paths enabled in the registry? Maybe you should consider normalizing to use "\\?\" extended paths, which allows using long paths all the way back to Windows NT 4 (1996). – Eryk Sun Jan 17 '20 at 06:46
  • @ErykSun Yeah I've read about that prefix but it seems to have its own compatibility issues (also, its purpose is not specifically for enabling long paths, but rather to disable parsing of path strings). In my case, this program will only be used by Windows 10 PCs and we could use a group policy for it. However I'm also interested in this because there are other useful things that can be put in the manifest that PyInstaller ought to support, or at least allow without overwriting them, e.g. `dpiAware`. At least it is possible to use an external manifest, so not too onerous, just not ideal. – blah238 Jan 17 '20 at 10:17
  • When embedded, the application manifest is just a PE resource. You could replace it in the file as a follow-up build step -- at least until PyInstaller gets fixed. – Eryk Sun Jan 17 '20 at 15:30
  • I considered that but apparently using `mt.exe` on PyInstaller apps breaks them: https://stackoverflow.com/a/14654743 -- is there another method you know of? – blah238 Jan 17 '20 at 16:49
  • You could try embedding a manifest on the source executable that it concatenates with the zipped application. – Eryk Sun Jan 17 '20 at 19:38
  • I think that's essentially what this answer describes: https://stackoverflow.com/questions/43823824/how-do-you-add-a-manifest-to-pyinstaller-compiled-exe/47806933#47806933 They had to modify the PyInstaller source to do it though. Is there another way? – blah238 Jan 18 '20 at 22:36
  • I was thinking to ignore any options to modify the manifest at runtime and just modify the manifest that's in the original executables in the package directory. It's still modifying files in the package, however. – Eryk Sun Jan 18 '20 at 23:14

1 Answers1

1

This is now implemented in PyInstaller 4.2: https://pyinstaller.readthedocs.io/en/v4.2/CHANGES.html

(Windows) Enable longPathAware option in built application’s manifest in order to support long file paths on Windows 10 v.1607 and later. (#5424)

blah238
  • 1,796
  • 2
  • 18
  • 51
  • how exactly do you enable this option? do you need to modify the application manifest post compile? If so, how does that look like? – joshp Mar 17 '22 at 17:58
  • I see in your original post you have `true`, but I'm wondering if you can simply specify as a command line argument during compilation or something? – joshp Mar 17 '22 at 18:04