This post is an update to an older post on the same topic. macOS has changed and I had a few things to add. Rather than keep modifying the older post, I decided to make this new one.
As MacAdmins, most of the scripts we write will use tools that require administrator or super user/root privileges. The good news here that many of the management tools we can use to run scripts on clients already run with root privileges. The pre
– and postinstall
scripts in installation packages (pkgs), the agent for your management system, and scripts executed as LaunchDaemons all run with root privileges.
However, some commands need to be run not as root, but as the user.
For example, the defaults
command can be used to read or set a specific setting for a user. When your script, executed by your management system, is running as root and contains this command:
defaults write com.apple.dock orientation left
Then it will write this preference into root
’s home directory in /var/root/Library/Preferences/com.apple.dock.plist
. This is probably not what you intended to do.
Get the Current User
To get the correct behavior, you need to run the command as a user. Then the problem is as which user you want to run as. In many cases the answer is the user that is currently logged in.
I have written a few posts about how to determine the currently logged in user from shell scripts and will use the solution from those:
currentUser=$( echo "show State:/Users/ConsoleUser" | scutil | awk '/Name :/ { print $3 }' )
This will return the currently logged in user or loginwindow
when there is none. This is the Posix sh compatible syntax, which will also run with bash or zsh.
Running as User
There are two ways to run a command as the current user. The first is with sudo
:
sudo -u "$currentUser" defaults write com.apple.dock orientation left
The second is with launchctl asuser
.
uid=$(id -u "$currentUser")
launchctl asuser $uid launchctl load com.example.agent
The launchctl
command uses the numerical user ID instead of the user’s shortname so we need generate that first.
It used to be that the sudo
solution would not work in all contexts, but the launchctl asuser
solution would. This changed at some point during the Mojave release time.
Now, the lauchctl asuser
works and is required when you want to load and unload LaunchAgents (which run as the user), but it does not seem to work in other contexts any more.
So, for most use cases, you want to use the sudo
solution but in some you need the launchctl
form. The good news here is, that you can play it safe and use both at the same time:
launchctl asuser "$uid" sudo -u "$currentUser" command arguments
This works for all commands in all contexts. This is, however, a lot to type and memorize. I built a small shell function that I use in many of my scripts. Paste this at the beginning of your scripts:
# convenience function to run a command as the current user
# usage:
# runAsUser command arguments...
runAsUser() {
if [ "$currentUser" != "loginwindow" ]; then
launchctl asuser "$uid" sudo -u "$currentUser" "$@"
else
echo "no user logged in"
# uncomment the exit command
# to make the function exit with an error when no user is logged in
# exit 1
fi
}
and then you can use the function like this:
runAsUser defaults write com.apple.dock orientation left
runAsUser launchctl load com.example.agent
Note: the function, as written above, will simply do nothing when the Mac is sitting at the login window with no user logged in. You can uncomment the exit 1
line to make the script exit with an error in that case. In your script, you should generally check whether a user is logged in and handle that situation before you use the runAsUser
function. For example you could use:
if [ -z "$currentUser" -o "$currentUser" = "loginwindow" ]; then
echo "no user logged in, cannot proceed"
exit 1
fi
Insert this at the beginning of your code (but after the declaration of the currentUser
variable) and you can assume that a user is logged in and safely use the $currentUser
variable and the runAsUser
function afterwards. The exact detail on when and how you should check for a logged in user depends on the workflow of your script. In general, earlier is better.
When to Run as User
Generally, you should run as the user when the command interacts with the user interface, user processes and applications, or user data. As MacAdmins these are common commands you should run as the user;
defaults
, when reading or changing a user’s preferencesosascript
open
launchctl load|unload
for Launch Agents (not Launch Daemons)
This is not a complete list. Third party configuration scripts may need to be run as root or user. You will need to refer to documentation or, in many cases, just determine the correct action by trial and error.
Sample Script
I have put together a script that combines the above code into a working example.
Lovely! This double whammy helped me finally get an Apple Script to run in the “correct” end user context when executed from a JSS policy & script as root.
Thank you.