Имя файла, заканчивающееся точкой и использующее FileInfo C#
У меня возникла проблема, когда FileInfo не видит файл, содержащий точку в конце своего имени. Я знаю, что Windows запрещает именовать файлы таким образом, но эти данные пришли из другой операционной системы.
Я могу создать проблемный файл в Windows с помощью командной строки:
echo "test" > "\\?\C:\Test\BadFileName."
и это код, который я использовал для проверки файла:
DateTime origAccessDate;
DateTime OrigCreateDate;
long sizeBytes;
string path = @"\\?\C:\Test\BadFileName.";
FileInfo fi = new FileInfo(path);
try
{
origAccessDate = fi.LastAccessTime;
OrigCreateDate = fi.CreationTime;
sizeBytes = fi.Length;
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
Проблема возникает, когда FileInfo вызывается по пути. Свойство Exists имеет значение false, даже если вы можете скопировать / вставить путь, чтобы подтвердить его правильность. Цель не в том, чтобы переименовать файл для чтения, а в том, чтобы прочитать его на месте (как есть).
3 ответа
Поскольку это явно неподдерживаемый сценарий, я сомневаюсь, что это можно сделать без использования низкоуровневого доступа к файлам.
Что вы можете попытаться сделать, это бросить FileInfo
и идти сFile.Exists(path)
а также File.ReadAllBytes(path)
, Это может быть в состоянии обойти проблему.
Доступ к файлу с использованием SafeFileHandle
Следующее не проверено
Создание экземпляра UnmanagedFileLoader
(Код ниже, взят из MSDN), позволяет создавать SafeFileHandle
объект, который может быть передан в FileStream
конструктор следующим образом:
UnmanagedFileLoader ufl = new UnmanagedFileLoader(path);
FileStream fs = new FileStream(ufl.Handle, FileMode.Open);
Примечание: не забудьте позвонить ufl.Handle.Dispose()
,
Это должно дать вам больше, скажем так, прямой доступ к файлу, и, таким образом, обойти принудительное применение правильного имени файла, которое имеет Windows.
UnmanagedFileLoader Code
class UnmanagedFileLoader
{
public const short FILE_ATTRIBUTE_NORMAL = 0x80;
public const short INVALID_HANDLE_VALUE = -1;
public const uint GENERIC_READ = 0x80000000;
public const uint GENERIC_WRITE = 0x40000000;
public const uint CREATE_NEW = 1;
public const uint CREATE_ALWAYS = 2;
public const uint OPEN_EXISTING = 3;
// Use interop to call the CreateFile function.
// For more information about CreateFile,
// see the unmanaged MSDN reference library.
[DllImport("kernel32.dll", SetLastError = true, CharSet=CharSet.Unicode)]
static extern SafeFileHandle CreateFile(string lpFileName, uint dwDesiredAccess,
uint dwShareMode, IntPtr lpSecurityAttributes, uint dwCreationDisposition,
uint dwFlagsAndAttributes, IntPtr hTemplateFile);
private SafeFileHandle handleValue = null;
public UnmanagedFileLoader(string Path)
{
Load(Path);
}
public void Load(string Path)
{
if (Path == null || Path.Length == 0)
{
throw new ArgumentNullException("Path");
}
// Try to open the file.
handleValue = CreateFile(Path, GENERIC_WRITE, 0, IntPtr.Zero, OPEN_EXISTING, 0, IntPtr.Zero);
// If the handle is invalid,
// get the last Win32 error
// and throw a Win32Exception.
if (handleValue.IsInvalid)
{
Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
}
}
public SafeFileHandle Handle
{
get
{
// If the handle is valid,
// return it.
if (!handleValue.IsInvalid)
{
return handleValue;
}
else
{
return null;
}
}
}
}
Доступ к дате файла с помощью Windows API
GetFileTimeSample
класс ниже, взятый из www.pinvoke.net использует другой вызов Windows API, в частности GetFileTime
, Эта реализация является лишь примером, вы наверняка сможете адаптировать ее, чтобы получить только нужную вам дату. В своем текущем виде он выведет все три даты.
Использование:
DateTime fileDateCreated;
DateTime fileDateAccessed;
DateTime fileDateModified;
GetFileTimeSample.GetFileTimes(path, out fileDateCreated, out fileDateAccessed, out fileDateModified);
Начиная с C# 7.0 можно объявить out
Переменные прямо в вызове функции примерно так:
GetFileTimeSample.GetFileTimes(path, out DateTime fileDateCreated, out DateTime fileDateAccessed, out DateTime fileDateModified);
GetFileTimeSample
public class GetFileTimeSample
{
private const uint GENERIC_READ = 0x80000000;
private const uint FILE_SHARE_READ = 0x1;
private const uint FILE_ATTRIBUTE_NORMAL = 0x80;
private const int INVALID_HANDLE_VALUE = -1;
private const uint OPEN_EXISTING = 3;
[StructLayout(LayoutKind.Sequential)]
private struct FILETIME
{
public uint dwLowDateTime;
public uint dwHighDateTime;
}
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool CloseHandle(
IntPtr hObject
);
[DllImport("kernel32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall, SetLastError = true)]
private static extern IntPtr CreateFile(
string lpFileName,
uint dwDesiredAccess,
uint dwShareMode,
IntPtr SecurityAttributes,
uint dwCreationDisposition,
uint dwFlagsAndAttributes,
IntPtr hTemplateFile
);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool GetFileTime(
IntPtr hFile,
ref FILETIME lpCreationTime,
ref FILETIME lpLastAccessTime,
ref FILETIME lpLastWriteTime
);
public static void GetFileTimes(string FileName, out DateTime CreationTime, out DateTime LastAccessTime, out DateTime LastWriteTime)
{
CreationTime = DateTime.MinValue;
LastAccessTime = DateTime.MinValue;
LastWriteTime = DateTime.MinValue;
IntPtr ptr = IntPtr.Zero;
FILETIME ftCreationTime = new FILETIME();
FILETIME ftLastAccessTime = new FILETIME();
FILETIME ftLastWriteTime = new FILETIME();
try
{
ptr = CreateFile(FileName, GENERIC_READ, FILE_SHARE_READ, IntPtr.Zero, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, IntPtr.Zero);
if (ptr.ToInt32() == INVALID_HANDLE_VALUE)
Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
if (GetFileTime(ptr, ref ftCreationTime, ref ftLastAccessTime, ref ftLastWriteTime) != true)
Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
CreationTime = DateTime.FromFileTimeUtc((((long)ftCreationTime.dwHighDateTime) << 32) | ((uint)ftCreationTime.dwLowDateTime));
LastAccessTime = DateTime.FromFileTimeUtc((((long)ftLastAccessTime.dwHighDateTime) << 32) | ((uint)ftLastAccessTime.dwLowDateTime));
LastWriteTime = DateTime.FromFileTimeUtc((((long)ftLastWriteTime.dwHighDateTime) << 32) | ((uint)ftLastWriteTime.dwLowDateTime));
}
catch (Exception e)
{
throw (e);
}
finally
{
if (ptr !=IntPtr.Zero && ptr.ToInt32() != INVALID_HANDLE_VALUE) CloseHandle(ptr);
}
}
}
Вызовы метода:
string path = @"C:\Test\BadFileName.";
DateTime createDate = cmdGetCreateDate(path);
DateTime accessDate = cmdGetAccessDate(path);
long bytes = cmdGetSizeBytes(path);
Методы:
private DateTime cmdGetCreateDate(string path)
{
DateTime createDate = new DateTime();
int lastSlash = path.LastIndexOf(Convert.ToChar("\\"));
string file = path.Substring(lastSlash + 1);
string folder = path.Substring(0, lastSlash);
string cmdexe = @"C:\Windows\System32\cmd.exe";
string args = @"/c dir /T:C /A:-D """ + folder + "\"";
Process procCreateDate = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = cmdexe,
Arguments = args,
UseShellExecute = false,
RedirectStandardOutput = true,
CreateNoWindow = true
}
};
procCreateDate.Start();
string output = procCreateDate.StandardOutput.ReadToEnd();
if (!output.Contains(file))
{
return createDate; //File not found
}
string p = @"\b\d{2}/\d{2}/\d{4}\b\s+\d{2}:\d{2} ..";
Regex rx = new Regex(p);
Match m = rx.Match(output);
if (m.Success)
{
DateTime.TryParse(m.Value, out createDate);
}
return createDate;
}
private DateTime cmdGetAccessDate(string path)
{
DateTime accessDate = new DateTime();
int lastSlash = path.LastIndexOf(Convert.ToChar("\\"));
string file = path.Substring(lastSlash + 1);
string folder = path.Substring(0, lastSlash);
string cmdexe = @"C:\Windows\System32\cmd.exe";
string args = @"/c dir /T:A /A:-D """ + folder + "\"";
Process procCreateDate = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = cmdexe,
Arguments = args,
UseShellExecute = false,
RedirectStandardOutput = true,
CreateNoWindow = true
}
};
procCreateDate.Start();
string output = procCreateDate.StandardOutput.ReadToEnd();
if (!output.Contains(file))
{
return accessDate; //File not found
}
string p = @"\b\d{2}/\d{2}/\d{4}\b\s+\d{2}:\d{2} ..";
Regex rx = new Regex(p);
Match m = rx.Match(output);
if (m.Success)
{
DateTime.TryParse(m.Value, out accessDate);
}
return accessDate;
}
private long cmdGetSizeBytes(string path)
{
long bytes = -1;
int lastSlash = path.LastIndexOf(Convert.ToChar("\\"));
string file = path.Substring(lastSlash + 1);
string folder = path.Substring(0, lastSlash);
string cmdexe = @"C:\Windows\System32\cmd.exe";
string args = @"/c dir /A:-D """ + folder + "\"";
Process procCreateDate = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = cmdexe,
Arguments = args,
UseShellExecute = false,
RedirectStandardOutput = true,
CreateNoWindow = true
}
};
procCreateDate.Start();
string output = procCreateDate.StandardOutput.ReadToEnd();
if (!output.Contains(file))
{
return bytes; //File not found
}
string p = @"\d+ " + file;
Regex rx = new Regex(p);
Match m = rx.Match(output);
if (m.Success)
{
string[] splitVal = m.Value.Split(Convert.ToChar(" "));
bytes = Convert.ToInt64(splitVal[0]);
}
return bytes;
}
Вот способ выполнить операции с файлами через командную строку. Не самое элегантное решение, но, надеюсь, полезная ссылка.
using System;
using System.IO;
using System.Diagnostics;
namespace Stackru_FileNameShenanigans
{
class Program
{
static void Main(string[] args)
{
string contents;
DateTime origAccessDate;
DateTime origCreateDate;
long sizeBytes;
string path = @"\\?\C:\Test\BadFileName.";
try
{
contents = CommandLineFileOps.ReadAllText(path);
origAccessDate = CommandLineFileOps.LastAccessTime(path);
origCreateDate = CommandLineFileOps.CreationTime(path);
sizeBytes = CommandLineFileOps.Length(path);
Console.WriteLine($"Contents: {contents}");
Console.WriteLine($"OrigAccessDate: {origAccessDate}");
Console.WriteLine($"OrigCreateDate: {origCreateDate}");
Console.WriteLine($"SizeBytes: {sizeBytes}");
Console.ReadKey();
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
Console.ReadKey();
}
}
}
public static class CommandLineFileOps
{
public static string ReadAllText(string path)
{
string contents;
RunOnCommandLine($"type {path}", out contents);
contents = contents.Substring(0, contents.Length - 3);
return contents;
}
public static DateTime CreationTime(string path)
{
string output;
RunOnCommandLine($"dir /T:C {path}", out output);
string dateLine = output.Split('\n')[5];
string dateStr = dateLine.Replace(" ", "\n").Split('\n')[0];
return DateTime.Parse(dateStr);
}
public static DateTime LastAccessTime(string path)
{
string output;
RunOnCommandLine($"dir /T:A {path}", out output);
string dateLine = output.Split('\n')[5];
string dateStr = dateLine.Replace(" ", "\n").Split('\n')[0];
return DateTime.Parse(dateStr);
}
public static long Length(string path)
{
string output;
RunOnCommandLine($"dir {path}", out output);
string lengthLine = output.Split('\n')[6];
string lengthStr = lengthLine.Replace(" ", "\n").Split('\n')[2].Split(' ')[0];
return long.Parse(lengthStr);
}
private static int RunOnCommandLine(string line)
{
Process cmd = new Process();
cmd.StartInfo.FileName = "cmd.exe";
cmd.StartInfo.RedirectStandardInput = true;
cmd.StartInfo.RedirectStandardOutput = true;
cmd.StartInfo.CreateNoWindow = true;
cmd.StartInfo.UseShellExecute = false;
cmd.Start();
cmd.StandardInput.WriteLine(line);
cmd.StandardInput.Flush();
cmd.StandardInput.Close();
cmd.WaitForExit();
int exitCode = cmd.ExitCode;
return exitCode;
}
private static int RunOnCommandLine(string line, out string output)
{
string tempPath = Path.GetTempFileName();
int exitCode = RunOnCommandLine($"{line} > {tempPath}");
output = File.ReadAllText(tempPath);
File.Delete(tempPath);
return exitCode;
}
}
}