Storing app preferences / data for all users

I have a non-MAS, non-sandboxed mac app. The app needs to be able to read a small configuration string that is global to all users on the machine. The app is installed by an installer which will run with admin permissions.

In the old days, people may have modified a file inside the app bundle itself, but we know that’s not a good idea due to code-signing issues.

What’s the current best practices way do do this?

  • Adding a plist file to /Library/Application Support/MyApp
  • using the defaults command with globalDoman?
 defaults write -globalDomain MyAppSpecialKey foobar
  • something else?

It looks like SpecialFolder.SharedDocuments will get you to /User/Shared/ on macOS. You should be able to treat that like Application Support, make a subfolder for yourself and work within that.

Thanks Tim.

I’d really like to use the CFPreferences system if possible (because my app already does that).

If I poke around on my system, I notice that other apps seem to do this, for example BBEdit has a plist file in two places:

/Users/username/Library/Preferences/com.barebones.bbedit.plist 
and
/Library/Preferences/com.barebones.bbedit.plist 

It looks like you can access plists using the ‘defaults’ command by providing a specific path name (although there is a warning about this changing ‘in an upcoming major release’):

     filepath  Domains may also be specified as a path to an arbitrary plist file, with or without the '.plist' extension. For
               example:

                     defaults read ~/Library/Containers/com.apple.TextEdit/Data/Library/Preferences/com.apple.TextEdit.plist

               normally gives the same result as the two previous examples.  In the following example:

                     defaults write ~/Desktop/TestFile foo bar

               will write the key 'foo' with the value 'bar' into the plist file 'TestFile.plist' that is on the user's desk-
               top. If the file does not exist, it will be created. If it does exist, the key-value pair will be added, over-
               writing the value of 'foo' if it already existed.

               WARNING: The defaults command will be changed in an upcoming major release to only operate on preferences
               domains. General plist manipulation utilities will be folded into a different command-line program.

So I think the solution for now is to do something like this:

  dim sh as new shell
  dim cmd as string = "/usr/bin/defaults read /Library/Preferences/com.mycompany.myapp " + key
  dim value = sh.execute(cmd)

you should use the DOMAIN parameter instead of a path name

and you might use PlistBuddy instead of defaults as things have changed with defaults in ways that nay affect this

[quote=435801:@Norman Palardy]you should use the DOMAIN parameter instead of a path name

and you might use PlistBuddy instead of defaults as things have changed with defaults in ways that nay affect this[/quote]

Say more? I can’t see a way to use the domain parameter to distinguish between the two locations (/Library/Preferences vs. /Users/[username]/Library/Preferences )

user domain means user specific so it goes in ~/Library/Preferences/etc etc etc
global domain means all users /Library/Preferences/etc etc etc

Why are you mucking around with the defaults command? There are several better ways around (macoslib, MBS, etc).

[quote=435837:@Norman Palardy]user domain means user specific so it goes in ~/Library/Preferences/etc etc etc
global domain means all users /Library/Preferences/etc etc etc[/quote]

I thought this was the case, too - but it doesn’t work, which is why I wrote this question.

If you don’t believe me, try it:

# this creates ~/Library/Preferences/com.foobar.plist and sets the key foo=bar
defaults write com.foobar foo bar

# you would expect this to do the same in /Library/Preferences/com.foobar.plist
# but it fails with an error
sudo defaults write -g com.foobar foo bar

It turns out the global domain is actualy Global Global - e.g. it’s a single plist that applies system-wide (e.g. it’s not a global domain that works for a single com.myapp domain)

One reason: an Installer app which needs to set preferences in two locations - the “all users” location requires administration permissions, which is easy to do using the command line with the AuthorizationMBS tool.

Another reason: I simply want to find out how it all works so it can be documented, so that people can use CFPreferences or ‘defaults’ as needed.

After more research, here’s what I’ve found.

On macOS there are actually several different locations, and the terminology is confusing/inconsistent.

When using the /usr/bin/defaults tool:


# to save data for the current user
defaults write com.foobar foo bar   # writes to ~/Library/Preferences/com.foobar.plist 

# to save data for all users (requires authentication)
sudo defaults write /Library/Preferences/com.foobar foo bar   # writes to /Library/Preferences/com.foobar.plist 

# to save data available for the current user only on the current host (e.g. the current physical machine)
defaults -currentHost write com.foobar foo bar   # writes to ~/Library/Preferences/ByHost/com.foobar.[GUID].plist where [GUID] is a guid of the current mac

# to save data in a truly global namespace (note: be careful, as you risk key collisions with other apps, you probably should NOT do this!!!
sudo defaults write -globalHost foo bar   # writes foo=bar to /Library/Preferences/GlobalPreferences.plist  (which is a hidden file)

If you are reading/writing these keys using CFPreferences, the following combinations work:

    // this is Swift code but could be adopted to whatever declare/plugin you are using in Xojo
    // bundleID should be in the com.mycompany.myapp  format
       # reads data for a single user from ~/Library/Preferences/bundleID.plist
        let val = CFPreferencesCopyAppValue(key as CFString, bundleID as CFString)
        
       # reads data for all users from /Library/Preferences/bundleID.plist
        let val = CFPreferencesCopyValue(key as CFString, bundleID as CFString, kCFPreferencesAnyUser, kCFPreferencesAnyHost)

       # reads data for a single user and single machine from ~/Library/Preferences/ByHost/bundleID.[GUID].plist
        let val = CFPreferencesCopyValue(key as CFString, bundleID as CFString, kCFPreferencesCurrentUser, kCFPreferencesCurrentHost)

Additional notes:

  • Do not try to set keys by writing the plist files as text files - this sometimes works, but often fails, becuase the plist files are really just shadow copies of the OS’s internal database system.
  • to set keys in /Library/Preferences you need to authenticate, but to read these keys an app doesn’t need any special permissions.

Since macOS 10.9; Apple have advised against manually editing preference files (even if the application doesn’t use NSUserDefaults/CFPrefs).

Even deleting a preferences file to “reset” an apps preferences now causes problems. I’ve personally experienced the prefs getting frozen, can’t be changed via API or manual editing. Killing the daemon doesn’t help, only solution is to restart the machine.