Menu bar customization

Jason
Contributor

What has everyone found to be the most effective way of customizing the menu bar? Personally I'd like to have certain extras enforced on the menu bar, but allow the user to add additional ones if they like. For example:

Keychain status, AirPort, HomeSync, Battery, Clock, Volume, VPN

I've seen that "defaults write com.apple.systemuiserver menuExtras -array-add "<Extra>.menu"" will work. But it lacks the ability to see if that extra is already in the menu (so i get multiples if i just re-run it).

24 REPLIES 24

jennifer
Contributor

I've been using com.apple.systemuiserver as you mentioned, though I've never been able to reliably get the binoculars that show remote access to work. (It will often work for the first user that logs in, but not subsequent ones)

I'd be interested in what you find.

CasperSally
Valued Contributor II

We use MCX to force homesync off menu bar and battery on. I tested config profiles doing same prefs works as well.

Jason
Contributor

@CasperSally: I see the "Managed Menu Extras" under Managed Preferences. How did you do the same with Configuration Profiles? I see that I could use Custom Settings to deploy a PLIST, but wouldn't that wipe out any menu's that a user would enable that we didn't care to enforce? I just want to ensure a few certain ones are on and then let the user have a choice for the others. Only in my first couple months using Mac's, so sorry for the noob question.

Thanks

mm2270
Legendary Contributor II

This isn't as easy as it sounds, certainly when using Managed Preferences (MCX) We use it here and unfortunately, if a user adds one of their own Menu Extras in,. its fine until they restart their Mac or log out and back in. At that point, the managed menu bar settings take over and any Menu Extras they added are removed.

This may be different when using Configuration Profiles, but I can't say since we don't use them for that here.
I'm not sure if there's a good solution to this issue.
We get occasional gripes/complaints from users that their Menu Extras aren't "sticking" Our solution for now is to instruct them to add the Menu Extra directly to their Account's Login Items by locating it in /System/Library/CoreServices/Menu Extras/ and dragging it into their Login Items. This has the effect of "opening" the Menu Extra at login, and you can still enforce whichever ones you want to have show up there at every login.

Although you could change the MCX to a One time only setting, that would mean if a user removes it, it won't come back at next login.

tkimpton
Valued Contributor II

You possibly may be able to add the items as a login item via a script.

See here for an example https://github.com/tkimpton/Scripts/blob/master/Bash/AddLoginItemADPassMon.sh

Jason
Contributor

I was able to write up a script that accomplishes the task. It parses the systemuiserver plist for references to the menu's I'm looking at and if something is missing it opens the menu to put it in. Haven't done much testing with it yet, but it works when I run it directly. It's failing when put in a policy due to running as root. Any suggestions on how to get it to update the plist for the logged on user? You can see I've tried a few things to get "currUser" which haven't worked.

#!/bin/bash

# This script adds items to the menu bar if they are not currently shown.
# Author: Jason Olsen
# Date: 05/12/2014

#currUser=$(who | grep "console" | cut -d" " -f1)
currUser='ls -l /dev/console | cut -d " " -f4'

Keychain=$(su - "${currUser}" -c 'grep "Keychain.menu" /Users/$currUser/Library/Preferences/com.apple.systemuiserver.plist -c')
if [ $Keychain == 0 ]; then
    echo "Adding Keychain status to menu" 
    open '/Applications/Utilities/Keychain Access.app/Contents/Resources/Keychain.menu'
fi

AirPort=$(grep "AirPort.menu" /Users/$currUser/Library/Preferences/com.apple.systemuiserver.plist -c)
if [ $AirPort == 0 ]; then
    echo "Adding AirPort status to menu" 
    open '/System/Library/CoreServices/Menu Extras/AirPort.menu'
fi

HomeSync=$(grep "HomeSync.menu" /Users/$currUser/Library/Preferences/com.apple.systemuiserver.plist -c)
if [ $HomeSync == 0 ]; then
    echo "Adding HomeSync status to menu" 
    open '/System/Library/CoreServices/Menu Extras/HomeSync.menu'
fi

Battery=$(grep "Battery.menu" /Users/$currUser/Library/Preferences/com.apple.systemuiserver.plist -c)
if [ $Battery == 0 ]; then
    echo "Adding Battery status to menu" 
    open '/System/Library/CoreServices/Menu Extras/Battery.menu'
fi

Bluetooth=$(grep "Bluetooth.menu" /Users/$currUser/Library/Preferences/com.apple.systemuiserver.plist -c)
if [ $Bluetooth == 0 ]; then
    echo "Adding Bluetooth status to menu" 
    open '/System/Library/CoreServices/Menu Extras/Bluetooth.menu'
