Starting/stopping a launchd agent for all users with GUI sessions

MacOS

Question or issue on macOS:

I need to be able to start/stop a per-session GUI agent from a root level daemon.

Similar issues are discussed here, here and here.

What I want to be able to do is basically

for num in `ps ax | grep [s]bin/launchd | cut -c 1-5`; 
do 
    if [ $num -ne 1 ]; 
    then 
        sudo launchctl bsexec $num launchctl (un)load -S Aqua /Library/LaunchAgents/com.mycompany.mydaemon.plist; 
    fi; 
done

but this only starts/stops one instance and it runs as root in the current GUI session. If I leave the sudo off there start I get

task_for_pid() (os/kern) failure
Couldn't switch to new bootstrap port: (ipc/send) invalid port right

I’ve tried messing around with a variety of other permutations of bsexec (including calling a secondary script from bsexec with the load/unload command), but I can never get the instance to start as anything other than root and never in another GUI session.

I also tried messing around with su – … and sudo -u …, but had no luck there either (as many people have discussed in the above linked articles and elsewhere).

Does anybody have any thoughts?

EDIT:
I tried doing this with a wrapper tool as suggested below by Graham Lee, but I get the following error:

launch_msg(): Socket is not connected

This is the command line command, wrapper, and script I’m using (501 is the userid and 63093 the pid of launchd for another user logged in to the system):

Command line:

sudo launchctl bsexec 63093 /path/TestSetUIDAndExecuteTool 501 /path/LoadBillingDialogAgent

Wrapper:

#import 

int main (int argc, const char * argv[]) {
  NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

  if (argc != 3) {
    NSLog(@"Tool called with improper arguments");
    return -1;
  }

  int uid = [[NSString stringWithUTF8String:argv[1]] intValue];
  // TODO: REMOVE
  NSLog(@"Setting uid to |%i|", uid);

  setuid(uid);
  // TODO: REMOVE
  char *command = (char *)argv[2];
  NSLog(@"Executing command |%s|", command);
  system(command);

  [pool drain];
  return 0;
}

Script:

/bin/launchctl load -S Aqua /Library/LaunchAgents/com.company.agent.plist

How to solve this problem?

Solution no. 1:

Using launchctl bsexec is correct, but you need to launch a wrapper tool which drops UID to the target user before running the ‘real’ agent executable. Oh, and it’s probably better to look for loginwindow processes, as those are the leaders of the login sessions (though launchd is very likely to work too).

Solution no. 2:

It looks like per-user launchd instances are running not in the same bootstrap namespace as launchctl launched from Terminal.

By using Dock.app as pid donor and some sudo magic:

ps aux | grep Dock.app | grep -v grep | awk '{system("sudo launchctl bsexec "$2" sudo -u "$1" launchctl load -S Aqua /Library/LaunchAgents/com.my.agent.plist")}'

it is possible to launch agent in all running sessions.

Not neat, but works.

Update: will not work on 10.7. Yes, agent will be launched, but as I can see from tests not in the correct context.

Solution no. 3:

Based on the discussions here and this script, I didn’t think a wrapper tool should be necessary. These two bash scripts may help others too.


Unload Agents

#!/bin/bash for id in `ps aux | grep -v grep | grep MyAgent | awk {'print $2'}` do launchctl bsexec $id launchctl unload /Library/LaunchAgents/myAgent.plist done 

Replace ‘MyAgent’ with the name of your Launch Agent.


Load Agents

#!/bin/bash for pid_uid in $(ps -axo pid,uid,args | grep -i "[l]oginwindow.app" | awk '{print $1 "," $2}'); do pid=$(echo $pid_uid | cut -d, -f1) uid=$(echo $pid_uid | cut -d, -f2) launchctl bsexec "$pid" chroot -u "$uid" / launchctl load /Library/LaunchAgents/myAgent.plist done 

Called from a root daemon, this will load and unload the Launch Agent referenced in myAgent.plist for all logged-in users.

Note that due to ‘rootless’ in OS X El Capitan (10.11), the use of bsexec may no longer work, but up to 10.10, this should be fine.

Solution no. 4:

I had the same problem.
To solve this, use a pid “under” launchd, the pid of a process that launchd has started.

The pid you commit to ‘launchctl bsexec’ is used to find the right bootstrap. If you use the pid of launchd (from the user context) than you work in the root launchd bootstrap. If you use pe. the Finder or the Dock pid of the user, you can work in this “per-user” bootstrap

Hope this helps!