Summary: Honorary Scripting Guy, Boe Prox, shows us how to connect to the USN change journal by using Windows PowerShell.
Honorary Scripting Guy and Windows PowerShell MVP, Boe Prox, here today, filling in for my good friend, the Scripting Guy. Over the course of the next few days, I am going to be talking about a topic that many of you might not have heard about, which deals with the update sequence number (USN) change journal. We will use Windows PowerShell to take a walk-through, with help from PInvoke, to connect to the change journal and view the entries within the journal.
First, a quick overview of the USN change journal...
This is journal contains entries of changes made to the volume that you are looking at. This could be a local system drive or another drive that is connected to a system. Depending on the size of the journal, you could go back several days and view all of the changes that have been made, ranging from created files to deleted files, including their time stamps and locations.
You might think that something like this would be off limits for us to view. It can actually be viewed by using fsutil, but that only returns text, which as you know, doesn’t really provide much use to us who live and breathe Windows PowerShell and demand that objects be returned.
So with that, let’s get started by building our code so we can make that connection to the change journal!
I mentioned earlier that PInvoke would be playing a part in all of this and that begins now! Between pinvoke.net and looking up information on MSDN, I can find exactly what I need. In fact, this site has some great information about the various functions that are needed: Change Journals.
I can see from my research that I need the CreateFile function to get a handle to the change journal, which will get a lot of use in future posts. It also retrieves an object that will list some useful information about the change journal.
Everything I do with PInvoke is done using reflection, so if you were expecting some C# code that is compiled by using Add-Type...well maybe next time! You can thank fellow Windows PowerShell MVP, Matt Graeber, for getting me hooked on this approach.
I am going to have a type called PoshChJournal when this is all done, so I can perform a Try/Catch against the type to see if it actually exists. If not, I'll perform the necessary steps to build it.
Try {
[void][PoshChJournal]
}
Assuming it is not already available, I'll start building the pieces needed, such as the Enums, Structs, and Methods.
#region Module Builder
$Domain = [AppDomain]::CurrentDomain
$DynAssembly = New-Object System.Reflection.AssemblyName('ChJournalAssembly')
$AssemblyBuilder = $Domain.DefineDynamicAssembly($DynAssembly, [System.Reflection.Emit.AssemblyBuilderAccess]::Run) # Only run in memory
$ModuleBuilder = $AssemblyBuilder.DefineDynamicModule('ChJournal', $False)
#endregion Module Builder
#region Enum
#region EIOControlCode Enum
$EnumBuilder = $ModuleBuilder.DefineEnum('EIOControlCode', 'Public', [uint32])
[void]$EnumBuilder.DefineLiteral('FSCTL_QUERY_USN_JOURNAL', [uint32] 0x900f4)
[void]$EnumBuilder.DefineLiteral('FSCTL_READ_USN_JOURNAL', [uint32] 0x900bb)
[void]$EnumBuilder.CreateType()
#endregion EIOControlCode Enum
#endregion Enum
#region STRUCT
#region USN_JOURNAL_DATA
$Attributes = 'AutoLayout, AnsiClass, Class, Public, SequentialLayout, Sealed, BeforeFieldInit'
$STRUCT_TypeBuilder = $ModuleBuilder.DefineType('USN_JOURNAL_DATA', $Attributes, [System.ValueType], 8)
[void]$STRUCT_TypeBuilder.DefineField('UsnJournalID', [long], 'Public')
[void]$STRUCT_TypeBuilder.DefineField('FirstUsn', [long], 'Public')
[void]$STRUCT_TypeBuilder.DefineField('NextUsn', [long], 'Public')
[void]$STRUCT_TypeBuilder.DefineField('LowestValidUsn', [long], 'Public')
[void]$STRUCT_TypeBuilder.DefineField('MaxUsn', [long], 'Public')
[void]$STRUCT_TypeBuilder.DefineField('MaximumSize', [long], 'Public')
[void]$STRUCT_TypeBuilder.DefineField('AllocationDelta', [long], 'Public')
[void]$STRUCT_TypeBuilder.CreateType()
#endregion USN_JOURNAL_DATA
#endregion STRUCT
#region Initialize Type Builder
$TypeBuilder = $ModuleBuilder.DefineType('PoshChJournal', 'Public, Class')
#endregion Initialize Type Builder
#region Methods
#region CreateFile
$PInvokeMethod = $TypeBuilder.DefineMethod(
'CreateFile', #Method Name
[Reflection.MethodAttributes] 'PrivateScope, Public, Static, HideBySig, PinvokeImpl', #Method Attributes
[IntPtr ], #Method Return Type
[Type[]] @(
[string], # Filename
[System.IO.FileAccess], # Access
[System.IO.FileShare], # Share
[intptr], # Security attributes
[System.IO.FileMode], # Creation Disposition
[System.IO.FileAttributes], # Flags and Attributes
[intptr] # Template File
) #Method Parameters
)
$DllImportConstructor = [Runtime.InteropServices.DllImportAttribute].GetConstructor(@([String]))
$FieldArray = [Reflection.FieldInfo[]] @(
[Runtime.InteropServices.DllImportAttribute].GetField('EntryPoint'),
[Runtime.InteropServices.DllImportAttribute].GetField('SetLastError')
[Runtime.InteropServices.DllImportAttribute].GetField('ExactSpelling')
)
$FieldValueArray = [Object[]] @(
'CreateFile', #CASE SENSITIVE!!
$True,
$False
)
$SetLastErrorCustomAttribute = New-Object Reflection.Emit.CustomAttributeBuilder(
$DllImportConstructor,
@('kernel32.dll'),
$FieldArray,
$FieldValueArray
)
$PInvokeMethod.SetCustomAttribute($SetLastErrorCustomAttribute)
#endregion CreateFile
#region DeviceIoControl
$PInvokeMethod = $TypeBuilder.DefineMethod(
'DeviceIoControl', #Method Name
[Reflection.MethodAttributes] 'PrivateScope, Public, Static, HideBySig, PinvokeImpl', #Method Attributes
[bool ], #Method Return Type
[Type[]] @(
[intptr], # hDevice
[uint32], # IOControlCode
[long].MakeByRefType(), # InBuffer
[int], # InBufferSize
[USN_JOURNAL_DATA].MakeByRefType(), # OutBuffer
[int], # OutBufferSize
[int].MakeByRefType(), # lpBytesReturned
[intptr] # lpOverlapped
) #Method Parameters
)
$DllImportConstructor = [Runtime.InteropServices.DllImportAttribute].GetConstructor(@([String]))
$FieldArray = [Reflection.FieldInfo[]] @(
[Runtime.InteropServices.DllImportAttribute].GetField('EntryPoint'),
[Runtime.InteropServices.DllImportAttribute].GetField('SetLastError')
[Runtime.InteropServices.DllImportAttribute].GetField('ExactSpelling')
)
$FieldValueArray = [Object[]] @(
'DeviceIoControl', #CASE SENSITIVE!!
$True,
$False
)
$SetLastErrorCustomAttribute = New-Object Reflection.Emit.CustomAttributeBuilder(
$DllImportConstructor,
@('kernel32.dll'),
$FieldArray,
$FieldValueArray
)
$PInvokeMethod.SetCustomAttribute($SetLastErrorCustomAttribute)
$PInvokeMethod = $TypeBuilder.DefineMethod(
'DeviceIoControl', #Method Name
[Reflection.MethodAttributes] 'PrivateScope, Public, Static, HideBySig, PinvokeImpl', #Method Attributes
[bool ], #Method Return Type
[Type[]] @(
[intptr], # hDevice
[uint32], # IOControlCode
[intptr], # InBuffer
[int], # InBufferSize
[intptr], # OutBuffer
[int], # OutBufferSize
[int].MakeByRefType(), # lpBytesReturned
[intptr] # lpOverlapped
) #Method Parameters
)
$DllImportConstructor = [Runtime.InteropServices.DllImportAttribute].GetConstructor(@([String]))
$FieldArray = [Reflection.FieldInfo[]] @(
[Runtime.InteropServices.DllImportAttribute].GetField('EntryPoint'),
[Runtime.InteropServices.DllImportAttribute].GetField('SetLastError')
[Runtime.InteropServices.DllImportAttribute].GetField('ExactSpelling')
)
$FieldValueArray = [Object[]] @(
'DeviceIoControl', #CASE SENSITIVE!!
$True,
$False
)
$SetLastErrorCustomAttribute = New-Object Reflection.Emit.CustomAttributeBuilder(
$DllImportConstructor,
@('kernel32.dll'),
$FieldArray,
$FieldValueArray
)
$PInvokeMethod.SetCustomAttribute($SetLastErrorCustomAttribute)
#endregion DeviceIoControl
#region CloseHandle
$PInvokeMethod = $TypeBuilder.DefineMethod(
'CloseHandle', #Method Name
[Reflection.MethodAttributes] 'PrivateScope, Public, Static, HideBySig, PinvokeImpl', #Method Attributes
[bool ], #Method Return Type
[Type[]] @(
[intptr] # Handle
) #Method Parameters
)
$DllImportConstructor = [Runtime.InteropServices.DllImportAttribute].GetConstructor(@([String]))
$FieldArray = [Reflection.FieldInfo[]] @(
[Runtime.InteropServices.DllImportAttribute].GetField('EntryPoint'),
[Runtime.InteropServices.DllImportAttribute].GetField('SetLastError')
[Runtime.InteropServices.DllImportAttribute].GetField('ExactSpelling')
)
$FieldValueArray = [Object[]] @(
'CloseHandle', #CASE SENSITIVE!!
$True,
$False
)
$SetLastErrorCustomAttribute = New-Object Reflection.Emit.CustomAttributeBuilder(
$DllImportConstructor,
@('kernel32.dll'),
$FieldArray,
$FieldValueArray
)
$PInvokeMethod.SetCustomAttribute($SetLastErrorCustomAttribute)
#endregion CloseHandle
#endregion Methods
#region Create Type
[void]$TypeBuilder.CreateType()
#endregion Create Type
Now we can verify that all of this worked by running through some of the methods in the type and looking at the Enum and Structs.
Now that we are done with that, it is time to make our connection to the change journal. First off, I want to define some variables prior to opening the file handle on local drive C, and I'll specify what type of access I want to drive C when using CreateFile().
$FileName = "\\.\c:"
$Access = [System.IO.FileAccess]::Read -BOR [System.IO.FileAccess]::Write
$ShareAccess = [System.IO.FileShare]::Read -BOR [System.IO.FileShare]::Write
$FileMode = [System.IO.FileMode]::Open
Now I can get the file handle for drive C. This handle will be used in all operations involving the change journal, so ensuring that we get it is crucial for the rest of the things that we will be doing.
$hVol = [PoshChJournal]::CreateFile(
$FileName,
$Access,
$ShareAccess,
[intptr]::Zero,
$FileMode,
0,
[intptr]::Zero
)
We have a handle returned from the CreateFile() method, and that means that we can move on to the last operation of the day, which is using DeviceIOControl() to perform the necessary action to pull the change journal data to our structure. This will tell us more information about it.
If you are wondering...
If the result happened to be a -1, this means an error occurred, and we would have to see what happened by using the following code:
If ($hVol -eq -1) {
Write-Warning ("CreateFile failed: 0x{0:x}" -f [System.Runtime.InteropServices.Marshal]::GetHRForLastWin32Error())
}
As before, I need to specify a couple of variables prior to running my method:
$JournalData = New-Object USN_JOURNAL_DATA
[long]$dwBytes=0
Now we can call DeviceIOControll and take note of [EIOControlCode]::FSCTL_QUERY_USN_JOURNAL, which tells the system to look at the change journal. I'll be using DeviceIOControl in other examples, but using a different EIOControlCode, based on what I need to do.
$return = [PoshChJournal]::DeviceIoControl(
$hVol,
[EIOControlCode]::FSCTL_QUERY_USN_JOURNAL,
[ref]$Null,
0,
[ref]$JournalData,
[System.Runtime.InteropServices.Marshal]::SizeOf([type][USN_JOURNAL_DATA]),
[ref]$dwBytes,
[intptr]::Zero
)
Assuming that $Return is $True, we know that this was successful, and we can now look at the referenced structure, $JournalData, to see what it has:
Perfect! You might be wondering what each of these properties mean. Let’s run through each:
- UsnJournalID: The current journal identifier. A journal is assigned a new identifier when it is created, and it can be stamped with a new identifier during the course of its existence.
- FirstUsn: The number of the first record that can be read from the journal
- NextUsn: The number of the next record to be written to the journal
- LowestValidUsn: The first record that was written into the journal for this journal instance
- MaxUsn: The largest USN that the change journal supports
- MaximumSize: The target maximum size for the change journal, in bytes
- AllocationDelta: The number of bytes of disk memory added to the end and removed from the beginning of the change journal each time memory is allocated or deallocated
The last thing that we need to do is to close the handle that we opened:
[void][PoshChJournal]::CloseHandle($hVol)
With that, we are finished making a connection to the USN change journal. In tomorrow’s post, I'll dive into viewing the change journal entries.
We invite you to follow the Scripting Guys on Twitter and Facebook. If you have any questions, send email to the Scripting Guy at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. Until then, see ya!
Boe Prox, Windows PowerShell MVP and Honorary Scripting Guy