fi

Clock=$(grep "Clock.menu" /Users/$currUser/Library/Preferences/com.apple.systemuiserver.plist -c)
if [ $Clock == 0 ]; then
    echo "Adding Clock status to menu" 
    open '/System/Library/CoreServices/Menu Extras/Clock.menu'
fi

Volume=$(grep "Volume.menu" /Users/$currUser/Library/Preferences/com.apple.systemuiserver.plist -c)
if [ $Volume == 0 ]; then
    echo "Adding Volume status to menu" 
    open '/System/Library/CoreServices/Menu Extras/Volume.menu'
fi

mm2270
Legendary Contributor II

@Jason, I wouldn't try running the commands as the user. Get the logged in user and use defaults against the account's com.apple.systemuiserver plist instead. Like this for example:

#!/bin/sh

currentUser=$( ls -l /dev/console | awk '{print $3}' )
userHome=$( dscl . read /Users/$currentUser NFSHomeDirectory | awk '{print $NF}' )

MenuExtras=$( defaults read "$userHome/Library/Preferences/com.apple.systemuiserver.plist" menuExtras | awk -F'"' '{print $2}' )

if [[ $( echo "${MenuExtras}" | grep "Keychain.menu" ) ]]; then
      echo "do something"
else
      echo "do something else"
fi

Just change what you grep for in each instance to check for the specific Menu Extras.

mm2270
Legendary Contributor II

Hi again @Jason. you got me thinking a bit about this process. Your solution is a pretty good one if you decide not to go the MCX or Config Profile route.

Here'a a modified version I just whipped up, It uses an array that you can specify in the script. Note that you enter the full path to the Menu Extra as it would show up on the command line, since that's how the plist stores the information.

It loops through each one, checking the logged in user's systemuiserver.plist file, and if the Menu Extra isn't present, opens it, or just echoes back that its already present. It may not be perfect, as in some cases, the plist doesn't get updated right away when a user Command drags an item off the menu bar, but in testing it seems to work pretty reliably. The good thing about using an array is that all you ever have to do is add/remove whichever Menu Extra items you want into the list on the top.
Here's the script. Check it out and see if it does what you want-

#!/bin/bash

PreferredMenuExtras=(
"/Applications/Utilities/Keychain Access.app/Contents/Resources/Keychain.menu"
"/System/Library/CoreServices/Menu Extras/AirPort.menu"
"/System/Library/CoreServices/Menu Extras/Battery.menu"
"/System/Library/CoreServices/Menu Extras/Bluetooth.menu"
"/System/Library/CoreServices/Menu Extras/Clock.menu"
"/System/Library/CoreServices/Menu Extras/Eject.menu"
)

currentUser=$( ls -l /dev/console | awk '{print $3}' )
userHome=$( dscl . read /Users/$currentUser NFSHomeDirectory | awk '{print $NF}' )

MenuExtras=$( defaults read "$userHome/Library/Preferences/com.apple.systemuiserver.plist" menuExtras | awk -F'"' '{print $2}' )

for menuExtra in "${PreferredMenuExtras[@]}"; do
    menuShortName=$( echo "${menuExtra}" | awk -F'/' '{print $NF}' )
    if [[ $( echo "${MenuExtras}" | grep "${menuExtra}" ) ]]; then
        echo "Menu Extra "${menuShortName}" present"
    else
        echo "Menu Extra "${menuShortName}" not in plist. Opening..."
        open "${menuExtra}"
    fi
done

Obviously, edit the "PreferredMenuExtras" array to put the ones you want in there.

One last note. I haven't actually tested the "open" part of the script from a policy. Its possible this may not work and you may need to run the command as the user for it to work.

ops
New Contributor III
One last note. I haven't actually tested the "open" part of the script from a policy. Its possible this may not work and you may need to run the command as the user for it to work.

Tested. Works. Love it. Thanks!

Jason
Contributor
One last note. I haven't actually tested the "open" part of the script from a policy. Its possible this may not work and you may need to run the command as the user for it to work. Tested. Works. Love it. Thanks!

I'm still actually trying to get that "open" part to work. It runs fine when I execute the script directly, but not when I run it from JAMF. Here is the script as it currently exists for me:

#!/bin/bash

