Disabling System Preference Panes – the hard/wrong way.

Background

When managing systems, there is often a need or desire to disable certain system preference panes to avoid users making configuration errors or enabling services that we as admins would rather they didn’t.

For most of my environments in education with shared machines the big two preference panes that I disable are iCloud and Profiles.

Its pretty trivial to do this with a configuration profile and using the following payload:

<key>DisabledPreferencePanes</key>
  <array>
     <string>com.apple.preferences.icloud</string>
     <string>com.apple.preferences.configurationprofiles</string>
  </array>

Usually installing that profile on your machines is enough. But sometimes I have some “special” machines that require even further lockdown.

In the old days of MCX and Workgroup Manager, I would simply apply another MCX setting to these machines at a group level or computer level, MCX on the client would then composite all these settings and apply all of the valid payloads to make one big honking MCX payload.

Problem

Unfortunately Configuration Profiles don’t do this kind of compositing. So if you are managing a `<key>` which in this case is `DisabledPreferencePanes` then the newer configuration profile takes over. So if I were to push a new configuration profile to my special machines requesting that the Bluetooth preference pane by disabled ie.

<key>DisabledPreferencePanes</key>
  <array>
    <string>com.apple.preferences.bluetooth</string>
  </array>

Then I loose the disabling of iCloud and Profiles. Of course I could include these in my new config profile so that it includes all three. But this gets hard to manage and remember pretty quick when at scale.

Because I’m lazy, and a lazy sys admin is a good sys admin right? I would rather just take a request from a user/admin/tech to lock down $X preference pane and apply that to the requested machines, rather than investigate to see what other panes are already locked down and include them in my new configuration profile for that machine – yes thats probably the better option.

So what is the answer? Well Config Profiles are just plists, so we can apply them from the command line. I say can because there is a caveat with 10.9 and 10.10 that you should already be aware of, the infamous cfprefsd. The defaults command is cfprefsd savvy so there are no issues with using that, its only when we have to re-enable a disabled preference pane that things get awkward. More on that later.

Solution?

In my case, instead of applying a base configuration profile to disable iCloud and Profiles. Which I find limiting for my environment. I do this via a script instead.

The script contents are :

defaults write /Library/Preferences/com.apple.systempreferences \ 
DisabledPreferencePanes -array-add '<string>com.apple.preferences.icloud</string>'

defaults write /Library/Preferences/com.apple.systempreferences \
DisabledPreferencePanes -array-add '<string>com.apple.preferences.configurationprofiles</string>'

Then, later if I get a request to also disable the Bluetooth preference pane I can simply send out the following one liner script and have it add Bluetooth to the disabled list.

defaults write /Library/Preferences/com.apple.systempreferences \
DisabledPreferencePanes -array-add '<string>com.apple.preference.bluetooth</string>'

I’m specifying the CFBundleIdentifier of the preference pane here ie. com.apple.preference.bluetooth. To make sure you use the correct CFBundleIdentifier you can easily extract this ID with the command

grep -A 1 CFBundleIdentifier /System/Library/PreferencePanes/YourPreferencePaneName.prefPane/Contents/Info.plist

Some preference panes might also live in /Library/PreferencePanes or ~/Library/PreferencePanes so be aware of that also.

Now the awkwardness

Removing the restriction however is a little bit more difficult. Unfortunately we can’t just do:

defaults write /Library/Preferences/com.apple.systempreferences \
DisabledPreferencePanes -array-delete '<string>com.apple.preference.bluetooth</string>'

Unfortunately items in arrays can only be accessed by their index number, starting at 0.

For example:

Using plist buddy we can get the array that contains the disabled system preference panes pretty easily:

# /usr/libexec/PlistBuddy -c "Print :DisabledPreferencePanes:" /Library/Preferences/com.apple.systempreferences.plist

Array {
     com.apple.preferences.configurationprofiles
     com.apple.preferences.icloud
     com.apple.preferences.bluetooth
}

So if we wanted to remove the com.apple.preferences.bluetooth string from the array we would have to do

# /usr/libexec/PlistBuddy -c "Delete :DisabledPreferencePanes:2" /Library/Preferences/com.apple.systempreferences.plist

Notice the number 2 is because it is the 3rd item, but the index starts at 0 remember.

ie:

  0  com.apple.preferences.configurationprofiles
  1  com.apple.preferences.icloud
  2  com.apple.preferences.bluetooth

So ok thats cool, but we don’t want to have to do all that work, we want to be able to say remove com.apple.preferences.bluetooth where ever it exists in the array, it might be number 4 or 2.

So we would need to walk through the array, locate our item, determine what its index is and then feed that index number to plistbuddy for it to remove it for us. But then PlistBuddy isn’t CFPrefs savvy, and defaults can’t handle complex plist operations like this so to do this properly we need to use Python and the CoreFoundation frameworks.

Mr Neagle tried to help me out with some example python and links to the developer documentation:

https://developer.apple.com/library/mac/documentation/CoreFoundation/Conceptual/CFPropertyLists/CFPropertyLists.html

https://gist.github.com/gregneagle/55ef3a0c0232074a1f58#file-update_complex_pref_v1-py

But it was all a bit too much for my brain. But if you are feeling up to it feel free to show me how it should be done!

So my solution is the script here : https://github.com/hunty1/Scripts/blob/master/Enable:Disable%20System%20Preference%20Panes/set_panes.sh

Essentially you just call it and give it some arguments like:

./set_panes.sh --disable com.apple.preferences.bluetooth
./set_panes.sh --enable com.apple.preferences.bluetooth

I just install this script into /usr/local/bin on my client machines and then I can easily just send them a single command from my management system to enable or disable the panes I want.

Its pretty trivial to get a list of what panes are currently disabled and put that into a Factor fact, Munki Report item, Casper Extension Attribute etc etc

Something like this will get you the results, then its just a matter of wrapping it up in the format your management system wants:

defaults read /Library/Preferences/com.apple.systempreferences DisabledPreferencePanes
Advertisements

4 comments

  1. Thanks for this, but in example, some corrections are better to try and test
    regards

    # /usr/libexec/PlistBuddy -c “Print :DisabledPreferencePanes:” /Library/Preferences/com.apple.systempreferences.plist

    # /usr/libexec/PlistBuddy -c “Delete :DisabledPreferencePanes:2” /Library/Preferences/com.apple.systempreferences.plist

    Like

  2. Hello, this is vey nice. I’m trying to run the script on OS X Yosemite and it works for e.g. iCloud and Bluetooth but when I try to disable the Network preference icon it doesn’t work.
    I did ./set_panes.sh –disable com.apple.preferences.Network and I can see the entry in the plist file but the system ignores it. This would be extremely useful as users authenticate against AD and always like to enable wireless causing issues.
    Any help appreciated 🙂 Thanks

    Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s