PathTooLongException C# 4.5 - c#

I having trouble of copying some folder 260+ chars (for example: F:\NNNNNNNNNNNNNNNN\NNNNNNNNNNN\ROOT\$RECYCLE.BIN\S-1-5-21-3299053755-4209892151-505108915-1000\$RMSL3U8\NNNNNNNNN NNNNNNNN\NNNNNNNNNNN\NNNNNNNNNN\NNNNNNNNNN\publish\Application Files\TNNNNNNNNNNNN_1_0_0_0\NNNNNNNNNNNN.exe.manifest) to some other place with standart DrectoryInfo.Create(); adding \?\ or \?\UNC\ (like "\\?\UNC\") just throw another ArgumentException.
What am i doing wrong? What else i can do without using Directory.SetCurrentDirectory() ?

Actually you need to call win32 from c#. We have done this
using System;
using System.IO;
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;
public static class LongPath
{
static class Win32Native
{
[StructLayout(LayoutKind.Sequential)]
public class SECURITY_ATTRIBUTES
{
public int nLength;
public IntPtr pSecurityDescriptor;
public int bInheritHandle;
}
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern bool CreateDirectory(string lpPathName, SECURITY_ATTRIBUTES lpSecurityAttributes);
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern SafeFileHandle CreateFile(string lpFileName, int dwDesiredAccess, FileShare dwShareMode, SECURITY_ATTRIBUTES securityAttrs, FileMode dwCreationDisposition, int dwFlagsAndAttributes, IntPtr hTemplateFile);
}
public static bool CreateDirectory(string path)
{
return Win32Native.CreateDirectory(String.Concat(#"\\?\", path), null);
}
public static FileStream Open(string path, FileMode mode, FileAccess access)
{
SafeFileHandle handle = Win32Native.CreateFile(String.Concat(#"\\?\", path), (int)0x10000000, FileShare.None, null, mode, (int)0x00000080, IntPtr.Zero);
if (handle.IsInvalid)
{
throw new System.ComponentModel.Win32Exception();
}
return new FileStream(handle, access);
}
}
A sample code:
string path = #"c:\".PadRight(255, 'a');
LongPath.CreateDirectory(path);
path = String.Concat(path, #"\", "".PadRight(255, 'a'));
LongPath.CreateDirectory(path);
string filename = Path.Combine(path, "test.txt");
FileStream fs = LongPath.Open(filename, FileMode.CreateNew, FileAccess.Write);
using (StreamWriter sw = new StreamWriter(fs))
{
sw.WriteLine("abc");
}

There's a great library on Microsoft TechNet for overcoming the long filenames problem, it's called Delimon.Win32.I​O Library (V4.0) and it has its own versions of key methods from System.IO
For example, you would replace:
System.IO.Directory.GetFiles
with
Delimon.Win32.IO.Directory.GetFiles
which will let you handle long files and folders.
From the website:
Delimon.Win32.IO replaces basic file functions of System.IO and
supports File & Folder names up to up to 32,767 Characters.
This Library is written on .NET Framework 4.0 and can be used either
on x86 & x64 systems. The File & Folder limitations of the standard
System.IO namespace can work with files that have 260 characters in a
filename and 240 characters in a folder name (MAX_PATH is usually
configured as 260 characters). Typically you run into the
System.IO.PathTooLongException Error with the Standard .NET Library.

Yes, using the standard APIs will give you this kind of limitations (255 chars IIRC).
From .NET you can use the AlphaFS project which lets you use very long paths (using the "\\?\" style) and mimics the System.IO namespace.
You will probably be able to use this library just as if you were using System.IO, for example : AlphaFS.Win32.Filesystem.File.Copy() instead System.IO.File.Copy()
If you don't want or cannot use AlphaFS you'll have to pinvoke the Win32 API

Related

FileSystemWatcher locks parent folder, is there a fix or an alternative?

Whether in .NET Core or .NET Framework, the use of a FileSystemWatcher on a folder makes it impossible to delete any parent folder with File Explorer.
File Explorer will ask you for admin rights. Launching File Explorer as admin doesn't fix the issue.
The only way that I can make it work is to set the FileSystemWatcher to the root drive, and include subdirectories, which could lead to big performance issues, and a buffer overflow on the FSW.
FileSystemWatcher fsw = new FileSystemWatcher(#"C:\One\Two\Three");
fsw.EnableRaisingEvents = true;
You will not be able to delete folders 'One' or 'Two'.
The problem starts as soon as set 'EnableRaisingEvents' to true, but if you don't, you will not get any notification.
Is there a way to overcome this problem, or an alternative to FileSystemWatcher?
#EricP gave the root cause: The FSW internally makes a call to CreateFileW to open a handle to the monitored directory Three, which tells Windows to prevent deleting the parent folders One and Two.
But the CreateFileW call is made with read+write+delete sharing mode, meaning Windows should allow any other processes to delete the monitored folder Three.
So there's no direct fix for your problem, but there is a workaround:
Delete Three first, which should release the handle, then delete Two and/or One, which should be allowed.
Here's a code sample reproing this without any FileSystemWatcher code, just direct Windows P/Invokes:
using Microsoft.Win32.SafeHandles;
using System;
using System.IO;
using System.Runtime.InteropServices;
namespace MyNamespace
{
public static class MyClass
{
static void Main()
{
string path = #"C:\Users\YOURUSERNAME\Desktop\parent\child";
if (!Directory.Exists(path))
{
throw new Exception("Make sure to create the directories before testing.");
}
// This will lock the directory
SafeFileHandle _directoryHandle = CreateFile(
lpFileName: path,
dwDesiredAccess: 0x0001, //FILE_LIST_DIRECTORY
dwShareMode: FileShare.Read | FileShare.Delete | FileShare.Write,
dwCreationDisposition: FileMode.Open,
dwFlagsAndAttributes:
0x02000000 /*FILE_FLAG_BACKUP_SEMANTICS*/ |
0x40000000 /*FILE_FLAG_OVERLAPPED*/
);
Console.WriteLine("Try to manually delete the 'parent' directory using File Explorer. It should fail.");
Console.WriteLine("Now try to manually delete the 'child' directory, then the 'parent' directory, using File Explorer. It should succeed.");
Console.WriteLine("Press enter to exit.");
System.Diagnostics.Debugger.Break();
_directoryHandle.Dispose();
}
internal enum BOOL : int
{
FALSE = 0,
TRUE = 1,
}
[StructLayout(LayoutKind.Sequential)]
internal struct SECURITY_ATTRIBUTES
{
internal uint nLength;
internal IntPtr lpSecurityDescriptor;
internal BOOL bInheritHandle;
}
[DllImport("kernel32.dll", EntryPoint = "CreateFileW", SetLastError = true, CharSet = CharSet.Unicode, BestFitMapping = false, ExactSpelling = true)]
private static extern unsafe SafeFileHandle CreateFilePrivate(
string lpFileName,
int dwDesiredAccess,
FileShare dwShareMode,
SECURITY_ATTRIBUTES* lpSecurityAttributes,
FileMode dwCreationDisposition,
int dwFlagsAndAttributes,
IntPtr hTemplateFile);
internal static unsafe SafeFileHandle CreateFile(
string lpFileName,
int dwDesiredAccess,
FileShare dwShareMode,
FileMode dwCreationDisposition,
int dwFlagsAndAttributes)
{
return CreateFilePrivate(lpFileName, dwDesiredAccess, dwShareMode, null, dwCreationDisposition, dwFlagsAndAttributes, IntPtr.Zero);
}
}
}

Determine whether destination for file move is on same filesystem in C#

I have a Windows Forms application which uses File.Move to move files in response to user request. This is not an asynchronous method, but when the destination is on the same drive it happens almost instantly (I assume because it can just rewrite the header and not move anything). However, it blocks the application if the destination is not on the same drive. Not desirable.
So naturally I thought about switching to async. It looks like the way to do this is to use file streams and use Stream.CopyToAsync before removing the original. That's fine, except if it is on the same drive, this is a lot of wasteful copying.
Is there a reliable way to determine if a proposed destination for a file is on a different drive than that drive is currently stored on before deciding which operation to perform? I could obviously look at the drive letter, but that seems kind of hacky and I think there are some cases like junction points where it will not work correctly.
Another possible choice is to use something like Task.Run(() => File.Move(x)), although I have a vaguely bad feeling about that.
I think the best effort for this is going to be comparing volume serial numbers. GetFileInformationByHandle() is able to get the volume serial number of a file handle. This is definitely a YMMV for RAID / striped volumes, so if those apply, I would test this solution for the performance. It does work for junction points.
The DllImport for it is
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool GetFileInformationByHandle(
IntPtr hFile,
out BY_HANDLE_FILE_INFORMATION lpFileInformation);
We also need a way to get suitable file handles to use. Since we're working with potential destinations, if we assume the destination directory already exists, we can use CreateFile() to get a directory handle.
For CreateFile(), the DllImport is
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr 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);
We'll also need CloseHandle(),
[DllImport("kernel32.dll", SetLastError = true)]
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
[SuppressUnmanagedCodeSecurity]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool CloseHandle(IntPtr hObject);
With all those pieces in place, create a wrapper method that returns true if the volume serial number of the supplied paths match. It obviously makes sense throw more specific exceptions, but for the sake of brevity:
static bool SameVolume(string src, string dest)
{
IntPtr srcHandle = IntPtr.Zero, destHandle = IntPtr.Zero;
try
{
srcHandle = CreateFile(src, FileAccess.Read, FileShare.ReadWrite, IntPtr.Zero,
FileMode.Open, (FileAttributes) 0x02000000, IntPtr.Zero);
destHandle = CreateFile(dest, FileAccess.Read, FileShare.ReadWrite, IntPtr.Zero,
FileMode.Open, (FileAttributes) 0x02000000, IntPtr.Zero);
var srcInfo = new BY_HANDLE_FILE_INFORMATION();
var destInfo = new BY_HANDLE_FILE_INFORMATION();
if (!GetFileInformationByHandle(srcHandle, out srcInfo))
{
throw new Exception(Marshal.GetLastWin32Error().ToString());
}
if (!GetFileInformationByHandle(destHandle, out destInfo))
{
throw new Exception(Marshal.GetLastWin32Error().ToString());
}
return srcInfo.VolumeSerialNumber == destInfo.VolumeSerialNumber;
}
finally
{
if (srcHandle != IntPtr.Zero)
{
CloseHandle(srcHandle);
}
if (destHandle != IntPtr.Zero)
{
CloseHandle(destHandle);
}
}
}
Alternatively, you could just use
Task.Run(() => File.Move(x))
or another asynchronous construct to get this done without locking up the application's interface. I can't see anything wrong with that.

Constructing a FileStream from a handle produced by CreateFile gives an empty stream

I'm attempting to write a simple program that would take a CSV consisting of two columns, and given a keyword from the first column, return the corresponding value from the second one. The problem is that I need the CSV to be in an alternate data stream to make the program as portable as possible (I want to make it so that when the user drops a CSV file on the executable, the CSV is overwritten). That is why I'm trying to use WinAPI's CreateFile function -- .NET doesn't support alternate data streams. Unfortunately, I'm failing miserably.
In the current state, the program is supposed to read a file named test.csv. I want to do CreateFile on it, convert the intPtr handle to a SafeFileHandle and then pass the SafeFileHandle to a FileStream constructor. The best I've been able to achieve is an empty stream though. It doesn't seem to me that the program is actually getting the right handle. When I try "CREATE_ALWAYS" or "CREATE_NEW" instead of "OPEN_ALWAYS", I'm getting an "Invalid Handle" error, no matter what I do with the rest of the parameters. With "OPEN_ALWAYS", when I check the value of "stream.Name", I'm getting "Unknown".
Here is the code:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.IO;
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;
namespace Searcher
{
public partial class Searcher : Form
{
public static List<string> keywords;
public static List<string> values;
public Searcher()
{
InitializeComponent();
//byte[] path = Encoding.UTF8.GetBytes("C:\\Users\\as\\Documents\\test.csv");
SafeFileHandle safeADSHandle = NativeMethods.CreateFileW("test.csv",
NativeConstants.GENERIC_READ,
NativeConstants.FILE_SHARE_READ,
IntPtr.Zero,
NativeConstants.OPEN_ALWAYS,
0,
IntPtr.Zero);
//var safeADSHandle = new SafeFileHandle(handle, true);
if (safeADSHandle.IsInvalid)
{
Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
}
var stream = new FileStream(safeADSHandle, FileAccess.Read);
MessageBox.Show(stream.Name);
var reader = new StreamReader(stream);
Searcher.keywords = new List<string>();
Searcher.values = new List<string>();
while (!reader.EndOfStream)
{
var line = reader.ReadLine();
var values = line.Split(',');
Searcher.keywords.Add(values[0]);
Searcher.values.Add(values[1]);
}
cbKeyword.DataSource = Searcher.keywords;
cbKeyword.AutoCompleteSource = AutoCompleteSource.ListItems;
}
private void btnSearch_Click(object sender, EventArgs e)
{
tbResult.Text = Searcher.values[cbKeyword.SelectedIndex];
}
}
}
public partial class NativeMethods
{
[DllImportAttribute("kernel32.dll", SetLastError = true, EntryPoint = "CreateFile")]
public static extern SafeFileHandle CreateFileW(
[InAttribute()] [MarshalAsAttribute(UnmanagedType.LPWStr)] string lpFileName,
uint dwDesiredAccess,
uint dwShareMode,
[InAttribute()] System.IntPtr lpSecurityAttributes,
uint dwCreationDisposition,
uint dwFlagsAndAttributes,
[InAttribute()] System.IntPtr hTemplateFile
);
}
public partial class NativeConstants
{
/// GENERIC_WRITE -> (0x40000000L)
public const int GENERIC_WRITE = 1073741824;
public const uint GENERIC_READ = 2147483648;
/// FILE_SHARE_DELETE -> 0x00000004
public const int FILE_SHARE_DELETE = 4;
/// FILE_SHARE_WRITE -> 0x00000002
public const int FILE_SHARE_WRITE = 2;
/// FILE_SHARE_READ -> 0x00000001
public const int FILE_SHARE_READ = 1;
/// OPEN_ALWAYS -> 4
public const int OPEN_ALWAYS = 4;
public const int CREATE_NEW = 1;
}
Edit I've changed the code above slightly to reflect the changes after the comments. Now I'm not converting from IntPtr to SafeFileHandle. One thing to mention perhaps is that I before this change I tried reading the value of handle.ToString() to see if it was changing and it was -- it's a random number.
The reason this is not working is because you have specified an entry point without a character set. When you don't specify a character set in DllImport, the default is CharSet.Ansi, which means platform invoke will search for the function as follows:
First for an unmangled exported function, i.e. the name CreateFile from the entry point that you specified (which does not exist in kernel32.dll)
Next if the unmangled name was not found, it will search for the mangled name based on the charset, so it will search for CreateFileA
So it will find the CreateFileA exported function, which assumes any strings passed in to it are in 1-byte character ANSI format. However, you are marshalling the string as a wide string. Note that the wide-character version of the function is called CreateFileW (this distinction between ANSI and wide character versions of functions is common in the Windows API).
To fix this you just need to make sure that the marshalling of your parameters matches up with what the function you are importing expects. So you could remove the EntryPoint field, in which case it will use the C# method name to find the exported function instead (so it will find CreateFileW).
However to make it even clearer, I would write your platform invoke code as follows:
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern IntPtr 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);
I took this from the pinvoke.net website, which should be your go-to for writing pinvoke code rather than trying to bash it out yourself (especially if you aren't familiar with the Windows API or marshalling :).
The reason there was no error is probably because CreateFile was creating the file since it wouldn't have been able to find it.
The file name will appear as "[Unknown]" though. I suspect this is because the code to get a file name from a handle is non-trivial.

List Hard Links of a file (in C#)

I want to write a program that shows the files of another drive with hard links.
I want to keep both hardlinks consistent in filename and other things, so I have to get a function/method where I can list all current hard links of a file.
For example:
I have a file C:\file.txt and a second hard link to D:\file.txt.
Then I rename D:\file.txt to D:\file_new.txt.
I now want to be able to also rename the hardlink on the C drive as well.
So I need a function which returns for D:\file_new.txt that there are the following hardlinks:
C:\file.txt
D:\file_new.txt
then I can rename the hard link on C:\ also to get D:\file_new.txt
So I need to get all hard links of a physical file.
Or: All hard links of a file addressed with a hard link.
Hope somebody can help!
Edit:
Oliver noticed that hard links can't be used on different disks. thanks... So I extend the question to: What do I need? Junction Points? Symbolic Links? It should also work with files not only with folders!
the following code should work well (originally postet by Peter provost on PowerShell Code Repository):
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using Microsoft.Win32.SafeHandles;
using FILETIME = System.Runtime.InteropServices.ComTypes.FILETIME;
namespace HardLinkEnumerator
{
public static class Kernel32Api
{
[StructLayout(LayoutKind.Sequential)]
public struct BY_HANDLE_FILE_INFORMATION
{
public uint FileAttributes;
public FILETIME CreationTime;
public FILETIME LastAccessTime;
public FILETIME LastWriteTime;
public uint VolumeSerialNumber;
public uint FileSizeHigh;
public uint FileSizeLow;
public uint NumberOfLinks;
public uint FileIndexHigh;
public uint FileIndexLow;
}
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
static extern SafeFileHandle CreateFile(
string lpFileName,
[MarshalAs(UnmanagedType.U4)] FileAccess dwDesiredAccess,
[MarshalAs(UnmanagedType.U4)] FileShare dwShareMode,
IntPtr lpSecurityAttributes,
[MarshalAs(UnmanagedType.U4)] FileMode dwCreationDisposition,
[MarshalAs(UnmanagedType.U4)] FileAttributes dwFlagsAndAttributes,
IntPtr hTemplateFile);
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool GetFileInformationByHandle(SafeFileHandle handle, out BY_HANDLE_FILE_INFORMATION lpFileInformation);
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool CloseHandle(SafeHandle hObject);
[DllImport("kernel32.dll", SetLastError=true, CharSet=CharSet.Unicode)]
static extern IntPtr FindFirstFileNameW(
string lpFileName,
uint dwFlags,
ref uint stringLength,
StringBuilder fileName);
[DllImport("kernel32.dll", SetLastError = true, CharSet=CharSet.Unicode)]
static extern bool FindNextFileNameW(
IntPtr hFindStream,
ref uint stringLength,
StringBuilder fileName);
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool FindClose(IntPtr fFindHandle);
[DllImport("kernel32.dll")]
static extern bool GetVolumePathName(string lpszFileName,
[Out] StringBuilder lpszVolumePathName, uint cchBufferLength);
[DllImport("shlwapi.dll", CharSet = CharSet.Auto)]
static extern bool PathAppend([In, Out] StringBuilder pszPath, string pszMore);
public static int GetFileLinkCount(string filepath)
{
int result = 0;
SafeFileHandle handle = CreateFile(filepath, FileAccess.Read, FileShare.Read, IntPtr.Zero, FileMode.Open, FileAttributes.Archive, IntPtr.Zero);
BY_HANDLE_FILE_INFORMATION fileInfo = new BY_HANDLE_FILE_INFORMATION();
if (GetFileInformationByHandle(handle, out fileInfo))
result = (int)fileInfo.NumberOfLinks;
CloseHandle(handle);
return result;
}
public static string[] GetFileSiblingHardLinks(string filepath)
{
List<string> result = new List<string>();
uint stringLength = 256;
StringBuilder sb = new StringBuilder(256);
GetVolumePathName(filepath, sb, stringLength);
string volume = sb.ToString();
sb.Length = 0; stringLength = 256;
IntPtr findHandle = FindFirstFileNameW(filepath, 0, ref stringLength, sb);
if (findHandle.ToInt32() != -1)
{
do
{
StringBuilder pathSb = new StringBuilder(volume, 256);
PathAppend(pathSb, sb.ToString());
result.Add(pathSb.ToString());
sb.Length = 0; stringLength = 256;
} while (FindNextFileNameW(findHandle, ref stringLength, sb));
FindClose(findHandle);
return result.ToArray();
}
return null;
}
}
}
Maybe i misunderstand your questions, but hardlinks can't go from one drive to another. They can only exist on a single drive.
Within the .Net framwork there is no support to get these informations. But the Win32 API can provide you with these informations.
Take a look at this article. It may help you.
Update
As far as i know it is not possible to do it between different drives. Junction Points are definitely not your friend cause it only works on foldes. But after reading this wikipedia article it seems that you can do it on Vista and Win7 with symbolic links. There is also a link to this shell extension which seems to cover everything you can do with these NTFS special features. Maybe with this you can check if your goal is reachable and maybe afterwards check the MSDN for the desired Win32 API function.
Note:
Hard links can only be files on the same volume, which contradicts the requirements of the question, which led to a follow-up question in the question body that the OP himself answered.
Given the title of the question, however, users who find this post by googling are most likely interest in a solution to the problem as stated in the title: given a file, how can I find all hard links to it (which are by definition all on the same volume).
The solution below is a streamlined and modernized adaptation of Marcel Nolte's helpful answer.
Its behavior and constraints are:
For a given input file, its array of hard links is returned, as full file paths, which includes the input file's path itself.
If the file has only one hard link (itself), or you specify a directory, only that file's / directory's full path is returned.
If the path refers to a volume that doesn't support hard links, or the path doesn't exist, null is returned.
NiKiZe notes that you cannot query hardlinks via a CIFS/SMB connection (network drive).
The following is a self-contained Windows console application that you should be able to compile and run as-is; the method of interest is HardLinkHelper.GetHardLinks():
using System;
using System.Text;
using System.Collections.Generic;
using System.Runtime.InteropServices;
namespace demo
{
public static class Program
{
public static void Main()
{
// Sample file that is known to have (one) hard link.
var file = Environment.ExpandEnvironmentVariables(#"%SYSTEMROOT%\explorer.exe");
foreach (var link in HardLinkHelper.GetHardLinks(file) ?? new string[] { "n/a" })
{
Console.WriteLine(link);
}
}
}
public static class HardLinkHelper
{
#region WinAPI P/Invoke declarations
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
static extern IntPtr FindFirstFileNameW(string lpFileName, uint dwFlags, ref uint StringLength, StringBuilder LinkName);
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
static extern bool FindNextFileNameW(IntPtr hFindStream, ref uint StringLength, StringBuilder LinkName);
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool FindClose(IntPtr hFindFile);
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
static extern bool GetVolumePathName(string lpszFileName, [Out] StringBuilder lpszVolumePathName, uint cchBufferLength);
public static readonly IntPtr INVALID_HANDLE_VALUE = (IntPtr)(-1); // 0xffffffff;
public const int MAX_PATH = 65535; // Max. NTFS path length.
#endregion
/// <summary>
//// Returns the enumeration of hardlinks for the given *file* as full file paths, which includes
/// the input path itself.
/// </summary>
/// <remarks>
/// If the file has only one hardlink (itself), or you specify a directory, only that
/// file's / directory's full path is returned.
/// If the path refers to a volume that doesn't support hardlinks, or the path
/// doesn't exist, null is returned.
/// </remarks>
public static string[] GetHardLinks(string filepath)
{
StringBuilder sbPath = new StringBuilder(MAX_PATH);
uint charCount = (uint)sbPath.Capacity; // in/out character-count variable for the WinAPI calls.
// Get the volume (drive) part of the target file's full path (e.g., #"C:\")
GetVolumePathName(filepath, sbPath, (uint)sbPath.Capacity);
string volume = sbPath.ToString();
// Trim the trailing "\" from the volume path, to enable simple concatenation
// with the volume-relative paths returned by the FindFirstFileNameW() and FindFirstFileNameW() functions,
// which have a leading "\"
volume = volume.Substring(0, volume.Length - 1);
// Loop over and collect all hard links as their full paths.
IntPtr findHandle;
if (INVALID_HANDLE_VALUE == (findHandle = FindFirstFileNameW(filepath, 0, ref charCount, sbPath))) return null;
List<string> links = new List<string>();
do
{
links.Add(volume + sbPath.ToString()); // Add the full path to the result list.
charCount = (uint)sbPath.Capacity; // Prepare for the next FindNextFileNameW() call.
} while (FindNextFileNameW(findHandle, ref charCount, sbPath));
FindClose(findHandle);
return links.ToArray();
}
}
}
I found a solution:
First I don't have to use hard links (since they can't point to an other disk). I have to use symbolic links instead. So I have one hard linked file on the original disk and symbolic links on other disks to this file. The limitation is OS must be Vista or newer.
Second I have to be able to find out where the symbolic link is pointing to.
Here I found a good example how to find out the information I need:
http://www.codeproject.com/KB/vista/ReparsePointID.aspx
The only thing I don't managed is to find all symbolic links from a specific file (hard link). I guess there is no out of the box solution and I have to recurse all symbolic links and test the target. But in my case that's no problem.
I hope that can help others!
I used the HardLinkHelper class in a project, and it works great for finding hard links on Windows NTFS drives.
Here's my version of HardLinkHelper class with the following changes:
Does not use StringBuilder, because Microsoft recommends to avoid using StringBuilder on pinvokes.
It has the member variables (INVALID_HANDLE_VALUE & MAX_PATH) private.
Null is never returned, and instead empty list is returned for non-existing path or unsupported path. This allows safe use of foreach on return value.
Added ReturnEmptyListIfOnlyOne input variable which when set to true, allows calling function to use it in a foreach, where the foreach loop will only be entered if the file has multiple shared hard links.
Example usage:
foreach (var link in HardLinkHelper.GetHardLinks(entry.Path, true))
public static class HardLinkHelper {
#region WinAPI P/Invoke declarations
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
static extern IntPtr FindFirstFileNameW(string lpFileName, uint dwFlags, ref uint StringLength, char[] LinkName);
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
static extern bool FindNextFileNameW(IntPtr hFindStream, ref uint StringLength, char[] LinkName);
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool FindClose(IntPtr hFindFile);
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
static extern bool GetVolumePathName(string lpszFileName, [Out] char[] lpszVolumePathName, uint cchBufferLength);
private static readonly IntPtr INVALID_HANDLE_VALUE = (IntPtr)(-1); // 0xffffffff;
private const int MAX_PATH = 65535; // Max. NTFS path length.
#endregion
/// <summary>
/// Checks for hard links on a Windows NTFS drive associated with the given path.
/// </summary>
/// <param name="filepath">Fully qualified path of the file to check for shared hard links</param>
/// <param name="ReturnEmptyListIfOnlyOne">Set true, to return populated list only for files having multiple hard links</param>
/// <returns>
/// Empty list is returned for non-existing path or unsupported path.
/// Single hard link paths returns empty list if ReturnEmptyListIfOnlyOne is true. If false, returns single item list.
/// For multiple shared hard links, returns list of all the shared hard links.
/// </returns>
public static List<string> GetHardLinks(string filepath, bool ReturnEmptyListIfOnlyOne = false) {
List<string> links = new List<string>();
try {
Char[] sbPath = new Char[MAX_PATH + 1];
uint charCount = (uint)MAX_PATH;
GetVolumePathName(filepath, sbPath, (uint)MAX_PATH); // Must use GetVolumePathName, because Path.GetPathRoot fails on a mounted drive on an empty folder.
string volume = new string(sbPath).Trim('\0');
volume = volume.Substring(0, volume.Length - 1);
Array.Clear(sbPath, 0, MAX_PATH); // Reset the array because these API's can leave garbage at the end of the buffer.
IntPtr findHandle;
if (INVALID_HANDLE_VALUE != (findHandle = FindFirstFileNameW(filepath, 0, ref charCount, sbPath))) {
do {
links.Add((volume + new string(sbPath)).Trim('\0')); // Add the full path to the result list.
charCount = (uint)MAX_PATH;
Array.Clear(sbPath, 0, MAX_PATH);
} while (FindNextFileNameW(findHandle, ref charCount, sbPath));
FindClose(findHandle);
}
}
catch (Exception ex) {
//Logger.Instance.Info($"GetHardLinks: Exception, file: {filepath}, reason: {ex.Message}, stacktrace {ex.StackTrace}");
}
if (ReturnEmptyListIfOnlyOne && links.Count < 2)
links.Clear();
return links;
}
}
try:
using System.IO;
string[] filePathsC = Directory.GetFiles(#"c:\");
string[] filePathsD = Directory.GetFiles(#"d:\");
and loop through the arrays, find the files and change the name
EDIT:
By reading the comments I know that I answered before I knew what a hardlink is. I realise now that this answer is not helping.

Help using PInvoke CreateDirectory () in C#

I'm using C# for WinForms app in VS2010 and I needed to create a directory in which the path was too large for the .NET methods (248 char limit, I believe) and ran across suggestions from google to use the Unicode Win32 CreateDirectory(). I had initially tried calling it using Unicode and passed parameters but after several failed attempts, I've reduced the code and am using EXACTLY the code found here:
http://www.pinvoke.net/default.aspx/Structures/SECURITY_ATTRIBUTES.html
I am still getting the same error:
System.AccessViolationException was caught
Message=Attempted to read or write protected memory. This is often an indication that other memory is corrupt.
Admittedly, I don't know anything about calling the Win32 functions, I'm really just pulling what I can find online and trying to learn. Can anyone tell me what I'm doing wrong? Removing non-essential code for the question, I have:
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Configuration;
using System.IO;
using System.Runtime.InteropServices;
using System.Security.AccessControl;
using System.Text;
namespace RFCGenerator
{
[StructLayout(LayoutKind.Sequential)]
public struct SECURITY_ATTRIBUTES
{
public int nLength;
public IntPtr lpSecurityDescriptor;
public int bInheritHandle;
}
public class RFC
{
[DllImport("kernel32.dll")]
static extern bool CreateDirectory(string lpPathName, SECURITY_ATTRIBUTES lpSecurityAttributes);
protected void CopyDirectory(Uri Source, Uri Destination)
{
SECURITY_ATTRIBUTES lpSecurityAttributes = new SECURITY_ATTRIBUTES();
DirectorySecurity security = new DirectorySecurity();
lpSecurityAttributes.nLength = Marshal.SizeOf(lpSecurityAttributes);
byte[] src = security.GetSecurityDescriptorBinaryForm();
IntPtr dest = Marshal.AllocHGlobal(src.Length);
Marshal.Copy(src, 0, dest, src.Length);
lpSecurityAttributes.lpSecurityDescriptor = dest;
string path = #"C:\Test";
CreateDirectory(path, lpSecurityAttributes);
}
}
}
UPDATE: using Hans' suggestion, I did get this to work locally. However, when I attempt to create a directory using a UNC address, such as passing in:
path = #"\\mydomain.com\foo\bar\newfolder"
I now get:
System.ComponentModel.Win32Exception was caught
Message=The filename, directory name, or volume label syntax is incorrect
I have verified that \\mydomain.com\foo\bar\ does exist.
SOLUTION:
Using Hans' code and a minor modification to check if it's UNC path (reference: http://msdn.microsoft.com/en-us/library/aa365247%28VS.85%29.aspx, under "Maximum Path Length Limitation"):
string UnicodePath = (path.StartsWith(#"\\")) ? #"\\?\UNC\" + (path.Remove(0, 2)) : #"\\?\" + path;
if (!CreateDirectory(UnicodePath, IntPtr.Zero))
throw new System.ComponentModel.Win32Exception();
You are not using the Unicode version, that requires CharSet = CharSet.Unicode in the [DllImport] declaration. Furthermore, creating directories with long names has nothing to do with the security attribute. You have to prefix the name with #"\\?\". Thus:
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
static extern bool CreateDirectory(string lpPathName, IntPtr lpSecurityAttributes);
...
if (!CreateDirectory(#"\\?\" + path, IntPtr.Zero))
throw new Win32Exception();

Categories

Resources