Handle PathtoolongException with Shareddrive [duplicate] - 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

C# PInvoke I can't open the volume with CreateFile

I have to open the volume with CreateFile from PInvoke:
[DllImport("kernel32.dll", SetLastError = true)]
static extern SafeFileHandle CreateFile(string lpFileName, uint dwDesiredAccess, FileShare dwShareMode, IntPtr securityAttrs, FileMode dwCreationDisposition, FileOptions dwFlagsAndAttributes, IntPtr hTemplateFile);
But when I use it:
var fileName = #"C:\Users\myuser\Desktop\file.txt"
var root = Path.GetPathRoot(fileName);
var volumePath = #"\\.\" + root.Substring(0, root.Length - 1);
var volume = CreateFile(volumePath, GENERIC_READ, FileShare.Read, IntPtr.Zero, FileMode.Open, FileOptions.None, IntPtr.Zero);
if (volume.IsInvalid)
throw new Win32Exception(Marshal.GetLastWin32Error());
It prints:
Access is denied
However, if I open it with elevated privileges, it prints:
The process cannot access the file because it is being used by another process
I've been trying everything, even picking up code from a lot of places but I can't make it work.
Using the PInvoke code taken from here:
var volume = CreateFile(volumePath, EFileAccess.GenericRead, EFileShare.Read|EFileShare.Write, IntPtr.Zero, ECreationDisposition.OpenExisting, 0, IntPtr.Zero);
I have managed to get it working.

Undo DeleteOnClose on FileStream

