9

I have read through countless posts on this topic, but can't wrap my brain around the whole thing. I have an old VB6 app that used to install in a folder on the root directory. Within that folder were subfolders:
\data subfolder for access databases
\quotes subfolder where user generated word documents would be stored and \backup where my program periodically backed up a copy of the mdb's

It worked great and life was simple. Now, when we try to install on Windows 7 and Windows 8, the program gets installed into program files (x86) folder. The programs run fine, but all database updates and word files are stored in a virtual store in the users folder which is not what we want.

I know why this is happening and I know I need to modify my program to accomodate Windows 7/8. Can someone please give me a clear explanation/recommendation on how to the following:

I know appdata folder under "users" is the recommended place to store the databases/word files/backups. However, on my system that is hidden and I would like it to be easily accessible to the user (not hidden), so I was thinking MyDocuments folder?

I've read this thread: Where should I store application specific settings? which gives me some sense on how to find the files/locations in code, although it's not clear how to find MyDocuments. Can anyone help?

Here is where I become even more confused. If I wanted to use something like MyDocuments\CompanyName or even the Appdata folder mentioned in the link above, how do I tell the package/deployment wizard to install the mdb files and the folders mentioned above into this specific folder? Is there a macro for the Appdata or MyDocuments folders in the PDW that I can use? I'm on an XP machine and so my path would be different than for a Windows 7/8 machine.

Any help/insight would be greatly appreciated.

Community
  • 1
  • 1
John Marzion
  • 113
  • 2
  • 6
  • 1
    Dumping apps into funny places like a directory under the system drive root and letting all users be Administrators or Power Users along with a ton of appcompat shims allowed a lot of people to pretend that WinXP was a Win9x machine. The world is far more complicated and because such techniques left gaping holes in security the PDW is only useful for simple applications now. – Bob77 Mar 20 '13 at 21:29
  • so are you saying that using PDW there is no simple way to install the mdb files/folders into either the users MyDocument folder or AppData folder? Would Inno make it easier? – John Marzion Mar 20 '13 at 22:11
  • Personally I'd avoid Inno like the plague. It can be powerful but you can also easily create flaky setups that "break" other applications on the same PC. No installer can install into a user's folder since it has no way to know what user or users will log on and expect to use your application. – Bob77 Mar 20 '13 at 22:13
  • Here's an idea: If you only have per-user data (no shared databases or global settings) use the PDW to build your setup putting everything under Program Files. The have a "first run" detection and action in your application to create the per-user folder and copy data into there from the master/template copy in the Program Files folder. - Oops, I see Cody has already suggested this below. – Bob77 Mar 20 '13 at 22:16
  • You can also handle shared (per-machine) data in a similar manner. Add another "first machine run" detection and action to your application. If the per-machine folder and file(s) are not present then re-run yourself elevated passing a command line argument. On this run you see the argument, verify you are running as admin, then create the ProgramData subfolder, set the required security on it, and copy per-machine files there. – Bob77 Mar 20 '13 at 22:24
  • Good question with good answers. I've upvoted and favorited. But looking at the answers, you should consider: the world is a much different place than your VB6 program thought it was. Maybe it's time to actually upgrade to VB.NET and give your application another ten years of useful life? – John Saunders Mar 22 '13 at 05:17
  • I would love to upgrade to vb.net, but it's not my expertise and I have a full time job and 3 kids that keep me busy. – John Marzion Mar 22 '13 at 22:05

2 Answers2

7

I know why this is happening and I know I need to modify my program to accomodate Windows 7/8.

Good, then I'll try not to beat a dead horse (although this is one I regularly like to give a good flogging because so many people misunderstand it). I'll only point out that the fact your program ever worked correctly, even on the older versions of Windows, was a fortunate consequence of a latent bug. The "Program Files" folder was never meant to be writeable. You're not just working around some silly "issue" of newer versions of Windows. This is something that should have been done right from the beginning.

I know appdata folder under "users" is the recommended place to store the databases/word files/backups. However, on my system that is hidden and I would like it to be easily accessible to the user (not hidden), so I was thinking MyDocuments folder?

I know this is complicated, so let me see if I can try to make it as simple as possible…

