Troubleshooting LaunchD Scheduled Script
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
Posted on 02-05-2021 05:14 PM
I've be tasked with creating a deferral prompt for a Managed OS workflow. I've gotten everything figured out....except for LaunchD to actually trigger the deferred policy at a later time. Here's the problem section of code.
## Writes a simple script that exists just to call the deferred policy
echo "/bin/launchctl unload /Library/LaunchDaemons/managedOS.plist" >> /Library/Application Support/JAMF/callManagedOSCountdown.sh
echo "/bin/rm /Library/LaunchDaemons/managedOS.plist" >> /Library/Application Support/JAMF/callManagedOSCountdown.sh
echo "/usr/local/bin/jamf policy -event $policyTriggerDeferred &" >> /Library/Application Support/JAMF/callManagedOSCountdown.sh
chown root:wheel /Library/Application Support/JAMF/callManagedOSCountdown.sh
chmod 644 /Library/Application Support/JAMF/callManagedOSCountdown.sh
## WRITE LAUNCHDAEMON
#Create the plist
defaults write /Library/LaunchDaemons/managedOS.plist Label -string "managedOSDeferral"
#Add program argument to have it run the update script
defaults write /Library/LaunchDaemons/managedOS.plist ProgramArguments -array -string /bin/sh -string "/Library/Application Support/JAMF/callManagedOSCountdown.sh"
#Set the run inverval to run
defaults write /Library/LaunchDaemons/managedOS.plist StartInterval -integer $delayChosen
#Set run at load
defaults write /Library/LaunchDaemons/managedOS.plist RunAtLoad -boolean yes
#Set ownership
chown root:wheel /Library/LaunchDaemons/managedOS.plist
chmod 644 /Library/LaunchDaemons/managedOS.plist
## LOAD LAUNCHDAEMON
/bin/launchctl load /Library/LaunchDaemons/managedOS.plist
I tried using cat
to write the script locally and it always failed. Echoing in works, but is less than ideal. That script runs correctly when run manually on the machine.
As for the LaunchDaemon aspects, I have no idea what's going on. Sometimes it loads with a status of 127. Most of the time nothing happens. No errors to standard output. No logging of any kind even if I add that in. No feedback when it's loaded manually on the machine rather than through the script. I'm still new to LaunchD in general, so I have no idea what I'm missing here. Any suggestions?
I've been testing on macOS 10.12 through 11 with same results on all platforms.
- Labels:
-
Scripts

- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
Posted on 02-05-2021 07:51 PM
@McAwesome Exactly how did writing your script via cat
fail? That's a much more common mechanism of creating a LaunchDaemon .plist than using defaults write
(although I commend your creativity for that approach).
In terms of diagnosing what's wrong with a LaunchDaemon, I'd highly recommend LaunchControl by soma-zone (they also produced this excellent launchd tutorial: https://launchd.info)
You should also see this article on the launchctl 2.0 syntax which provides an improved mechanism for loading/unloading LaunchDaemons/Agents: https://babodee.wordpress.com/2016/04/09/launchctl-2-0-syntax/
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
Posted on 02-06-2021 11:22 AM
@sdagley I think I figured out why cat
wasn't working. I had those segments embedded in an if
section and indented to match that. For whatever reason that meant the script kept getting Unexpected End Of File errors. Removing the indentions resolved that part of the issue. Here's the revised version of that chunk.
## Writes a simple script that exists just to call the deferred policy
Cat > /Library/Application Support/JAMF/callManagedOSCountdown.sh <<EOF
/bin/launchctl unload /Library/LaunchDaemons/local.managedOS.plist
/bin/rm /Library/LaunchDaemons/local.managedOS.plist
/usr/local/bin/jamf policy -event $policyTriggerDeferred &
EOF
chown root:wheel /Library/Application Support/JAMF/callManagedOSCountdown.sh
chmod 644 /Library/Application Support/JAMF/callManagedOSCountdown.sh
## WRITE LAUNCHDAEMON
#Create the plist
cat > /Library/LaunchDaemons/local.managedOS.plist <<EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>local.managedos</string>
<key>ProgramArguments</key>
<array>
<string>/bin/sh</string>
<string>/Library/Application Support/JAMF/callManagedOSCountdown.sh</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>StartInterval</key>
<integer>$delayChosen</integer>
</dict>
</plist>
EOF
#Set ownership
chown root:wheel /Library/LaunchDaemons/local.managedOS.plist
chmod 644 /Library/LaunchDaemons/local.managedOS.plist
## LOAD LAUNCHDAEMON
launchctl load /Library/LaunchDaemons/local.managedOS.plist
Now the issue I'm running into is that either it never loads or if the string pointing to the script is incorrect it will load with status 127. I wasn't able to get the bootstrap
commands to work reliably either, so since the launchd devs over there said it's likely safe for a while I'm relying on it.
Any ideas on how to correct for that part?

- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
Posted on 02-06-2021 08:56 PM
@McAwesome A few suggestions...
When you're writing files contains parameter substitutions with cat
you need to make sure you're using the correct form. Take a look at this thread on Stack Overflow: How to cat <<EOF >> a file containing code? for examples
Your label local.managedos
capitalization doesn't match your LaunchDaemon .plist name local.managedOS.plist
You need to escape the space in "Application Support" in your ProgramArguments array Turns out you don't want to escape spaces in the ProgramArguments array
As for using bootstrap
and booutout
, they offer precise control over the context for executing the daemon. The context for load
and unload
depend on the context running the command unless you also use the subcommand asuser
.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
Posted on 02-08-2021 08:03 AM
@sdagley OK I figured out what I was doing wrong with Bootstrap and that seems to now be working reliably loading it under system now, though I'm not sure if for this purpose it's better to do that under the current signed in user or stick with system.
I wasn't escaping that character because it caused the 127 error I was getting. I now realize that was because I needed to add a space after Escaping that space causes errors and doesn't line up with the examples on that lanchd.info site. Same goes for adding a space into the <string>/bin/sh </string>
so it would run as /bin/sh /Library/Application Support/JAMF/callManagedOSCountdown.sh
instead of /bin/sh/Library/Application Support/JAMF/callManagedOSCountdown.sh
, which definitely does not exist. Now I'm at error 78, which is if nothing else at least an improvement over what I was getting before, though not a particularly useful error code./bin/sh
bit. So I'm back at square 1.
Thanks again for the help you've given. It's pointed me in the right direction.

- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
Posted on 02-08-2021 10:16 AM
@McAwesome Have you tried LaunchControl to see what it thinks of your LaunchDaemon?
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
Posted on 02-08-2021 10:33 AM
@sdagley I have. No errors, doesn't load, and attempting to force load fails for unknown reasons. That was how I discovered that I shouldn't escape the space in Application Support
or it'd actually fill in Application\ Support
.

- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
Posted on 02-08-2021 11:29 AM
@McAwesome Can you post the current version of your script and LaunchDaemon? If the LaunchDaemon is getting a clean bill of health from LaunchControl there's probably something we're missing.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
Posted on 02-08-2021 12:00 PM
@sdagley Here is the current version of the deferral section. I tried swapping over to scheduling it for a specific date and time rather than having the start time be every X seconds. This seems like it'd be the more reliable route for what I am going for. I can verify that the delayTime variables all get populated both in the main script and in the plist file correctly.
#If the user select Upgrade Later, or some other value is returned, then the user is told how to run the upgrade themselves
else
# Making log more legible
echo "----------SECOND PROMPT"
delayTime=$(( $currentTime + $delayChosen ))
echo "User has chosen to delay until after $(date -r $delayTime +'%b %d, %Y %r')"
## Writes a simple script that exists just to call the deferred policy
cat > /Library/Application Support/JAMF/callManagedOSCountdown.sh <<EOF
#!/bin/sh
/bin/launchctl bootout system /Library/LaunchDaemons/local.managedOS.plist
/bin/rm /Library/LaunchDaemons/local.managedOS.plist
/usr/local/bin/jamf policy -event $policyTriggerDeferred &
EOF
chown root:wheel /Library/Application Support/JAMF/callManagedOSCountdown.sh
chmod 755 /Library/Application Support/JAMF/callManagedOSCountdown.sh
## WRITE LAUNCHDAEMON
#Create the plist
cat > /Library/LaunchDaemons/local.managedOS.plist <<EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>local.managedOS</string>
<key>ProgramArguments</key>
<array>
<string>/bin/sh</string>
<string>/Library/Application Support/JAMF/callManagedOSCountdown.sh</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>StartCalendarInterval</key>
<dict>
<key>Month</key>
<integer>$(date -r $delayTime +%m)</integer>
<key>Day</key>
<integer>$(date -r $delayTime +%d)</integer>
<key>Hour</key>
<integer>$(date -r $delayTime +%H)</integer>
<key>Minute</key>
<integer>$(date -r $delayTime +%M)</integer>
</dict>
<key>StandardErrorPath</key>
<string>/Users/<<redacted>>/stderr.log</string>
<key>StandardOutPath</key>
<string>/Users/<<redacted>>/stdout.log</string>
</dict>
</plist>
EOF
#Set ownership
chown root:wheel /Library/LaunchDaemons/local.managedOS.plist
chmod 755 /Library/LaunchDaemons/local.managedOS.plist
## Bootstrap The LaunchDaemon
launchctl bootstrap system /Library/LaunchDaemons/local.managedOS.plist
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
Posted on 02-08-2021 12:14 PM
I can provide the entire script as well, though I'll have to verify it doesn't reference anything specific to my workplace that isn't essential. I don't think it does, other than where I was temporarily storing the logs.

- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
Posted on 02-08-2021 01:21 PM
@McAwesome This is bringing back memories of a problem I was having running a script that would call jamfHelper from a LaunchDaemon where the behavior differed depending on if the policy was being run from Self Service or a periodic checkin. I finally ended up triggering the script using this for ProgramArguments in my LaunchDaemon:
<key>ProgramArguments</key>
<array>
<string>/usr/local/jamf/bin/jamf</string>
<string>runScript</string>
<string>-script</string>
<string>NameOfScript.sh</string>
<string>-path</string>
<string>/Library/Application Support/MyOrg</string>
</array>
Before you consider that, 2 last things to suggest:
- Use 644 as the permissions for the LaunchDaemon .plist
- Remove the .plist extension in your call to launchctl bootout
(so it's just /bin/launchctl bootout system /Library/LaunchDaemons/local.managedOS
)
WARNING - In case anyone finds this post and thinks of trying the jamf runScript
verb, don't. The Jamf Pro 10.28.0 release notes list it as deprecated.
