Using below record structure and function I get the FileName,Reference Numbers,Versions etc. at high speed. (trying with Delphi, Win 7)
Problem is that I don't get the file DateTime (TimeStamp.QuadPart = 0)
I have added the working code. For testing I am adding the filename (shows correct) and timestamp (shows empty) to memo1
procedure Tform_main.Button1Click(Sender: TObject);
begin
FillFileListFromUSNJournal('C') ;
end;
procedure Tform_main.FillFileListFromUSNJournal(pDrive:Char);
var
ARootHandle: Cardinal;
AMFTEnumBuff: Pointer;
begin
ARootHandle := GetRootHandle(pDrive);
if AllocMFTEnumBuffer(ARootHandle,AMFTEnumBuff) then
EnumMFTEntries(ARootHandle, AMFTEnumBuff, MFTEnumCallback, @pDrive) ;
end;
function Tform_main.MFTEnumCallback(AUSN: PUSNRecord; Extra: Pointer): Boolean;
var
AName,ProgressMsg: String;
Drive: Char;
begin
Drive := PChar(Extra)^;
Result := True;
USNRecFromPointer(AUSN) ;
end;
{
** uMFT.pas
** zm
** created: 13.11.2010
**
* Copyright (c) 2010, Zeljko Marjanovic <xxxxxxr@gmail.com>
* This code is licensed under MPL 1.1
* For details, see http://www.mozilla.org/MPL/MPL-1.1.html
*
}
unit uMFT;
interface
uses
Windows, SysUtils;
const
FILE_DEVICE_FILE_SYSTEM = $00000009;
METHOD_NEITHER = 3;
METHOD_BUFFERED = 0;
FILE_ANY_ACCESS = 0;
FILE_SPECIAL_ACCESS = 0;
FILE_READ_ACCESS = 1;
FILE_WRITE_ACCESS = 2;
ERROR_JOURNAL_DELETE_IN_PROGRESS = 1178;
ERROR_JOURNAL_NOT_ACTIVE = 1179;
ERROR_JOURNAL_ENTRY_DELETED = 1181;
FSCTL_GET_OBJECT_ID = $9009c;
FSCTL_ENUM_USN_DATA = (FILE_DEVICE_FILE_SYSTEM shl 16) or (FILE_ANY_ACCESS shl 14) or (44 shl 2) or METHOD_NEITHER;
FSCTL_READ_USN_JOURNAL = (FILE_DEVICE_FILE_SYSTEM shl 16) or (FILE_ANY_ACCESS shl 14) or (46 shl 2) or METHOD_NEITHER;
FSCTL_CREATE_USN_JOURNAL = (FILE_DEVICE_FILE_SYSTEM shl 16) or (FILE_ANY_ACCESS shl 14) or (57 shl 2) or METHOD_NEITHER;
FSCTL_QUERY_USN_JOURNAL = (FILE_DEVICE_FILE_SYSTEM shl 16) or (FILE_ANY_ACCESS shl 14) or (61 shl 2) or METHOD_BUFFERED;
FSCTL_DELETE_USN_JOURNAL = (FILE_DEVICE_FILE_SYSTEM shl 16) or (FILE_ANY_ACCESS shl 14) or (62 shl 2) or METHOD_BUFFERED;
USN_PAGE_SIZE = $1000;
USN_REASON_DATA_OVERWRITE = $00000001;
USN_REASON_DATA_EXTEND = $00000002;
USN_REASON_DATA_TRUNCATION = $00000004;
USN_REASON_NAMED_DATA_OVERWRITE = $00000010;
USN_REASON_NAMED_DATA_EXTEND = $00000020;
USN_REASON_NAMED_DATA_TRUNCATION = $00000040;
USN_REASON_FILE_CREATE = $00000100;
USN_REASON_FILE_DELETE = $00000200;
USN_REASON_EA_CHANGE = $00000400;
USN_REASON_SECURITY_CHANGE = $00000800;
USN_REASON_RENAME_OLD_NAME = $00001000;
USN_REASON_RENAME_NEW_NAME = $00002000;
USN_REASON_INDEXABLE_CHANGE = $00004000;
USN_REASON_BASIC_INFO_CHANGE = $00008000;
USN_REASON_HARD_LINK_CHANGE = $00010000;
USN_REASON_COMPRESSION_CHANGE = $00020000;
USN_REASON_ENCRYPTION_CHANGE = $00040000;
USN_REASON_OBJECT_ID_CHANGE = $00080000;
USN_REASON_REPARSE_POINT_CHANGE = $00100000;
USN_REASON_STREAM_CHANGE = $00200000;
USN_REASON_CLOSE = $80000000;
USN_DELETE_FLAG_DELETE = $00000001;
USN_DELETE_FLAG_NOTIFY = $00000002;
USN_DELETE_VALID_FLAGS = $00000003;
USNREC_MAJVER_OFFSET = 4;
USNREC_MINVER_OFFSET = 8;
USNREC_FR_OFFSET = 8;
USNREC_PFR_OFFSET = 16;
USNREC_USN_OFFSET = 24;
USNREC_TIMESTAMP_OFFSET = 32;
USNREC_REASON_OFFSET = 40;
USNREC_SINFO_OFFSET = 44;
USNREC_SECID_OFFSET = 48;
USNREC_FA_OFFSET = 52;
USNREC_FNL_OFFSET = 56;
USNREC_FN_OFFSET = 58;
IOCTL_DISK_BASE = $00000007;
IOCTL_DISK_GET_PARTITION_INFO = (IOCTL_DISK_BASE shl 16) or (FILE_READ_ACCESS shl 14) or ($0001 shl 2) or METHOD_BUFFERED;
PARTITION_IFS = $07;
type
USN_JOURNAL_DATA = record
UsnJournalID: UInt64;
FirstUsn: Int64;
NextUsn: Int64;
LowestValidUsn: Int64;
MaxUsn: Int64;
MaximumSize: UInt64;
AllocationDelta: UInt64;
end;
TUSNJournalData = USN_JOURNAL_DATA;
PUSNJournalData = ^TUSNJournalData;
MFT_ENUM_DATA = record
StartFileReferenceNumber: UInt64;
LowUsn: Int64;
HighUsn: Int64;
end;
TMFTEnumData = MFT_ENUM_DATA;
PMFTEnumData = ^TMFTEnumData;
CREATE_USN_JOURNAL_DATA = record
MaximumSize: UInt64;
AllocationDelta: UInt64;
end;
TCreateUSNJournalData = CREATE_USN_JOURNAL_DATA;
PCreateUSNJournalData = ^TCreateUSNJournalData;
USN_RECORD = record
RecordLength: Cardinal;
MajorVersion: Word;
MinorVersion: Word;
FileReferenceNumber: UInt64;
ParentFileReferenceNumber: UInt64;
Usn: Int64;
TimeStamp: LARGE_INTEGER;
Reason: Cardinal;
SourceInfo: Cardinal;
SecurityId: Cardinal;
FileAttributes: Cardinal;
FileNameLength: Word;
FileNameOffset: Word;
FileName: PWideChar;// PWChar; [ss]
end;
TUSNRecord = USN_RECORD;
PUSNRecord = ^TUSNRecord;
TMFTEnumCallback = function(AUSN: PUSNRecord; Extra: Pointer = nil): Boolean of object;
PUInt64 = ^UInt64;
EMFTException = class(Exception);
TUSNRecChangeType = (uceNew, uceDeleted, uceRenamed);
function USNRecFromPointer(const P: Pointer): TUSNRecord;
function GetRootHandle(const Drive: Char): Cardinal;
function CreateUSNJournal(ARootHandle: Cardinal; MaxSize, AllocationDelta: UInt64): Boolean;
function AllocMFTEnumBuffer(ARootHandle: Cardinal; var AMFTEnumBuff: Pointer): boolean;
function EnumMFTEntries(ARootHandle: Cardinal; AMFTEnumBuff: Pointer; EnumCallBack: TMFTEnumCallback;Extra: Pointer = nil): Boolean;
implementation
uses main ;
function USNRecFromPointer(const P: Pointer): TUSNRecord;
var
PA: PAnsiChar;
begin
PA := PAnsiChar(P);
Result.RecordLength := PInteger(PA)^ ;
Result.MajorVersion := PInteger(PA + USNREC_MAJVER_OFFSET)^;
Result.MinorVersion := PInteger(PA + USNREC_MINVER_OFFSET)^;
Result.FileReferenceNumber := PUInt64(PA + USNREC_FR_OFFSET)^;
Result.ParentFileReferenceNumber := PUInt64(PA + USNREC_PFR_OFFSET)^;
Result.USN := PInt64(PA + USNREC_USN_OFFSET)^;
Result.TimeStamp.QuadPart := PInt64(PA + USNREC_TIMESTAMP_OFFSET)^;
Result.Reason := PCardinal(PA + USNREC_REASON_OFFSET)^;
Result.SourceInfo := PCardinal(PA + USNREC_SINFO_OFFSET)^;
Result.SecurityId := PCardinal(PA + USNREc_SECID_OFFSET)^;
Result.FileAttributes := PCardinal(PA + USNREC_FA_OFFSET)^;
Result.FileNameLength := PWord(PA + USNREC_FNL_OFFSET)^;
Result.FileNameOffset := PWord(PA + USNREC_FN_OFFSET)^;
Result.FileName := PWideChar(Integer(P) + Result.FileNameOffset);
if form_Main.memo1.lines.Count<100 then
form_Main.memo1.lines.add(Result.FileName+' '+InttoStr(Result.TimeStamp.QuadPart)) ;
end;
// this requires admin privileges
function GetRootHandle(const Drive: Char): Cardinal;
var
W: WideString;
RootHandle: Cardinal;
Flags: Cardinal;
Access: Cardinal;
begin
Flags := 0;
W := '\\.\' + Drive + ':';
Access := GENERIC_READ;
RootHandle := CreateFileW(PWChar(W), Access, FILE_SHARE_READ or FILE_SHARE_WRITE, nil, OPEN_EXISTING, Flags, 0);
if RootHandle <> INVALID_HANDLE_VALUE then
Result := RootHandle
else
raise EMFTException.Create(SysErrorMessage(GetLastError));
end;
function CreateUSNJournal(ARootHandle: Cardinal; MaxSize, AllocationDelta: UInt64): Boolean;
var
BytesRet: Cardinal;
CreateData: TCreateUSNJournalData;
begin
CreateData.MaximumSize := MaxSize;
CreateData.AllocationDelta := AllocationDelta;
Result := DeviceIoControl(ARootHandle, FSCTL_CREATE_USN_JOURNAL, @CreateData, sizeof(TCreateUSNJournalData), nil, 0, BytesRet, nil);
end;
function AllocMFTEnumBuffer(ARootHandle: Cardinal; var AMFTEnumBuff: Pointer): boolean;
var
USNBuf: TUSNJournalData;
ErrCode,BytesRet: Cardinal;
EnumBuf: PMFTEnumData;
begin
result:=false;
if DeviceIoControl(ARootHandle, FSCTL_QUERY_USN_JOURNAL, nil, 0, @USNBuf, sizeof(TUSNJournalData), BytesRet, nil) then
begin
GetMem(EnumBuf, sizeof(TMFTEnumData));
EnumBuf.StartFileReferenceNumber := 0;
EnumBuf.LowUsn := 0;
EnumBuf.HighUsn := USNBuf.NextUsn;
AMFTEnumBuff := EnumBuf;
result:=true;
exit;
end;
ErrCode:=GetLastError;
if ErrCode = ERROR_JOURNAL_NOT_ACTIVE then
begin
// journal does not exist, create a new1 one
if CreateUSNJournal(ARootHandle, $10000000, $100000) then
begin
if DeviceIoControl(ARootHandle, FSCTL_QUERY_USN_JOURNAL, nil, 0, @USNBuf, sizeof(TUSNJournalData), BytesRet, nil) then
begin
GetMem(EnumBuf, sizeof(TMFTEnumData));
EnumBuf.StartFileReferenceNumber := 0;
EnumBuf.LowUsn := 0;
EnumBuf.HighUsn := USNBuf.NextUsn;
AMFTEnumBuff := EnumBuf;
result:=true;
exit;
end;
ErrCode:=GetLastError;
end
else
begin
ErrCode:=GetLastError;
end;
end;
end;
//v main function
function EnumMFTEntries(ARootHandle: Cardinal; AMFTEnumBuff: Pointer; EnumCallBack: TMFTEnumCallback;Extra: Pointer): Boolean;
const
BUF_SIZE = sizeof(UInt64) + $10000;
var
P: Pointer;
MFTEnum: Pointer;
BytesRet: Cardinal;
PUSN: PUSNRecord;
// TUSN: TUSNRecord;
begin
Result := False;
if (ARootHandle = INVALID_HANDLE_VALUE) or (AMFTEnumBuff = nil) then
Exit;
MFTEnum := AMFTEnumBuff;
GetMem(P, BUF_SIZE);
try
ZeroMemory(P, BUF_SIZE);
while DeviceIoControl(ARootHandle, FSCTL_ENUM_USN_DATA, MFTEnum, sizeof(TMFTEnumData), P, BUF_SIZE, BytesRet, nil) do
begin
PUSN := PUSNRecord(Integer(P) + sizeof(Int64));
while BytesRet > 60 do
begin
if (not EnumCallBack(PUSN, Extra)) then
Exit;
if PUSN.RecordLength > 0 then
Dec(BytesRet, PUSN.RecordLength)
else
break;
PUSN := PUSNRecord(Cardinal(PUSN) + PUSN.RecordLength);
if form_Main.memo1.lines.Count>100 then
break ;
end;
CopyMemory(MFTEnum, P, sizeof(Int64));
end;
Result := True;
finally
FreeMem(P);
end;
end;
//^ main function
end.
Time stamps come up 0 because FSCTL_QUERY_USN_JOURNAL
does not populate that information. This is not explicitly stated in the control code's documentation and I cannot find any official documentation where it is. But there are places that mention a USN_RECORD
is only partially populated. Like in this link which is given from USN_RECORD_V2
's documentation, although I'm not even sure what FSCTL the former link is talking about.
Anyway, this is inline with all the examples I can find including Microsoft's own. Below is a Delphi translation of this code. The USN information retrieved by using a FSCTL_QUERY_USN_JOURNAL
control code is used in a DeviceIoControl
call using FSCTL_READ_USN_JOURNAL
to retrieve detailed information.
There are few exceptions of a direct translation, one is the retrieval of the file name, which I didn't understand how the C++ code is doing. Another is printing time stamps as this is your requirement. Also, you need to add error checking and protection blocks for resources like the volume handle or memory, etc..
The code uses the declarations from the helper unit you have included in the question. I checked the time stamps against a 3rd party freeware utility which I found here and they are in accordance.
program Project1;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils,
windows,
uMFT;
type
USN = LONGLONG;
const
BUF_LEN = 4096;
var
hVol: THandle;
JournalData: TUSNJournalData;
dwBytes: DWORD;
dwRetBytes: DWORD;
ReadData: TReadUSNJournalData;
i: Integer;
Buffer: array [0..BUF_LEN - 1] of Byte;
UsnRecord: PUSNRecord;
FileName: PWideChar;
SysTime: TSystemTime;
begin
hVol := CreateFile( '\\.\c:', GENERIC_READ,
FILE_SHARE_READ or FILE_SHARE_WRITE, nil, OPEN_EXISTING, 0, 0);
if hVol = INVALID_HANDLE_VALUE then begin
Writeln(Format('CreateFile failed (%d)', [GetLastError]));
Exit;
end;
if not DeviceIoControl(hVol, FSCTL_QUERY_USN_JOURNAL, nil, 0, @JournalData,
SizeOf(JournalData), dwBytes, nil) then begin
Writeln(Format('Query journal failed (%d)', [GetLastError]));
Exit;
end;
ZeroMemory(@ReadData, SizeOf(ReadData));
ReadData.ReasonMask := $FFFFFFFF;
ReadData.UsnJournalID := JournalData.UsnJournalID;
Writeln(Format('Journal ID: %x', [JournalData.UsnJournalID]));
Writeln(Format('FirstUsn: %x' + sLineBreak, [JournalData.FirstUsn]));
for i := 0 to 10 do begin
FillChar(Buffer, BUF_LEN, 0);
if not DeviceIoControl(hVol, FSCTL_READ_USN_JOURNAL, @ReadData,
SizeOf(ReadData), @Buffer, BUF_LEN, dwBytes, nil) then begin
Writeln(Format('Read journal failed (%d)', [GetLastError]));
Exit;
end;
dwRetBytes := dwBytes - SizeOf(USN);
// Find the first record
UsnRecord := PUsnRecord(NativeInt(@Buffer) + SizeOf(USN));
Writeln('****************************************');
while dwRetBytes > 0 do begin
Writeln(Format('USN: %x', [UsnRecord.Usn]));
GetMem(FileName, UsnRecord.FileNameLength + SizeOf(Char));
Move(Pointer(NativeInt(UsnRecord) + UsnRecord.FileNameOffset)^, FileName^,
UsnRecord.FileNameLength);
FileName[UsnRecord.FileNameLength div 2] := #0;
Writeln(Format('File name: %s', [FileName]));
FreeMem(FileName);
FileTimeToSystemTime(@UsnRecord.TimeStamp, SysTime);
Writeln(Format('Time stamp: %s', [DateTimeToStr(SystemTimeToDateTime(SysTime))]));
Writeln(Format('Reason: %x', [UsnRecord.Reason]));
Writeln;
dwRetBytes := dwRetBytes - UsnRecord.RecordLength;
// Find the next record
UsnRecord := PUsnRecord(NativeInt(UsnRecord) + UsnRecord.RecordLength);
end;
end;
CloseHandle(hVol);
Writeln;
Writeln('End of sample');
Readln;
end.