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
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.
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)
[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 )
[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.
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.