Windows中的唯一文件标识符

58

有没有一种方法可以唯一地识别文件(和可能的目录)在其生命周期内,而不管移动、重命名和内容修改?(适用于Windows 2000及更高版本)。复制文件应该给该副本一个独特的标识符。

我的应用程序将各种元数据与单个文件关联起来。如果文件被修改、重命名或移动,自动检测并更新文件关联将非常有用。

FileSystemWatcher可以提供通知这些类型更改的事件,但它使用的内存缓冲区可能会很容易填满(并且事件会丢失),如果许多文件系统事件迅速发生。

哈希是没用的,因为文件的内容可以更改,因此哈希也会更改。

我曾考虑过使用文件创建日期,但有几种情况下这不是唯一的(即当复制多个文件时)。

我也听说过NTFS中的文件SID(安全ID?),但我不确定这是否符合我要寻找的东西。

有什么想法吗?

5个回答

35

下面是一段代码,可以返回一个唯一的文件索引。

经过一番研究,我想出了ApproachA()。感谢Mattias和Rubens提供的链接,让我得到了ApproachB()。在我的基本测试中,两种方法都返回相同的文件索引。

以下是来自MSDN的一些注意事项:

文件ID的支持是特定于文件系统的。文件ID不能保证随时间保持唯一,因为文件系统可以重新使用它们。 在某些情况下,文件的文件ID可能会随时间变化。

在FAT文件系统中,文件ID是从包含目录的第一个簇和该文件条目所在目录中的字节偏移量生成的。一些碎片整理产品会更改此字节偏移量(Windows内置除碎片整理不会这样做)。因此,FAT文件ID可能会随时间变化。重命名FAT文件系统中的文件也可能会更改文件ID,但仅当新文件名比旧文件名长时才会发生。

在NTFS文件系统中,文件保持相同的文件ID,直到被删除。您可以使用ReplaceFile函数将一个文件替换为另一个文件,而不更改文件ID。但是,替换后的文件的文件ID,而不是被替换的文件,将保留为结果文件的文件ID。

以上第一个加粗的评论让我感到担忧。它并不清楚这个声明是否仅适用于FAT,它似乎与第二个加粗的文本相矛盾。我想进一步测试是唯一确定的方法。

