Private/Get-UsnJournalNative.ps1
|
<#
.SYNOPSIS Reads USN journal records through native file system control calls (private). .DESCRIPTION Get-UsnJournalNative uses FSCTL_QUERY_USN_JOURNAL and FSCTL_READ_USN_JOURNAL instead of parsing localized fsutil output. The C# ParseBuffer and ReadJournalChunks methods are cross-platform synthetic seams. Native failures before the first record are returned to the caller for fsutil fallback; failures after records are returned as partial evidence. .NOTES All native allocation, marshaling, pinning, and buffer walking stays in C# for Windows PowerShell 5.1 compatibility and Constrained Language Mode fallback. No type is exported from the module. #> $script:UsnJournalNativeTypeError = $null $script:UsnJournalNativeUnavailableReason = $null $script:UsnJournalNativeEffectiveLanguageMode = 'Unavailable' $script:UsnJournalNativeDynamicCompilationExpected = $false function Initialize-UsnJournalNativeType { [CmdletBinding()] [OutputType([bool])] param() $effectiveLanguageMode = Get-WsaEffectiveLanguageMode $dynamicCompilationExpected = Test-WsaDynamicCompilationExpected $script:UsnJournalNativeEffectiveLanguageMode = $effectiveLanguageMode $script:UsnJournalNativeDynamicCompilationExpected = $dynamicCompilationExpected $script:UsnJournalNativeUnavailableReason = $null if (-not $dynamicCompilationExpected) { $script:UsnJournalNativeUnavailableReason = $effectiveLanguageMode $script:UsnJournalNativeTypeError = "Dynamic compilation is not expected in effective language mode '$effectiveLanguageMode'." Write-Warning "Initialize-UsnJournalNativeType: native USN journal reading is unavailable in effective language mode '$effectiveLanguageMode'; callers must use the fsutil fallback." return $false } if ([bool]('UsnJournalNative' -as [type])) { return $true } $usnJournalNativeSource = @' using System; using System.Collections.Generic; using System.Runtime.InteropServices; using Microsoft.Win32.SafeHandles; public static class UsnJournalNative { private const uint FSCTL_READ_USN_JOURNAL = 0x000900BB; private const uint FSCTL_QUERY_USN_JOURNAL = 0x000900F4; private const uint GENERIC_READ = 0x80000000; private const uint FILE_SHARE_READ = 0x00000001; private const uint FILE_SHARE_WRITE = 0x00000002; private const uint OPEN_EXISTING = 3; private const int ERROR_INVALID_FUNCTION = 1; private const int ERROR_ACCESS_DENIED = 5; private const int ERROR_NOT_SUPPORTED = 50; private const int ERROR_INVALID_PARAMETER = 87; private const int ERROR_MORE_DATA = 234; private const int ERROR_JOURNAL_DELETE_IN_PROGRESS = 1178; private const int ERROR_JOURNAL_NOT_ACTIVE = 1179; private const int ERROR_JOURNAL_ENTRY_DELETED = 1181; private const int InitialBufferSize = 64 * 1024; private const int MaximumBufferSize = 1024 * 1024; [StructLayout(LayoutKind.Sequential, Pack = 8)] internal struct USN_JOURNAL_DATA_V0 { internal ulong UsnJournalID; internal long FirstUsn; internal long NextUsn; internal long LowestValidUsn; internal long MaxUsn; internal ulong MaximumSize; internal ulong AllocationDelta; } [StructLayout(LayoutKind.Sequential, Pack = 8)] internal struct USN_JOURNAL_DATA_V1 { internal ulong UsnJournalID; internal long FirstUsn; internal long NextUsn; internal long LowestValidUsn; internal long MaxUsn; internal ulong MaximumSize; internal ulong AllocationDelta; internal ushort MinSupportedMajorVersion; internal ushort MaxSupportedMajorVersion; } [StructLayout(LayoutKind.Sequential, Pack = 8)] internal struct USN_JOURNAL_DATA_V2 { internal ulong UsnJournalID; internal long FirstUsn; internal long NextUsn; internal long LowestValidUsn; internal long MaxUsn; internal ulong MaximumSize; internal ulong AllocationDelta; internal ushort MinSupportedMajorVersion; internal ushort MaxSupportedMajorVersion; internal uint Flags; internal ulong RangeTrackChunkSize; internal long RangeTrackFileSizeThreshold; } [StructLayout(LayoutKind.Sequential, Pack = 8)] internal struct READ_USN_JOURNAL_DATA_V0 { internal long StartUsn; internal uint ReasonMask; internal uint ReturnOnlyOnClose; internal ulong Timeout; internal ulong BytesToWaitFor; internal ulong UsnJournalID; } [StructLayout(LayoutKind.Sequential, Pack = 8)] internal struct READ_USN_JOURNAL_DATA_V1 { internal long StartUsn; internal uint ReasonMask; internal uint ReturnOnlyOnClose; internal ulong Timeout; internal ulong BytesToWaitFor; internal ulong UsnJournalID; internal ushort MinMajorVersion; internal ushort MaxMajorVersion; } [StructLayout(LayoutKind.Sequential, Pack = 1)] internal struct USN_RECORD_COMMON_HEADER { internal uint RecordLength; internal ushort MajorVersion; internal ushort MinorVersion; } [StructLayout(LayoutKind.Sequential, Pack = 1)] internal struct USN_RECORD_V2_HEADER { internal uint RecordLength; internal ushort MajorVersion; internal ushort MinorVersion; internal ulong FileReferenceNumber; internal ulong ParentFileReferenceNumber; internal long Usn; internal long TimeStamp; internal uint Reason; internal uint SourceInfo; internal uint SecurityId; internal uint FileAttributes; internal ushort FileNameLength; internal ushort FileNameOffset; } [StructLayout(LayoutKind.Sequential, Pack = 1)] internal struct FILE_ID_128 { [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] internal byte[] Identifier; } [StructLayout(LayoutKind.Sequential, Pack = 1)] internal struct USN_RECORD_V3_HEADER { internal uint RecordLength; internal ushort MajorVersion; internal ushort MinorVersion; internal FILE_ID_128 FileReferenceNumber; internal FILE_ID_128 ParentFileReferenceNumber; internal long Usn; internal long TimeStamp; internal uint Reason; internal uint SourceInfo; internal uint SecurityId; internal uint FileAttributes; internal ushort FileNameLength; internal ushort FileNameOffset; } public class UsnJournalRecord { public ushort MajorVersion; public ushort MinorVersion; public string FileName; public ulong FileReferenceNumber; public ulong ParentFileReferenceNumber; public byte[] FileReferenceNumber128; public byte[] ParentFileReferenceNumber128; public long Usn; public DateTime? TimeStamp; public uint ReasonMask; public uint FileAttributes; } public class UsnBufferParseResult { public List<UsnJournalRecord> Records = new List<UsnJournalRecord>(); public List<string> Diagnostics = new List<string>(); public long NextUsn; public bool Success; public string Error; } public class UsnJournalReadResult { public List<UsnJournalRecord> Records = new List<UsnJournalRecord>(); public List<string> Notes = new List<string>(); public long NextUsn; public bool Success; public string Error; } public class UsnChunkReadContext { public long StartUsn; public byte[] OutputBuffer; public bool UseVersion1; public ushort MinMajorVersion; public ushort MaxMajorVersion; } public class UsnChunkReadResult { public bool Success; public uint BytesReturned; public int Win32Error; } public delegate UsnChunkReadResult UsnChunkReader(UsnChunkReadContext context); [DllImport( "kernel32.dll", EntryPoint = "CreateFileW", ExactSpelling = true, CharSet = CharSet.Unicode, SetLastError = true)] internal static extern SafeFileHandle CreateFileW( string lpFileName, uint dwDesiredAccess, uint dwShareMode, IntPtr lpSecurityAttributes, uint dwCreationDisposition, uint dwFlagsAndAttributes, IntPtr hTemplateFile); [DllImport("kernel32.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool DeviceIoControl( SafeFileHandle hDevice, uint dwIoControlCode, IntPtr lpInBuffer, uint nInBufferSize, IntPtr lpOutBuffer, uint nOutBufferSize, out uint lpBytesReturned, IntPtr lpOverlapped); public static UsnBufferParseResult ParseBuffer(byte[] buffer, int bytesReturned, int maxRecords) { UsnBufferParseResult result = new UsnBufferParseResult(); result.Success = false; if (buffer == null) { result.Error = "The USN journal buffer is null."; return result; } if (maxRecords < 1) { result.Error = "MaxRecords must be greater than zero."; return result; } if (bytesReturned < 8) { result.Error = "The USN journal buffer is shorter than its 8-byte continuation USN."; return result; } if (bytesReturned > buffer.Length) { result.Error = "The returned byte count exceeds the USN journal buffer length."; return result; } result.NextUsn = BitConverter.ToInt64(buffer, 0); int offset = 8; while (offset < bytesReturned && result.Records.Count < maxRecords) { int remaining = bytesReturned - offset; if (remaining < 8) { result.Error = string.Format( "Trailing partial USN record header at buffer offset {0}: only {1} bytes remain.", offset, remaining); return result; } uint recordLength = BitConverter.ToUInt32(buffer, offset); ushort majorVersion = BitConverter.ToUInt16(buffer, offset + 4); ushort minorVersion = BitConverter.ToUInt16(buffer, offset + 6); if (recordLength < 8) { result.Error = string.Format( "Invalid RecordLength {0} at buffer offset {1}: a record must be at least 8 bytes.", recordLength, offset); return result; } if (recordLength > (uint)remaining) { result.Error = string.Format( "Invalid RecordLength {0} at buffer offset {1}: only {2} bytes remain.", recordLength, offset, remaining); return result; } if ((recordLength & 7) != 0) { result.Error = string.Format( "Invalid RecordLength {0} at buffer offset {1}: FSCTL records must be 8-byte aligned.", recordLength, offset); return result; } if (majorVersion != 2 && majorVersion != 3) { result.Diagnostics.Add(string.Format( "Skipped unsupported USN record major version {0} at buffer offset {1} (RecordLength {2}).", majorVersion, offset, recordLength)); offset += (int)recordLength; continue; } int fixedPrefixSize = majorVersion == 2 ? 60 : 76; if (recordLength < (uint)fixedPrefixSize) { result.Error = string.Format( "USN_RECORD_V{0} at buffer offset {1} has RecordLength {2}, shorter than its {3}-byte fixed prefix.", majorVersion, offset, recordLength, fixedPrefixSize); return result; } UsnJournalRecord record = new UsnJournalRecord(); record.MajorVersion = majorVersion; record.MinorVersion = minorVersion; ushort fileNameLength; ushort fileNameOffset; long fileTime; if (majorVersion == 2) { record.FileReferenceNumber = BitConverter.ToUInt64(buffer, offset + 8); record.ParentFileReferenceNumber = BitConverter.ToUInt64(buffer, offset + 16); record.Usn = BitConverter.ToInt64(buffer, offset + 24); fileTime = BitConverter.ToInt64(buffer, offset + 32); record.ReasonMask = BitConverter.ToUInt32(buffer, offset + 40); record.FileAttributes = BitConverter.ToUInt32(buffer, offset + 52); fileNameLength = BitConverter.ToUInt16(buffer, offset + 56); fileNameOffset = BitConverter.ToUInt16(buffer, offset + 58); } else { record.FileReferenceNumber128 = new byte[16]; record.ParentFileReferenceNumber128 = new byte[16]; Array.Copy(buffer, offset + 8, record.FileReferenceNumber128, 0, 16); Array.Copy(buffer, offset + 24, record.ParentFileReferenceNumber128, 0, 16); record.Usn = BitConverter.ToInt64(buffer, offset + 40); fileTime = BitConverter.ToInt64(buffer, offset + 48); record.ReasonMask = BitConverter.ToUInt32(buffer, offset + 56); record.FileAttributes = BitConverter.ToUInt32(buffer, offset + 68); fileNameLength = BitConverter.ToUInt16(buffer, offset + 72); fileNameOffset = BitConverter.ToUInt16(buffer, offset + 74); } if ((fileNameLength & 1) != 0) { result.Error = string.Format( "Invalid FileNameLength {0} at buffer offset {1}: UTF-16 names require an even byte count.", fileNameLength, offset); return result; } if (fileNameOffset < fixedPrefixSize) { result.Error = string.Format( "Invalid FileNameOffset {0} at buffer offset {1}: the name overlaps the {2}-byte fixed prefix.", fileNameOffset, offset, fixedPrefixSize); return result; } if ((int)fileNameOffset + (int)fileNameLength > (int)recordLength) { result.Error = string.Format( "File name at buffer offset {0} extends beyond RecordLength {1} (FileNameOffset {2}, FileNameLength {3}).", offset, recordLength, fileNameOffset, fileNameLength); return result; } record.FileName = System.Text.Encoding.Unicode.GetString( buffer, offset + (int)fileNameOffset, (int)fileNameLength); try { record.TimeStamp = DateTime.FromFileTimeUtc(fileTime); } catch (ArgumentOutOfRangeException) { record.TimeStamp = null; result.Diagnostics.Add(string.Format( "Invalid FILETIME {0} at buffer offset {1}; TimeStamp is null.", fileTime, offset)); } result.Records.Add(record); offset += (int)recordLength; } result.Success = true; return result; } public static UsnJournalReadResult ReadJournal(string driveLetter, int maxRecords, uint reasonMask) { UsnJournalReadResult result = new UsnJournalReadResult(); result.Success = false; if (string.IsNullOrWhiteSpace(driveLetter)) { result.Error = "DriveLetter cannot be empty."; return result; } if (maxRecords < 1) { result.Error = "MaxRecords must be greater than zero."; return result; } string drive = driveLetter.Trim().TrimEnd(':', '\\').ToUpperInvariant(); if (drive.Length != 1 || !((drive[0] >= 'A' && drive[0] <= 'Z'))) { result.Error = "DriveLetter must contain exactly one ASCII letter."; return result; } string volumePath = @"\\.\" + drive + ":"; try { using (SafeFileHandle volume = CreateFileW( volumePath, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, IntPtr.Zero, OPEN_EXISTING, 0, IntPtr.Zero)) { if (volume == null || volume.IsInvalid) { int openError = Marshal.GetLastWin32Error(); result.Error = FormatNativeError("CreateFileW", openError); return result; } byte[] queryBuffer = new byte[80]; uint queryBytesReturned = 0; bool queryOk; int queryError = 0; GCHandle queryPin = GCHandle.Alloc(queryBuffer, GCHandleType.Pinned); try { queryOk = DeviceIoControl( volume, FSCTL_QUERY_USN_JOURNAL, IntPtr.Zero, 0, queryPin.AddrOfPinnedObject(), (uint)queryBuffer.Length, out queryBytesReturned, IntPtr.Zero); if (!queryOk) { queryError = Marshal.GetLastWin32Error(); } } finally { queryPin.Free(); } if (!queryOk) { result.Error = FormatNativeError("FSCTL_QUERY_USN_JOURNAL", queryError); return result; } if (queryBytesReturned < 56 || queryBytesReturned > (uint)queryBuffer.Length) { result.Error = string.Format( "FSCTL_QUERY_USN_JOURNAL returned an invalid byte count: {0}.", queryBytesReturned); return result; } USN_JOURNAL_DATA_V0 queryData = new USN_JOURNAL_DATA_V0(); queryData.UsnJournalID = BitConverter.ToUInt64(queryBuffer, 0); queryData.FirstUsn = BitConverter.ToInt64(queryBuffer, 8); queryData.NextUsn = BitConverter.ToInt64(queryBuffer, 16); queryData.LowestValidUsn = BitConverter.ToInt64(queryBuffer, 24); queryData.MaxUsn = BitConverter.ToInt64(queryBuffer, 32); queryData.MaximumSize = BitConverter.ToUInt64(queryBuffer, 40); queryData.AllocationDelta = BitConverter.ToUInt64(queryBuffer, 48); result.NextUsn = queryData.NextUsn; if (queryData.FirstUsn < queryData.LowestValidUsn) { result.Notes.Add(string.Format( "Possible USN journal discontinuity: FirstUsn {0} is below LowestValidUsn {1}.", queryData.FirstUsn, queryData.LowestValidUsn)); } bool useVersion1 = queryBytesReturned >= 64; ushort minMajorVersion = 0; ushort maxMajorVersion = 0; if (useVersion1) { ushort supportedMin = BitConverter.ToUInt16(queryBuffer, 56); ushort supportedMax = BitConverter.ToUInt16(queryBuffer, 58); minMajorVersion = Math.Max((ushort)2, supportedMin); maxMajorVersion = Math.Min((ushort)3, supportedMax); if (minMajorVersion > maxMajorVersion) { result.Error = string.Format( "The journal supports major versions {0} through {1}; this reader supports only V2 and V3 records.", supportedMin, supportedMax); return result; } } UsnJournalReadResult readResult = ReadJournalChunks( queryData.FirstUsn, queryData.NextUsn, maxRecords, useVersion1, minMajorVersion, maxMajorVersion, delegate(UsnChunkReadContext context) { UsnChunkReadResult chunkResult = new UsnChunkReadResult(); chunkResult.Success = ReadChunk( volume, queryData.UsnJournalID, context.StartUsn, reasonMask, context.UseVersion1, context.MinMajorVersion, context.MaxMajorVersion, context.OutputBuffer, out chunkResult.BytesReturned, out chunkResult.Win32Error); return chunkResult; }); result.Records = readResult.Records; foreach (string note in readResult.Notes) { result.Notes.Add(note); } result.NextUsn = readResult.NextUsn; result.Error = readResult.Error; result.Success = readResult.Success; } } catch (Exception exception) { result.Error = "Native USN journal read failed: " + exception.Message; result.Success = false; } return result; } public static UsnJournalReadResult ReadJournalChunks( long startUsn, long targetNextUsn, int maxRecords, bool useVersion1, ushort minMajorVersion, ushort maxMajorVersion, UsnChunkReader chunkReader) { UsnJournalReadResult result = new UsnJournalReadResult(); result.Success = false; result.NextUsn = targetNextUsn; if (maxRecords < 1) { result.Error = "MaxRecords must be greater than zero."; return result; } if (chunkReader == null) { result.Error = "The USN journal chunk reader is null."; return result; } int bufferSize = InitialBufferSize; byte[] readBuffer = new byte[bufferSize]; while (startUsn < targetNextUsn && result.Records.Count < maxRecords) { UsnChunkReadContext context = new UsnChunkReadContext(); context.StartUsn = startUsn; context.OutputBuffer = readBuffer; context.UseVersion1 = useVersion1; context.MinMajorVersion = minMajorVersion; context.MaxMajorVersion = maxMajorVersion; UsnChunkReadResult chunkResult = chunkReader(context); if (chunkResult == null) { result.Error = "The USN journal chunk reader returned no result."; break; } if (!chunkResult.Success && useVersion1 && IsVersion1FallbackError(chunkResult.Win32Error)) { useVersion1 = false; context.UseVersion1 = false; context.MinMajorVersion = 0; context.MaxMajorVersion = 0; chunkResult = chunkReader(context); if (chunkResult == null) { result.Error = "The USN journal chunk reader returned no result."; break; } } if (!chunkResult.Success) { if (chunkResult.Win32Error == ERROR_MORE_DATA) { if (bufferSize >= MaximumBufferSize) { result.Error = "FSCTL_READ_USN_JOURNAL requires more than the 1 MiB buffer ceiling (ERROR_MORE_DATA)."; break; } bufferSize = Math.Min(bufferSize * 2, MaximumBufferSize); readBuffer = new byte[bufferSize]; continue; } result.Error = FormatNativeError("FSCTL_READ_USN_JOURNAL", chunkResult.Win32Error); break; } if (chunkResult.BytesReturned > (uint)readBuffer.Length) { result.Error = string.Format( "FSCTL_READ_USN_JOURNAL returned {0} bytes for a {1}-byte buffer.", chunkResult.BytesReturned, readBuffer.Length); break; } UsnBufferParseResult parsed = ParseBuffer( readBuffer, (int)chunkResult.BytesReturned, maxRecords - result.Records.Count); result.NextUsn = parsed.NextUsn; foreach (string diagnostic in parsed.Diagnostics) { result.Notes.Add(diagnostic); } result.Records.AddRange(parsed.Records); if (!parsed.Success) { result.Error = "Malformed USN journal data: " + parsed.Error; break; } if (result.Records.Count >= maxRecords) { break; } if (parsed.NextUsn == startUsn) { result.Notes.Add("USN journal continuation did not advance; collection stopped to avoid an infinite loop."); break; } if (parsed.NextUsn < startUsn) { result.Error = string.Format( "Invalid USN continuation: next USN {0} is below start USN {1}.", parsed.NextUsn, startUsn); break; } startUsn = parsed.NextUsn; } result.Success = result.Error == null; return result; } private static bool ReadChunk( SafeFileHandle volume, ulong journalId, long startUsn, uint reasonMask, bool useVersion1, ushort minMajorVersion, ushort maxMajorVersion, byte[] outputBuffer, out uint bytesReturned, out int win32Error) { bytesReturned = 0; win32Error = 0; int inputSize = useVersion1 ? Marshal.SizeOf(typeof(READ_USN_JOURNAL_DATA_V1)) : Marshal.SizeOf(typeof(READ_USN_JOURNAL_DATA_V0)); IntPtr inputBuffer = Marshal.AllocHGlobal(inputSize); try { if (useVersion1) { READ_USN_JOURNAL_DATA_V1 input = new READ_USN_JOURNAL_DATA_V1(); input.StartUsn = startUsn; input.ReasonMask = reasonMask; input.ReturnOnlyOnClose = 0; input.Timeout = 0; input.BytesToWaitFor = 0; input.UsnJournalID = journalId; input.MinMajorVersion = minMajorVersion; input.MaxMajorVersion = maxMajorVersion; Marshal.StructureToPtr(input, inputBuffer, false); } else { READ_USN_JOURNAL_DATA_V0 input = new READ_USN_JOURNAL_DATA_V0(); input.StartUsn = startUsn; input.ReasonMask = reasonMask; input.ReturnOnlyOnClose = 0; input.Timeout = 0; input.BytesToWaitFor = 0; input.UsnJournalID = journalId; Marshal.StructureToPtr(input, inputBuffer, false); } GCHandle outputPin = GCHandle.Alloc(outputBuffer, GCHandleType.Pinned); try { bool readOk = DeviceIoControl( volume, FSCTL_READ_USN_JOURNAL, inputBuffer, (uint)inputSize, outputPin.AddrOfPinnedObject(), (uint)outputBuffer.Length, out bytesReturned, IntPtr.Zero); if (!readOk) { win32Error = Marshal.GetLastWin32Error(); } return readOk; } finally { outputPin.Free(); } } finally { Marshal.FreeHGlobal(inputBuffer); } } private static bool IsVersion1FallbackError(int win32Error) { return win32Error == ERROR_INVALID_FUNCTION || win32Error == ERROR_NOT_SUPPORTED || win32Error == ERROR_INVALID_PARAMETER; } private static string FormatNativeError(string operation, int win32Error) { switch (win32Error) { case ERROR_ACCESS_DENIED: return operation + " failed: access denied; elevation is required (Win32 error 5)."; case ERROR_JOURNAL_DELETE_IN_PROGRESS: return operation + " failed: USN journal deletion is in progress (Win32 error 1178)."; case ERROR_JOURNAL_NOT_ACTIVE: return operation + " failed: the USN journal is not active (Win32 error 1179)."; case ERROR_JOURNAL_ENTRY_DELETED: return operation + " failed: the requested USN entry was deleted or invalidated by journal rollover (Win32 error 1181)."; default: return string.Format( "{0} failed with Win32 error {1}.", operation, win32Error); } } } '@ try { Add-Type -TypeDefinition $usnJournalNativeSource -ErrorAction Stop $script:UsnJournalNativeTypeError = $null return $true } catch { $script:UsnJournalNativeTypeError = $_.Exception.Message $script:UsnJournalNativeUnavailableReason = 'NativeCompilationFailed' Write-Warning "Initialize-UsnJournalNativeType: native USN compilation failed; callers must use the fsutil fallback: $($_.Exception.Message)" return $false } } function Get-UsnJournalNative { [CmdletBinding()] [OutputType([PSCustomObject])] param( [Parameter()] [ValidateNotNullOrEmpty()] [string]$DriveLetter = 'C', [Parameter()] [ValidateRange(1, 1000000)] [int]$MaxRecords = 10000, [Parameter()] [uint32]$ReasonMask = [uint32]::MaxValue ) if (-not (Initialize-UsnJournalNativeType)) { return New-Object PSObject -Property ([ordered]@{ Records = @() Success = $false Error = "Native USN journal type initialization failed: $script:UsnJournalNativeTypeError" Source = 'Native' NextUsn = [long]0 Notes = @() Incomplete = $false EffectiveLanguageMode = $script:UsnJournalNativeEffectiveLanguageMode DynamicCompilationExpected = $script:UsnJournalNativeDynamicCompilationExpected UnavailableReason = $script:UsnJournalNativeUnavailableReason }) } $isWindowsHost = $false try { $isWindowsHost = ([Environment]::OSVersion.Platform -eq [PlatformID]::Win32NT) } catch { $isWindowsHost = ($env:OS -eq 'Windows_NT') } if (-not $isWindowsHost) { return New-Object PSObject -Property ([ordered]@{ Records = @() Success = $false Error = 'Native USN journal reading is only available on Windows.' Source = 'Native' NextUsn = [long]0 Notes = @() Incomplete = $false EffectiveLanguageMode = $script:UsnJournalNativeEffectiveLanguageMode DynamicCompilationExpected = $script:UsnJournalNativeDynamicCompilationExpected UnavailableReason = $script:UsnJournalNativeUnavailableReason }) } try { $nativeResult = [UsnJournalNative]::ReadJournal($DriveLetter, $MaxRecords, $ReasonMask) $records = @($nativeResult.Records) $notes = @($nativeResult.Notes | ForEach-Object { [string]$_ }) $incomplete = (-not $nativeResult.Success -and $records.Count -gt 0) if ($incomplete) { $notes += "Native USN journal collection is incomplete: $($nativeResult.Error)" } return New-Object PSObject -Property ([ordered]@{ Records = $records Success = [bool]$nativeResult.Success Error = if ($null -ne $nativeResult.Error) { [string]$nativeResult.Error } else { $null } Source = 'Native' NextUsn = [long]$nativeResult.NextUsn Notes = $notes Incomplete = $incomplete EffectiveLanguageMode = $script:UsnJournalNativeEffectiveLanguageMode DynamicCompilationExpected = $script:UsnJournalNativeDynamicCompilationExpected UnavailableReason = $script:UsnJournalNativeUnavailableReason }) } catch { return New-Object PSObject -Property ([ordered]@{ Records = @() Success = $false Error = "Native USN journal invocation failed: $($_.Exception.Message)" Source = 'Native' NextUsn = [long]0 Notes = @() Incomplete = $false EffectiveLanguageMode = $script:UsnJournalNativeEffectiveLanguageMode DynamicCompilationExpected = $script:UsnJournalNativeDynamicCompilationExpected UnavailableReason = $script:UsnJournalNativeUnavailableReason }) } } |