PreferredMenuExtras=(
    "/Applications/Utilities/Keychain Access.app/Contents/Resources/Keychain.menu"
    "/System/Library/CoreServices/Menu Extras/AirPort.menu"
    "/System/Library/CoreServices/Menu Extras/Battery.menu"
    "/System/Library/CoreServices/Menu Extras/Bluetooth.menu"
    "/System/Library/CoreServices/Menu Extras/Clock.menu"
    "/System/Library/CoreServices/Menu Extras/Eject.menu"
)

## Get user context
currentUser=$3;echo "Current User: $currentUser"
userHome=$( dscl . read /Users/$currentUser NFSHomeDirectory | awk '{print $NF}' )

## Get list of current menu items
MenuExtras=$( defaults read "$userHome/Library/Preferences/com.apple.systemuiserver.plist" menuExtras | awk -F'"' '{print $2}' )
su $currentUser
for menuExtra in "${PreferredMenuExtras[@]}"; do
    menuShortName=$( echo "${menuExtra}" | awk -F'/' '{print $NF}' )
    if [[ $( echo "${MenuExtras}" | grep "${menuExtra}" ) ]]; then
        ## Menu extra already exists
        echo "Menu Extra "${menuShortName}" present"
    else
        ## Menu extra doesn't exist
        echo "Menu Extra "${menuShortName}" not in plist. Opening "${menuExtra}"..."
        menuItem=$menuExtra
        ## Open Menu extra so it is added to menu bar
        sudo -u $currentUser open "$menuItem"
        whoami
    fi
done

It differs slightly from the previously posted script in that the open command gets run as the logged on user (and not root). But now I'm getting an error stating: LSOpenURLsWithRole() failed with error -10810 for the file /System/Library/CoreServices/Menu Extras/Bluetooth.menu

So still working through it.

mm2270
Legendary Contributor II

Hi @Jason, so in the Get user context section where the script gets the "loggedInUser" and other stuff, add this line just below everything else-

loggedInPID=$( ps -axj | awk "/^$loggedInUser/ && /Dock.app/ {print $2;exit}" )

Then replace the line that does sudo -u $currentUser open "$menuItem" toward the end with the following syntax-

/bin/launchctl bsexec "${loggedInPID}" sudo -iu "${loggedInUser}" "open "$menuItem""

The launchctl bsexec trick usually works when simply doing sudo -u user something doesn't work. Give that a try and see how you fare with that.

That LSOpenURLsWithRole error you see is just Mavericks being a biotch and not letting you run your open command in the user context. The above launchctl bsexec still isn't 100% guaranteed to work, but it usually does.

Jason
Contributor

The same issue seems to appear, but now I'm getting a second error as well:

Menu Extra "Battery.menu" not in plist. Opening "/System/Library/CoreServices/Menu Extras/Battery.menu"... LSOpenURLsWithRole() failed with error -10810 for the file /System/Library/CoreServices/Menu Extras/Battery.menu. launchctl bsexec failed: Inappropriate ioctl for device

Initially I was receiving an error when I ran against Dock.app, even from Terminal, so I changed it to loginwindow which was able to get me the PID. I also changed loggedInUser to currentUser since that's what I had already. It works from Terminal though. Are there any other ways around this to Open that menu extra?

#!/bin/bash

PreferredMenuExtras=(
    "/Applications/Utilities/Keychain Access.app/Contents/Resources/Keychain.menu"
    "/System/Library/CoreServices/Menu Extras/AirPort.menu"
    "/System/Library/CoreServices/Menu Extras/Battery.menu"
    "/System/Library/CoreServices/Menu Extras/Bluetooth.menu"
    "/System/Library/CoreServices/Menu Extras/Clock.menu"
    "/System/Library/CoreServices/Menu Extras/Eject.menu"
)

## Get user context
currentUser=$3;echo "Current User: $currentUser"
userHome=$( dscl . read /Users/$currentUser NFSHomeDirectory | awk '{print $NF}' )
loggedInPID=$( ps -axj | awk "/loginwindow/ {print $2;exit}" )

## Get list of current menu items
MenuExtras=$( defaults read "$userHome/Library/Preferences/com.apple.systemuiserver.plist" menuExtras | awk -F'"' '{print $2}' )
su $currentUser
for menuExtra in "${PreferredMenuExtras[@]}"; do
    menuShortName=$( echo "${menuExtra}" | awk -F'/' '{print $NF}' )
    if [[ $( echo "${MenuExtras}" | grep "${menuExtra}" ) ]]; then
        ## Menu extra already exists
        echo "Menu Extra "${menuShortName}" present"
    else
        ## Menu extra doesn't exist
        echo "Menu Extra "${menuShortName}" not in plist. Opening "${menuExtra}"..."
        menuItem=$menuExtra
        ## Open Menu extra so it is added to menu bar
        #sudo -u $currentUser open "$menuItem"
        #whoami
        /bin/launchctl bsexec "${loggedInPID}" sudo -iu "${currentUser}" "open "$menuItem""
    fi