I'm having a class that is using a FileStream internally using the FileOption: DeleteOnClose. The normal behavior is when I allocate the class with a filename, I do not use the DeleteOnClose, otherwise I use it.
The only problem is that I happens sometimes that I need to undo the DeleteOnClose.
I would be too lengthy to explain the deeper details here. Of course I could create a copy and copy the contents of the FileStream that have been opened with DeleteOnClose to another FileStream but the file size is too large (>= 30GB) so that this approach is impractical.
Deleting the file manually does not work, since the classes are more or less used as memory containers that need to be handled by the GC. Also when something happens it's not helpful to have dead files lying around.
So I was hoping if there is a way to undo the DeleteOnClose attribute as this can be done, e.g. with SetFileAttributes where e.g. the Temporary flag can be set/unset.
With the comments of TheGeneral and TonPlooij I created a small example to test FileDispositionInfo, but somehow this also doesn't work (copied from http://source.roslyn.codeplex.com/#Roslyn.Test.Utilities/TempFiles/DisposableFile.cs,4d5c94058d1b4cd3):
using Microsoft.Win32.SafeHandles;
using System;
using System.IO;
using System.Runtime.InteropServices;
namespace ConsoleApp11
{
class Program
{
[DllImport("kernel32.dll", PreserveSig = false)]
private static extern void SetFileInformationByHandle(SafeFileHandle handle, int fileInformationClass, ref uint fileDispositionInfoDeleteFile, int bufferSize);
private const int FileDispositionInfo = 4;
internal static void PrepareDeleteOnCloseStreamForDisposal(FileStream stream)
{
// tomat: Set disposition to "delete" on the stream, so to avoid ForeFront EndPoint
// Protection driver scanning the file. Note that after calling this on a file that's open with DeleteOnClose,
// the file can't be opened again, not even by the same process.
uint trueValue = 1;
SetFileInformationByHandle(stream.SafeFileHandle, FileDispositionInfo, ref trueValue, sizeof(uint));
}
/// <summary>
/// Marks given file for automatic deletion when all its handles are closed.
/// Note that after doing this the file can't be opened again, not even by the same process.
/// </summary>
internal static void DeleteFileOnClose(string fullPath)
{
using (var stream = new FileStream(fullPath, FileMode.Create, FileAccess.ReadWrite, FileShare.Delete| FileShare.ReadWrite, 8))
{
PrepareDeleteOnCloseStreamForDisposal(stream);
}
}
static void Main(string[] args)
{
DeleteFileOnClose("D:\\test.dat");
Console.WriteLine("Done.");
Console.ReadKey();
}
}
}
To answer you question directly, no you cant change the option mid way through. The file stream uses the winapi flag FILE_FLAG_DELETE_ON_CLOSE, this cant be changed mid way through, its essentially associated with the file handle, when the handle is closed the operating system cleans up the file, that is it and no work around.
If you want different behavior you will have to implement it yourself after the fact. ie deleting the file after its closed or not.
The best alternative I came up with was to create a hard-link to the temporary file if I want to persist it. DeleteOnClose apparently does not seem to actually delete the file, but only the link to the file, which means if we create another hard link to it before closing the file stream it will be persisted (with a new name).
This of course requires a file system that supports hard links, and I'm not sure if this will work on MacOS/Linux, but on Windows NTFS it seems to work as advertised.
So something like the following appears to work, if it is acceptable to use a temporary file name until we decide to persist it:
public class TemporaryFile : FileStream
{
[DllImport("Kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern bool CreateHardLink(string lpFileName, string lpExistingFileName, IntPtr lpSecurityAttributes);
public TemporaryFile(string path, FileMode fileMode = FileMode.Create, FileAccess fileAccess = FileAccess.ReadWrite, FileShare fileShare = FileShare.ReadWrite | FileShare.Delete, int bufferSize = 4096, FileOptions options = FileOptions.None)
: base(path, fileMode, fileAccess, fileShare, bufferSize, GetFileOptions(options))
{
}
public void PersistTo(string path)
{
if (!CreateHardLink(path, Name, IntPtr.Zero))
{
Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
}
}
private static FileOptions GetFileOptions(FileOptions options)
{
return options | FileOptions.DeleteOnClose;
}
}
Which could then be used like:
using var tempFile = new TemporaryFile(#"c:\tempFile.tmp");
// Write data to TempFile
if (shouldFileBePersisted)
tempFile.PersistTo(#"c:\permanentFileName.txt");
It isn't a perfect solution, but this worked for my purposes, so thought I'd share it.

Shortenned path still throwing PathTooLongException

I need to create files with path exceeding the MAX_PATH limit. What I expected to work is to shorten the already existing directory name like this:
public static String GetShortPathName(String longPath)
{
StringBuilder shortPath = new StringBuilder(longPath.Length + 1);
if (0 == GetShortPathName(longPath, shortPath, shortPath.Capacity))
{
throw new Exception("Path shortenning failed");
}
return shortPath.ToString();
}
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
private static extern Int32 GetShortPathName(String path, StringBuilder shortPath, Int32 shortPathLength);
and then use the result to create the new file like:
using (FileStream writeStream = File.Create(shortPath + newFile))
...
But it still throws PathTooLongException like before. Any idea why?
I know I can use Delimon Lib or other lib, but I just cannot figure out why it does not work.
Thanks a lot.

Send raw data to print not working

I want to send raw data to print, avoiding printer selection (fast print).
I am trying to use this helper provided by Microsoft: https://support.microsoft.com/en-us/kb/322091#top
However, when I call to the method:
RawPrinterHelper.SendStringToPrinter(pd.PrinterSettings.PrinterName, s);
My printer starts to works (makes some noise) but It never takes the white paper and starts to print.
I have tried it with my two printers and the behavior is the same in both printers. Also I discard the possibility that the printers are broken because I can print other documents.
What can be wrong?
Try this:
using System;
using System.Drawing;
using System.Drawing.Printing;
using System.IO;
using System.Runtime.InteropServices;
using System.Windows.Forms;
public class RawPrinterHelper
{
// Structure and API declarions:
[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Ansi)]
public class DOCINFOA
{
[MarshalAs(UnmanagedType.LPStr)] public string pDocName;
[MarshalAs(UnmanagedType.LPStr)] public string pOutputFile;
[MarshalAs(UnmanagedType.LPStr)] public string pDataType;
}
[DllImport("winspool.Drv", EntryPoint="OpenPrinterA", SetLastError=true, CharSet=CharSet.Ansi, ExactSpelling=true, CallingConvention=CallingConvention.StdCall)]
public static extern bool OpenPrinter([MarshalAs(UnmanagedType.LPStr)] string szPrinter, out IntPtr hPrinter, IntPtr pd);
[DllImport("winspool.Drv", EntryPoint="ClosePrinter", SetLastError=true, ExactSpelling=true, CallingConvention=CallingConvention.StdCall)]
public static extern bool ClosePrinter(IntPtr hPrinter);
[DllImport("winspool.Drv", EntryPoint="StartDocPrinterA", SetLastError=true, CharSet=CharSet.Ansi, ExactSpelling=true, CallingConvention=CallingConvention.StdCall)]
public static extern bool StartDocPrinter( IntPtr hPrinter, Int32 level, [In, MarshalAs(UnmanagedType.LPStruct)] DOCINFOA di);
[DllImport("winspool.Drv", EntryPoint="EndDocPrinter", SetLastError=true, ExactSpelling=true, CallingConvention=CallingConvention.StdCall)]
public static extern bool EndDocPrinter(IntPtr hPrinter);
[DllImport("winspool.Drv", EntryPoint="StartPagePrinter", SetLastError=true, ExactSpelling=true, CallingConvention=CallingConvention.StdCall)]
public static extern bool StartPagePrinter(IntPtr hPrinter);
[DllImport("winspool.Drv", EntryPoint="EndPagePrinter", SetLastError=true, ExactSpelling=true, CallingConvention=CallingConvention.StdCall)]
public static extern bool EndPagePrinter(IntPtr hPrinter);
[DllImport("winspool.Drv", EntryPoint="WritePrinter", SetLastError=true, ExactSpelling=true, CallingConvention=CallingConvention.StdCall)]
public static extern bool WritePrinter(IntPtr hPrinter, IntPtr pBytes, Int32 dwCount, out Int32 dwWritten );
// SendBytesToPrinter()
// When the function is given a printer name and an unmanaged array
// of bytes, the function sends those bytes to the print queue.
// Returns true on success, false on failure.
public static bool SendBytesToPrinter( string szPrinterName, IntPtr pBytes, Int32 dwCount)
{
Int32 dwError = 0, dwWritten = 0;
IntPtr hPrinter = new IntPtr(0);
DOCINFOA di = new DOCINFOA();
bool bSuccess = false; // Assume failure unless you specifically succeed.
di.pDocName = "My C#.NET RAW Document";
di.pDataType = "RAW";
// Open the printer.
if( OpenPrinter( szPrinterName.Normalize(), out hPrinter, IntPtr.Zero ) )
{
// Start a document.
if( StartDocPrinter(hPrinter, 1, di) )
{
// Start a page.
if( StartPagePrinter(hPrinter) )
{
// Write your bytes.
bSuccess = WritePrinter(hPrinter, pBytes, dwCount, out dwWritten);
EndPagePrinter(hPrinter);
}
EndDocPrinter(hPrinter);
}
ClosePrinter(hPrinter);
}
// If you did not succeed, GetLastError may give more information
// about why not.
if( bSuccess == false )
{
dwError = Marshal.GetLastWin32Error();
}
return bSuccess;
}
public static bool SendFileToPrinter( string szPrinterName, string szFileName )
{
// Open the file.
FileStream fs = new FileStream(szFileName, FileMode.Open);
// Create a BinaryReader on the file.
BinaryReader br = new BinaryReader(fs);
// Dim an array of bytes big enough to hold the file's contents.
Byte []bytes = new Byte[fs.Length];
bool bSuccess = false;
// Your unmanaged pointer.
IntPtr pUnmanagedBytes = new IntPtr(0);
int nLength;
nLength = Convert.ToInt32(fs.Length);
// Read the contents of the file into the array.
bytes = br.ReadBytes( nLength );
// Allocate some unmanaged memory for those bytes.
pUnmanagedBytes = Marshal.AllocCoTaskMem(nLength);
// Copy the managed byte array into the unmanaged array.
Marshal.Copy(bytes, 0, pUnmanagedBytes, nLength);
// Send the unmanaged bytes to the printer.
bSuccess = SendBytesToPrinter(szPrinterName, pUnmanagedBytes, nLength);
// Free the unmanaged memory that you allocated earlier.
Marshal.FreeCoTaskMem(pUnmanagedBytes);
return bSuccess;
}
public static bool SendStringToPrinter( string szPrinterName, string szString )
{
IntPtr pBytes;
Int32 dwCount;
// How many characters are in the string?
// Fix from Nicholas Piasecki:
// dwCount = szString.Length;
dwCount = (szString.Length + 1) * Marshal.SystemMaxDBCSCharSize;
// Assume that the printer is expecting ANSI text, and then convert
// the string to ANSI text.
pBytes = Marshal.StringToCoTaskMemAnsi(szString);
// Send the converted ANSI string to the printer.
SendBytesToPrinter(szPrinterName, pBytes, dwCount);
Marshal.FreeCoTaskMem(pBytes);
return true;
}
}
Fran_gg7, I had the same issue recently. Firstly, turn on the persistence of documents on the printer. This will allow you to see if the printer successfully received the print request.
You will see items in the printer queue and they will remain there.
In my scenario the print request was correctly being sent to the printer, however I was testing it on a laser printer that ultimately was unable to interpret the raw string data I was passing to it.
I tested the same output on a label printer that could understand the ZPL (zebra programming language) I was passing it and boom it worked fine.
Have a look at this for a detailed explanation
Hope this helps.
In cases where you are using the MSDN example from here https://support.microsoft.com/en-us/kb/322091#top but are trying to use an array of bytes instead of a string or file... This may help
public static void SendBytesToLocalPrinter(byte[] data, string printerName)
{
var size = Marshal.SizeOf(data[0]) * data.Length;
var pBytes = Marshal.AllocHGlobal(size);
try
{
SendBytesToPrinter(printerName, pBytes, size);
}
finally
{
Marshal.FreeCoTaskMem(pBytes);
}
}
This does not change with the byte array encoding. This is useful if you are trying to send some utf8 encoded byte sequences with some binary (ie. image) data mixed in as was the case for our team.
If printing plain text to Dot Matrix using "RawPrinterHelper" Method, it would only work properly for me when I manually added a printer, selected Generic / Text Only, and selected the USB001 port that was assigned to my USB connected printer (okidata in my test). Then RawPrinterHelper.SendStringToPrinter would behave very much like an 'lpd to lpt1:'
Had exactly the same issue with my ZEBRA ZD420 printer.
Sending ZPL string to printer only the data light flashing shortly without printing.
I changed only
Marshal.StringToCoTaskMemAnsi(szString);
to
Marshal.StringToCoTaskMemUTF8(szString);
and it works !

How to get the location of the first byte of the file on a disk?

I need to checksum every single file on a given USB disk in a C# application. I suspect the bottleneck here is the actual read off the disk so I'm looking to make this as fast as possible.
I suspect this would be much quicker if I could read the files on the disk sequentially, in the actual order they appear on the disk (assuming the drive is not fragmented).
How can I find this information for each file from it's standard path? i.e. given a file at "F:\MyFile.txt", how can I find the start location of this file on the disk?
I'm running a C# application in Windows.
Now... I don't really know if it will be useful for you:
[StructLayout(LayoutKind.Sequential)]
public struct StartingVcnInputBuffer
{
public long StartingVcn;
}
public static readonly int StartingVcnInputBufferSizeOf = Marshal.SizeOf(typeof(StartingVcnInputBuffer));
[StructLayout(LayoutKind.Sequential)]
public struct RetrievalPointersBuffer
{
public uint ExtentCount;
public long StartingVcn;
public long NextVcn;
public long Lcn;
}
public static readonly int RetrievalPointersBufferSizeOf = Marshal.SizeOf(typeof(RetrievalPointersBuffer));
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern SafeFileHandle CreateFileW(
[MarshalAs(UnmanagedType.LPWStr)] 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);
[DllImport("kernel32.dll", ExactSpelling = true, SetLastError = true, CharSet = CharSet.Auto)]
static extern bool DeviceIoControl(IntPtr hDevice, uint dwIoControlCode,
ref StartingVcnInputBuffer lpInBuffer, int nInBufferSize,
out RetrievalPointersBuffer lpOutBuffer, int nOutBufferSize,
out int lpBytesReturned, IntPtr lpOverlapped);
// Returns a FileStream that can only Read
public static void GetStartLogicalClusterNumber(string fileName, out FileStream file, out long startLogicalClusterNumber)
{
SafeFileHandle handle = CreateFileW(fileName, FileAccess.Read | (FileAccess)0x80 /* FILE_READ_ATTRIBUTES */, FileShare.Read, IntPtr.Zero, FileMode.Open, 0, IntPtr.Zero);
if (handle.IsInvalid)
{
throw new Win32Exception();
}
file = new FileStream(handle, FileAccess.Read);
var svib = new StartingVcnInputBuffer();
int error;
RetrievalPointersBuffer rpb;
int bytesReturned;
DeviceIoControl(handle.DangerousGetHandle(), (uint)589939 /* FSCTL_GET_RETRIEVAL_POINTERS */, ref svib, StartingVcnInputBufferSizeOf, out rpb, RetrievalPointersBufferSizeOf, out bytesReturned, IntPtr.Zero);
error = Marshal.GetLastWin32Error();
switch (error)
{
case 38: /* ERROR_HANDLE_EOF */
startLogicalClusterNumber = -1; // empty file. Choose how to handle
break;
case 0: /* NO:ERROR */
case 234: /* ERROR_MORE_DATA */
startLogicalClusterNumber = rpb.Lcn;
break;
default:
throw new Win32Exception();
}
}
Note that the method will return a FileStream that you can keep open and use to read the file, or you can easily modify it to not return it (and not create it) and then reopen the file when you want to hash it.
To use:
string[] fileNames = Directory.GetFiles(#"D:\");
foreach (string fileName in fileNames)
{
try
{
long startLogicalClusterNumber;
FileStream file;
GetStartLogicalClusterNumber(fileName, out file, out startLogicalClusterNumber);
}
catch (Exception e)
{
Console.WriteLine("Skipping: {0} for {1}", fileName, e.Message);
}
}
I'm using the API described here: https://web.archive.org/web/20160130161216/http://www.wd-3.com/archive/luserland.htm . The program is much easier because you only need the initial Logical Cluster Number (the first version of the code could extract all the LCN extents, but it would be useless, because you have to hash a file from first to last byte). Note that empty files (files with length 0) don't have any cluster allocated. The function returns -1 for the cluster (ERROR_HANDLE_EOF). You can choose how to handle it.
If your drives are SSD or based on memory stick technology - forget it.
Memory sticks and other similar devices are generally based on SSD (or similar) technology, where the problem of random read/write access is actually not a problem. So you can just enumerate files and run your checksum.
You can try running this in several threads, but I am not sure that could speed up the process, it's something you may need to test. It may also vary from device to device.
Bonus
#xanatos mentioned an interesting point: "I always noticed that copying thousand of files on a memory stick is much slower than copying a single big file"
It is indeed much faster to copy one big file, rather than a pile of small files. And the reason is (usually) not because the files are located close to each other, so it's easier for hardware to read them sequentially. The problem comes to the OS that needs to keep tracking of each file.
If you ever run a procmon on Windows, you would observe huge amount of FileCreates, FileReads and FileWrites. In order to copy 100 files, OS would open each file, read its content, write to another file, close both files + lots of update operations that are sent to the file system, such as update attributes for both files, update security descriptors for both files, update directory information etc. So one copy operation has many satellite operations.

Categories