Tuesday, May 29, 2007

Has a File Been Virtualized?

If you've been working with or reading about Windows Vista, you know there's a new feature called virtualization that causes writes to files in "protected" folders (such as C:\Program Files\Some Folder) to be redirected to a user-specific folder (C:\Users\user name\AppData\Local\VirtualStore\Program Files\Some Folder). While this is sort of a good thing--it means pre-Vista applications won't give an error when they update data stored in their own directory--it can be a problem for a variety of reasons:
  • Since the files are redirected to a user-specific location, other users on the same system won't see each others' changes.
  • Backing up the files in the application folder won't back up the user-specific files if the backup program is Vista-aware.
  • If the user turns off User Access Control (UAC) or virtualization, the application not only breaks because it still can't write to the protected folders, it also doesn't see the virtualized files any more so data entered by the user appears to be gone.

So, I've been reworking those parts of Stonefield Query that expected to write to the current or application folder. It was actually fairly easy to do because most file paths were referenced with an application object property (e.g. USE (oApp.cAppDataDir) + "SomeTable") so simply changing the value of the property redirected the files to a location the user has write access to. I then ran the application and looked for any files that appeared in my virtualization folder, and turned off virtualization and tested to see if anything broke. I found a few stragglers where I hadn't put a path on the filename (typically writing to some temporary files) and fixed them.

However, one thing puzzled me: a table that wasn't being written to was virtualized. In tracking it down, I found that the virtualized table was created immediately after the USE command. To make matters even odder, when I turn off virtualization, the table wasn't virtualized and the application didn't give an error. Also, while the DBF and FPT files were virtualized, the CDX was not.

I guess this makes sense. Since I was opening the table in read-write mode (ie. no NOUPDATE clause), I suspect VFP was doing some minor update to the table behind the scenes (e.g. writing to the header of DBF and FPT files for locking purposes), resulting in it being virtualized. You can see this effect if you open one of the classes in the FFC subdirectory of the VFP home directory; simply opening it immediately causes its virtualization. With virtualization turned off, the table was automatically read-only since it's in a protected folder, so there's no need to virtualize it. Adding a NOUPDATE clause to the table (since it's a meta data table, it's used for reference purposes only) took care of that.

However, the SDK for Stonefield Query includes an editor program for that table, so that program does open the table for read-write purposes and will likely write to the table (otherwise it wouldn't be an editor). The problem is that the user may be unaware that the table is virtualized and will likely distribute the wrong instance of the table (the one in the application folder rather than the virtualized one) after making changes to it. So, I figured I should let them know which table they're writing to so they know which one to distribute.

However, how can you tell if a file has been virtualized? If you open a file in C:\Program Files\Some Folder and that file has been virtualized, the operating system actually opens the virtualized copy instead of the one you specified, and the application can't tell the difference.

I settled on the obvious solution: check whether a virtualized copy of the file in question exists. If so, we know that's the one Vista will open. Here's some code to do that. First, we get the directory the application is running in (not necessarily the current directory) by calling GetAppDirectory (a routine I wrote years ago; I'll show you the code later). Then we call SpecialFolders, a routine I discussed in my Finding the Paths for Special Folders blog entry, to get the current user's local app data path (C:\Users\user name\AppData\Local\). Notice that this code uses SUBSTR(lcDirectory, 3) to strip the drive letter, colon, and backslash from the application folder name. We then check if the desired file exists in the virtualization folder.

lcDirectory   = GetAppDirectory()
lcVirtualPath = SpecialFolders('LocalAppData') + ;
'VirtualStore\' + substr(lcDirectory, 3)
lcFile = lcVirtualPath + 'SomeTable.DBF'
if file(lcFile)
* the table has been virtualized
endif

Here's the code for GetAppDirectory. It handles runtime and development time conditions differently.

local lcPath
if version(2) = 2
lcPath = justpath(sys(16, 0))
if atc('PROCEDURE', lcPath) > 0
lcPath = substr(lcPath, rat(':', lcPath) - 1)
endif atc('PROCEDURE', lcPath) > 0
else
lcPath = addbs(justpath(_vfp.ServerName))
endif version(2) = 2
return lcPath

1 comment:

Anonymous said...

Doug,
I found the exact same problem and tried the approach you mention above. The problem I am having now though is that it appears Vista will not "ALWAYS" use the virtualized file just because it exists. Another problem I ran into is that I have several user accounts on my dev machine with various security configurations. I try to test under the most restrictive securty settings. The virtualization problem is user profile specific. So one user profile might have a completely different copy of, say "sdtmeta.dbf" than another. I also, oddly enough found that writes to a dbf with a cdx, does not always virtualize the cdx, even though any write to the dbf should update the index, one would think. It doesn't appear that turning off UAC is an option either, because then you can't write to the single copy of the files, and NONE of them get virtualized. Users on Vista is not really a big deal as long as you install their files where Vista won't screw them up, but ...
Development on Vista is going to be a real problem!!!