我们如何通过C#访问MFT

我需要在我的.net应用程序中使用C#访问Windows MFT(主文件表)。
我搜索了这个,但没有找到任何好结果。 我一直在搜索过去2天的信息,但一直无法找到相同的信息。

我不是在寻找确切的代码来做同样的事情,我只是在寻找可以让我开始的一些信息。

我唯一能弄清楚的是我必须使用P / Invoke。
我想知道我将用于访问MFT的function。
如果您能够提供一些代码示例,那就太棒了。

首先,你必须拥有并拥有足够的权限来访问MFT – 这本身就是一种痛苦。 然后,您必须获取卷上文件/文件夹的句柄 – 对于最后一步中的调用…即在循环中调用Windows API(称为DeviceIOControl)并从返回的API调用中读取条目 – 这是它自己的特殊头痛。

从概念上讲 – 这看起来像:

static void Main( string[ ] args ) { if ( Privileges.HasBackupAndRestorePrivileges ) { using ( var volume = GetVolumeHandle( "C:\\" ) ) { ReadMft( volume ); } } } 

如果你依次采取其中的每一个,断言足够的权限是最不明确的部分。 有一个Windows API可以更改正在运行的令牌的权限 – 您可以使用它来添加必要的权限。 这是我用来断言这些特权的类的摘录。 你可以断言更多的特权 – 但这应该足以阅读MFT。