done

ops
New Contributor III
It differs slightly from the previously posted script in that the open command gets run as the logged on user (and not root).

@Jason What's the reasoning behind this? Why not just run the script as root using Casper?

Jason
Contributor
@Jason What's the reasoning behind this? Why not just run the script as root using Casper?

When I first started trying to get this working I think I ran it as root (mostly because I couldn't figure out how to run it as anything else). It didn't have any impact though and no errors were displayed. I was assuming that I need to run the commands as the logged on user to have it impact that users session (since the settings are user specific).

ops
New Contributor III

@Jason If you could humour me: locally, without casper, what happens if you paste mm2270's script into a terminal after sudo -s with say, the keychain access menu item added?

sudo -s

Type your password

#!/bin/bash

PreferredMenuExtras=(
"/Applications/Utilities/Keychain Access.app/Contents/Resources/Keychain.menu"
"/System/Library/CoreServices/Menu Extras/AirPort.menu"
"/System/Library/CoreServices/Menu Extras/Battery.menu"
"/System/Library/CoreServices/Menu Extras/Bluetooth.menu"
"/System/Library/CoreServices/Menu Extras/Clock.menu"
"/Applications/Utilities/Keychain Access.app/Contents/Resources/Keychain.menu"
)

currentUser=$( ls -l /dev/console | awk '{print $3}' )
userHome=$( dscl . read /Users/$currentUser NFSHomeDirectory | awk '{print $NF}' )

MenuExtras=$( defaults read "$userHome/Library/Preferences/com.apple.systemuiserver.plist" menuExtras | awk -F'"' '{print $2}' )

for menuExtra in "${PreferredMenuExtras[@]}"; do
    menuShortName=$( echo "${menuExtra}" | awk -F'/' '{print $NF}' )
    if [[ $( echo "${MenuExtras}" | grep "${menuExtra}" ) ]]; then
        echo "Menu Extra "${menuShortName}" present"
    else
        echo "Menu Extra "${menuShortName}" not in plist. Opening..."
        open "${menuExtra}"
    fi
done

Chris_Hafner
Valued Contributor II

OK, so this thread isn't that old but I just wanted to comment on @mm2270 's original suggestion. I've been testing it under 10.10 with local accounts and it's working brilliantly!

Chris_Hafner
Valued Contributor II

However, what I'd really like to do is to create a profile that sets up the VN menu extra along with the VPN profile. Since we abandoned mcx a LONG time ago, it would be great to regain this functionality. I'm about to start playing with some of the manifests and mcxToProfile from jamfnation here but I'd love to know if anyone has a more modern solution... or at least thoughts.

Cayde-6
Valued Contributor

@Chris_Hafner Did you manage to create Config Profiles for the menubars in the end?

Chris_Hafner
Valued Contributor II

Actually, no, though I'm going to go back to looking at it. FYI, I didn't do it because I ended up moving onto other projects and had a working solution. However, now that you bring it up...

larry_barrett
Valued Contributor

Works in Catalina 10.15.2

If you have it be Once Per User you should be able to avoid duplicates.

20e78afe4b0f42b8adc0cd554d015b4d

493655bd9d7f4b19a915c124ea7c6bfd

highlandtel
New Contributor II

@larry_barrett Like your solution. Just some observations.

The Volume does not work under BigSur Beta 20A5364e. The Volume.menu doesn't work when just opening it from the GUI, while VPN.menu works both from JAMF policy and GUI. Some of the .menu files that resided in /System/Library/CoreServices/Menu Extras/ are no longer there (ie Bluetooth). Still looking for them...

I am hoping these are just beta issues.

I would also have the policy run at every login, I don't get duplicate icons for the VPN. Still testing the others.

gachowski
Valued Contributor II

@highlandtel

I used the bluetooth menu bar item to pass a CIS benchmark? DId you find it or the moved menu bar items?

C

mikelaundrie
New Contributor

After some experimenting, modifying the com.apple.controlcenter.plist file will allow you to add wifi and volume back to the men bar.

DBruner
New Contributor

I'm also using this to pass the 10.15 CIS Benchmark 2.1.3 for Bluetooth. Was wondering if this could also be used for 4.2 to show Wi-Fi status in menu bar?