Retrieving a Registry Key LastWriteTime Using PowerShell

While navigating through the registry, you may have noticed that there is something missing from there that you normally see in other places such as the file system. That little thing is a timestamp that might tell you when a particular key has been modified. In this case, you can’t see it because it is not openly visible to be seen. In fact, the timestamp is only available on the registry key itself, not on the values within the key. If a value gets updated, removed or added under the key, the timestamp on the key gets updated.

In order to actually view this information you would have to export the registry key in question and make sure to save it as a txt file so that when you open it up, it would list the lastwritetime of the key for you to see.

image

Now wouldn’t be great if we could somehow use some sort of command line approach to retrieving the same timestamp? Well, with a little bit of work, we can accomplish this using PowerShell with some help from our friends with p/invoke and some reflection!

First let’s start off with getting the proper signature which happens to be RegQueryInfoKey from pinvoke.net

image

More information about this actual function can be found at the following link:

http://msdn.microsoft.com/en-us/library/windows/desktop/ms724902%28v=vs.85%29.aspx

I will be making a small change to this with the last parameter: lpftLastWriteTime. Instead of an IntPtr I will be using long as the Ref so it will be easier to convert the value to a DateTime object.

With that, I am going to build out the signature using reflection. Unlike some of my other scripts that use this approach, I only have to build the RegQueryInfoKey signature and have no need to worry about any Enums or Structs.

 
#region Create Win32 API Object
Try {
    [void][advapi32]
} Catch {
    #region Module Builder
    $Domain = [AppDomain]::CurrentDomain
    $DynAssembly = New-Object System.Reflection.AssemblyName('RegAssembly')
    $AssemblyBuilder = $Domain.DefineDynamicAssembly($DynAssembly, [System.Reflection.Emit.AssemblyBuilderAccess]::Run) # Only run in memory
    $ModuleBuilder = $AssemblyBuilder.DefineDynamicModule('RegistryTimeStampModule', $False)
    #endregion Module Builder
 
    #region DllImport
    $TypeBuilder = $ModuleBuilder.DefineType('advapi32', 'Public, Class')
 
    #region RegQueryInfoKey Method
    $PInvokeMethod = $TypeBuilder.DefineMethod(
        'RegQueryInfoKey', #Method Name
        [Reflection.MethodAttributes] 'PrivateScope, Public, Static, HideBySig, PinvokeImpl', #Method Attributes
        [IntPtr], #Method Return Type
        [Type[]] @(
            [Microsoft.Win32.SafeHandles.SafeRegistryHandle], #Registry Handle
            [System.Text.StringBuilder], #Class Name
            [UInt32 ].MakeByRefType(),  #Class Length
            [UInt32], #Reserved
            [UInt32 ].MakeByRefType(), #Subkey Count
            [UInt32 ].MakeByRefType(), #Max Subkey Name Length
            [UInt32 ].MakeByRefType(), #Max Class Length
            [UInt32 ].MakeByRefType(), #Value Count
            [UInt32 ].MakeByRefType(), #Max Value Name Length
            [UInt32 ].MakeByRefType(), #Max Value Name Length
            [UInt32 ].MakeByRefType(), #Security Descriptor Size           
            [long].MakeByRefType() #LastWriteTime
        ) #Method Parameters
    )
 
    $DllImportConstructor = [Runtime.InteropServices.DllImportAttribute].GetConstructor(@([String]))
    $FieldArray = [Reflection.FieldInfo[]] @(       
        [Runtime.InteropServices.DllImportAttribute].GetField('EntryPoint'),
        [Runtime.InteropServices.DllImportAttribute].GetField('SetLastError')
    )
 
    $FieldValueArray = [Object[]] @(
        'RegQueryInfoKey', #CASE SENSITIVE!!
        $True
    )
 
    $SetLastErrorCustomAttribute = New-Object Reflection.Emit.CustomAttributeBuilder(
        $DllImportConstructor,
        @('advapi32.dll'),
        $FieldArray,
        $FieldValueArray
    )
 
    $PInvokeMethod.SetCustomAttribute($SetLastErrorCustomAttribute)
    #endregion RegQueryInfoKey Method
 
    [void]$TypeBuilder.CreateType()
    #endregion DllImport
}
#endregion Create Win32 API object