您的应用程序需要在可以实际获得必要权限的帐户下运行 – 管理员帐户是好的。 此外,备份操作员将工作。

 public static class Privileges { private static int asserted = 0; private static bool hasBackupPrivileges = false; public static bool HasBackupAndRestorePrivileges { get { return AssertPriveleges( ); } } ///  /// First time this method is called, it attempts to set backup privileges for the current process. /// Subsequently, it returns the results of that first call. ///  private static bool AssertPriveleges( ) { bool success = false; var wasAsserted = Interlocked.CompareExchange( ref asserted, 1, 0 ); if ( wasAsserted == 0 ) // first time here? come on in! { success = AssertPrivelege( NativeMethods.SE_BACKUP_NAME ) AssertPrivelege( NativeMethods.SE_RESTORE_NAME ); hasBackupPrivileges = success; } return hasBackupPrivileges; } private static bool AssertPrivelege( string privelege ) { IntPtr token; var tokenPrivileges = new NativeMethods.TOKEN_PRIVILEGES( ); tokenPrivileges.Privileges = new NativeMethods.LUID_AND_ATTRIBUTES[ 1 ]; var success = NativeMethods.OpenProcessToken( NativeMethods.GetCurrentProcess( ), NativeMethods.TOKEN_ADJUST_PRIVILEGES, out token ) && NativeMethods.LookupPrivilegeValue( null, privelege, out tokenPrivileges.Privileges[ 0 ].Luid ); try { if ( success ) { tokenPrivileges.PrivilegeCount = 1; tokenPrivileges.Privileges[ 0 ].Attributes = NativeMethods.SE_PRIVILEGE_ENABLED; success = NativeMethods.AdjustTokenPrivileges( token, false, ref tokenPrivileges, Marshal.SizeOf( tokenPrivileges ), IntPtr.Zero, IntPtr.Zero ) && ( Marshal.GetLastWin32Error( ) == 0 ); } if ( !success ) { Console.WriteLine( "Could not assert privilege: " + privelege ); } } finally { NativeMethods.CloseHandle( token ); } return success; } } 

一旦你超越了这个障碍,剩下的就是 – 嗯…仍然是一个默默无闻的节日。 您必须获得文件或文件夹的句柄 – 具有备份语义。 您可以在您所访问的卷上的任何旧文件上打开FileStream,而FileStream将具有可用于后续调用的句柄。 这不是我的应用程序所做的 – 但我的应用程序必须做这个答案不必做的事情。

  internal static SafeFileHandle GetVolumeHandle( string pathToVolume, NativeMethods.EFileAccess access = NativeMethods.EFileAccess.AccessSystemSecurity | NativeMethods.EFileAccess.GenericRead | NativeMethods.EFileAccess.ReadControl ) { var attributes = ( uint ) NativeMethods.EFileAttributes.BackupSemantics; var handle = NativeMethods.CreateFile( pathToVolume, access, 7U, IntPtr.Zero, ( uint ) NativeMethods.ECreationDisposition.OpenExisting, attributes, IntPtr.Zero ); if ( handle.IsInvalid ) { throw new IOException( "Bad path" ); } return handle; } 

对于ReadMft – 有一个相当复杂的Windows API函数 – DeviceIOControl – 它接受具有各种输入的史诗缓冲区并返回包含令人费解的各种输出的缓冲区。 它是一种用于查询各种设备信息的全能API – 包含MFT的卷是一种设备。

要读取MFT,请使用设备IO控制代码FSCTL_ENUM_USN_DATA调用DeviceIOControl,该控制代码为MFT中的每条记录返回一条USN记录。 每次调用都有很多记录 – 在每次调用之后,您将使用前一次调用返回的第一位信息参数化循环中的下一个调用。

顺便说一句 – 我在我的代码中重命名了Windows API调用,使它们看起来更像.Net。 我不确定将来会这样做。

特别说明 :您为每个文件获取一条记录 – 无论有多少硬链接 – 您必须执行其他调用以枚举硬链接。

文件系统层次结构在您从调用中返回的结构的FileReferenceNumber和ParentFileReferenceNumber中进行编码。 您名义上将这些usn记录保存到列表中,按FileReferenceNumber排序并为ParentFileReferenceNumber创建二级索引 – 或者类似的东西。 出于说明的目的,此代码仅转储MFT条目。

此示例使用不安全的代码 – 并修复包含输入和输出的缓冲区的位置。 有不同的方法可以解决这个问题 – 但这很好,很有活力。 如果使用此选项,则必须在项目设置中允许不安全的代码。

 public unsafe static bool ReadMft( SafeHandle volume ) { var outputBufferSize = 1024 * 1024; var input = new NativeMethods.MFTEnumDataV0( ); var usnRecord = new NativeMethods.UsnRecordV2( ); var outputBuffer = new byte[ outputBufferSize ]; var okay = true; var doneReading = false; try { fixed ( byte* pOutput = outputBuffer ) { input.StartFileReferenceNumber = 0; input.LowUsn = 0; input.HighUsn = long.MaxValue; using ( var stream = new MemoryStream( outputBuffer, true ) ) { while ( !doneReading ) { var bytesRead = 0U; okay = NativeMethods.DeviceIoControl ( volume.DangerousGetHandle( ), NativeMethods.DeviceIOControlCode.FsctlEnumUsnData, ( byte* ) &input.StartFileReferenceNumber, ( uint ) Marshal.SizeOf( input ), pOutput, ( uint ) outputBufferSize, out bytesRead, IntPtr.Zero ); if ( !okay ) { var error = Marshal.GetLastWin32Error( ); okay = error == NativeMethods.ERROR_HANDLE_EOF; if ( !okay ) { Console.WriteLine( "Crap! Windows error " + error.ToString( ) ); break; } else { doneReading = true; } } input.StartFileReferenceNumber = stream.ReadULong( ); while ( stream.Position < bytesRead ) { usnRecord.Read( stream ); //-->>>>>>>>>>>>>>>>> //--> just an example of reading out the record... Console.WriteLine( "FRN:" + usnRecord.FileReferenceNumber.ToString( ) ); Console.WriteLine( "Parent FRN:" + usnRecord.ParentFileReferenceNumber.ToString( ) ); Console.WriteLine( "File name:" + usnRecord.FileName ); Console.WriteLine( "Attributes: " + ( NativeMethods.EFileAttributes ) usnRecord.FileAttributes ); Console.WriteLine( "Timestamp:" + usnRecord.TimeStamp ); //-->>>>>>>>>>>>>>>>>>> } stream.Seek( 0, SeekOrigin.Begin ); } } } } catch ( Exception ex ) { Console.Write( ex ); okay = false; } return okay; } 

我做了一些可能有点俗气的事情来为自己节省大量工作 – 我将伪序列化方法添加到Windows API结构中 – 这样他们就可以从流中读取自己。 例如,用于在前面的代码中读取缓冲区的usnRecord是一个Windows API结构 – 但是实现了一个序列化接口:

 [StructLayout( LayoutKind.Sequential )] internal struct UsnRecordV2: IBinarySerialize { public uint RecordLength; public ushort MajorVersion; public ushort MinorVersion; public ulong FileReferenceNumber; public ulong ParentFileReferenceNumber; public long Usn; public long TimeStamp; public UsnReason Reason; public uint SourceInfo; public uint SecurityId; public uint FileAttributes; public ushort FileNameLength; public ushort FileNameOffset; public string FileName; ///  /// Note how the read advances to the FileNameOffset and reads only FileNameLength bytes. ///  public void Read( Stream stream ) { var startOfRecord = stream.Position; RecordLength = stream.ReadUInt( ); MajorVersion = stream.ReadUShort( ); MinorVersion = stream.ReadUShort( ); FileReferenceNumber = stream.ReadULong( ); ParentFileReferenceNumber = stream.ReadULong( ); Usn = stream.ReadLong( ); TimeStamp = stream.ReadLong( ); Reason = ( UsnReason ) stream.ReadUInt( ); SourceInfo = stream.ReadUInt( ); SecurityId = stream.ReadUInt( ); FileAttributes = stream.ReadUInt( ); FileNameLength = stream.ReadUShort( ); FileNameOffset = stream.ReadUShort( ); stream.Position = startOfRecord + FileNameOffset; FileName = Encoding.Unicode.GetString( stream.ReadBytes( FileNameLength ) ); stream.Position = startOfRecord + RecordLength; } /// We never write instances of this structure void IBinarySerialize.Write( Stream stream ) { throw new NotImplementedException( ); } } 

…其中IBinarySerialze是:

 public interface IBinarySerialize { /// Reads an object's data from a  void Read( Stream stream ); /// Writes an objects serializable content to a  void Write( Stream stream ); } 

在该结构中使用流扩展方法。 基本上,他们从BinaryReader中解脱出来。 为什么? 因为在.Net 3.5中 – 我最初必须写这个–BCL BinaryReader将关闭你包裹它的流 – 我有很多地方,这是无法容忍的。

 internal static class StreamingExtensions { public static ushort ReadUShort( this Stream stream ) { return BitConverter.ToUInt16( ReadBytes( stream, 2 ), 0 ); } public static uint ReadUInt( this Stream stream ) { return BitConverter.ToUInt32( ReadBytes( stream, 4 ), 0 ); } public static long ReadLong( this Stream stream ) { return BitConverter.ToInt64( ReadBytes( stream, 8 ), 0 ); } public static ulong ReadULong( this Stream stream ) { return BitConverter.ToUInt64( ReadBytes( stream, 8 ), 0 ); } public static byte[ ] ReadBytes( this Stream stream, int length, bool throwIfIncomplete = false ) { var bytes = new byte[ length ]; var bytesRead = 0; var offset = 0; if ( length > 0 ) { while ( offset < length ) { bytesRead = stream.Read( bytes, offset, length - offset ); if ( bytesRead == 0 ) { if ( throwIfIncomplete ) throw new InvalidOperationException( "incomplete" ); break; } offset += bytesRead; } } return bytes; } } 

为了完整起见,这里有本机方法,枚举,常量和噪音。 大部分都是来自PInvoke.net,但同样......很多这些东西的名字都是.Net-ified。 向纯粹主义者道歉。

 internal class NativeMethods { internal const int ERROR_HANDLE_EOF = 38; //--> Privilege constants.... internal const UInt32 SE_PRIVILEGE_ENABLED = 0x00000002; internal const string SE_BACKUP_NAME = "SeBackupPrivilege"; internal const string SE_RESTORE_NAME = "SeRestorePrivilege"; internal const string SE_SECURITY_NAME = "SeSecurityPrivilege"; internal const string SE_CHANGE_NOTIFY_NAME = "SeChangeNotifyPrivilege"; internal const string SE_CREATE_SYMBOLIC_LINK_NAME = "SeCreateSymbolicLinkPrivilege"; internal const string SE_CREATE_PERMANENT_NAME = "SeCreatePermanentPrivilege"; internal const string SE_SYSTEM_ENVIRONMENT_NAME = "SeSystemEnvironmentPrivilege"; internal const string SE_SYSTEMTIME_NAME = "SeSystemtimePrivilege"; internal const string SE_TIME_ZONE_NAME = "SeTimeZonePrivilege"; internal const string SE_TCB_NAME = "SeTcbPrivilege"; internal const string SE_MANAGE_VOLUME_NAME = "SeManageVolumePrivilege"; internal const string SE_TAKE_OWNERSHIP_NAME = "SeTakeOwnershipPrivilege"; //--> For starting a process in session 1 from session 0... internal const int TOKEN_DUPLICATE = 0x0002; internal const uint MAXIMUM_ALLOWED = 0x2000000; internal const int CREATE_NEW_CONSOLE = 0x00000010; internal const uint TOKEN_ADJUST_PRIVILEGES = 0x0020; internal const int TOKEN_QUERY = 0x00000008; [DllImport( "advapi32.dll", SetLastError = true )] [return: MarshalAs( UnmanagedType.Bool )] internal static extern bool OpenProcessToken( IntPtr ProcessHandle, UInt32 DesiredAccess, out IntPtr TokenHandle ); [DllImport( "kernel32.dll" )] internal static extern IntPtr GetCurrentProcess( ); [DllImport( "advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode )] [return: MarshalAs( UnmanagedType.Bool )] internal static extern bool LookupPrivilegeValue( string lpSystemName, string lpName, out LUID lpLuid ); [DllImport( "advapi32.dll", SetLastError = true )] [return: MarshalAs( UnmanagedType.Bool )] internal static extern bool AdjustTokenPrivileges( IntPtr TokenHandle, [MarshalAs( UnmanagedType.Bool )]bool DisableAllPrivileges, ref TOKEN_PRIVILEGES NewState, Int32 BufferLength, IntPtr PreviousState, IntPtr ReturnLength ); [DllImport( "kernel32.dll", ExactSpelling = true, SetLastError = true, CharSet = CharSet.Unicode )] [return: MarshalAs( UnmanagedType.Bool )] internal static unsafe extern bool DeviceIoControl( IntPtr hDevice, DeviceIOControlCode controlCode, byte* lpInBuffer, uint nInBufferSize, byte* lpOutBuffer, uint nOutBufferSize, out uint lpBytesReturned, IntPtr lpOverlapped ); [DllImport( "kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode )] internal static extern SafeFileHandle CreateFile( string lpFileName, EFileAccess dwDesiredAccess, uint dwShareMode, IntPtr lpSecurityAttributes, uint dwCreationDisposition, uint dwFlagsAndAttributes, IntPtr hTemplateFile ); [DllImport( "kernel32.dll", SetLastError = true )] [return: MarshalAs( UnmanagedType.Bool )] internal static extern bool CloseHandle( IntPtr hObject ); [Flags] internal enum EMethod: uint { Buffered = 0, InDirect = 1, OutDirect = 2, Neither = 3 } [Flags] internal enum EFileAccess: uint { GenericRead = 0x80000000, GenericWrite = 0x40000000, GenericExecute = 0x20000000, GenericAll = 0x10000000, Delete = 0x10000, ReadControl = 0x20000, WriteDAC = 0x40000, WriteOwner = 0x80000, Synchronize = 0x100000, StandardRightsRequired = 0xF0000, StandardRightsRead = ReadControl, StandardRightsWrite = ReadControl, StandardRightsExecute = ReadControl, StandardRightsAll = 0x1F0000, SpecificRightsAll = 0xFFFF, AccessSystemSecurity = 0x1000000, MaximumAllowed = 0x2000000 } [Flags] internal enum EFileDevice: uint { Beep = 0x00000001, CDRom = 0x00000002, CDRomFileSytem = 0x00000003, Controller = 0x00000004, Datalink = 0x00000005, Dfs = 0x00000006, Disk = 0x00000007, DiskFileSystem = 0x00000008, FileSystem = 0x00000009, InPortPort = 0x0000000a, Keyboard = 0x0000000b, Mailslot = 0x0000000c, MidiIn = 0x0000000d, MidiOut = 0x0000000e, Mouse = 0x0000000f, MultiUncProvider = 0x00000010, NamedPipe = 0x00000011, Network = 0x00000012, NetworkBrowser = 0x00000013, NetworkFileSystem = 0x00000014, Null = 0x00000015, ParallelPort = 0x00000016, PhysicalNetcard = 0x00000017, Printer = 0x00000018, Scanner = 0x00000019, SerialMousePort = 0x0000001a, SerialPort = 0x0000001b, Screen = 0x0000001c, Sound = 0x0000001d, Streams = 0x0000001e, Tape = 0x0000001f, TapeFileSystem = 0x00000020, Transport = 0x00000021, Unknown = 0x00000022, Video = 0x00000023, VirtualDisk = 0x00000024, WaveIn = 0x00000025, WaveOut = 0x00000026, Port8042 = 0x00000027, NetworkRedirector = 0x00000028, Battery = 0x00000029, BusExtender = 0x0000002a, Modem = 0x0000002b, Vdm = 0x0000002c, MassStorage = 0x0000002d, Smb = 0x0000002e, Ks = 0x0000002f, Changer = 0x00000030, Smartcard = 0x00000031, Acpi = 0x00000032, Dvd = 0x00000033, FullscreenVideo = 0x00000034, DfsFileSystem = 0x00000035, DfsVolume = 0x00000036, Serenum = 0x00000037, Termsrv = 0x00000038, Ksec = 0x00000039, // From Windows Driver Kit 7 Fips = 0x0000003A, Infiniband = 0x0000003B, Vmbus = 0x0000003E, CryptProvider = 0x0000003F, Wpd = 0x00000040, Bluetooth = 0x00000041, MtComposite = 0x00000042, MtTransport = 0x00000043, Biometric = 0x00000044, Pmi = 0x00000045 } internal enum EFileIOCtlAccess: uint { Any = 0, Special = Any, Read = 1, Write = 2 } internal enum DeviceIOControlCode: uint { FsctlEnumUsnData = ( EFileDevice.FileSystem << 16 ) | ( 44 << 2 ) | EMethod.Neither | ( EFileIOCtlAccess.Any << 14 ), FsctlReadUsnJournal = ( EFileDevice.FileSystem << 16 ) | ( 46 << 2 ) | EMethod.Neither | ( EFileIOCtlAccess.Any << 14 ), FsctlReadFileUsnData = ( EFileDevice.FileSystem << 16 ) | ( 58 << 2 ) | EMethod.Neither | ( EFileIOCtlAccess.Any << 14 ), FsctlQueryUsnJournal = ( EFileDevice.FileSystem << 16 ) | ( 61 << 2 ) | EMethod.Buffered | ( EFileIOCtlAccess.Any << 14 ), FsctlCreateUsnJournal = ( EFileDevice.FileSystem << 16 ) | ( 57 << 2 ) | EMethod.Neither | ( EFileIOCtlAccess.Any << 14 ) } /// Control structure used to interrogate MFT data using DeviceIOControl from the user volume [StructLayout( LayoutKind.Sequential )] internal struct MFTEnumDataV0 { public ulong StartFileReferenceNumber; public long LowUsn; public long HighUsn; } /// A structure resurned form USN queries ///  /// FileName is synthetic...composed during a read of the structure and is not technically /// part of the Win32 API's definition...although the actual FileName is contained /// "somewhere" in the structure's trailing bytes, according to FileNameLength and FileNameOffset. /// /// Alignment boundaries are enforced, and so, the RecordLength /// may be somewhat larger than the accumulated lengths of the members plus the FileNameLength. ///  [StructLayout( LayoutKind.Sequential )] internal struct UsnRecordV2: IBinarySerialize { public uint RecordLength; public ushort MajorVersion; public ushort MinorVersion; public ulong FileReferenceNumber; public ulong ParentFileReferenceNumber; public long Usn; public long TimeStamp; public UsnReason Reason; public uint SourceInfo; public uint SecurityId; public uint FileAttributes; public ushort FileNameLength; public ushort FileNameOffset; public string FileName; /// Note how the read advances to the FileNameOffset and reads only FileNameLength bytes public void Read( Stream stream ) { var startOfRecord = stream.Position; RecordLength = stream.ReadUInt( ); MajorVersion = stream.ReadUShort( ); MinorVersion = stream.ReadUShort( ); FileReferenceNumber = stream.ReadULong( ); ParentFileReferenceNumber = stream.ReadULong( ); Usn = stream.ReadLong( ); TimeStamp = stream.ReadLong( ); Reason = ( UsnReason ) stream.ReadUInt( ); SourceInfo = stream.ReadUInt( ); SecurityId = stream.ReadUInt( ); FileAttributes = stream.ReadUInt( ); FileNameLength = stream.ReadUShort( ); FileNameOffset = stream.ReadUShort( ); stream.Position = startOfRecord + FileNameOffset; FileName = Encoding.Unicode.GetString( stream.ReadBytes( FileNameLength ) ); stream.Position = startOfRecord + RecordLength; } void IBinarySerialize.Write( Stream stream ) { throw new NotImplementedException( ); } } /// Structure returned from USN query that describes the state of the journal [StructLayout( LayoutKind.Sequential )] internal struct UsnJournalDataV1: IBinarySerialize { public ulong UsnJournalId; public long FirstUsn; public long NextUsn; public long LowestValidUsn; public long MaxUsn; public ulong MaximumSize; public ulong AllocationDelta; public ushort MinSupportedMajorVersion; public ushort MaxSupportedMajorVersion; public void Read( Stream stream ) { UsnJournalId = stream.ReadULong( ); FirstUsn = stream.ReadLong( ); NextUsn = stream.ReadLong( ); LowestValidUsn = stream.ReadLong( ); MaxUsn = stream.ReadLong( ); MaximumSize = stream.ReadULong( ); AllocationDelta = stream.ReadULong( ); MinSupportedMajorVersion = stream.ReadUShort( ); MaxSupportedMajorVersion = stream.ReadUShort( ); } void IBinarySerialize.Write( Stream stream ) { throw new NotImplementedException( ); } } [StructLayout( LayoutKind.Sequential )] internal struct LUID { public UInt32 LowPart; public Int32 HighPart; } [StructLayout( LayoutKind.Sequential )] internal struct LUID_AND_ATTRIBUTES { public LUID Luid; public UInt32 Attributes; } internal struct TOKEN_PRIVILEGES { public UInt32 PrivilegeCount; [MarshalAs( UnmanagedType.ByValArray, SizeConst = 1 )] // !! think we only need one public LUID_AND_ATTRIBUTES[ ] Privileges; } [Flags] internal enum EFileAttributes: uint { ///  None = 0, //--> these are consistent w/ .Net FileAttributes... Readonly = 0x00000001, Hidden = 0x00000002, System = 0x00000004, Directory = 0x00000010, Archive = 0x00000020, Device = 0x00000040, Normal = 0x00000080, Temporary = 0x00000100, SparseFile = 0x00000200, ReparsePoint = 0x00000400, Compressed = 0x00000800, Offline = 0x00001000, NotContentIndexed = 0x00002000, Encrypted = 0x00004000, //--> additional CreateFile call attributes... Write_Through = 0x80000000, Overlapped = 0x40000000, NoBuffering = 0x20000000, RandomAccess = 0x10000000, SequentialScan = 0x08000000, DeleteOnClose = 0x04000000, BackupSemantics = 0x02000000, PosixSemantics = 0x01000000, OpenReparsePoint = 0x00200000, OpenNoRecall = 0x00100000, FirstPipeInstance = 0x00080000 } /// Reasons the file changed (from USN journal) [Flags] public enum UsnReason: uint { BASIC_INFO_CHANGE = 0x00008000, CLOSE = 0x80000000, COMPRESSION_CHANGE = 0x00020000, DATA_EXTEND = 0x00000002, DATA_OVERWRITE = 0x00000001, DATA_TRUNCATION = 0x00000004, EA_CHANGE = 0x00000400, ENCRYPTION_CHANGE = 0x00040000, FILE_CREATE = 0x00000100, FILE_DELETE = 0x00000200, HARD_LINK_CHANGE = 0x00010000, INDEXABLE_CHANGE = 0x00004000, NAMED_DATA_EXTEND = 0x00000020, NAMED_DATA_OVERWRITE = 0x00000010, NAMED_DATA_TRUNCATION = 0x00000040, OBJECT_ID_CHANGE = 0x00080000, RENAME_NEW_NAME = 0x00002000, RENAME_OLD_NAME = 0x00001000, REPARSE_POINT_CHANGE = 0x00100000, SECURITY_CHANGE = 0x00000800, STREAM_CHANGE = 0x00200000, None = 0x00000000 } internal enum ECreationDisposition: uint { New = 1, CreateAlways = 2, OpenExisting = 3, OpenAlways = 4, TruncateExisting = 5 } } 

你可以使用这个由Danny Couture用C#编写的https://sourceforge.net/projects/ntfsreader/开源库。

我测试了它,它的性能很好。 它可以在不到2秒的时间内解析具有超过100000条目(文件和文件夹)的NTFS驱动器。

Everything.exe (桌面搜索应用程序)也访问相同的。我希望你能在everything.exe的源代码中找到一些信息。

当Everything.exe首次运行时,它会根据NTFS主文件表中的文件元数据创建卷上每个文件和文件夹的名称索引。[3] 默认情况下,所有已安装的NTFS卷都已编制索引。[4] 创建后,应用程序会从NTFS更改日志中不断更新索引。[5] 所有内容都在此索引中搜索与用户搜索表达式匹配的文件名,用户搜索表达式可以是目标文件名的片段或正则表达式,[6]在输入搜索词时显示中间结果。