Как получить путь с учетом регистра в Windows? - PullRequest
30 голосов
/ 21 января 2011

Мне нужно знать, какой является реальным путем для данного пути.

Например:

Реальный путь: d: \ src \ File.txt
Ипользователь дал мне: D: \ src \ file.txt
Мне нужно в результате: d: \ src \ File.txt

Ответы [ 7 ]

17 голосов
/ 21 января 2011

Вы можете использовать эту функцию:

[DllImport("kernel32.dll", SetLastError=true, CharSet=CharSet.Auto)]
static extern uint GetLongPathName(string ShortPath, StringBuilder sb, int buffer);

[DllImport("kernel32.dll")]
static extern uint GetShortPathName(string longpath, StringBuilder sb, int buffer); 

protected static string GetWindowsPhysicalPath(string path)
{
        StringBuilder builder = new StringBuilder(255);

        // names with long extension can cause the short name to be actually larger than
        // the long name.
        GetShortPathName(path, builder, builder.Capacity);

        path = builder.ToString();

        uint result = GetLongPathName(path, builder, builder.Capacity);

        if (result > 0 && result < builder.Capacity)
        {
            //Success retrieved long file name
            builder[0] = char.ToLower(builder[0]);
            return builder.ToString(0, (int)result);
        }

        if (result > 0)
        {
            //Need more capacity in the buffer
            //specified in the result variable
            builder = new StringBuilder((int)result);
            result = GetLongPathName(path, builder, builder.Capacity);
            builder[0] = char.ToLower(builder[0]);
            return builder.ToString(0, (int)result);
        }

        return null;
}
8 голосов
/ 21 января 2011

Как старый таймер, я всегда использовал FindFirstFile для этой цели.Перевод .Net:

Directory.GetFiles(Path.GetDirectoryName(userSuppliedName), Path.GetFileName(userSuppliedName)).FirstOrDefault();

Это дает вам только правильный регистр для части пути в имени файла, а не для всего пути.

Комментарий JeffreyLWhitledge предоставляет ссылку на рекурсивную версиюэто может работать (хотя и не всегда), чтобы решить полный путь.

3 голосов
/ 21 января 2011

Чтобы получить фактический путь к файлу (это не будет работать для папок), выполните следующие действия:

  1. Вызовите CreateFileMapping, чтобы создать сопоставление для файла.
  2. Позвоните GetMappedFileName, чтобы получить имя файла.
  3. Используйте QueryDosDevice для преобразования его в имя пути в стиле MS-DOS.

Если вам хочется написать более надежную программу, которая также работает с каталогами (но с большими трудностями и несколькими недокументированными функциями), выполните следующие действия:

  1. Получить дескриптор файла / папки с помощью CreateFile или NtOpenFile.
  2. Позвоните NtQueryObject, чтобы получить полное имя пути.
  3. Вызовите NtQueryInformationFile с помощью FileNameInformation, чтобы получить путь, относящийся к объему.
  4. Используя два указанных выше пути, получите компонент пути, который представляет сам том. Например, если вы получите \Device\HarddiskVolume1\Hello.txt для первого пути и \Hello.txt для второго, теперь вы знаете, что путь тома равен \Device\HarddiskVolume1.
  5. Используйте либо плохо документированные управляющие коды ввода-вывода Mount Manager, либо QueryDosDevice, чтобы преобразовать часть тома полного пути в стиле NT с буквой диска.

Теперь у вас есть реальный путь к файлу.

2 голосов
/ 05 февраля 2018

Альтернативное решение

Вот решение, которое помогло мне переместить файлы между Windows и сервером, используя пути с учетом регистра. Он идет по дереву каталогов и исправляет каждую запись с помощью GetFileSystemEntries(). Если часть пути недопустима (UNC или имя папки), то она исправляет путь только до этой точки, а затем использует исходный путь для того, что не может найти. В любом случае, надеюсь, это сэкономит время других при решении той же проблемы.

private string GetCaseSensitivePath(string path)
{
    var root = Path.GetPathRoot(path);
    try
    {
        foreach (var name in path.Substring(root.Length).Split(Path.DirectorySeparatorChar))
            root = Directory.GetFileSystemEntries(root, name).First();
    }
    catch (Exception e)
    {
        // Log("Path not found: " + path);
        root += path.Substring(root.Length);
    }
    return root;
}
2 голосов
/ 20 апреля 2015

Поскольку ответ Борха не работает для томов, где имена 8.3 отключены, здесь предлагается рекурсивная реализация, которую предлагает Tergiver (работает с файлами и папками, а также с файлами и папками общих папок UNC, но не на именах их компьютеров или их общих ресурсахимена).

Несуществующий файл или папки не являются проблемой, то, что существует, проверяется и исправляется, но вы можете столкнуться с проблемами перенаправления папок, например, при попытке получить правильный путь к «C: \ WinDoWs \ sYsteM32 \».driVErs \ eTC \ Hosts "вы получите" C: \ Windows \ System32 \ drivers \ eTC \ hosts "в 64-битных окнах, поскольку нет папки" etc "с" C: \ Windows \ sysWOW64 \ drivers ".

