この質問は、私が取り組んでいるプロジェクトにとって非常に重要だったので、ようやく(ほぼ)100%機能するようになりました。
注:以下のコードスニペットはすべてC#です。
正しい方向とドキュメントを教えてくれたHannesdeJagerからの以前の回答のおかげで、VSSスナップショットまたは通常のAPIでは機能しないその他の特別なデバイスからUSNジャーナルを読み取ることができるようになりました。私の場合、VDDK(VMware SDK for VMディスク)を使用してマウントされたVMwareスナップショットを意味します。
また、すばらしいプロジェクトからのコードを再利用またはインポートしました。
他の誰かが興味を持っている場合に備えて、私は今使用しているコードを共有します。まだかなり粗雑な状態ですが、機能しています。
それはどのように機能しますか?
まず、必要なUsnジャーナルコンポーネントにアクセスする必要があります。これらは、非表示のエントリのADS(代替データストリーム)としてデバイスのルートにあります。標準の名前空間を使用してアクセスすることはできませんSystem.IO
。そのため、以前にAlphaFSプロジェクトを使用したと言いましたが、ピンボークCreateFile()
でReadFile()
十分なはずです。
1/2
エントリ\$Extend\$UsnJrnl:$Max
には、ジャーナルの現在の状態に関するグローバル情報が含まれています。最も重要な部分は、usnジャーナルID(複数のVSSスナップショットを比較する場合にジャーナルがリセットされていないことを確認するために使用できます)と最小の有効なUSNジャーナルシーケンス番号です。
USNジャーナルの構造:
// 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
エントリ\$Extend\$UsnJrnl:$J
にはジャーナルレコードが含まれています。これはスパースファイルであるため、ディスク使用量はサイズよりもはるかに少なくなります。
最初の質問に答えるために、以前のVSSスナップショットから最大使用USNシーケンスを知り、それを別のスナップショットのシーケンスと比較するにはどうすればよいでしょうか。NextUsnの値は、$Usnjrnl:$J
エントリのサイズと同じです。
2つのスナップショット間で変更されたレコードを解析する場合は、「新しい」vssスナップショットUSNジャーナルで、レコードの解析を開始する前に「参照」VSSスナップショットの最大USNを探すことができます。
一般的に、各USNジャーナルエントリは$J
、ジャーナルエントリ自体が配置されている内部のオフセットである一意のID(USN番号)として表示されます。各エントリのサイズは可変であるため、順番に読み取るには、次のように計算する必要があります。
next entry offset inside $J =
offset of current entry (or its USN sequennce number + length of current entry
幸い、レコード長はUSNエントリレコードのフィールドでもあります。十分に言って、ここにUSNレコードクラスがあります:
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;
}
}
/// <summary>
/// USN Record Constructor
/// </summary>
/// <param name="p">Buffer pointer to first byte of the USN Record</param>
public UsnEntry(IntPtr ptrToUsnRecord){
RecordLength = (UInt32)Marshal.ReadInt32(ptrToUsnRecord); //record size
FileReferenceNumber = (UInt64)Marshal.ReadInt64(ptrToUsnRecord, FR_OFFSET);
ParentFileReferenceNumber = (UInt64)Marshal.ReadInt64(ptrToUsnRecord, PFR_OFFSET);
USN = (Int64)Marshal.ReadInt64(ptrToUsnRecord, USN_OFFSET);
Reason = (UInt32)Marshal.ReadInt32(ptrToUsnRecord, REASON_OFFSET);
_fileAttributes = (UInt32)Marshal.ReadInt32(ptrToUsnRecord, FA_OFFSET);
short fileNameLength = Marshal.ReadInt16(ptrToUsnRecord, FNL_OFFSET);
short fileNameOffset = Marshal.ReadInt16(ptrToUsnRecord, FN_OFFSET);
Name = Marshal.PtrToStringUni(new IntPtr(ptrToUsnRecord.ToInt32() + fileNameOffset), fileNameLength / sizeof(char));
}
public int CompareTo(UsnEntry other){
return string.Compare(this.Name, other.Name, true);
}
public override string ToString(){
return string.Format ("[UsnEntry: RecordLength={0}, USN={1}, FileReferenceNumber={2}, ParentFileReferenceNumber={3}, Reason={4}, Name={5}, OldName={6}, IsFolder={7}, IsFile={8}", RecordLength, USN, (int)FileReferenceNumber, (int)ParentFileReferenceNumber, Reason, Name, OldName, IsFolder, IsFile);
}
}
USNジャーナルを解析し、有効な最も低いものから始めてそのエントリを抽出できるコードの最小部分を分離しようとしました。レコードの長さは可変であることを忘れないでください。また、一部のレコードが空の次のレコードを指していることにも注意してください(通常はレコード長である最初の4バイトはゼロになります)。この場合、次のレコードを取得するまで、4バイトをシークして解析を再試行します。この動作は、Pythonで同様の解析ツールを作成した人からも報告されているので、ここではそれほど間違っていないと思います。
string vol = @"\\?\path_to_your_VSS_snapshot";
string maxHandle = vol + @"\$Extend\$UsnJrnl:$Max";
string rawJournal= vol + @"\$Extend\$UsnJrnl:$J";
// cannot use regular System.IO here, but pinvoking ReadFile() should be enough
FileStream maxStream = Alphaleonis.Win32.Filesystem.File.OpenRead(maxHandle);
byte[] maxData = new byte[32];
maxStream.Read(maxData, 0, 32);
//first valid entry
long lowestUsn = BitConverter.ToInt64(maxData, 24);
// max (last) entry, is the size of the $J ADS
IntPtr journalDataHandle = Win32Api.CreateFile(rawJournal,
0,
Win32Api.FILE_SHARE_READ| Win32Api.FILE_SHARE_WRITE,
IntPtr.Zero, Win32Api.OPEN_EXISTING,
0, IntPtr.Zero);
Win32Api.BY_HANDLE_FILE_INFORMATION fileInfo = new Win32Api.BY_HANDLE_FILE_INFORMATION();
Win32Api.GetFileInformationByHandle(journalDataHandle, out fileInfo);
Win32Api.CloseHandle(journalDataHandle);
long lastUsn = fileInfo.FileSizeLow;
int read = 0;
byte[] usnrecord;
byte[] usnraw = new byte[4]; // first byte array is to store the record length
// same here : pinvoke ReadFile() to avoid AlphaFS dependancy
FileStream rawJStream = Alphaleonis.Win32.Filesystem.File.OpenRead(rawJournal);
int recordSize = 0;
long pos = lowestUsn;
while(pos < newUsnState.NextUsn){
seeked = rawJStream.Seek(pos, SeekOrigin.Begin);
read = rawJStream.Read(usnraw, 0, usnraw.Length);
recordSize = BitConverter.ToInt32(usnraw, 0);
if(recordSize == 0){
pos = pos+4;
continue;
}
usnrecord = new byte[recordSize];
rawJStream.Read(usnrecord, 4, recordSize-4);
Array.Copy(usnraw, 0, usnrecord, 0, 4);
fixed (byte* p = usnrecord){
IntPtr ptr = (IntPtr)p;
// here we use the previously defined UsnEntry class
Win32Api.UsnEntry entry = new Win32Api.UsnEntry(ptr);
Console.WriteLine ("entry: "+entry.ToString());
ptr = IntPtr.Zero;
}
pos += recordSize;
}
これが私が使うピンボークです:
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 uint FileIndexHigh;
public uint FileIndexLow;*/
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);
}
これは間違いなく地球上で最高のコードではありませんが、同じことをしなければならない人にとっては良い出発点になると思います。