[更新:在我的测试中,当文件从一个NTFS硬盘驱动器移动到另一个NTFS硬盘驱动器时,文件索引/ID会发生变化。]

    public class WinAPI
    {
        [DllImport("ntdll.dll", SetLastError = true)]
        public static extern IntPtr NtQueryInformationFile(IntPtr fileHandle, ref IO_STATUS_BLOCK IoStatusBlock, IntPtr pInfoBlock, uint length, FILE_INFORMATION_CLASS fileInformation);

        public struct IO_STATUS_BLOCK
        {
            uint status;
            ulong information;
        }
        public struct _FILE_INTERNAL_INFORMATION {
          public ulong  IndexNumber;
        } 

        // Abbreviated, there are more values than shown
        public enum FILE_INFORMATION_CLASS
        {
            FileDirectoryInformation = 1,     // 1
            FileFullDirectoryInformation,     // 2
            FileBothDirectoryInformation,     // 3
            FileBasicInformation,         // 4
            FileStandardInformation,      // 5
            FileInternalInformation      // 6
        }

        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern bool GetFileInformationByHandle(IntPtr hFile,out BY_HANDLE_FILE_INFORMATION lpFileInformation);

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

  public class Test
  {
       public ulong ApproachA()
       {
                WinAPI.IO_STATUS_BLOCK iostatus=new WinAPI.IO_STATUS_BLOCK();

                WinAPI._FILE_INTERNAL_INFORMATION objectIDInfo = new WinAPI._FILE_INTERNAL_INFORMATION();

                int structSize = Marshal.SizeOf(objectIDInfo);

                FileInfo fi=new FileInfo(@"C:\Temp\testfile.txt");
                FileStream fs=fi.Open(FileMode.Open,FileAccess.Read,FileShare.ReadWrite);

                IntPtr res=WinAPI.NtQueryInformationFile(fs.Handle, ref iostatus, memPtr, (uint)structSize, WinAPI.FILE_INFORMATION_CLASS.FileInternalInformation);

                objectIDInfo = (WinAPI._FILE_INTERNAL_INFORMATION)Marshal.PtrToStructure(memPtr, typeof(WinAPI._FILE_INTERNAL_INFORMATION));

                fs.Close();

                Marshal.FreeHGlobal(memPtr);   

                return objectIDInfo.IndexNumber;

       }

       public ulong ApproachB()
       {
               WinAPI.BY_HANDLE_FILE_INFORMATION objectFileInfo=new WinAPI.BY_HANDLE_FILE_INFORMATION();

                FileInfo fi=new FileInfo(@"C:\Temp\testfile.txt");
                FileStream fs=fi.Open(FileMode.Open,FileAccess.Read,FileShare.ReadWrite);

                WinAPI.GetFileInformationByHandle(fs.Handle, out objectFileInfo);

                fs.Close();

                ulong fileIndex = ((ulong)objectFileInfo.FileIndexHigh << 32) + (ulong)objectFileInfo.FileIndexLow;

                return fileIndex;   
       }
  }

10
好的,我来翻译一下。请注意,为了使翻译更加通俗易懂,我会尽量简化原文,并保留原意。原文:Nice, I tried it out, but found one problem: it's not working for file like Microsoft Office suite (doc, docx, xls...) because everytime you made a changes, the Office tend to delete the file, and create a new file to replace it, this result in reference number changed, although the reference number still unique. It cant work to detect changes in those files, and maybe some other programs will have similar approach too. So guess I will back to my CreationTime method...翻译:很好,我试过了,但是发现一个问题:它不能处理像Microsoft Office套件(doc、docx、xls等)这样的文件,因为每次进行更改时,Office倾向于删除文件并创建一个新文件来替换它,这导致引用号码变化了,尽管引用号码仍然唯一。它无法检测这些文件中的更改,而且其他一些程序可能也会采取类似的方法。所以我想我会回到我的CreationTime方法…… - VHanded
就我个人而言,我无法让Ashley Henderson的ApproachA起作用,但是ApproachB可以。我能够确认VHanded的注释,即当您修改Word(docx)文档时,FileID确实会更改,这太糟糕了,因为Office文件是我想要跟踪的文件。 - Jeffrey Roughgarden
2
每次你做出更改,Office 就会倾向于删除文件......该死,AutoCAD 也是如此。回到 FileSystemWatcher - CAD bloke
由于“Handle”属性已被弃用,这方面有任何更新吗? - Thomas

27
如果你调用 GetFileInformationByHandle 函数,你将在 BY_HANDLE_FILE_INFORMATION 结构体中获取文件ID(nFileIndexHigh/Low)。这个ID在卷内是唯一的,即使你移动文件(在该卷内)或重命名它,ID也不会改变。
如果可以假设使用 NTFS 文件系统,你可能还想考虑使用备用数据流来存储元数据。

谢谢提供链接。我实际上找到了另一个API调用,返回相同的ID,但需要更多的工作。我可能会发布我编写的代码。备用数据流也可能很有用,因为我只会在USB键/外部驱动器上遇到FAT。然而,我听说一些反病毒/安全软件可能会在向文件添加隐藏数据时出现问题。 - Ash
8
GetFileInformationByHandle的文档中写道:“nFileIndexLow:与文件相关联的唯一标识符的低位部分。该值仅在文件至少被一个进程打开时有用。如果没有进程打开它,则索引可能会在下次打开文件时更改。” - Integer Poet
哦,这个条款在MSDN文档中似乎不再存在了?经验来看,在NTFS文件系统上,我发现文件索引在重新启动后仍然保持唯一。 - sqweek
文档还指出:文件ID的支持是特定于文件系统的。由于文件系统可以重复使用它们,因此不能保证文件ID随时间的唯一性。在某些情况下,文件的文件ID可能会随时间而改变。(https://learn.microsoft.com/de-ch/windows/desktop/api/fileapi/ns-fileapi-_by_handle_file_information) - Peanut
在NTFS和REFS上,文件ID在文件的生命周期内不会改变。该文档是针对FAT系列(FAT、FAT32、exFAT)和可能其他文件系统添加的,其中重命名文件(例如)将更改文件ID。https://needleinathreadstack.wordpress.com/2020/09/22/file-id-cheat-sheet/ - Alnoor

4

1

您可以使用创建时间戳作为文件UID的一种方法,只需确保在将文件扫描到程序中时,调整与已遇到的任何createtimes相同的createtimes,使它们略微不同,因为显然即使在NTFS上,如果它们是通过大量文件复制同时创建的,则几个文件可能会具有相同的TS,但在正常情况下,您不会获得重复项,并且调整将很少或没有。


0
用户还提到了唯一目录标识。这个过程比检索文件的唯一信息要复杂一些,但是仍然是可能的。它需要您调用适当的CREATE_FILEfunction并设置特定标志。有了这个句柄,您可以在Ash的answer中调用GetFileInformationByHandle函数。
这也需要导入kernel32.dll
        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern SafeFileHandle CreateFile(
            string lpFileName,
            [MarshalAs(UnmanagedType.U4)] FileAccess dwDesiredAccess,
            [MarshalAs(UnmanagedType.U4)] FileShare dwShareMode,
            IntPtr securityAttributes,
            [MarshalAs(UnmanagedType.U4)] FileMode dwCreationDisposition,
            uint dwFlagsAndAttributes,
            IntPtr hTemplateFile
        );

稍后我会更详细地解释这个答案。但是,通过上面的链接回答,这应该开始有意义了。我的新最爱资源是pinvoke,它帮助我处理了 .Net C# 签名可能性。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接