views:

540

answers:

2

My application is currently storing settings in an INI file under the current user's profile (C:\Documents and Settings*\<CurrentUser>*\Application Data\MyApplication\MySettings.ini under WinXP). But I've realised some of these settings are unique to the machine not the user and thus want (actually need) to save them in a single location for all users.

Is there a folder location on Windows XP (and up) where I can store user independent settings?

NOTE: I don't want to store them in the same folder as my application nor do I want to store them in the registry.

I notice there is an "All Users" folder under "C:\Documents and Settings\"? Should I be storing under there?

Bonus Points: I'm more likely to award the answer to whoever can also tell me how to return this path from Windows in Delphi 7.

+11  A: 

For XP, Windows provides SHGetFolderPath() to get a known location. The CSIDL that you're looking for is CSIDL_COMMON_APPDATA, described as:

The file system directory that contains application data for all users. A typical path is "C:\Documents and Settings\All Users\Application Data". This folder is used for application data that is not user specific. For example, an application can store a spell-check dictionary, a database of clip art, or a log file in the CSIDL_COMMON_APPDATA folder. This information will not roam and is available to anyone using the computer.

For Vista and later, this has been replaced with SHGetKnownFolderPath() although SHGetFolderPath() is still available as a wrapper function for that. If you use the real Vista call, you should use FOLDERID_ProgramData instead of CSIDL_COMMON_APPDATA.

This link here seems to show a way of doing it.

It seems to boil down to this (treat this with circumspection, I don't know Delphi that well):

function ShGetKnownFolderPath (
    const rfid:   TGUID;
    dwFlags:      DWord;
    hToken:       THandle;
    out ppszPath: PWideChar): HResult;
var
    Shell: HModule;
    Fn: TShGetKnownFolderPath;
begin
    Shell := LoadLibrary ('shell32.dll');
    Win32Check(Shell <> 0);
    try
        @Fn := GetProcAddress (Shell, 'SHGetKnownFolderPath');
        Win32Check (Assigned (Fn));
        Result := Fn (rfid, dwFlags, hToken, ppszPath);
    finally
        FreeLibrary (Shell);
    end;
end;

 

function GetKnownFolderPath (
    const rfid: TGUID;
    dwFlags:    DWord;
    hToken:     THandle): WideString;
var
    buffer: PWideChar;
    ret: HResult;
begin
    ret :=ShGetKnownFolderPath (rfid, dwFlags, hToken, buffer);
    OleCheck (ret);
    try
        Result := buffer;
    finally
        CoTaskMemFree (buffer);
    end;
end;

This page provides a list of all the CSIDL_* and FOLDERID_* values. Keep in mind you should be using these functions for your user-specific data as well, not hard-coded values like "C:\Documents and Settings\<CurrentUser>\Application Data\". It may be that different language versions of Windows use different directory names or it's possible that users can freely move their data areas around.

paxdiablo
That is the correct API to use, and the correct folderid to ask for in this situation. However, there is still one thing than needs to be clarified :- files created in that location are RW to administrators and owners, but R for other users.Is it important that every _user_ (i.e. non UAC elevated admin or user) can write to the ini file?If so it is necessary, upon creation, to adjust the access control list of the file or folder to included an explicit all users RW entry.
Chris Becke
Ouch! Thanks for the heads up Chris...so how do I do that? Or is there a better location which doesn't have this restriction?
Ben Daniel
No, this is the best location. If you want the users to edit settings than make an override system. Default and common settings are in the "All User" folder and user specific in appropriate use profile. This way you have global read-only settings that users can override with their own. This way they do not change each others settings.
Runner
+4  A: 

I'd recommend using the open source JEDI Code Library for this sort of thing.

In JclShell.pas you'll find GetSpecialFolderLocation()

YourDataFolder := GetSpecialFolderLocation(CSIDL_COMMON_APPDATA);

It's free, well tested, works with all Windows versions and using it will insulate you from future changes to the Windows API.

LachlanG
I've just got trouble with this. In my case, I wanted to get "Common Documents" folder and I used JclSysInfo.GetCommonDocumentsFolder(which uses GetSpecialFolderLocation, and that invokes SHGetSpecialFolderLocation).In several WinXP environment, on which 'Common Documents' are hidden by settings, SHGetSpecialFolderLocation always fails, but SHGetFolderPath works. So I have to quit using JclSysInfo...
benok
"'Shared Documents' is hidden from 'My Computer'" is correct. sorry.
benok