Enabling, Restricting, and Hiding Third-Party System Preference Panes on Mac

As time goes on, it’s staggering to think about the amount of things we learn and then later happily (but at times also unhappily) forget when it becomes no longer relevant or useful. Much to my surprise, something I had long forgotten (mostly unpleasant memories) came flooding back to me from a relatively simple question: can third-party System Preferences panes be managed via profile?

Back when the only Jamf Pro option was on-premises, Jamf provided this Jamfnation Article as a guide for admins to natively manage third-party preference panes. By default, only a very small (and now very dated) native list of preference panes could be enabled, restricted, or hidden entirely via the System Preferences payload. Without too too much effort (if you knew what you’re doing) you could add your own checkboxes with the desired display name and the corresponding CFBundleIdentifier for the preference pane in order to selectively allow, deny, or hide these preference panes right from within the native Jamf profile builder.

Due to the fact the method of adding checkbox options involved changing the actual XML code of a particular JSS file, which got reset to its original state after every JSS upgrade, this required making the desired changes after every upgrade… Not a fun time! Shoutout to my fellow MacAdmins who can relate.

I’d even forgotten I’d written a Feature Request to add this functionality, which sadly like many a good Jamfnation FR:

To be fair, it’s not completely Jamf’s fault as it is a relatively trivial problem to solve and not necessarily one that’s needed to be made by a vendor when so many great open source solutions available…

All we need then is a way to create a System Preferences profile payload, have a list of our needed preference pane bundle ids, and have these listed under either the appropriate allowed, denied, or hidden list. Thankfully, I had taken the time to put some of this old work into code that with a little digging revealed a number of good options to start with.

So as of this writing, the ProfileManifests project which provides both ProfileCreator and iMazing’s Profile Editor with its magical powers now includes a number of third-party preference panes in the System Preferences payload! At the moment this is limited to a handful of items that I personally cared about (native Apple preference panes sorted alphabetically first, followed by third-party options), but it’s trivial to add more.

Don’t see an option you need, file an issue!

So how do you use this functionality with your own MDM? First, you must decide one of 3 paths. Do you want to:

  • Enable/Allow only the selected preference panes and disable all others
  • Disable/Restrict only the selected preference panes and enable all others
  • Hide only the selected preference panes and show all others

The best way to choose from the above options is to determine how you’d want to handle a new (perhaps an unknown, untrusted, or untested) preference pane addition. Would you want this enabled or disabled by default?

If you want to auto disable, I’d recommend the Enabled Preference Panes option which requires you to select everything in the list you expressly want to be enabled. If you want to auto enable, use the Disabled Preference Panes option instead. I have personally not tested the hide route, or combining any of these options, so test test test before if for whatever reason you decide to go that route.

To be clear, you don’t have to go with a one size fits all approach for your Mac fleet in this regard. You can certainly configure multiple profiles to auto allow or deny preference panes and target them to different groups of users or machines. Don’t want people to sign in to iCloud but don’t want to restrict this for your CEO? Make a special profile just for them.

Once you have your System Preferences profile settings the way you want them, export your profile and import that sucker in your MDM! No need have your MDM vendor build in native support for this…

That said, if your MDM vendor is Jamf don’t forget to sign your profile before you upload it.

Happy preference pane managing.

Okta LDAP User Group Membership with Jamf Pro: The Missing Link

For some time now, I’ve had the annoying problem of being unable to use LDAP group membership from Okta within Jamf Pro. Following this guide https://jrdsgl.com/integrating-okta-ldap-to-jamf-pro/ (although there are a number of others out on the interwebs) I was able to successfully query users and groups, but critically not user group membership.

Having previously worked at an organization that still bound Macs to Active Directory (say what you will), I had long enjoyed the administrative benefits of this functionality by being able to assign LDAP groups to limitations and exclusions within automated and Self Service policy scopes. This provides two very useful features:

  1. It allowed me to limit sensitive CLI and Self Service policies only for IT technician use on any device.
  2. I could provide conditional access to select users based on their group membership, regardless of the Mac they might use.

Through the LDAP test mechanism within Jamf, I was able to query my and other users successfully (returning the object ID) but for the life of me could never query a given user and group (which the user was a part of) and return “Yes”.

