Getting a Folder ID C#

2020-03-23 17:20发布

问题:

I am trying to track a files and folders through their lifetime (they may be moved or renamed). I have done searching and found that FileSystemWatcher might be the most popular way to track files and folders, however, this will not work for me as the application may or may not be always running. I have chosen to try to track the folders via an ID.

I have found a way to track files from an ID from the answer to this stack post. I can successfully grab file IDs based on Approach B in this answer.

While searching I found this stack post stating he found his solution using FSCTL_GET_OBJECT_ID. I have spent quite a bit of time trying to figure out how to use this function, but I can not wrap my head around it. I have essentially zero experience calling native windows functions from within C#.

Can anyone give me a push in the right direction for this? I feel like I must be missing something obvious.

Is there a reason C# can't access file/folder IDs? Is tracking files/folders uncommon?

Edit, adding code:

        static uint returnVal;

    //Working example to get File ID
    public static string GetFileId(string path)
    {
        WinAPI.BY_HANDLE_FILE_INFORMATION objectFileInfo = new WinAPI.BY_HANDLE_FILE_INFORMATION();

        FileInfo fi = new FileInfo(path);
        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.ToString();
    }

    public static string GetFolderId(string path)
    {
        //Get a handle on the given folder
        IntPtr cFile = WinAPI.CreateFile(
            path,
            WinAPI.GENERIC_READ,
            FileShare.Read,
            IntPtr.Zero,
            (FileMode)WinAPI.OPEN_EXISTING,
            WinAPI.FILE_FLAG_BACKUP_SEMANTICS,
            IntPtr.Zero);

        Console.WriteLine(path);
        Console.WriteLine(cFile);

        if ((int)cFile != -1)
        {
            int cFileSize = Marshal.SizeOf(typeof(IntPtr));
            Console.WriteLine("cFile size = {0}", cFileSize);

            IntPtr cFileBlob = Marshal.AllocHGlobal(cFileSize);
            uint numBytesRead = 0;

            WinAPI.DeviceIoControl(cFile, WinAPI.FSCTL_GET_OBJECT_ID, IntPtr.Zero, 0, cFileBlob, (uint)cFileSize, ref numBytesRead, IntPtr.Zero);

            if (returnVal == 0)
            {
                Console.WriteLine(Marshal.GetLastWin32Error()); // Returning error 87 here
            }
        }

        //Should be returning the ID from the folder.
        return String.Empty;
    }

    public static void Main(string[] args)
    {
    Console.WriteLine(GetFileId(@"C:\Users\Matt\Desktop\TestDocument.txt"));
    Console.WriteLine(GetFolderId(@"C:\Users\Matt\Desktop"));
    }

}

class WinAPI
{
    // Win32 constants for accessing files.
    internal const int GENERIC_READ = unchecked((int)0x80000000);
    internal const int FILE_FLAG_BACKUP_SEMANTICS = unchecked((int)0x02000000);
    internal const int OPEN_EXISTING = unchecked((int)3);
    internal const int FSCTL_GET_OBJECT_ID = 0x0009009c;
    internal const int FSCTL_CREATE_OR_GET_OBJECT_ID = 0x000900c0;

    [DllImport("kernel32.dll", SetLastError = true)]
    public static extern bool DeviceIoControl(IntPtr hDevice, uint dwIoControlCode, IntPtr lpInBuffer, uint nInBufferSize, [Out] IntPtr lpOutBuffer, uint nOutBufferSize, ref uint lpBytesReturned, IntPtr lpOverlapped);

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

    [DllImport("kernel32.dll", SetLastError = true)]
    public static extern IntPtr CreateFile(
       String fileName,
       int dwDesiredAccess,
       System.IO.FileShare dwShareMode,
       IntPtr securityAttrs_MustBeZero,
       System.IO.FileMode dwCreationDisposition,
       int dwFlagsAndAttributes,
       IntPtr hTemplateFile_MustBeZero);

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

I am getting the error "87" back from DeviceIoControl, which is invalid parameter according to a post on MSDN (I am unable to post more links due to reputation restrictions.)

回答1:

Seems you got no problem with FileSystemWatcher. So, for how to use DeviceIoControl in C#, have a look of this answer:

Physical disk size not correct (IoCtlDiskGetDriveGeometry)

To this question, it's done with the following code:

class Program {
    const uint FSCTL_GET_OBJECT_ID=0x0009009c;