If you are interested in a more detailed explanation of this process, just check out one of my other articles with the reflection tag to learn more.

I can verify that I at least have everything compiled correctly by looking at the method.

 
[advapi32]::RegQueryInfoKey

image

At least this looks good. We now need to grab a registry key and then figure out what the timestamp is.

 
$RegistryKey = Get-Item 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\IniFileMapping\Autorun.inf'
$RegistryKey

image

What better key to use than the one that I showed in my previous example. Now I will create some common variables that will be used with the method.

 
#region Constant Variables
$ClassLength = 255
[long]$TimeStamp = $null
#endregion Constant Variables

With my registry key, I need the registry handle and the name that will be supplied to the method.

 
$ClassName = New-Object System.Text.StringBuilder $RegistryKey.Name
$RegistryHandle = $RegistryKey.Handle

I had to use a StringBuilder because it is required by one of the method parameters. Now that we have enough information, we can proceed with gathering the timestamp.

 
[advapi32]::RegQueryInfoKey(
    $RegistryHandle,
    $ClassName,
    [ref]$ClassLength,
    $Null,
    [ref]$Null,
    [ref]$Null,
    [ref]$Null,
    [ref]$Null,
    [ref]$Null,
    [ref]$Null,
    [ref]$Null,
    [ref]$TimeStamp
)

SNAGHTML5719a54

Most of the parameters here can be $Null, which is why they are that way. We know that this was successful by the return of the IntPtr 0. Any other value would mean that something happened and that would need to be investigated.

We are not quite done yet! We have the timestamp, but it is in a non-usable format and should be converted into a DateTime object using [datetime]::FromFileTime().

 
#Convert to DateTime Object
$LastWriteTime = [datetime]::FromFileTime($TimeStamp)
 
#Return object
$Object = [pscustomobject]@{
    FullName = $RegistryKey.Name
    Name = $RegistryKey.Name -replace '.*\\(.*)','$1'
    LastWriteTime = $LastWriteTime
}
$Object.pstypenames.insert(0,'Microsoft.Registry.Timestamp')
$Object

image

If you compare this to what I showed earlier with the manual exporting of the registry key to a text file, you will see that these times are exactly the same. Well, this also includes the seconds, which the exported file does not.

Now wouldn’t it be nice to have a function that does all of this work for you? Of course you would! I wrote a function called Get-RegistryKeyLastWriteTime that will allow you to get the registry key timestamp from either a remote or local system.

Let’s give it a couple of runs to see how it works. The first example shows how you can pipe an existing registry key object into the function to get the timestamp.

 
$RegistryKey = Get-Item "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\IniFileMapping\Autorun.inf"
$RegistryKey | Get-RegistryKeyTimestamp | Format-List

image

The next example runs against a remote system and allows you to specify the hive and subkey that you wish to query.

 
Get-RegistryKeyTimestamp -Computername boe-pc -RegistryHive LocalMachine –SubKey  'SOFTWARE\Microsoft\Windows NT\CurrentVersion\IniFileMapping\Autorun.inf' |
Format-List

image

Works like a champ! Be sure to download this and give it a run and let me know what you think!

Download Get-RegistryKeyLastWriteTime

https://gallery.technet.microsoft.com/scriptcenter/Get-RegistryKeyLastWriteTim-63f4dd96

This entry was posted in powershell and tagged , , , , , . Bookmark the permalink.

1 Response to Retrieving a Registry Key LastWriteTime Using PowerShell

  1. AWebUser says:

    Thank you for this posting, and the related link to your fully functional PS1 script. Your analysis of the use of .net assemblies (including getting the function signature correct) is non-trvial and interesting.

Leave a comment