Недокументированный подход, предложенный @ RbMm в комментариях (установка PEB
s IsLongPathAwareProcess
бита на 1 ), сработал (по крайней мере, в версии Windows 10)Я был на: 1903). Так как этот подход является родным подходом C / C ++, мне просто нужно было перевести его в нечто, потребляемое C #.
Существует несколько подходов, которые можно использовать (C ++ / CLI, pinvoke). Так как я просто делал «проверку концепции», я решил просто использовать pinvoke и немного ленив в качестве кода.
Примечания:
- Этот код не проверяет версию, чтобы убедиться, что он работает в версии Windows 10, которая поддерживает
IsLongPathAwareProcess
. - Ограниченная проверка ошибок
- Я включил только начальную часть структуры
PEB
, поскольку это все, что необходимо для доступа к биту, который я хочу изменить. - Скорее всего, есть более краткий способ установки
IsLongPathAwareProcess
бит, чем подход, который я использовал (Marshal.StructureToPtr
) - Включает тест, который работает, когда выполняется вызов
SetIsLongPathAwareProcess
, и завершается неудачей, когда он не вызывается - Уведомление вкод теста, который я удаляю
C:\Test
(чтобы я мог повторно выполнить тест несколько раз без уже существующего каталога)
Тестовый код:
class Program
{
static void Main(string[] args)
{
SetIsLongPathAwareProcess();
string reallyLongDirectory = @"C:\Test\abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ\abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ\abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
reallyLongDirectory = reallyLongDirectory + @"\abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ\abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ\abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
reallyLongDirectory = reallyLongDirectory + @"\abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ\abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ\abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
CreateDirTest(reallyLongDirectory);
Directory.Delete(@"C:\Test", recursive: true);
}
private static void CreateDirTest(string name)
{
Console.WriteLine($"Creating a directory that is {name.Length} characters long");
// Test managed CreateDirectory
Directory.CreateDirectory(name);
// Test Native CreateDirectory. Note this method will only create the "last part" of the directory (not the full path).
// Also note that it fails if the directory already exists.
// https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createdirectorya
string nativeName = Path.Combine(name, "TestEnding");
var r = Native.CreateDirectory(nativeName, null);
if (!r)
{
int currentError = Marshal.GetLastWin32Error();
Debug.Fail($"Native.CreateDirectory failed: {currentError}");
}
}
// Adapted from https://www.pinvoke.net/default.aspx/ntdll.ntqueryinformationprocess
private static void SetIsLongPathAwareProcess()
{
var currentProcess = Process.GetCurrentProcess();
IntPtr hProc = currentProcess.Handle;
IntPtr pPbi = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(Native.PROCESS_BASIC_INFORMATION)));
IntPtr outLong = Marshal.AllocHGlobal(sizeof(long));
int status = Native.NtQueryInformationProcess(hProc, 0, pPbi, (uint)Marshal.SizeOf(typeof(Native.PROCESS_BASIC_INFORMATION)), outLong);
Marshal.FreeHGlobal(outLong);
//STATUS_SUCCESS = 0
if (status == 0)
{
var pbi = Marshal.PtrToStructure<Native.PROCESS_BASIC_INFORMATION>(pPbi);
var pPeb = pbi.PebBaseAddress;
var peb = Marshal.PtrToStructure<Native.PEB_Beginning>(pPeb);
var bitField1 = peb.BitField1;
peb.BitField1 = bitField1 | Native.PEBBitField1.IsLongPathAwareProcess;
Marshal.StructureToPtr<Native.PEB_Beginning>(peb, pPeb, false);
}
//Free allocated space
Marshal.FreeHGlobal(pPbi);
}
static class Native
{
// http://pinvoke.net/default.aspx/Structures/PROCESS_BASIC_INFORMATION.html
[StructLayout(LayoutKind.Sequential, Pack = 1)]
internal struct PROCESS_BASIC_INFORMATION
{
public IntPtr ExitStatus;
public IntPtr PebBaseAddress;
public IntPtr AffinityMask;
public IntPtr BasePriority;
public UIntPtr UniqueProcessId;
public IntPtr InheritedFromUniqueProcessId;
public int Size
{
get { return (int)Marshal.SizeOf(typeof(PROCESS_BASIC_INFORMATION)); }
}
}
[Flags]
internal enum PEBBitField1 : byte
{
None = 0,
ImageUsesLargePages = 1 << 0,
IsProtectedProcess = 1 << 1,
IsImageDynamicallyRelocated = 1 << 2,
SkipPatchingUser32Forwarders = 1 << 3,
IsPackagedProcess = 1 << 4,
IsAppContainer = 1 << 5,
IsProtectedProcessLight = 1 << 6,
IsLongPathAwareProcess = 1 << 7
}
// Note: this only contains the "beginning" of the PEB structure
// but that is all we need for access to the IsLongPathAwareProcess bit
// Used as a guide: https://github.com/processhacker/processhacker/blob/master/phnt/include/ntpebteb.h#L75
// Related: https://docs.microsoft.com/en-us/windows/win32/api/winternl/ns-winternl-peb
[StructLayout(LayoutKind.Sequential, Pack = 1)]
internal struct PEB_Beginning
{
Byte InheritedAddressSpace;
Byte ReadImageFileExecOptions;
Byte BeingDebugged;
public PEBBitField1 BitField1;
};
// https://www.pinvoke.net/default.aspx/ntdll.ntqueryinformationprocess
[DllImport("ntdll.dll", SetLastError = true)]
internal static extern int NtQueryInformationProcess(IntPtr processHandle, int processInformationClass, IntPtr processInformation, uint processInformationLength, IntPtr returnLength);
// The below is for test purposes only:
// Include CreateDirectory for testing if the long path aware changes work or not.
// Requires SECURITY_ATTRIBUTES
// http://pinvoke.net/default.aspx/kernel32/CreateDirectory.html?diff=y
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto, BestFitMapping = false)]
internal static extern bool CreateDirectory(String path, SECURITY_ATTRIBUTES lpSecurityAttributes);
// https://www.pinvoke.net/default.aspx/Structures/SECURITY_ATTRIBUTES.html
[StructLayout(LayoutKind.Sequential)]
internal class SECURITY_ATTRIBUTES
{
internal int nLength = 0;
// don't remove null, or this field will disappear in bcl.small
internal unsafe byte* pSecurityDescriptor = null;
internal int bInheritHandle = 0;
}
}
}
Если вы используете этот код в консольном приложении, которое, естественно, longPathAware
, вы все равно можете проверить, что оно работает правильно, вручную установив longPathAware
в false
в app.manifest
:
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings>
<longPathAware xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">false</longPathAware>
</windowsSettings>
</application>