- 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'
* the table has been virtualized
Here's the code for GetAppDirectory. It handles runtime and development time conditions differently.
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
lcPath = addbs(justpath(_vfp.ServerName))
endif version(2) = 2