Policy not running correctly when called from LaunchDaemon

Contributor III

Hi All,

I have a launch Daemon that calls a custom trigger in JAMF which runs a script.

The bulk of the script runs fine but there is a section to clean up the existing LaunchDaemon which isn't working as expected. It's meant to bootout the launchdaemon and delete it. It manages to bootout the LaunchDaemon but then the whole script just stops running after this step and there is no recon to JAMF or anything it's like the whole process is just killed once the LaunchDaemon is booted out.

If I open terminal and run a "sudo jamf policy -event TRIGGER" then the whole script runs exactly as its meant to.

I'm baffled and could do with some help if anyone has come across a similar issue?


Valued Contributor II

post script?

The whole script is pretty long but I know the script works fine its just when its run from the LaunchDaemon it behaves odd.

Contributor III

I've also pulled the clean up part of the script out and put it as a one liner at the end of the policy as an "Execute Command". The one liner is "launchctl bootout system /Library/LaunchDaemons/com.MY.LAUNCHD.plist && rm -f /Library/LaunchDaemons/com.MY.LAUNCHD.plist". This works fine and boots the launchDaemon out and deletes it but again seems to kill the JAMF process and there is no recon or any activity after the launchdaemon is booted out and deleted.

It's very odd!

Valued Contributor II

what about if you - lauchctl unload, then delete?

edit.. and what if you move to a background process? you can't && but do you need to? 

unload has the same behaviour :(

Sorry how do you mean move to a background process?

Esteemed Contributor II

@perryd84 Your script will be terminated when you use bootout to kill the LaunchDaemon. You can definitely rm the files for the script and LaunchDaemon .plist before doing the bootcut though as an rm doesn't remove the file until it's not busy.

Here's the pattern I use to do the final cleanup when I'm triggering scripts via a LaunchDaemon:

# Nuke script and the LaunchDaemon 
# Do the rm's before the bootout because the script terminates when the LaunchDaemon is killed
/bin/rm "/Library/Application Support/OrgName/ScriptName.sh"
/bin/rm "/Library/LaunchDaemons/com.orgname.launchdname.plist"
/bin/launchctl bootout system "/Library/LaunchDaemons/com.orgname.launchdname.plist"

This could work.

But the issue I would run into with this is that the bootout part seems to stop the JAMF process running and so then I don't get an inventory update at the end of the policy to say if its run or not.

Esteemed Contributor II

@perryd84 Are you triggering another Jamf Pro policy from your script? If so you will need to make sure that policy has completed before continuing execution of your script. 

No the launch Daemon triggers a policy which runs a script.

Legendary Contributor III

I don't think this is unexpected. If the LaunchDaemon is running the script, then when you direct it to unload or bootout the LaunchDaemon, the script it was running will stop. I think, though not sure, that this is by design, probably to prevent runaway orphaned processes.

I think you will need to rethink how to do this. For example, you can delete the LaunchDaemon plist, so on next bootup, it won't run since the job will not be there in the LaunchDaemons folder. This isn't ideal, since it technically leaves the process running but without it being attached to the job. But it may be an option.

Also, why not do the recon before you try unloading the job or removing it? Is there some reason the inventory collection would need to happen at the very end? You can have the recon happen within the script rather than as an item in the policy.

When you call this from Jamf directly, it of course has no trouble running, and completing, since it wasn't actually called by the LaunchDaemon itself. The jamf process is running independently from the daemon, so it's not affected if the daemon gets stopped.

So the launch Daemon is running a JAMF Policy and the policy runs the script. It's almost like the launch Daemon is holding the JAMF process and then kills it when the launch Daemon is booted out. I always thought whatever the daemon is calling runs independent as it's own process?

The recon is just the maintenance payload running an inventory update at the end of the policy but the policy never finishes so the inventory is never updated and JAMF never shows if the policy ran.

Valued Contributor

@perryd84 why does the script need to run with a launch daemon? Couldn't you just run it with a policy with a recurring check-in trigger?

The daemon is written from another script which puts in a time stamp for the policy to be run. This way I can have it run 5min, 15min, 30min etc and not have to wait for a check-in to run it.

Contributor III

So my work around is to as follows:

  • Daemon triggers the JAMF policy
  • Policy runs the script
  • Script does whats it's meant to do then deletes the old/existing daemon
  • The script then writes an empty daemon with no trigger or action
  • JAMF Policy finishes correctly and shows logs as desired

This way the daemon stays loaded/bootstraped but it will never trigger or do anything until it's written to again at a future time.

Not the cleanest way to do it but running bootout is not an option as it kills the JAMF process. Thanks for all the suggestions I might figure out a nicer way to do this another time.

Legendary Contributor III

Could you elaborate a little more on what the end goal is here? Because if it's what I think it might be, I may have another suggestion for you.

It sounds like you're using this LaunchDaemon as a way to have the Mac immediately run a policy from the Jamf Pro server, similar to how we were once able to do thru Jamf Remote. Is that the end goal? Or is it for some other purpose?

Yeah something along those lines. I basically want the launch Daemon to run a policy at a set time which is set by a previous policy. So the previous policy sets the Daemon to run after 5min or 10min or 15min etc

Legendary Contributor III

You may want to do something like, craft your LaunchDaemon to watch a file, using the WatchPaths key. On change, the daemon can run the script in it's program argument to check the contents of the file. I recommend a plist. It can have a value in it for the time you specify, for example, 5, 10 or 15 and also any other information, such as a custom event trigger, and when it gathers these details, tell it to do a jamf policy to run the specified policy.

I'm doing something like this by using a special Configuration Profile that I can push over MDM to my Macs. I have a LaunchDaemon that watches the contents of that plist in /Library/Managed\ Preferences and reads information from the plist. I can have the target devices call almost any policy (in scope of course) on demand by it's policy ID or custom trigger. I don't need to wait for the device to check in on the next scheduled Jamf check-in.

Is that kind of what you're looking to do?

Valued Contributor

@mm2270  wrote:

When you call this from Jamf directly, it of course has no trouble running, and completing, since it wasn't actually called by the LaunchDaemon itself.

Does the LaunchDaemon binary have tcc permissions for handoff to the jamf executable, which ultimately then calls the policy?
When you used the 'jamf policy -event' command, everything is initiated by the jamf binary which is likely whitelisted with full disk access (i'm guessing).
The best way to rule this out would be run one or more of the following and see if something pops up in the logs;

/usr/bin/log show --predicate 'subsystem == "com.apple.TCC"' --info --last 1h | grep deny
/usr/bin/log show --predicate 'subsystem == "com.apple.TCC"' --info --last 1h | grep binary_path
/usr/bin/log show --predicate 'subsystem == "com.apple.TCC"' --info --last 1h | grep AttributionChain

 Worth a shot to cover this base if you haven't already.