Comment puis-je obtenir le chemin sensible à la casse sur Windows?

J'ai besoin de savoir quel est le chemin réel d'un chemin donné.

Par exemple:

Le chemin réel est: d:srcFile.txt
Et l'utilisateur me donne: D:srcfile.txt
J'ai besoin en conséquence: d:srcFile.txt

27
demandé sur bdukes 2011-01-21 22:49:10

7 réponses

Vous pouvez utiliser cette fonction:

[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;
}
17
répondu Borja 2011-01-21 19:51:04

En tant qu'ancien, j'ai toujours utilisé FindFirstFile à cette fin. La traduction. Net est:

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

Cela ne vous donne que le boîtier correct pour la partie nom de fichier du chemin, pas le chemin entier.

Le Commentaire de JeffreyLWhitledge fournit un lien vers une version récursive qui peut fonctionner (mais pas toujours) pour résoudre le chemin complet.

7
répondu Tergiver 2013-04-30 15:09:27

La façon d'obtenir le chemin réel d'un fichier (cela ne fonctionnera pas pour les dossiers) est de suivre ces étapes:

  1. Appel de CreateFileMapping pour créer un mappage pour le fichier.
  2. appelez GetMappedFileName pour obtenir le nom du fichier.
  3. Utilisez QueryDosDevice pour le convertir en un nom de chemin de style MS-DOS.

Si vous avez envie d'écrire un programme plus robuste qui fonctionne également avec des répertoires (mais avec plus de douleur et quelques fonctionnalités non documentées), procédez comme suit:

  1. obtenir une poignée pour le fichier/dossier avec CreateFile ou NtOpenFile.
  2. appelez NtQueryObject pour obtenir le nom complet du chemin.
  3. appelez NtQueryInformationFile avec FileNameInformation pour obtenir le chemin relatif au volume.
  4. en utilisant les deux chemins ci-dessus, obtenez le composant du chemin qui représente le volume lui-même. Par exemple, si vous obtenez \Device\HarddiskVolume1\Hello.txt pour le premier chemin et \Hello.txt, pour la seconde, vous savez maintenant que le volume du chemin est \Device\HarddiskVolume1.
  5. utilisez les Codes de contrôle D'E/S du Gestionnaire de montage mal documentés ou QueryDosDevice pour convertir le volume partie du chemin de style NT complet avec la lettre de lecteur.

Maintenant, vous avez le chemin réel du fichier.

3
répondu Mehrdad 2011-01-21 20:01:37

Comme la réponse de Borja ne fonctionne pas pour les volumes où les noms 8.3 sont désactivés, voici l'implémentation récursive suggérée par Tergiver (fonctionne pour les fichiers et dossiers, ainsi que les fichiers et dossiers des partages UNC mais pas sur leurs noms de machine ni leurs noms de partage).

Les fichiers ou dossiers Non existants ne posent aucun problème, ce qui existe est vérifié et corrigé, mais vous pouvez rencontrer des problèmes de redirection de dossier, par exemple en essayant d'obtenir le chemin correct de "C:\WinDoWs\sYsteM32\driVErs\eTC\Hosts" vous obtiendrez "C:\Windows\System32\drivers\eTC\hosts" sur un windows 64 bits car il n'y a pas de dossier " etc "avec " C:\Windows\sysWOW64\drivers".

Scénario D'Essai:

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

Utilisation:

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

Code:

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;
    }

}
1
répondu Christoph 2015-04-20 15:13:51

Voici une solution alternative, fonctionne sur les fichiers et les répertoires. Utilise GetFinalPathNameByHandle, qui est uniquement pris en charge pour les applications de bureau sur Vista/Server2008 ou supérieur selon docs.

Notez qu'il résoudra un lien symbolique si vous lui en donnez un, ce qui fait partie de la recherche du chemin "final".

// 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
répondu scobi 2016-12-31 15:44:34

Solution Alternative

Voici une solution qui a fonctionné pour moi pour déplacer des fichiers entre Windows et un serveur en utilisant des chemins sensibles à la casse. Il parcourt l'arborescence des répertoires et corrige chaque entrée avec GetFileSystemEntries(). Si une partie du chemin est invalide (UNC ou nom de dossier), il corrige le chemin uniquement jusqu'à ce point, puis utilise le chemin d'origine pour ce qu'il ne peut pas trouver. Quoi qu'il en soit, j'espère que cela permettra d'économiser du temps aux autres lorsqu'ils traitent du même problème.

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;
}
0
répondu Roberto 2018-02-05 16:43:27

Sous Windows, les chemins sont insensibles à la casse. Donc, les deux chemins sont également réels.

Si vous voulez obtenir une sorte de chemin avec une capitalisation canonique (c'est-à-dire comment Windows pense qu'il devrait être capitalisé), vous pouvez appeler FindFirstFile() avec le chemin comme masque, puis prendre le nom complet du fichier trouvé. Si le chemin est invalide, vous n'obtiendrez pas de nom canonique, naturellement.

-3
répondu Seva Alekseyev 2011-01-21 19:53:56