Skip to content

.NET Framework 4.6.2 Long Paths: Illegal Characters in path exception #1754

@zhawkins-viasat

Description

@zhawkins-viasat

Environment

  • Pythonnet version: 2.5.2
  • Python version: 3.8.10
  • Operating System: Windows 10
  • .NET Runtime: 4.6.2

Details

  • .NET Framework 4.6.2 supports long paths by default. When calling an assembly that a targets .NET Framework 4.6.2, long path support is disabled and causes an Illegal characters in path exception when using a FileStream constructor:
System.ArgumentException: Illegal characters in path.
   at System.Security.Permissions.FileIOPermission.EmulateFileIOPermissionChecks(String fullPath)
   at System.IO.FileStream.Init(String path, FileMode mode, FileAccess access, Int32 rights, Boolean useRights, FileShare share, Int32 bufferSize, FileOptions options, SECURITY_ATTRIBUTES secAttrs, String msgPath, Boolean bFromProxy, Boolean useLongPath, Boolean checkHost)
   at System.IO.FileStream..ctor(String path, FileMode mode, FileAccess access, FileShare share)

Workaround:
For now, the workaround is to create a python.exe.config application config file with the following:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <startup>
    <supportedRuntime version=".NETFramework,Version=v4.6.2"/>
  </startup>
  <runtime>
    <AppContextSwitchOverrides value="Switch.System.IO.UseLegacyPathHandling=false;Switch.System.IO.BlockLongPaths=false" />
  </runtime> 
</configuration>

This, however, creates a global solution which is not desired. An alternate workaround is to explicitly update the AppContext variables in the Python script before any code is executed (credit to https://stackoverflow.com/questions/53193280/long-path-workaround-not-working-on-some-installs):

type = Type.GetType("System.AppContext")
if type:
    AppContext.SetSwitch("Switch.System.IO.UseLegacyPathHandling", False)
    AppContext.SetSwitch("Switch.System.IO.BlockLongPaths", False)

    switchType = Type.GetType("System.AppContextSwitches")
    if switchType:
        # We also have to reach into System.AppContextSwitches and manually update the cached private versions of these properties (don't ask me why):

        legacyField = switchType.GetField("_useLegacyPathHandling", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic)
        if legacyField:
            legacyField.SetValue(None, -1) # <- caching uses 0 to indicate no value, -1 for false, 1 for true.

        blockingField = switchType.GetField("_blockLongPaths", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic)
        if blockingField:
            blockingField.SetValue(None, -1) # <- caching uses 0 to indicate no value, -1 for false, 1 for true.

Again, not an ideal solution because this uses reflection to set values of private variables by name.

Ideally, the CLR loader would load the proper AppContext defaults based on the version of target framework like is shown here:

https://referencesource.microsoft.com/#mscorlib/system/AppContext/AppContextDefaultValues.Defaults.cs,60

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions