In the old days, we used to store our programs' data and configuration in the same location as our programs/binaries. It's easier to manage, indeed. While you still be allowed to do that, but in UAC enabled Windows now you can not alter those files unless the user has administrative privilege AND explicitly permit the program to do that action. In some programs with some kind of "critical" mission (e.g. disk defragmenter, anti virus, installer, or uninstaller) this should be no problem. The user will understand that the program has important mission. But what if our program is doing ordinary actions, like text editing, games, or news reader? They will be curious if such ordinary tasks requires elevation, while in fact we only need the elevation to alter our configuration file.
The answer is simple, just don't put your program's data and configuration file under those "critical" folders. However you can not just put them into some random locations, or the user maybe confused and delete them accidentally. There are some locations predefined by Windows which you can use for this purpose. Utilization of these predefined locations also means you can achieve some kind of uniformity, thus easier to handle than putting in random location.
This article will explain my own strategy in deploying programs under UAC enabled Windows by utilizing the predefined folder locations.
Some Predefined Locations
Windows has predefined some locations. By predefined I mean some locations that you can get by asking Windows with supplying a constant value. So given the same environment (i.e. same interactive user that logged on and no critical windows configuration change since the last query), the query will return the same value all the time.
Some of these predefined folder locations that we will use are:
This folder is unique for each user. So each user will get different folder when asking Windows for this folder. But for the same user the returned folder stays the same. Note that the content of this folder is subject for roaming profile, it will be copied to wherever machine the user is logging on if they (and if their networking system support) use/activate their roaming profile.
This folder is very useful if you want to store program's data or configuration that only applicable for a specific user. For example, this folder (or LOCAL_APPDATA folder if you don't want roaming capability) is the perfect choice to store user's preference, such as prefered GUI arrangements, most recent files, or things like that.
Same like APPDATA, this folder is unique for each user. But its content will not copied over to another machine even if the user using roaming profile. It's a real local folder. Same like APPDATA, this folder is perfect choice to store user's preference.
This folder is unique for a machine. So every user will get the same folder when asking windows for this folder. This folder is very useful if you want to place program's data or configuration that is applicable for all users. Originally the content of this folder is writable only by admin users. Standard users can only read from this location. But you can create your own folder in this location and adjust the required read/write permissions on your folder.
This folder is similar with COMMON_APPDATA. The difference is that by default its content is available to read and write by all users. So storing files here will be exposed uniformly to all users without need to adjusting write access for non-admin users.
This folder is usually unique for each user. So each user will get different folder when asking Windows. But note that I have said "usually unique" instead of "unique". That's because users can easily modify their My Documents folders which may caused several users have their "My Documents" pointing to the same folder.
I usually use this folder as the starting folder when displaying "Save As" dialog when the user wants to save something. I never use this folder to save program's data or configuration. Not only because it will break its "My" part (means only users should store documents there, not us), but also due to its rather unreliability (remember that users can easily change it, which will make us to "start over" again with our program's data and configuration).
- Always store binaries of your software under Program Files folder.
This way in UAC enabled Windows you will get lesser possibility of virus or any other malware programs hijack your programs. Because they need administrative privilege to alter anything stored under Program Files. In order to get that they need to ask explicitly for permission from the user. Now they can't infect your programs stealthily anymore.
- Store critical program's data or configuration under COMMON_APPDATA.
What I mean by critical is any data that should be altered only by administrative users.
- Store program's data or configuration that is common or shared throughout the machine AND must be WRITABLE by all users in COMMON_DOCUMENTS folder.
- Store users preference data or configuration under APPDATA or LOCAL_APPDATA, depends on whether you want the data for roaming or not.
- Offer to store any file that saved manually by users under MY DOCUMENTS folder.
By following this strategy, I never have problem with UAC blocking access to my program's data or configuration file. Only in rare occasion when the user specifically ask to alter files under COMMON_APPDATA my program will elevate itself (thus asking for permission). I never altered files under "Program Files" so I don't have to ask for elevation each time my program runs.
Now how do we get these folders programmatically? To get them, you need to ask Windows using api SHGetSpecialFolderLocation and SHGetPathFromIDList. These apis are contained in shell32.dll library. Most Delphi versions (I don't know which exactly, but at least from Delphi 7) already imported them. Usually you only need to add ShlObj unit in your uses list. But since windows api and Delphi have different implementation of string values, we need some adjustment before getting the folder information in proper format.
Here is Delphi code to get those predefined folder:
// function to ask windows for a special folder and convert the "windows string" into // Delphi string function GetSpecialFolder(const ASpecialFolderID: Integer): string; var vSFolder : pItemIDList; vSpecialPath : array[0..MAX_PATH] of Char; begin SHGetSpecialFolderLocation(0, ASpecialFolderID, vSFolder); SHGetPathFromIDList(vSFolder, vSpecialPath); Result := StrPas(vSpecialPath); end; // now the constants that we must pass to windows to specify which special folder we want const // constant to get APPDATA folder CSIDL_APPDATA = $001A; // constant to get LOCAL_APPDATA folder CSIDL_LOCAL_APPDATA = $001C; // constant to get COMMON_APPDATA folder CSIDL_COMMON_APPDATA = $0023; // constant to get COMMON_DOCUMENTS folder CSIDL_COMMON_DOCUMENTS = $002E; // constant to get MY DOCUMENTS folder CSIDL_PERSONAL = $0005; // example how to use the GetSpecialFolder function and the constants function GetLocalAppDataFolder: string; begin Result := GetSpecialFolder(CSIDL_LOCAL_APPDATA); end;
Edited by LuthfiHakim, 25 November 2012 - 08:01 AM.