The "App Data" folders are for data intended for internal use by your application and not intended to be seen or manipulated directly by the user.

The "My Documents" folder is for everything that the user should be able to see and directly manipulate; in other words, user data instead of application data.

And then there also per-user and global variants of each of these two folder types. In the case of "App Data", the per-user variant is further subdivided into local (which stays on the local machine only) and roaming (which follows the user account to any machine on the network to which she logs on).

So if you want the files to be "easily accessible to the user", then you should definitely use the "My Documents" folder. That's what it's for—user documents.

The reason why all of these complicated rules exist is because misbehaved applications by such popular vendors as Microsoft and Adobe had (have?) a tendency to dump junk into the user's "My Documents" folder, stuff that the user was never intended to interact with directly. Users would then open their documents folder and see a bunch of stuff they never put there. "What are all these things? These are not mine. Where are my documents?" My mother just asked me this last week when I updated her PC.

it's not clear how to find MyDocuments. Can anyone help?

From VB 6? The easy way is the one suggested by Bob and Mark in the question you looked at. This involves creating a Shell object and querying it for the location of the folder(s) you're interested in.

The part that's missing is the constant that corresponds to the "My Documents" folder. You'll find a complete list of constants here. The one you want is named ssfPERSONAL (because in the old days when this API was created, Windows wasn't as friendly and the "My Documents" name hadn't been invented yet), which has the value &H05. So the code looks like:

Const ssfPERSONAL = &H05

Dim strMyDocsPath As String
strMyDocsPath = CreateObject("Shell.Application").NameSpace(ssfPERSONAL).Self.Path

If I wanted to use something like MyDocuments\CompanyName or even the Appdata folder mentioned in the link above, how do I tell the package/deployment wizard to install the mdb files and the folders mentioned above into this specific folder? Is there a macro for the Appdata or MyDocuments folders in the PDW that I can use?

There's not much that I can add to this that Bob didn't already cover in his answer, other than to recommend that while you're modernizing this VB application, you also consider ditching the antiquated PDW in favor of a better installer.

Personally, I'm a huge fan of Inno Setup, which is a free installer utility for Windows applications that offers more features than you can shake a stick at. It works very well with VB 6 applications. I've used it many times for that myself. I think it's fairly easy to use, at least for the basics. It supports all sorts of advanced customizations, and some of that can be tricky if you don't learn well by reading documentation, but it is all very doable. I've even stumbled my way through writing Delphi code to customize Inno Setup projects!

