问题
I need to know which is the real path of a given path.
For example:
The real path is: d:\src\File.txt
And the user give me: D:\src\file.txt
I need as a result: d:\src\File.txt
回答1:
You can use this function:
[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;
}
回答2:
As an old-timer, I always used FindFirstFile for this purpose. The .Net translation is:
Directory.GetFiles(Path.GetDirectoryName(userSuppliedName), Path.GetFileName(userSuppliedName)).FirstOrDefault();
This only gets you the correct casing for the filename portion of the path, not then entire path.
JeffreyLWhitledge's comment provides a link to a recursive version that can work (though not always) to resolve the full path.
回答3:
The way to get the actual path of a file (this won't work for folders) is to follow these steps:
- Call
CreateFileMapping
to create a mapping for the file. - Call
GetMappedFileName
to get the name of the file. - Use
QueryDosDevice
to convert it to an MS-DOS-style path name.
If you feel like writing a more robust program that also works with directories (but with more pain and a few undocumented features), follow these steps:
- Get a handle to the file/folder with
CreateFile
orNtOpenFile
. - Call
NtQueryObject
to get the full path name. - Call
NtQueryInformationFile
withFileNameInformation
to get the volume-relative path. - Using the two paths above, get the component of the path that represents the volume itself. For example, if you get
\Device\HarddiskVolume1\Hello.txt
for the first path and\Hello.txt
for the second, you now know the volume's path is\Device\HarddiskVolume1
. - Use either the poorly-documented Mount Manager I/O Control Codes or
QueryDosDevice
to convert substitute the volume portion of the full NT-style path with the drive letter.
Now you have the real path of the file.
回答4:
As Borja's answer does not work for volumes where 8.3 names are disabled, here the recursive implementation that Tergiver suggests (works for files and folders, as well as the files and folders of UNC shares but not on their machine names nor their share names).
Non-existing file or folders are no problem, what exists is verified and corrected, but you might run into folder-redirection issues, e.g when trying to get the correct path of "C:\WinDoWs\sYsteM32\driVErs\eTC\Hosts" you'll get "C:\Windows\System32\drivers\eTC\hosts" on a 64bit windows as there is no "etc" folder withing "C:\Windows\sysWOW64\drivers".
Test Scenario:
Directory.CreateDirectory(@"C:\Temp\SomeFolder");
File.WriteAllLines(@"C:\Temp\SomeFolder\MyTextFile.txt", new String[] { "Line1", "Line2" });
Usage:
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;
}
}
回答5:
Alternative Solution
Here is a solution that worked for me to move files between Windows and a server using case sensitive paths. It walks down the directory tree and corrects each entry with GetFileSystemEntries()
. If part of the path is invalid (UNC or folder name), then it corrects the path only up to that point and then uses the original path for what it can't find. Anyway, hopefully this will save others time when dealing with the same issue.
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;
}
回答6:
Here's an alternate solution, works on files and directories. Uses GetFinalPathNameByHandle, which is only supported for desktop apps on Vista/Server2008 or above according to docs.
Note that it will resolve a symlink if you give it one, which is part of finding the "final" path.
// 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);
}
}
回答7:
On Windows, paths are case-insensitive. So both paths are equally real.
If you want to get some kind of a path with canonical capitalization (i. e. how Windows thinks it should be capitalized), you can call FindFirstFile() with the path as a mask, then take the full name of the found file. If the path is invalid, then you won't get a canonical name, natually.
来源:https://stackoverflow.com/questions/4763117/how-can-i-obtain-the-case-sensitive-path-on-windows