After much trial and error, it occurred to me that guides consistently showed setting the ‘Membership Location’ option to ‘Group Object’ and that instead I might base this on the ‘User Object’ instead. If you’re at all familiar with Active Directory, when you access a user record there is a ‘Member Of’ tab which lists all the groups the user is part of. Sure enough, using the ‘User Object’ option and setting the value to memberOf correctly marked users as members of the appropriate groups!

If you have your LDAP configured correctly, you can achieve the previously mentioned benefits by configuring any Jamf policies with a Scope of ‘All Computers’ with Limitations based on the desired LDAP User groups. If you happen to have local admin users for specific management tasks, you can specify these individual usernames as well.

A potential undesirable side effect (depending on how you look at it) is that if you remotely connect to your users’ Macs and attempt trigger policies manually, simply running sudo jamf policy and a custom trigger will use the logged in user’s username by default. As a result, it may be necessary to run sudo jamf policy -username <user> where <user> is a user that’s a member of an LDAP group or is explicitly specified under your policy’s Limitations. While this solves a potential problem for IT staff, this leaves open an opportunity for clever users to take advantage of this (as I’ve written recently, the list of things a local admin user can’t do on an institutionally owned and managed Mac grows smaller and smaller…). In my own testing, use of the -username argument:

  • …with a valid LDAP username that is a member of an LDAP Group listed as a policy limitation will successfully trigger a policy.
  • …with a valid LDAP username this is not a member of an LDAP Group listed as a policy limitation will not trigger a policy.
  • …with a non-existent LDAP username will instead behave as if you hadn’t specified -username at all and use the logged-in user’s username.

Running Jamf Scripts with Custom Command Line Arguments at Runtime (Without Jamf Remote)

In a previous role, I had created custom scripts with Parameter values to be used in Jamf Remote by my IT support colleagues. This provided a simple interface for common support tasks while also being able to supply custom values to scripts without having to copy & paste complex command line arguments.

In the case where a Mac had an incorrect hostname, a support technician would simply launch & authenticate into the Jamf Remote app, select the appropriate device and script, enter the desired hostname in the appropriate Parameter value field, and click Go!

This was made possible by the fact that:

  1. All our Macs were on-premise or remotely connected via VPN.
  2. Our Jamf Pro Server was also on-premise.

In a Jamf Cloud & work from home environment, this simply isn’t possible in the same way even if you are able to remotely connect to your Macs. That said, there are still plenty of use cases where this functionality is useful. This is limited by the fact that scripts attached to policies can have custom variables assigned at the policy level but not at runtime. To accomplish the same hostname functionality would require the support technician to have access and comfort to edit a policy, change a Parameter value associated with it, and trigger the policy from the user’s Mac via sudo jamf policy -event <custom value>. Depending on the level of your support team, this may simply not be an option.

As of Jamf Pro Server version 10.28.0, the previously available jamf runScript for local scripts is being deprecated and while this did not achieve the same functionality of running scripts within Jamf it renewed my interest in finding a replacement.

Thankfully, a fellow Jamf user commented on a related feature request with a solution. With a simple bash script function added to your script tied to a policy, you can call Jamf policies on the command line and supply custom variables at runtime!

https://raw.githubusercontent.com/kennyb-222/Jamf-Policy-CLI-Parameters/main/jamf_script_params.sh

The function parses the Jamf policy command based on its process ID and then parses the parameter values to be used as part of the script.

To utilize this bash function, you’ll need to complete the following:

  1. Copy & paste the checkCustomParamaters function to your desired script(s) and call the function.
  2. In your script where you normally use $4$9, instead assign the appropriate $pXValue where X is the parameter value.
  3. Assign your script to a policy and make it available on an Ongoing basis with a custom trigger.

Now you can call your policy with your selected custom variables! The added benefit of this method is that you have more than just the normal parameter 4 – 9 values available in Jamf scripts.

sudo jamf policy -event <custom trigger name> -p1 <p1 value> -p2 <p2 value> ...

Now, we can accomplish the task of remotely changing a Mac’s hostname:

sudo jamf policy -event sethostname -p1 "new_hostname"
Password:
Checking for policies triggered by "sethostname" for user "testuser"...
Executing Policy Change Mac Hostname
Running script SetHostname...
Script exit code: 0
Script result: P1 value is new_hostname
Successfully changed hostname to new_hostname!

Running Recon...