Сценарий тестирования:

        Directory.CreateDirectory(@"C:\Temp\SomeFolder");
        File.WriteAllLines(@"C:\Temp\SomeFolder\MyTextFile.txt", new String[] { "Line1", "Line2" });

Использование:

        FileInfo myInfo = new FileInfo(@"C:\TEMP\SOMEfolder\MyTeXtFiLe.TxT");
        String myResult = myInfo.GetFullNameWithCorrectCase(); //Returns "C:\Temp\SomeFolder\MyTextFile.txt"

Код:

public static class FileSystemInfoExt {

    public static String GetFullNameWithCorrectCase(this FileSystemInfo fileOrFolder) {
        //Check whether null to simulate instance method behavior
        if (Object.ReferenceEquals(fileOrFolder, null)) throw new NullReferenceException();
        //Initialize common variables
        String myResult = GetCorrectCaseOfParentFolder(fileOrFolder.FullName);
        return myResult;
    }

    private static String GetCorrectCaseOfParentFolder(String fileOrFolder) {
        String myParentFolder = Path.GetDirectoryName(fileOrFolder);
        String myChildName = Path.GetFileName(fileOrFolder);
        if (Object.ReferenceEquals(myParentFolder, null)) return fileOrFolder.TrimEnd(new char[]{Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar });
        if (Directory.Exists(myParentFolder)) {
            //myParentFolder = GetLongPathName.Invoke(myFullName);
            String myFileOrFolder = Directory.GetFileSystemEntries(myParentFolder, myChildName).FirstOrDefault();
            if (!Object.ReferenceEquals(myFileOrFolder, null)) {
                myChildName = Path.GetFileName(myFileOrFolder);
            }
        }
        return GetCorrectCaseOfParentFolder(myParentFolder) + Path.DirectorySeparatorChar + myChildName;
    }

}
0 голосов
/ 31 декабря 2016

Вот альтернативное решение, работает с файлами и каталогами.Использует GetFinalPathNameByHandle, который поддерживается только для настольных приложений на Vista / Server2008 или выше в соответствии с документами.

Обратите внимание, что он разрешит символическую ссылку, если вы дадите ей ссылку, которая является частью поиска «окончательного» пути.

// http://www.pinvoke.net/default.aspx/shell32/GetFinalPathNameByHandle.html
[DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
static extern uint GetFinalPathNameByHandle(SafeFileHandle hFile, [MarshalAs(UnmanagedType.LPTStr)] StringBuilder lpszFilePath, uint cchFilePath, uint dwFlags);
private const uint FILE_NAME_NORMALIZED = 0x0;

static string GetFinalPathNameByHandle(SafeFileHandle fileHandle)
{
    StringBuilder outPath = new StringBuilder(1024);

    var size = GetFinalPathNameByHandle(fileHandle, outPath, (uint)outPath.Capacity, FILE_NAME_NORMALIZED);
    if (size == 0 || size > outPath.Capacity)
        throw new Win32Exception(Marshal.GetLastWin32Error());

    // may be prefixed with \\?\, which we don't want
    if (outPath[0] == '\\' && outPath[1] == '\\' && outPath[2] == '?' && outPath[3] == '\\')
        return outPath.ToString(4, outPath.Length - 4);

    return outPath.ToString();
}

// http://www.pinvoke.net/default.aspx/kernel32.createfile
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
static extern SafeFileHandle CreateFile(
     [MarshalAs(UnmanagedType.LPTStr)] string filename,
     [MarshalAs(UnmanagedType.U4)] FileAccess access,
     [MarshalAs(UnmanagedType.U4)] FileShare share,
     IntPtr securityAttributes, // optional SECURITY_ATTRIBUTES struct or IntPtr.Zero
     [MarshalAs(UnmanagedType.U4)] FileMode creationDisposition,
     [MarshalAs(UnmanagedType.U4)] FileAttributes flagsAndAttributes,
     IntPtr templateFile);
private const uint FILE_FLAG_BACKUP_SEMANTICS = 0x02000000;

public static string GetFinalPathName(string dirtyPath)
{
    // use 0 for access so we can avoid error on our metadata-only query (see dwDesiredAccess docs on CreateFile)
    // use FILE_FLAG_BACKUP_SEMANTICS for attributes so we can operate on directories (see Directories in remarks section for CreateFile docs)

    using (var directoryHandle = CreateFile(
        dirtyPath, 0, FileShare.ReadWrite | FileShare.Delete, IntPtr.Zero, FileMode.Open,
        (FileAttributes)FILE_FLAG_BACKUP_SEMANTICS, IntPtr.Zero))
    {
        if (directoryHandle.IsInvalid)
            throw new Win32Exception(Marshal.GetLastWin32Error());

        return GetFinalPathNameByHandle(directoryHandle);
    }
}
0 голосов
/ 21 января 2011

В Windows пути не чувствительны к регистру. Таким образом, оба пути одинаково реальны.

Если вы хотите получить какой-либо путь с канонической заглавной буквой (то есть, как Windows считает, что он должен быть заглавным), вы можете вызвать FindFirstFile () с путем в качестве маски, а затем взять полное имя найденного файла. Если путь неверен, то вы не получите каноническое имя, естественно.

...