c++windowsfilesystemsc++17long-path

How to enable "Long Path Aware" behavior for setting the current directory in a C++ windows console app


In a C++ console application on windows, i'm trying to break the MAX_PATH restriction for the SetCurrentDirectoryW function.

There are many similar questions already asked but none got a usable answer:

Doc Research

Apparently this might be possible by using application manifest files. The docs for SetCurrentDirectoryW state:

Tip Starting with Windows 10, version 1607, for the unicode version of this function (SetCurrentDirectoryW), you can opt-in to remove the MAX_PATH limitation. See the "Maximum Path Length Limitation" section of Naming Files, Paths, and Namespaces for details.

And from the general docs about Manifests:

Manifests are XML files that accompany and describe side-by-side assemblies or isolated applications. ... Application Manifests describe isolated applications. They are used to manage the names and versions of shared side-by-side assemblies that the application should bind to at run time. Application manifests are copied into the same folder as the application executable file or included as a resource in the application's executable file.

The docs about Assembly Manifests point out the difference to Application Manifests once more:

As a resource in a DLL, the assembly is available for the private use of the DLL. An assembly manifest cannot be included as a resource in an EXE. An EXE file may include an Application Manifests as a resource.

The docs about Application Manifests list the assembly and assemblyIdentity elements as required:

All other elements and attributes seem to be optional.

Additional requirements for the assembly element are:

Its first subelement must be a noInherit or assemblyIdentity element. The assembly element must be in the namespace "urn:schemas-microsoft-com:asm.v1". Child elements of the assembly must also be in this namespace, by inheritance or by tagging.

Finally, there's the longPathAware element which is optional but which should hopefully allow SetCurrentDirectoryW to use long paths:

Enables long paths that exceed MAX_PATH in length. This element is supported in Windows 10, version 1607, and later. For more information, see this article.

The section in the docs shows this example xml manifest:

<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
 ...
  <asmv3:application>
    <asmv3:windowsSettings xmlns:ws2="http://schemas.microsoft.com/SMI/2016/WindowsSettings">
      <ws2:longPathAware>true</ws2:longPathAware>
    </asmv3:windowsSettings>
  </asmv3:application>
 ...
</assembly>

It doesn't seem to exactly follow the rules from the assembly element:

The assembly element must be in the namespace "urn:schemas-microsoft-com:asm.v1". Child elements of the assembly must also be in this namespace, by inheritance or by tagging.

Tests

The test environment is:

The test application is a new c++ console application where i made the following change to the Additional Manifest Files: Additional Manifest Files

The source code is very simple; it's also on godbolt but without the manifest:

#include <iostream>
#include <string>
#include <windows.h> 

int main() {
    std::wstring const path = LR"(H:\test\longPaths\manySmallLongPaths\12345678\12345678\12345678\12345678\12345678\12345678\12345678\12345678\12345678\12345678\12345678\12345678\12345678\12345678\12345678\12345678\12345678\12345678\12345678\12345678\12345678\12345678\12345678\12345678\12345678\12345678\12345678\12345678\12345678\12345678\12345678\12345678\12345678\12345678\12345678\12345678\12345678\12345678\)";
    std::wstring const path2 = LR"(\\?\)" + path;
    if (!SetCurrentDirectoryW(path.c_str())) {
        printf("Exe SetCurrentDirectory failed 1 - (%d)\n", GetLastError());
        if (!SetCurrentDirectoryW(path2.c_str()))
            printf("Exe SetCurrentDirectory failed 2 - (%d)\n", GetLastError());
    }
}

Trying to put this all together, i think the following file might be a valid Application Manifest:

<?xml version='1.0' encoding='UTF-8' standalone='yes'?>
<assembly xmlns:asmv1='urn:schemas-microsoft-com:asm.v1' manifestVersion='1.0'>
    <asmv1:assemblyIdentity type='win32' name='my.test.app' version='1.0.0.0' />
    <asmv1:application>
        <asmv1:windowsSettings>
            <asmv1:longPathAware>true</asmv1:longPathAware>
        </asmv1:windowsSettings>
    </asmv1:application>
</assembly>

But compiling and starting the application results in the following error:

The application has failed to start because its side-by-side configuration is incorrect. Please see the application event log or use the command-line sxstrace.exe tool for more detail.

Using sxstrace.exe reveals:

INFO: Parsing Manifest File C:\test\longPaths.exe.
        INFO: Manifest Definition Identity is my.test.app,type="win32",version="1.0.0.0".
        ERROR: Line 2: The element ws1:longPathAware appears as a child of element urn:schemas-microsoft-com:asm.v1^windowsSettings which is not supported by this version of Windows.
ERROR: Activation Context generation failed.

Maybe

Child elements of the assembly must also be in this namespace

is not entirely true (anymore) or i interpreted this wrong. Trying with a completed example from the longPathAware element:

<assembly xmlns:asmv1="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
    <asmv1:assemblyIdentity type='win32' name='my.test.app' version='1.0.0.0' />
    <asmv3:application>
        <asmv3:windowsSettings xmlns:ws2="http://schemas.microsoft.com/SMI/2016/WindowsSettings">
            <ws2:longPathAware>true</ws2:longPathAware>
        </asmv3:windowsSettings>
    </asmv3:application>
</assembly>

runs the application successfully, but without long path awareness (windows error code 206 = ERROR_FILENAME_EXCED_RANGE):

Exe SetCurrentDirectory failed 1 - 206
Exe SetCurrentDirectory failed 2 - 206

I checked the final embedded resource but it's definitively there: enter image description here

To finish

i can only say that i don't know what else to test or if adding longPathAware element to the manifest is even possible with the type of application i'm trying to achieve that.

Maybe there's another api to change the current working folder of my application to a long path, and i would be fine with it, but at least _chdir and std::filesystem::current_path have the same limitations.

Workarounds

Using short names aka. 8.3 aliases might provide a limited work around.

For my cases this is often not feasible because short paths don't need to exists; they can be controlled system wide or per volume:

Sidenotes


Solution

  • The manifest applies to your application, it allows you to opt in to long path support.

    However, long path support must also be enabled system wide. This is the group policy "Computer Configuration > Administrative Templates > System > Filesystem > Enable Win32 long paths".

    [HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\FileSystem]
    "LongPathsEnabled"=dword:00000001
    

    This design makes no sense but it is what it is. You can't argue that it is in the name of compatibility because one could create long paths with \\?\ since at least Windows 2000.