【问题标题】:Filename ending with period and using FileInfo C#文件名以句点结尾并使用 FileInfo C#
【发布时间】:2017-08-23 19:51:56
【问题描述】:

我遇到了一个问题,即 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 属性为假,即使您可以复制/粘贴路径以确认其有效。目标不是为了读取而重命名文件,而是原样读取它。

【问题讨论】:

  • 重现:1) mkdir "C:\Test" 2) echo "test" > "\\?\C:\Test\BadFileName."清理:del "\\?\C:\Test\BadFileName."
  • 这个不错,我去看看。

标签: c# file-exists system.io.fileinfo


【解决方案1】:

由于这显然是一个不受支持的场景,我怀疑它是否可以在不使用一些低级文件访问的情况下完成。

你可以尝试做的是放弃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 代码

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);
    }
    }
}

【讨论】:

  • 非常好!我可以使用您的方法获取文件的大小,但不确定如何获取日期信息。
  • @complhex,这两种方法中的哪一种?如果您在谈论File 类,则有File.GetLastAccessTimeFile.GetLastWriteTime
【解决方案2】:

这是一种通过命令行执行文件操作的方法。不是最优雅的解决方案,但希望是有用的参考。

using System;
using System.IO;
using System.Diagnostics;

namespace StackOverflow_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;
        }
    }
}

【讨论】:

  • 我在 RunOnCommandLine 调用($)上有错误。将它们切换为 @ 并得到索引超出范围的错误。
  • 您可能正在编译较低版本的 .NET。 "$" 是一个插值运算符。它只允许您编写如下内容:“Contents:” + contents as $“Contents: {contents}”。此外,所有“@”都指定您使用的是文字字符串。这对于您必须转义许多字符(例如文件路径)的字符串非常有用。
  • 感谢您的帮助,我能够使用命令行解决问题。我将在下面发布我的解决方案。我唯一不能做的就是以秒精度捕捉时间。
  • 确实远非优雅,在使用像 C# 这样的编程语言时,这绝对不是一种可接受的方法。
【解决方案3】:

方法调用:

        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;
    }

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2022-01-17
    • 1970-01-01
    • 2019-01-02
    • 2013-10-29
    • 1970-01-01
    • 1970-01-01
    • 2011-05-03
    相关资源
    最近更新 更多