Как избежать исключения System.IO.PathTooLongException?
Мы постоянно сталкиваемся с этой проблемой...
Пример:
если у меня есть файл, который я хочу скопировать в другой каталог или в общий каталог UNC, и если длина пути превышает 248 (если я не ошибаюсь), он вызывает исключение PathTooLongException. Есть ли решение этой проблемы?
PS: есть ли настройки реестра, чтобы установить этот путь для более длинного набора символов?
10 ответов
Попробуйте это: Delimon.Win32.I O Library (V4.0) Это библиотека написана на.NET Framework 4.0
Delimon.Win32.IO заменяет основные файловые функции System.IO и поддерживает имена файлов и папок длиной до 32 767 символов.
https://gallery.technet.microsoft.com/DelimonWin32IO-Library-V40-7ff6b16c
Эта библиотека написана специально для преодоления ограничения.NET Framework на использование длинных путей и имен файлов. С помощью этой библиотеки вы можете программно просматривать, получать доступ, записывать, удалять и т. Д. Файлы и папки, которые не доступны в пространстве имен System.IO.Library
использование
Сначала добавьте ссылку на файл Delimon.Win32.IO.dll в свой проект (перейдите к файлу Delimon.Win32.IO.dll).
В вашем файле кода добавьте "используя Delimon.Win32.IO"
Используйте обычные объекты File & Directory, как если бы вы работали с System.IO
Это было подробно обсуждено командой BCL, см. Записи в блоге.
По сути, нет способа сделать это в коде.Net и придерживаться BCL. Слишком много функций полагаются на возможность канонизировать имя пути (что сразу вызывает использование функций, ожидающих выполнения MAX_PATH).
Вы можете обернуть все функции win32, которые поддерживают синтаксис "\\?\", С их помощью вы сможете реализовать набор функций с поддержкой длинных путей, но это будет громоздко.
Поскольку огромное количество инструментов (включая explorer[1]) не могут обрабатывать длинные имена путей, не рекомендуется идти по этому пути, если вы не удовлетворены тем, что все взаимодействие с полученной файловой системой происходит через вашу библиотеку (или ограниченное количество инструментов, которые построены, чтобы справиться с этим, как Robocopy)
В ответ на ваши конкретные потребности я бы выяснил, будет ли использование робокопии напрямую для выполнения этой задачи.
[1] В Vista есть способы смягчить проблему с помощью некоторого причудливого переименования, но в лучшем случае это хрупко)
Только один обходной путь, который я видел на этом... это может быть полезно
Проблема в ANSI-версиях Windows API. Одним из решений, которое необходимо тщательно протестировать, является принудительное использование Unicode-версий Windows API. Это можно сделать, предварив "\\?\
"к пути, который запрашивается.
Отличная информация, включая обходные пути, может быть найдена в следующих сообщениях в блоге от Microsoft Base Base Library (BCL) Team под названием "Длинные пути в.NET":
- http://blogs.msdn.com/b/bclteam/archive/2007/02/13/long-paths-in-net-part-1-of-3-kim-hamilton.aspx
- http://blogs.msdn.com/bclteam/archive/2007/03/26/long-paths-in-net-part-2-of-3-long-path-workarounds-kim-hamilton.aspx
- http://blogs.msdn.com/b/bclteam/archive/2008/07/07/long-paths-in-net-part-3-of-3-redux-kim-hamilton.aspx
Я использовал команду "subst", чтобы обойти проблему... http://www.techrepublic.com/article/mapping-drive-letters-to-local-folders-in-windows-xp/5975262
Мое решение Drive-Mapping работает нормально и стабильно, используя "NetWorkDrive.cs" и "NetWorkUNCPath.cs", которые перечислены ниже.
Тестовый пример:
if (srcFileName.Length > 260)
{
string directoryName = srcFileName.Substring(0, srcFileName.LastIndexOf('\\'));
var uncName = GetUNCPath(srcFileName.Substring(0, 2)) + directoryName.Substring(2);
using (NetWorkDrive nDrive = new NetWorkDrive(uncName))
{
drvFileName = nDrive.FullDriveLetter + Path.GetFileName(sourceFileName)
File.Copy(drvFileName, destinationFileName, true);
}
}
else
{
File.Copy(srcFileName, destinationFileName, true);
}
Код источникаNetWorkDrive.cs:
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.ComponentModel;
namespace SeekCopySupportTool.Business
{
public class NetWorkDrive : IDisposable
{
#region private fields
private string m_DriveLetter = string.Empty;
private string m_FullDriveLetter = string.Empty;
private bool m_Disposed = false;
//this list specifies the drive-letters, whitch will be used to map networkfolders
private string[] possibleDriveLetters = new string[]
{
"G:\\",
"H:\\",
"I:\\",
"J:\\",
"K:\\",
"L:\\",
"M:\\",
"N:\\",
"O:\\",
"P:\\",
"Q:\\",
"R:\\",
"S:\\",
"T:\\",
"U:\\",
"V:\\",
"W:\\",
"X:\\",
"Y:\\",
"Z:\\"
};
#endregion
#region public properties
public string DriveLetter
{
get { return m_DriveLetter; }
set { m_DriveLetter = value; }
}
public string FullDriveLetter
{
get { return m_FullDriveLetter; }
set { m_FullDriveLetter = value; }
}
#endregion
#region .ctor
public NetWorkDrive(string folderPath)
{
m_FullDriveLetter = MapFolderAsNetworkDrive(folderPath);
if (string.IsNullOrEmpty(m_FullDriveLetter))
{
throw new Exception("no free valid drive-letter found");
}
m_DriveLetter = m_FullDriveLetter.Substring(0,2);
}
#endregion
#region private methods
/// maps a given folder to a free drive-letter (f:\)
/// <param name="folderPath">the folder to map</param>
/// <returns>the drive letter in this format: "(letter):\" -> "f:\"</returns>
/// <exception cref="Win32Exception">if the connect returns an error</exception>
private string MapFolderAsNetworkDrive(string folderPath)
{
string result = GetFreeDriveLetter();
NETRESOURCE myNetResource = new NETRESOURCE();
myNetResource.dwScope = ResourceScope.RESOURCE_GLOBALNET;
myNetResource.dwType = ResourceType.RESOURCETYPE_ANY;
myNetResource.dwDisplayType = ResourceDisplayType.RESOURCEDISPLAYTYPE_SERVER;
myNetResource.dwUsage = ResourceUsage.RESOURCEUSAGE_CONNECTABLE;
myNetResource.lpLocalName = result.Substring(0,2);
myNetResource.lpRemoteName = folderPath;
myNetResource.lpProvider = null;
int errorcode = WNetAddConnection2(myNetResource, null, null, 0);
if(errorcode != 0)
{
throw new Win32Exception(errorcode);
}
return result;
}
private void DisconnectNetworkDrive()
{
int CONNECT_UPDATE_PROFILE = 0x1;
int errorcode = WNetCancelConnection2(m_DriveLetter, CONNECT_UPDATE_PROFILE, true);
if (errorcode != 0)
{
throw new Win32Exception(errorcode);
}
}
private string GetFreeDriveLetter()
{
//first get the existing driveletters
const int size = 512;
char[] buffer = new char[size];
uint code = GetLogicalDriveStrings(size, buffer);
if (code == 0)
{
return "";
}
List<string> list = new List<string>();
int start = 0;
for (int i = 0; i < code; ++i)
{
if (buffer[i] == 0)
{
string s = new string(buffer, start, i - start);
list.Add(s);
start = i + 1;
}
}
foreach (string s in possibleDriveLetters)
{
if (!list.Contains(s))
{
return s;
}
}
return null;
}
#endregion
#region dll imports
/// <summary>
/// to connect to a networksource
/// </summary>
/// <param name="netResource"></param>
/// <param name="password">null the function uses the current default password associated with the user specified by the username parameter ("" the function does not use a password)</param>
/// <param name="username">null the function uses the default user name (The user context for the process provides the default user name)</param>
/// <param name="flags"></param>
/// <returns></returns>
[DllImport("mpr.dll")]
//public static extern int WNetAddConnection2(ref NETRESOURCE netResource, string password, string username, int flags);
private static extern int WNetAddConnection2([In] NETRESOURCE netResource, string password, string username, int flags);
/// <summary>
/// to disconnect the networksource
/// </summary>
/// <param name="lpName"></param>
/// <param name="dwFlags"></param>
/// <param name="bForce"></param>
/// <returns></returns>
[DllImport("mpr.dll")]
private static extern int WNetCancelConnection2(string lpName, Int32 dwFlags, bool bForce);
/// <param name="nBufferLength"></param>
/// <param name="lpBuffer"></param>
/// <returns></returns>
[DllImport("kernel32.dll")]
private static extern uint GetLogicalDriveStrings(uint nBufferLength, [Out] char[] lpBuffer);
#endregion
#region enums/structs
/// <example>
/// NETRESOURCE myNetResource = new NETRESOURCE();
/// myNetResource.dwScope = 2;
/// myNetResource.dwType = 1;
/// myNetResource.dwDisplayType = 3;
/// myNetResource.dwUsage = 1;
/// myNetResource.LocalName = "z:";
/// myNetResource.RemoteName = @"\servername\sharename";
/// myNetResource.Provider = null;
/// </example>
[StructLayout(LayoutKind.Sequential)]
public class NETRESOURCE
{
public ResourceScope dwScope = 0;
public ResourceType dwType = 0;
public ResourceDisplayType dwDisplayType = 0;
public ResourceUsage dwUsage = 0;
public string lpLocalName = null;
public string lpRemoteName = null;
public string lpComment = null;
public string lpProvider = null;
};
public enum ResourceScope : int
{
RESOURCE_CONNECTED = 1,
RESOURCE_GLOBALNET,
RESOURCE_REMEMBERED,
RESOURCE_RECENT,
RESOURCE_CONTEXT
};
public enum ResourceType : int
{
RESOURCETYPE_ANY,
RESOURCETYPE_DISK,
RESOURCETYPE_PRINT,
RESOURCETYPE_RESERVED
};
public enum ResourceUsage
{
RESOURCEUSAGE_CONNECTABLE = 0x00000001,
RESOURCEUSAGE_CONTAINER = 0x00000002,
RESOURCEUSAGE_NOLOCALDEVICE = 0x00000004,
RESOURCEUSAGE_SIBLING = 0x00000008,
RESOURCEUSAGE_ATTACHED = 0x00000010,
RESOURCEUSAGE_ALL = (RESOURCEUSAGE_CONNECTABLE | RESOURCEUSAGE_CONTAINER | RESOURCEUSAGE_ATTACHED),
};
public enum ResourceDisplayType
{
RESOURCEDISPLAYTYPE_GENERIC,
RESOURCEDISPLAYTYPE_DOMAIN,
RESOURCEDISPLAYTYPE_SERVER,
RESOURCEDISPLAYTYPE_SHARE,
RESOURCEDISPLAYTYPE_FILE,
RESOURCEDISPLAYTYPE_GROUP,
RESOURCEDISPLAYTYPE_NETWORK,
RESOURCEDISPLAYTYPE_ROOT,
RESOURCEDISPLAYTYPE_SHAREADMIN,
RESOURCEDISPLAYTYPE_DIRECTORY,
RESOURCEDISPLAYTYPE_TREE,
RESOURCEDISPLAYTYPE_NDSCONTAINER
};
#endregion
#region IDisposable Members
public void Dispose()
{
Dispose(true);
}
#endregion
#region overrides/virtuals
public override string ToString()
{
return m_FullDriveLetter;
}
/// <param name="disposing"></param>
protected virtual void Dispose(bool disposing)
{
if (!m_Disposed)
{
if (disposing)
{
DisconnectNetworkDrive();
}
m_Disposed = true;
}
}
#endregion
}
}
Исходный кодNetWorkUNCPath.cs:
using System;
using System.Management;
namespace SeekCopySupportTool.Business
{
public class NetWorkUNCPath
{
// get UNC path
public static string GetUNCPath(string path)
{
if (path.StartsWith(@"\\"))
{
return path;
}
ManagementObject mo = new ManagementObject();
mo.Path = new ManagementPath(String.Format("Win32_LogicalDisk='{0}'", path));
// DriveType 4 = Network Drive
if (Convert.ToUInt32(mo["DriveType"]) == 4)
{
return Convert.ToString(mo["ProviderName"]);
}
// DriveType 3 = Local Drive
else if (Convert.ToUInt32(mo["DriveType"]) == 3)
{
return "\\\\" + Environment.MachineName + "\\" + path.Substring(0,1) + "$";
}
else
{
return path;
}
}
}
}
Я был удивлен, обнаружив, что решение Джейсона отлично работает на моем ПК с использованием Visual Studio 2019 и .Net Framework 4.7.2.
Изначально я написал что-то похожее на этот код
Dim sBaseDir = "D:\Documents\+Informatique\Application\@Visual Basic.NET\GetTestAchatsPosts\Site\communication-multimedia\ordinateurs"
Dim sLastDir = "2638.pc-acer-ne-charge-plus-malgre-un-retour-en-garantie-reviens-charge-mais-ne-charge-toujours-pas-garantie-passee-plus-dintervention-gra"
System.IO.Directory.CreateDirectory(sBaseDir & "\" & sLastDir)
Этот код генерирует ошибку 429-PathTooLongException
Затем я протестировал создание только последнего каталога
System.IO.Directory.SetCurrentDirectory(sBaseDir)
System.IO.Directory.CreateDirectory(sLastDir)
Этот код возвращает ту же ошибку 429-PathTooLongException
Затем я протестировал использование короткого базового каталога, чтобы увидеть, связана ли проблема с длиной последнего каталога или полного каталога.
System.IO.Directory.SetCurrentDirectory("D:\Documents")
System.IO.Directory.CreateDirectory(sLastDir)
Этот код работает. Но каталог создается в плохом месте.
Затем я протестировал уменьшение длины имени последнего каталога.
System.IO.Directory.SetCurrentDirectory(sBaseDir)
System.IO.Directory.CreateDirectory(sLastDir.Substring(0, 100))
Этот код работает, но последнее имя каталога уменьшено!
Затем я протестировал использование предложения Джейсона в 2 этапа.
System.IO.Directory.SetCurrentDirectory(sBaseDir)
System.IO.Directory.CreateDirectory("\\?\" & sLastDir)
Этот код дает сбой, указывая на то, что синтаксис каталога неверен!
Затем я также проверил тот же синтаксис при добавлении "." нить
System.IO.Directory.SetCurrentDirectory(sBaseDir)
System.IO.Directory.CreateDirectory("\\?\.\" & sLastDir)
Этот код дает сбой, указывая на то, что синтаксис каталога неверен!
Затем я протестировал, используя простую команду, префикс полного каталога с
"\\?"
System.IO.Directory.CreateDirectory("\\?\" & sBaseDir & "\" & sLastDir)
Этот код работает отлично, и это лучшее решение для меня.
Единственным неудобством является то, что каталог должен быть полным каталогом.
Предупреждение ! Нельзя смешивать символы «\» и «/»!
Я написал следующий код
и программа вылетает при выполнении
CreateDirectory() function !
Правильный код, который работает
sQuestionDir = "\\?\" & sArticleDir & "/" & sQuestionDir
If Not System.IO.Directory.Exists(sQuestionDir) Then
System.IO.Directory.CreateDirectory(sQuestionDir)
End If
Я надеюсь, что все эти тесты могут помочь другим решить их проблему.
В C# для меня это обходной путь:
/*make long path short by setting it to like cd*/
string path = @"\\godDamnLong\Path\";
Directory.SetCurrentDirectory(path);