In Inno Setup, you access the paths to system folders using one of the pre-defined constants. For example, {userdocs} and {commondocs}. These work much like (if not exactly like, it's been a long time) macros in PDW.

Of course, this still doesn't fix the problem Bob rightly points out: the concept of a user's "My Documents" folder doesn't really make much sense to an installer. Certainly you can access the global (public) "My Documents" folder because it is shared among all users, but this is probably not the one you want. The installer should be able to be run by any user with sufficient privileges, and in fact this will often be an administrator account that differs from the regular user account used to run your application. You can't assume a particular user account during setup, and your design must account for this.

It sounds like to me that a "template" approach would be a good one for you. The complete set of files used to set up a new user account could be stored in the "Program Files" folder. This is actually a good place for it because it is well protected there from erroneous modifications, and since you're only going to be reading from it, you can be guaranteed to have the necessary permissions. When the application is launched for the first time under a new user account, it could detect this fact and offer to set up the new user account for use with the application. At this point, you know that you're running under the correct user account, so you can use the above code to query the location of that user's "My Documents" folder. With the desired folder structure all set up in a subdirectory of your application's folder in "Program Files", setting up a new user account could be as simple as copying over that folder. If you need to gather information from the user or offer customization options, you could write a little "First Run Wizard".

Community
  • 1
  • 1
Cody Gray - on strike
  • 239,200
  • 50
  • 490
  • 574
  • 2
    wow. thank you for a very comprehensive response. I'm totally overwhelmed, but I'll spend some time digesting it. I really appreciate the time and effort you put into explaining the approach. – John Marzion Mar 20 '13 at 22:36
  • Can @Bob77 or anyone else give insight as to the best way to determine if it's the first run of an application? I can only think of adding a bogus text file during install and then checking if its there. If it is, copy the files to MyDocuments and delete the text file so next time it is skipped. Do I need to worry about persmissions or elevate to Administrator at all? How do I do this in code? thx in advance. – John Marzion Mar 23 '13 at 16:23
  • 1
    The most obvious choices are to either base detection on the presence of a per-user registry key (added during first run when not detected) or better yet something like a per-user INI file stored under the LocalAppData special folder. – Bob77 Apr 03 '13 at 18:15
  • @John Usually, the presence of whatever configuration you do on first run would indicate that it is not the first run. But if you do need a sentinel file, I'd go with Bob's second suggestion and use a per-user file in either the local or roaming AppData folder. This is, incidentally, precisely what Google Chrome does. It places an empty file with the name "First Run" in the local AppData folder. If the file is there, that indicates that Chrome has already been run once. – Cody Gray - on strike Apr 03 '13 at 22:11
4

I know appdata folder under "users" is the recommended place to store the databases/word files/backups. However, on my system that is hidden and I would like it to be easily accessible to the user (not hidden), so I was thinking MyDocuments folder?

Both of these are locations for per-user data. As such your installer cannot put anything here because under normal circumstances it won't even be running under the target runtime user and wouldn't know which one to target for this data.

Instead you'll want to decide which files are per-computer and which are per-user. Any per-user files should be created or copied by your application on "first run" which it should normally detect by checking for the presence of a per-user INI file in a per-user application folder (which it also needs to create) under LocalAppData.

Per-machine files go into an installer created folder under ProgramData (CommonAppData). In most cases your installer needs to not only create this "company/app" subfolder but also to set security on the folder to allow users to have the required access rights to items placed there. Then the installer can place any deployed per-machine data like settings files, MDBs, etc. into this folder.

An exception might be per-machine "template" files that your first-run action copies into user profiles, or other data that is read-only for the life of the application. Those can still be dumped into the Program Files subfolder next to the EXE (or in a subfolder, etc.).

The PDW is too old and primitive to do all of the installation activities required. That doesn't mean you can't hack up the setup1.vbp (provided by installing VB6 and the PDW) to make a custom setup1. But usually this isn't worth the trouble since you can't extend the PDW's "wizard" to have new screens and accept new information.

As Windows Installer came on the scene in 1998 Microsoft released a free update tool called Visual Studio 6.0 Installer 1.0 (and a year later or so 1.1) to supplement/replace the PDW.

Microsoft does not host the two download files comprising VSI 1.1 anymore though you might still find copies hosted by 3rd parties (virus scan anything like that though).

You can also use an Installer project type in a more recent version of Visual Studio (though I believe they pulled it starting in VS 2012). There are also things like the WiX tool suite and 3rd party Installer-based tools.

But even with VSI 1.1 you'll find that many things you need to have your package do cannot be specified within VSI 1.1, and this means you have to make some post-build tweaks to the MSI database. You can do this using Orca from the Installer SDK or via WSH scripts that use Windows Installer's automation interface to make these post-build edits.

I've read this thread: Where should I store application specific settings? which gives me some sense on how to find the files/locations in code, although it's not clear how to find MyDocuments. Can anyone help?

You use Shell operations either through its automation interface or API calls. This location is requested via the ssfPERSONAL, CSIDL_PERSONAL, or FOLDERID_Documents constants depending on which call you decide to make to obtain it.

Bob77
  • 13,167
  • 1
  • 29
  • 37
  • thank you for a great response. Not sure how to pull off what you are suggesting, but it points me in a direction. From the "I'm not doing it, but I just have to ask" department, what would happen in Windows 7 and 8 if I simply installed the programs in c:\myprogram with subfolders for mdbs, word documents,etc like I did way back in the day? Oh how i long for simpler times :) – John Marzion Mar 20 '13 at 22:37
  • 1
    As soon as any user opens these with R/W access Windows will make a private copy in the VirtualStore. This works in some cases as a "limp along" alternative but it has consequences that can lead to all sorts of confusion. It isn't intended as a development strategy but a way to limp along with an old program you have no source code to. – Bob77 Mar 21 '13 at 00:16