    public static String GetFileId(String path) {
        using(var fs=File.Open(
            path, 
            FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite)
            ) {
            WinAPI.BY_HANDLE_FILE_INFORMATION info;
            WinAPI.GetFileInformationByHandle(fs.Handle, out info);
            return String.Format(
                    "{0:x}", ((info.FileIndexHigh<<32)|info.FileIndexLow));
        }
    }

    public static WinAPI.FILE_OBJECTID_BUFFER GetFolderIdBuffer(String path) {
        using(var hFile=WinAPI.CreateFile(
            path,
            WinAPI.GENERIC_READ, FileShare.Read,
            IntPtr.Zero,
            (FileMode)WinAPI.OPEN_EXISTING,
            WinAPI.FILE_FLAG_BACKUP_SEMANTICS,
            IntPtr.Zero
            )) {
            if(null==hFile||hFile.IsInvalid)
                throw new Win32Exception(Marshal.GetLastWin32Error());

            var buffer=default(WinAPI.FILE_OBJECTID_BUFFER);
            var nOutBufferSize=Marshal.SizeOf(buffer);
            var lpOutBuffer=Marshal.AllocHGlobal(nOutBufferSize);
            var lpBytesReturned=default(uint);

            var result=
                WinAPI.DeviceIoControl(
                    hFile, FSCTL_GET_OBJECT_ID,
                    IntPtr.Zero, 0,
                    lpOutBuffer, nOutBufferSize,
                    ref lpBytesReturned, IntPtr.Zero
                    );

            if(!result)
                throw new Win32Exception(Marshal.GetLastWin32Error());

            var type=typeof(WinAPI.FILE_OBJECTID_BUFFER);

            buffer=(WinAPI.FILE_OBJECTID_BUFFER)
                Marshal.PtrToStructure(lpOutBuffer, type);

            Marshal.FreeHGlobal(lpOutBuffer);
            return buffer;
        }
    }

    public static void Main(String[] args) {
        Console.WriteLine(
            GetFileId(@"C:\Users\Matt\Desktop\TestDocument.txt"));

        var buffer=GetFolderIdBuffer(@"C:\Users\Matt\Desktop");

        var objectId=buffer.ObjectId
                .Reverse()
                .Select(x => x.ToString("x2"))
                .Aggregate(String.Concat);

        Console.WriteLine("{0}", objectId);
    }
}

class WinAPI {
    internal const int
        GENERIC_READ=unchecked((int)0x80000000),
        FILE_FLAG_BACKUP_SEMANTICS=unchecked((int)0x02000000),
        OPEN_EXISTING=unchecked((int)3);

    [StructLayout(LayoutKind.Sequential)]
    public struct FILE_OBJECTID_BUFFER {
        public struct Union {
            [MarshalAs(UnmanagedType.ByValArray, SizeConst=16)]
            public byte[] BirthVolumeId;

            [MarshalAs(UnmanagedType.ByValArray, SizeConst=16)]
            public byte[] BirthObjectId;

            [MarshalAs(UnmanagedType.ByValArray, SizeConst=16)]
            public byte[] DomainId;
        }

        [MarshalAs(UnmanagedType.ByValArray, SizeConst=16)]
        public byte[] ObjectId;

        public Union BirthInfo;

        [MarshalAs(UnmanagedType.ByValArray, SizeConst=48)]
        public byte[] ExtendedInfo;
    }

    [StructLayout(LayoutKind.Sequential)]
    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;
    }

    [DllImport("kernel32.dll", SetLastError=true)]
    public static extern bool DeviceIoControl(
        SafeFileHandle hDevice,
        uint dwIoControlCode,
        IntPtr lpInBuffer,
        uint nInBufferSize,
        [Out] IntPtr lpOutBuffer,
        int nOutBufferSize,
        ref uint lpBytesReturned,
        IntPtr lpOverlapped
        );

    [DllImport("kernel32.dll", SetLastError=true)]
    public static extern SafeFileHandle CreateFile(
        String fileName,
        int dwDesiredAccess,
        System.IO.FileShare dwShareMode,
        IntPtr securityAttrs_MustBeZero,
        System.IO.FileMode dwCreationDisposition,
        int dwFlagsAndAttributes,
        IntPtr hTemplateFile_MustBeZero
        );

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

The name space is required of using:

using Microsoft.Win32.SafeHandles;