Tuesday, May 14, 2019

Extending the SprintDLL File History example

A while back I showed in a Super User answer how to use my utility SprintDLL to configure File History from script. I just noticed a comment on this blog from someone who would like to do something similar but with different File History settings. (The comment was posted several months ago - sorry for the long wait!) I don't recall off the top of my head how I figured out all the parts of that script, and it could be neat to show how to put this together, so I'm writing this live as I investigate. We'll see how this goes.

First we'll need a test environment that can be easily reverted. I'm using a Hyper-V virtual machine running Windows 10 Home. While I wait for it to do all the Windows updates, I create the FileHistory$ share that the current script assumes, download SprintDLL v1.2, and copy the existing script from my old answer into a file. I also copy the same script into a text editor on my real machine in case we need to revert the VM.

After a little Windows Update troubleshooting and a lot of waiting, the VM is all updated. I take a checkpoint. To make sure we have a solid starting point, I try the existing script:

SprintDLL run filehistory.sprint

Sure enough, it enables File History, setting it to keep backups until space is needed and make them at the default rate of once per hour. Our goal now is to save copies every three hours and keep them for three months.

Previously I used SetLocalPolicy to set the retention policy, so let's look there again now. It's not very specific, so let's click through to the FH_LOCAL_POLICY_TYPE enum's documentation. It has one value to set the frequency, one for the "retention type", and one for the retention age. Clicking through to FH_RETENTION_TYPES, we find one value that never deletes old backups (the default, to keep "forever"), one to keep until space is needed (what my previous script used), and one that's "age based." The FH_LOCAL_POLICY_TYPE documentation also shows what the "numeric parameter" (second argument to SetLocalPolicy) means for each policy type: time in seconds for frequency, time in days for retention. So it looks like we need to make these calls:

fh->SetLocalPolicy(FH_RETENTION_TYPE, FH_RETENTION_AGE_BASED)
fh->SetLocalPolicy(FH_FREQUENCY, 10800)
fh->SetLocalPolicy(FH_RETENTION_AGE, 90)

Here's the most similar call from my old script:
// fh->SetLocalPolicy(FH_RETENTION_TYPE, FH_RETENTION_UNLIMITED)
newslot native setLocalPolicy
copyslot setLocalPolicy = vtbl field 9
call funcat setLocalPolicy /call thiscall /return uint (slotdata fhPtr, int 1, int 1)

Only the last one needs to be copied/changed - the previous two just get the function address and the first is a comment. FH_RETENTION_AGE_BASED is right after FH_RETENTION_UNLIMITED in the enum documentation, so I'm going to guess we should change that last 1 to a 2:
// fh->SetLocalPolicy(FH_RETENTION_TYPE, FH_RETENTION_AGE_BASED)
newslot native setLocalPolicy
copyslot setLocalPolicy = vtbl field 9
call funcat setLocalPolicy /call thiscall /return uint (slotdata fhPtr, int 1, int 2)

FH_FREQUENCY is the very first entry in its enum documentation, so it's probably value 0. (If that turns out to be wrong, we could probably dig up some H file from the SDK to look it up.) We already have the function address, so after the existing call we just need another call and a helpful comment:
// fh->SetLocalPolicy(FH_FREQUENCY, 10800)
call funcat setLocalPolicy /call thiscall /return uint (slotdata fhPtr, int 0, int 10800)

FH_RETENTION_AGE is right after FH_RETENTION_TYPE, so it's probably value 2.
// fh->SetLocalPolicy(FH_RETENTION_AGE, 90)
call funcat setLocalPolicy /call thiscall /return uint (slotdata fhPtr, int 2, int 90)

We're ready to test! I copy the script into the VM and run it:

Function at 140706149603792 returned 0
Function at 140706149608592 returned 0
Function at 140706149607024 returned 0
Function at 140706149607024 returned 0
Function at 140706149607024 returned 0
Function at 140706149607520 returned 0
Function at 140706149604096 returned 0
FhServiceOpenPipe returned 0
FhServiceReloadConfiguration returned 0
FhServiceClosePipe returned 0

Promising! Let's check out the File History control panel:


Hmm, File History was enabled and we got our frequency set, but not the retention time. Out of curiosity I check an XML configuration file in %localappdata%\Microsoft\Windows\FileHistory\Configuration (mentioned in the Super User question I answered), and it does seem to have received the setting:


I wonder if my guess of 30 days per month doesn't match Windows's idea, causing it to fail to match any of the entries in the "keep saved versions" dropdown and thereby show the default in the UI. I try setting it to "3 months" using the UI and checking the configuration file again. Interestingly, the <MinimumRetentionAge> value has now changed to 3. Maybe the documentation is wrong/outdated? I revert the VM and make a small change to the script:
// fh->SetLocalPolicy(FH_RETENTION_AGE, 3)
call funcat setLocalPolicy /call thiscall /return uint (slotdata fhPtr, int 2, int 3)

The output in the console after running the script is the same. Let's see what the control panel looks like now:


There we go! Apparently the retention age parameter is specified in months, not days.

The full, corrected script is available in a Gist.

No comments:

Post a Comment