This question was very important for the project I am working on, so I finally got (almost) 100% work.
Note. All of the code snippets below are in C #
Thanks to previous Hannes de Jager answers that pointed me in the right direction and documentation, I can now read the USN log from a VSS snapshot or any other special device that the regular API cannot work with; in my case, I mean VMware snapshots mounted using VDDK (VMware SDK for virtual disks).
I also reused or imported code from large projects:
In case anyone else is interested, I share the code that I'm using now, still in a rather rough state, but it works.
How it works?
First, you must use the required Usn log components. They are located at the root of the device as ADS (alternative data streams) in a hidden record. They cannot be accessed using the standard System.IO namespace, so I said earlier that I used the AlphaFS project, but for this I would have to hide CreateFile() and ReadFile() .
1/2
The \$Extend\$UsnJrnl:$Max contains global information about the current state of the log. The most important parts are the log identifier usn (which you can use to verify that the log was not reset if you want to compare multiple VSS snapshots) and the lowest valid USN log sequence number.
USN Journal Structure:
// can be directly extracted from $MAX entry using Bitconverter.ToUint64 public struct USN_JOURNAL_DATA{ public UInt64 MaximumSize; //offset 0 public UInt64 AllocationDelta; // offset 8 public UInt64 UsnJournalID; // offset 16 public Int64 LowestValidUsn; // offset 24 }
2/2
The \$Extend\$UsnJrnl:$J entry contains the journal entries. This is a sparse file, so its disk usage is much lower than its size.
To answer the initial question, how can I find out Max, the USN sequence used, from the previous VSS snapshot and compare it with a snapshot of another snapshot? Well, the NextUsn value is simply equal to the size of the $Usnjrnl:$J entry.
In your βnewβ USN USN snapshot log, you can find a βreferenceβ VSS max USN snapshot before you start analyzing records if you want to analyze the records changed between two snapshots.
Generally speaking, each USN journal entry as a unique identifier (USN), which is the offset within $J in which the journal entry itself is located. Each record has a variable size, so for sequential reading we must calculate:
next entry offset inside $J = offset of current entry (or its USN sequennce number + length of current entry
Fortunately, the record length is also the record field of the USN record. Stop saying here is the USN record class:
public class UsnEntry : IComparable<UsnEntry>{ private const int FR_OFFSET = 8; private const int PFR_OFFSET = 16; private const int USN_OFFSET = 24; private const int REASON_OFFSET = 40; private const int FA_OFFSET = 52; private const int FNL_OFFSET = 56; private const int FN_OFFSET = 58; public UInt32 RecordLength {get; private set;} public Int64 USN {get; private set;} public UInt64 FileReferenceNumber {get;private set;} public UInt64 ParentFileReferenceNumber {get; private set;} public UInt32 Reason{get; set;} public string Name {get; private set;} public string OldName{get; private set;} private UInt32 _fileAttributes; public bool IsFolder{ get{ bool bRtn = false; if (0 != (_fileAttributes & Win32Api.FILE_ATTRIBUTE_DIRECTORY)) bRtn = true; return bRtn; } } public bool IsFile{ get{ bool bRtn = false; if (0 == (_fileAttributes & Win32Api.FILE_ATTRIBUTE_DIRECTORY)) bRtn = true; return bRtn; } }
I tried to isolate the smallest part of the code that can parse the USN log and retrieve its entries, starting with the lowest valid. Remember that a record has a variable length; also note that some records point to the next empty record (the first 4 bytes, which are usually the length of the record, are nullified). In this case, I search for 4 bytes and repeat the parsing until I get the next record. This behavior has also been reported by people who wrote similar parsing tools in Python, so I think I'm not too wrong.
string vol = @"\\?\path_to_your_VSS_snapshot"; string maxHandle = vol + @"\$Extend\$UsnJrnl:$Max"; string rawJournal= vol + @"\$Extend\$UsnJrnl:$J";
Here are the pinwa that I use:
public class Win32Api{ [StructLayout(LayoutKind.Sequential, Pack = 1)] 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 FileID FileIndex; } [DllImport("kernel32.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool GetFileInformationByHandle( IntPtr hFile, out BY_HANDLE_FILE_INFORMATION lpFileInformation); [DllImport("kernel32.dll", SetLastError = true)] public static extern IntPtr CreateFile(string lpFileName, uint dwDesiredAccess, uint dwShareMode, IntPtr lpSecurityAttributes, uint dwCreationDisposition, uint dwFlagsAndAttributes, IntPtr hTemplateFile); }
This is definitely not the best piece of code on earth, but I think it will be a good starting point for those who need to do the same.