Why does WNetGetUniversalName fail with ERROR_NOT_SUPPORTED on some machines? - c#

I've reached the end of my rope with this one. I have some C# code that's trying to resolve paths containing drive letters mapped to network drives (e.g. "S:\") to UNC paths (e.g. "\\server\share\"). I'm doing this using WNetGetUniversalName via P/Invoke, but on some of my colleagues' machines (not mine, annoyingly) I'm seeing the function consistently fail with the error code ERROR_NOT_SUPPORTED.
Here's the code:
[DllImport("mpr.dll", CharSet = CharSet.Unicode)]
[return: MarshalAs(UnmanagedType.U4)]
static extern int WNetGetUniversalName(
string lpLocalPath,
[MarshalAs(UnmanagedType.U4)] int dwInfoLevel,
IntPtr lpBuffer,
[MarshalAs(UnmanagedType.U4)] ref int lpBufferSize);
/// <summary>
/// Gets the UNC path for the path passed in.
/// </summary>
/// <param name="path">The path for which we want the UNC path.</param>
/// <returns>The UNC path. Returns empty string if an error has occurred. </returns>
public static string GetUniversalPath(string path)
{
const int UNIVERSAL_NAME_INFO_LEVEL = 0x00000001;
const int ERROR_MORE_DATA = 234;
const int NOERROR = 0;
string retVal = null;
// Pointer to the memory buffer to hold the result.
IntPtr buffer = IntPtr.Zero;
try
{
// First, call WNetGetUniversalName to get the size.
// Passing (IntPtr)IntPtr.Size as the third parameter because WNetGetUniversalName doesn't
// like NULL, and IntPtr.Size will always be a properly-aligned (if not actually valid)
// IntPtr value.
int size = 0;
int apiRetVal = WNetGetUniversalName(path, UNIVERSAL_NAME_INFO_LEVEL, (IntPtr)IntPtr.Size, ref size);
if (apiRetVal == ERROR_MORE_DATA)
{
// Allocate the memory.
buffer = Marshal.AllocCoTaskMem(size);
// Now make the call.
apiRetVal = WNetGetUniversalName(path, UNIVERSAL_NAME_INFO_LEVEL, buffer, ref size);
if (apiRetVal == NOERROR)
{
// Now get the string. It's all in the same buffer, but
// the pointer is first, so offset the pointer by IntPtr.Size
// and pass to PtrToStringAnsi.
retVal = Marshal.PtrToStringAuto(new IntPtr(buffer.ToInt64() + IntPtr.Size), size);
retVal = retVal.Substring(0, retVal.IndexOf('\0'));
}
}
}
catch
{
// I know swallowing exceptions is nasty...
retVal = "";
}
finally
{
Marshal.FreeCoTaskMem(buffer);
}
return retVal;
}
I'm seeing the ERROR_NOT_SUPPORTED return value the first time WNetGetUniversalName is called. Like I said, it works every time on my machine but it always seems to fail on others.
UPDATE: I should probably add that in all cases, the operating system in use is Windows 7 Enterprise x64 with Service Pack 1.
UPDATE 2: I should have been clearer about the reason for my confusion. The documentation states that ERROR_NOT_SUPPORTED means that no network providers support UNC names. However, UNC names work fine on every machine where I've seen this problem. I'm wondering if anyone has seen this before and/or might be able to provide other possible explanations for ERROR_NOT_SUPPORTED being returned.

The documentation says that this return value means:
The dwInfoLevel parameter is set to UNIVERSAL_NAME_INFO_LEVEL, but the network provider does not support UNC names. (None of the network providers support this function.)
There's not much more to add to that.

Got it! After a bit of digging, I found the source of the problem. The machines where the call didn't work had a network provider called "Pismo File Mount" installed, apparently as part of something called the Pismo File Mount Audit Package.
I found the list of installed network providers as follows:
Open Network and Sharing Center.
Click "Change adapter settings" in the pane on the left.
Press ALT to bring up the menu bar, then select "Advanced Settings..." from the "Advanced" menu.
Go to the "Provider Order" tab.
This is what I saw:
I was able to resolve the problem by either:
Moving "Pismo File Mount" down below "Microsoft Windows Network" in the list of providers.
Uninstalling the Pismo File Mount Audit Package.
I still don't understand the problem as well as I'd like, but at least we have something to tell customers if they run into it.

Related

Accessing Fonts folder from console application with target framework 3.5

I need to get the path of a specific font in my c:/windows/ folder
The below code works perfectly when the target framework is 4.0
But my application can target only 3.5 and i need to use this in a console application c#
How can i achieve this ? Thanks.
string arialuniTff = path.Combine(System.Environment.GetFolderPath(System.Environment.SpecialFolder.Fonts), "arial.TTF");
Error Msg in 3.5 : 'System.Environment.SpecialFolder' does not contain a definition
The fonts folder is typically located at %windir%\Fonts, so you should be able to get the location like this:
Path.Combine(
System.Environment.GetEnvironmentVariable("windir"),
"Fonts");
It is a virtual folder, so in theory it could be located somewhere else. In practice, I've never seen that happen or heard of it happening. (Microsoft is confident enough in this location to reference it on their "how to install a font" page). I'm sure that if you're trying to locate a specific file name like that you have good error handling already, though.
Bonus information:
You might know this already, but if you need to know what classes, methods, etc. are available in a specific version of the .net framework, you can find out from MSDN. Go to the documentation page (say this one on Environment.SpecialFolder), and click on the ".NET Framework 4.5" link in the top left corner and choose a different version to see the page you are looking at as it was in that version.
Please refer How to get the path to CSIDL_COMMON_DOCUMENTS in .NET 3.5?
It provides the location for const int CSIDL_COMMON_DOCUMENTS = 0x002e;.
For Fonts folder, use const int CSIDL_FONTS = 0x0014;
It would be:
[DllImport("shell32.dll"), CharSet = CharSet.Auto]
static extern int SHGetFolderPath(IntPtr hwndOwner, int nFolder, IntPtr hToken, uint dwFlags, [Out] StringBuilder pszPath);
const int CSIDL_FONTS = 0x0014;
const int CSIDL_FLAG_CREATE = 0x8000;
StringBuilder sb = new StringBuilder();
int retVal = SHGetFolderPath(IntPtr.Zero,
CSIDL_FONTS | CSIDL_FLAG_CREATE,
IntPtr.Zero,
0,
sb);
Debug.Assert(retVal >= 0); // assert that the function call succeeded
String folderLocation = sb.ToString();

"No Disk" error using GDAL from C#/.NET

I am using Tamas Szekeres builds of GDAL including the C# bindings in a desktop GIS application using C# and .net 4.0
I am including the entire GDAL distribution in a sub-directory of my executable with the following folder structure:
\Plugins\GDAL
\Plugins\GDAL\gdal
\Plugins\GDAL\gdal-data
\Plugins\GDAL\proj
We are using EPSG:4326, and the software is built using 32-bit target since the GDAL C# API is using p/invoke to the 32-bit libraries (could try 64 bit since Tamas provides these, haven't gotten around to it yet).
When I run my application I get the following error
This error typically happens when software tries to access a device that is no longer attached, such as a removable drive. It is not possible to "catch" this exception because it pops up a system dialog.
After dismissing the dialog using any of the buttons, the software continues to execute as designed.
The error occurs the first time I call the following method
OSGeo.OSR.CoordinateTransformation.TransformPoint(double[] inout);
The strange stuff:
The error occurs on one, and only one computer (so far)
I've run this software in several other computers both 32 and 64 bit without problems
The error does not ocurr on the first run after compiling the GDAL shim library I am using, it only occurrs on each subsequent run
it happens regardless of release, or debug builds
it happens regardless of whether the debugger is attached or not
it happens regardless of whether I turn on or off Gdal.UseExceptions or Osr.UseExceptions();
disabling removable drives causes the bug to disappear. This is not what I consider a real solution as I will not be able to ask a customer to do this.
I have tried the following:
catching the error
changing GDAL directories and environment settings
changing computers and operating systems: this worked
used SysInternals ProcMon to trace what files are being opened with no luck, they all appear to be files that exist
I re-built the computer in question when the hard drive failed, to no avail.
"cleaning" the registry using CCleaner
files in GDAL Directory are unchanged on execution
Assumptions
Error is happening in unmanaged code
During GDAL initialization, some path is referring to a drive on the computer that is no longer attached.
I am also working on the assumption this is limited to a computer configuration error
Configuration
Windows 7 Pro
Intel Core i7 920 # 2,67GHz
12.0 GB RAM
64-bit OS
Drive C: 120 GB SSD with OS, development (Visual Studio 10), etc
Drive D: 1 TB WD 10,000k with data, not being accessed for data.
The Question
I either need a direction to trap the error, or a tool or technique that will allow me to figure out what is causing it. I don't want to release the software with the possibility that some systems will have this behaviour.
I have no experience with this library, but perhaps some fresh eyes might give you a brainwave...
Firstly, WELL WRITTEN QUESTION! Obviously this problem really has you stumped...
Your note about the error not occurring after a rebuild screams out: Does this library generate some kind of state file, in its binary directory, after it runs?
If so, it is possible that it is saving incorrect path information into that 'configuration' file, in a misguided attempt to accelerate its next start-up.
Perhaps scan this directory for changes between a 'fresh build' and 'first run'?
At very least you might find a file you can clean up on shut-down to avoid this alert...
HTH
Maybe you can try this:
Run diskmgmt.msc
Change the driveletter for Disk 2 (right click) if my assumption that Disk 2 is a Removable Disk is true
Run your application
If this removes the error, something in the application is referring to the old driveletter
It could be in the p/invoked libs
Maybe see: http://gcc.gnu.org/bugzilla/show_bug.cgi?id=46501 It talks about gcc somehow compiling a driveletter into a binary
+1 Great question, but It is not possible to "catch"
Its one of these awful solutions that will turn up on DailyWTF in 5 years. But for now it is stored here http://www.pinvoke.net/default.aspx/user32.senddlgitemmessage
using Microsoft.VisualBasic; //this reference is for the Constants.vbNo;
public partial class Form1 : Form
{
[DllImport("user32.dll")]
static extern IntPtr SendDlgItemMessage(IntPtr hDlg, int nIDDlgItem, uint Msg, UIntPtr wParam, IntPtr lParam);
[DllImport("user32.dll", SetLastError = true)]
public static extern IntPtr SetActiveWindow(IntPtr hWnd);
// For Windows Mobile, replace user32.dll with coredll.dll
[DllImport("user32.dll", SetLastError = true)]
static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
// Find window by Caption only. Note you must pass IntPtr.Zero as the first parameter.
[DllImport("user32.dll", EntryPoint = "FindWindow", SetLastError = true)]
static extern IntPtr FindWindowByCaption(IntPtr ZeroOnly, string lpWindowName);
[DllImport("user32.dll", SetLastError = true)]
static extern uint GetDlgItemText(IntPtr hDlg, int nIDDlgItem,[Out] StringBuilder lpString, int nMaxCount);
public void ClickSaveBoxNoButton()
{
//In this example, we've opened a Notepad instance, entered some text, and clicked the 'X' to close Notepad.
//Of course we received the 'Do you want to save...' message, and we left it sitting there. Now on to the code...
//
//Note: this example also uses API calls to FindWindow, GetDlgItemText, and SetActiveWindow.
// You'll have to find those separately.
//Find the dialog box (no need to find a "parent" first)
//classname is #32770 (dialog box), dialog box title is Notepad
IntPtr theDialogBoxHandle; // = null;
string theDialogBoxClassName = "#32770";
string theDialogBoxTitle = "Notepad";
int theDialogItemId = Convert.ToInt32("0xFFFF", 16);
StringBuilder theDialogTextHolder = new StringBuilder(1000);
//hardcoding capacity - represents maximum text length
string theDialogText = string.Empty;
string textToLookFor = "Do you want to save changes to Untitled?";
bool isChangeMessage = false;
IntPtr theNoButtonHandle; // = null;
int theNoButtonItemId = (int)Constants.vbNo;
//actual Item ID = 7
uint theClickMessage = Convert.ToUInt32("0x00F5", 16);
//= BM_CLICK value
uint wParam = 0;
uint lParam = 0;
//Get a dialog box described by the specified info
theDialogBoxHandle = FindWindow(theDialogBoxClassName, theDialogBoxTitle);
//a matching dialog box was found, so continue
if (theDialogBoxHandle != IntPtr.Zero)
{
//then get the text
GetDlgItemText(theDialogBoxHandle, theDialogItemId, theDialogTextHolder, theDialogTextHolder.Capacity);
theDialogText = theDialogTextHolder.ToString();
}
//Make sure it's the right dialog box, based on the text we got.
isChangeMessage = Regex.IsMatch(theDialogText, textToLookFor);
if ((isChangeMessage))
{
//Set the dialog box as the active window
SetActiveWindow(theDialogBoxHandle);
//And, click the No button
SendDlgItemMessage(theDialogBoxHandle, theNoButtonItemId, theClickMessage, (System.UIntPtr)wParam, (System.IntPtr)lParam);
}
}
It turns out there was no way to definitely answer this question.
I ended up "solving" the problem by figuring out that there was some hardware registered on the system that wasn't present. It is still a mystery to me why, after several years, only GDAL managed to provoke this bug.
I will put the inability to catch this exception down to the idiosyncrasies involved with p/invoke and the hardware error thrown at a very low level on the system.
You could add custom error handlers to gdal. This may help:
Link
http://trac.osgeo.org/gdal/ticket/2895

Why does FindMimeFromData recognize image/tiff on one host, but doesn't on another one?

I'm using FindMimeFromData from urlmon.dll for sniffing uploaded files' MIME type. According to MIME Type Detection in Internet Explorer, image/tiff is one of the recognized MIME types. It works fine on my development machine (Windows 7 64bit, IE9), but doesn't work on the test env (Windows Server 2003 R2 64bit, IE8) - it returns application/octet-stream instead of image/tiff.
The above article describes the exact steps taken to determine the MIME type, but since image/tiff is one of the 26 recognized types, it should end on step 2 (sniffing the actual data), so that file extensions and registered applications (and other registry stuff) shouldn't matter.
Oh and by the way, TIFF files actually are associated with a program (Windows Picture and Fax Viewer) on the test server. It's not that any reference to TIFF is absent in Windows registry.
Any ideas why it doesn't work as expected?
EDIT: FindMimeFromData is used like this:
public class MimeUtil
{
[DllImport("urlmon.dll", CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = false)]
private static extern int FindMimeFromData(
IntPtr pBC,
[MarshalAs(UnmanagedType.LPWStr)] string pwzUrl,
[MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.I1, SizeParamIndex = 3)] byte[] pBuffer,
int cbSize,
[MarshalAs(UnmanagedType.LPWStr)] string pwzMimeProposed,
int dwMimeFlags,
out IntPtr ppwzMimeOut,
int dwReserved);
public static string GetMimeFromData(byte[] data)
{
IntPtr mimetype = IntPtr.Zero;
try
{
const int flags = 0x20; // FMFD_RETURNUPDATEDIMGMIMES
int res = FindMimeFromData(IntPtr.Zero, null, data, data.Length, null, flags, out mimetype, 0);
switch (res)
{
case 0:
string mime = Marshal.PtrToStringUni(mimetype);
return mime;
// snip - error handling
// ...
default:
throw new Exception("Unexpected HRESULT " + res + " returned by FindMimeFromData (in urlmon.dll)");
}
}
finally
{
if (mimetype != IntPtr.Zero)
Marshal.FreeCoTaskMem(mimetype);
}
}
}
which is then called like this:
protected void uploader_FileUploaded(object sender, FileUploadedEventArgs e)
{
int bsize = Math.Min(e.File.ContentLength, 256);
byte[] buffer = new byte[bsize];
int nbytes = e.File.InputStream.Read(buffer, 0, bsize);
if (nbytes > 0)
string mime = MimeUtil.GetMimeFromData(buffer);
// ...
}
I was unable to reproduce your problem, however I did some research on the subject. I believe that it is as you suspect, the problem is with step 2 of MIME Type Detection: the hard-coded tests in urlmon.dll v9 differ from those in urlmon.dll v8.
The Wikipedia article on TIFF shows how complex the format is and that is has been a problem from the very beginning:
When TIFF was introduced, its extensibility provoked compatibility problems. The flexibility in encoding gave rise to the joke that TIFF stands for Thousands of Incompatible File Formats.
The TIFF Compression Tag section clearly shows many rare compression schemes that, as I suspect, have been omitted while creating the urlmon.dll hard-coded tests in earlier versions of IE.
So, what can be done to solve this problem? I can think of three solutions, however each of them brings different kind of new problems along:
Update the IE on your dev machine to version 9.
Apply the latest IE 8 updates on your dev machine. It is well known that modified versions of urlmon.dll are introduced frequently (eg. KB974455). One of them may contain the updated MIME hard-coded tests.
Distribute own copy of urlmon.dll with your application.
It seems that solutions 1 and 2 are the ones you should choose from. There may be a problem, however, with the production environment. As my experience shows the administrators of production env often disagree to install some updates for many reasons. It may be harder to convince an admin to update the IE to v9 and easier to install an IE8 KB update (as they are supposed to, but we all know how it is). If you're in control of the production env, I think you should go with solution 1.
The 3rd solution introduces two problems:
legal: It may be against the Microsoft's policies to distribute own copy of urlmon.dll
coding: you have to load the dll dynamically to call the FindMimeFromData function or at least customize your app's manifest file because of the Dynamic-Link Library Search Order. I assume you are aware, that it is a very bad idea just to manually copy a newer version of urlmon.dll to the system folder as other apps would most likely crash using it.
Anyway, good luck with solving your urlmon riddle.

Access denied while getting process path

I am trying to get process path by pid but I'm getting Win32Exception (access id denied).
The code looks like this:
string path = Process.GetProcessById(pid).MainModule.FileName
I have tried using OpenProcess with GetModuleFileNameEx but OpenProcess is returning 0. I even tried enabling SeDebugPrivilege according to C# – How to enable SeDebugPrivilege but it didn't help.
The above code works for most of the processes but throws error for SynTPHelper.exe (Synaptics Pointing Device Helper) The application is running under the same username as my code. Both, my application and the process run in 64 bit.
Is it possible to retrieve the path without running my application as an administrator?
Edit
Task Manager is able to 'open file location' even though I'm not running it as an administrator.
Finally I managed to solve it. As it turned out there is new function in Vista and above for getting process path and new process access (PROCESS_QUERY_LIMITED_INFORMATION):
QueryFullProcessImageName
Here is the code that works from non-elevated process:
private static string GetExecutablePathAboveVista(UIntPtr dwProcessId)
{
StringBuilder buffer = new StringBuilder(1024);
IntPtr hprocess = OpenProcess(ProcessAccessFlags.PROCESS_QUERY_LIMITED_INFORMATION, false, dwProcessId);
if (hprocess != IntPtr.Zero)
{
try
{
int size = buffer.Capacity;
if (QueryFullProcessImageName(hprocess, 0, buff, out size))
{
return buffer.ToString();
}
}
finally
{
CloseHandle(hprocess);
}
}
return string.Empty;
}
Well, it is certainly not unheard of for services to remove access rights so that even an administrator cannot open the process. A service has enough privileges to do so, DRM components like audiodg.exe readily do so. A mouse pad helper doesn't strike me as something that would require such protection. But what the hey, why would anybody ever need to mess with a mouse pad helper?

Best way to determine if two path reference to same file in C#

In the upcoming Java7, there is a new API to check if two file object are same file reference.
Are there similar API provided in the .NET framework?
I've search it over MSDN but nothing enlighten me.
I want it simple but I don't want to compare by filename which will cause problems with hard/symbolic links and different style of path. (e.g. \\?\C:\, C:\).
What I going to do is just prevent duplicated file being drag and dropped to my linklist.
As far as I can see (1) (2) (3) (4), the way JDK7 does it, is by calling GetFileInformationByHandle on the files and comparing dwVolumeSerialNumber, nFileIndexHigh and nFileIndexLow.
Per MSDN:
You can compare the VolumeSerialNumber and FileIndex members returned in the BY_HANDLE_FILE_INFORMATION structure to determine if two paths map to the same target; for example, you can compare two file paths and determine if they map to the same directory.
I do not think this function is wrapped by .NET, so you will have to use P/Invoke.
It might or might not work for network files. According to MSDN:
Depending on the underlying network components of the operating system and the type of server connected to, the GetFileInformationByHandle function may fail, return partial information, or full information for the given file.
A quick test shows that it works as expected (same values) with a symbolic link on a Linux system connected using SMB/Samba, but that it cannot detect that a file is the same when accessed using different shares that point to the same file (FileIndex is the same, but VolumeSerialNumber differs).
Edit: Note that #Rasmus Faber mentions the GetFileInformationByHandle function in the Win32 api, and this does what you want, check and upvote his answer for more information.
I think you need an OS function to give you the information you want, otherwise it's going to have some false negatives whatever you do.
For instance, does these refer to the same file?
\server\share\path\filename.txt
\server\d$\temp\path\filename.txt
I would examine how critical it is for you to not have duplicate files in your list, and then just do some best effort.
Having said that, there is a method in the Path class that can do some of the work: Path.GetFullPath, it will at least expand the path to long names, according to the existing structure. Afterwards you just compare the strings. It won't be foolproof though, and won't handle the two links above in my example.
Here is a C# implementation of IsSameFile using GetFileInformationByHandle:
NativeMethods.cs
public static class NativeMethods
{
[StructLayout(LayoutKind.Explicit)]
public struct BY_HANDLE_FILE_INFORMATION
{
[FieldOffset(0)]
public uint FileAttributes;
[FieldOffset(4)]
public FILETIME CreationTime;
[FieldOffset(12)]
public FILETIME LastAccessTime;
[FieldOffset(20)]
public FILETIME LastWriteTime;
[FieldOffset(28)]
public uint VolumeSerialNumber;
[FieldOffset(32)]
public uint FileSizeHigh;
[FieldOffset(36)]
public uint FileSizeLow;
[FieldOffset(40)]
public uint NumberOfLinks;
[FieldOffset(44)]
public uint FileIndexHigh;
[FieldOffset(48)]
public uint FileIndexLow;
}
[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool GetFileInformationByHandle(SafeFileHandle hFile, out BY_HANDLE_FILE_INFORMATION lpFileInformation);
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern SafeFileHandle CreateFile([MarshalAs(UnmanagedType.LPTStr)] string filename,
[MarshalAs(UnmanagedType.U4)] FileAccess access,
[MarshalAs(UnmanagedType.U4)] FileShare share,
IntPtr securityAttributes,
[MarshalAs(UnmanagedType.U4)] FileMode creationDisposition,
[MarshalAs(UnmanagedType.U4)] FileAttributes flagsAndAttributes,
IntPtr templateFile);
}
PathUtility.cs
public static bool IsSameFile(string path1, string path2)
{
using (SafeFileHandle sfh1 = NativeMethods.CreateFile(path1, FileAccess.Read, FileShare.ReadWrite,
IntPtr.Zero, FileMode.Open, 0, IntPtr.Zero))
{
if (sfh1.IsInvalid)
Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
using (SafeFileHandle sfh2 = NativeMethods.CreateFile(path2, FileAccess.Read, FileShare.ReadWrite,
IntPtr.Zero, FileMode.Open, 0, IntPtr.Zero))
{
if (sfh2.IsInvalid)
Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
NativeMethods.BY_HANDLE_FILE_INFORMATION fileInfo1;
bool result1 = NativeMethods.GetFileInformationByHandle(sfh1, out fileInfo1);
if (!result1)
throw new IOException(string.Format("GetFileInformationByHandle has failed on {0}", path1));
NativeMethods.BY_HANDLE_FILE_INFORMATION fileInfo2;
bool result2 = NativeMethods.GetFileInformationByHandle(sfh2, out fileInfo2);
if (!result2)
throw new IOException(string.Format("GetFileInformationByHandle has failed on {0}", path2));
return fileInfo1.VolumeSerialNumber == fileInfo2.VolumeSerialNumber
&& fileInfo1.FileIndexHigh == fileInfo2.FileIndexHigh
&& fileInfo1.FileIndexLow == fileInfo2.FileIndexLow;
}
}
}
Answer: There is no foolproof way in which you can compare to string base paths to determine if they point to the same file.
The main reason is that seemingly unrelated paths can point to the exact same file do to file system redirections (junctions, symbolic links, etc ...) . For example
"d:\temp\foo.txt"
"c:\othertemp\foo.txt"
These paths can potentially point to the same file. This case clearly eliminates any string comparison function as a basis for determining if two paths point to the same file.
The next level is comparing the OS file information. Open the file for two paths and compare the handle information. In windows this can be done with GetFileInformationByHandle. Lucian Wischik did an excellent post on this subject here.
There is still a problem with this approach though. It only works if the user account performing the check is able to open both files for reading. There are numerous items which can prevent a user from opening one or both files. Including but not limited to ...
Lack of sufficient permissions to file
Lack of sufficient permissions to a directory in the path of the file
File system change which occurs between the opening of the first file and the second such as a network disconnection.
When you start looking at all of these problems you begin to understand why Windows does not provide a method to determine if two paths are the same. It's just not an easy/possible question to answer.
First I thought it is really easy but this doesn't work:
string fileName1 = #"c:\vobp.log";
string fileName2 = #"c:\vobp.log".ToUpper();
FileInfo fileInfo1 = new FileInfo(fileName1);
FileInfo fileInfo2 = new FileInfo(fileName2);
if (!fileInfo1.Exists || !fileInfo2.Exists)
{
throw new Exception("one of the files does not exist");
}
if (fileInfo1.FullName == fileInfo2.FullName)
{
MessageBox.Show("equal");
}
Maybe this library helps http://www.codeplex.com/FileDirectoryPath. I haven't used it myself.
edit: See this example on that site:
//
// Path comparison
//
filePathAbsolute1 = new FilePathAbsolute(#"C:/Dir1\\File.txt");
filePathAbsolute2 = new FilePathAbsolute(#"C:\DIR1\FILE.TXT");
Debug.Assert(filePathAbsolute1.Equals(filePathAbsolute2));
Debug.Assert(filePathAbsolute1 == filePathAbsolute2);
If you need to compare the same filenames over and over again, I would suggest you look into canonalizing those names.
Under a Unix system, there is the realpath() function which canonalizes your path. I think that's generally the best bet if you have a complex path. However, it is likely to fail on volumes mounted via network connections.
However, based on the realpath() approach, if you want to support multiple volume including network volumes, you could write your own function that checks each directory name in a path and if it references a volume then determine whether the volume reference in both paths is the same. This being said, the mount point may be different (i.e. the path on the destination volume may not be the root of that volume) so it is not that easy to solve all the problems along the way, but it is definitively possible (otherwise how would it work in the first place?!)
Once the filenames properly canonalized a simple string comparison gives you the correct answer.
Rasmus answer is probably the fastest way if you don't need to compare the same filenames over and over again.
You could always perform an MD5 encode on both and compare the result. Not exactly efficient, but easier than manually comparing the files yourself.
Here is a post on how to MD5 a string in C#.

Categories

Resources