2

Files can have a change date. This date is not the same as the last modified date or the last access date. Change date is not visible through the UI or .NET API. There a two Win32 functions GetFileInformationByHandleEx for reading and SetFileInformationByHandle for writing file information.

I want to read out the change date, add some hours to it, and write the new date back as the change date of the file.

For now I have following code:

class Program
{
    static void Main(string[] args)
    {

        using (var file = new FileStream(@"c:\path\to\file", FileMode.Open))
        {
            var fileInfo = new FILE_BASIC_INFO();
            GetFileInformationByHandleEx(
                file.Handle,
                FILE_INFO_BY_HANDLE_CLASS.FileBasicInfo,
                out fileInfo,
                (uint)Marshal.SizeOf(fileInfo));

            SetFileInformationByHandle(
                file.Handle,
                FILE_INFO_BY_HANDLE_CLASS.FileBasicInfo,
                fileInfo,
                (uint)Marshal.SizeOf(fileInfo));
        }
    }

    [DllImport("kernel32.dll", SetLastError = true)]
    private static extern bool GetFileInformationByHandleEx(
        IntPtr hFile,
        FILE_INFO_BY_HANDLE_CLASS infoClass,
        out FILE_BASIC_INFO fileInfo,
        uint dwBufferSize);

    [DllImport("kernel32.dll", SetLastError = true)]
    private static extern bool SetFileInformationByHandle(
        IntPtr hFile,
        FILE_INFO_BY_HANDLE_CLASS infoClass,
        FILE_BASIC_INFO fileInfo,
        uint dwBufferSize);

    private enum FILE_INFO_BY_HANDLE_CLASS
    {
        FileBasicInfo = 0
    }

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
    private struct FILE_BASIC_INFO
    {
        public LARGE_INTEGER CreationTime;
        public LARGE_INTEGER LastAccessTime;
        public LARGE_INTEGER LastWriteTime;
        public LARGE_INTEGER ChangeTime;
        public uint FileAttributes;
    }

    [StructLayout(LayoutKind.Explicit, Size = 8)]
    private struct LARGE_INTEGER
    {
        [FieldOffset(0)]
        public Int64 QuadPart;
        [FieldOffset(0)]
        public UInt32 LowPart;
        [FieldOffset(4)]
        public Int32 HighPart;
    }
}

I can read out the change date into that awful structure LARGE_INTEGER. What I want to have is a function which can convert that type into a System.DateTime and vice versa.

The second problem that I have is that the siganture of the SetFileInformationByHandle method is wrong. I get a PInvokeStackImbalance with this additional information:

Additional information: A call to PInvoke function 'Program::SetFileInformationByHandle' has unbalanced the stack. This is likely because the managed PInvoke signature does not match the unmanaged target signature. Check that the calling convention and parameters of the PInvoke signature match the target unmanaged signature.

Who can help me?

Yavuz
  • 630
  • 6
  • 20

2 Answers2

0

To answer the first part..On how to convert "that awful Large_Interger" to DateTime.. The below code snippet should help..

using (var file = new System.IO.FileStream(@"sample.log", System.IO.FileMode.Open))
        {
            var fileInfo = new FILE_BASIC_INFO();
            GetFileInformationByHandleEx(
                file.Handle,
                FILE_INFO_BY_HANDLE_CLASS.FileBasicInfo,
                out fileInfo,
                (uint)System.Runtime.InteropServices.Marshal.SizeOf(fileInfo));

            var changeTime = DateTime.FromFileTime(fileInfo.ChangeTime.QuadPart);
            Console.WriteLine(changeTime);
            System.TimeSpan changedForHowLong = DateTime.Now.Subtract(changeTime);
            Console.WriteLine(changedForHowLong.Days);
        }

I tested the above snippet it seems to work fine.. Let me try repro'ing the issue you faced with the PInvokeStackImbalance..

Take Care,

Mithun Ashok
  • 346
  • 1
  • 4
  • Thank you Mithun. Works well. Since we cannot have both an accepted answer I will accept your's. – Yavuz Sep 16 '14 at 21:14
0

I found this signature on PInvoke

[DllImport("Kernel32.dll", SetLastError = true)]
private static extern bool SetFileInformationByHandle(
    IntPtr hFile,
    int FileInformationClass,
    IntPtr lpFileInformation,
    Int32 dwBufferSize);

Somehow this did not work. I had to change the type of the parameter lpFileInformation to FILE_BASIC_INFO to make it work.

This is the complete C# example called from PowerShell:

$fu = @"
using System;
using System.IO;
using System.Runtime.InteropServices;

public class FileUtility
{
    private struct FILE_BASIC_INFO
    {
        [MarshalAs(UnmanagedType.I8)]
        public Int64 CreationTime;
        [MarshalAs(UnmanagedType.I8)]
        public Int64 LastAccessTime;
        [MarshalAs(UnmanagedType.I8)]
        public Int64 LastWriteTime;
        [MarshalAs(UnmanagedType.I8)]
        public Int64 ChangeTime;
        [MarshalAs(UnmanagedType.U4)]
        public UInt32 FileAttributes;
    }

    [DllImport("Kernel32.dll", SetLastError = true)]
    private static extern bool SetFileInformationByHandle(
        IntPtr hFile,
        int FileInformationClass,
        FILE_BASIC_INFO lpFileInformation,
        Int32 dwBufferSize);

    public void SetFileChangeTime()
    {
        using (FileStream fs = new FileStream(@"c:\path\to\file", FileMode.Open))
        {
            FILE_BASIC_INFO fileInfo = new FILE_BASIC_INFO();
            fileInfo.ChangeTime = 943044610000000;
            SetFileInformationByHandle(
                fs.Handle,
                0,  // the same as FILE_INFO_BY_HANDLE_CLASS.FileBasicInfo
                fileInfo,
                Marshal.SizeOf(fileInfo));
        }
    }
}
"@

Add-Type -TypeDefinition $fu -IgnoreWarnings
$f = New-Object -TypeName FileUtility
$f.SetFileChangeTime()

I have run the example with the other date properties since they are shown in the explorer and it worked.

Edit

This code does not run in debug mode within VS. As mentioned above it throws the exception. Running the EXE in the command line does not throw an exception. But the change date is not updated. However it works only in PowerShell. Strange.

Yavuz
  • 630
  • 6
  • 20