Launchd plist erring with "127"

s_mcc
New Contributor II

I'm trying to inject a script and plist to our end users for running some updates. I've verified that the injection method for the plist works, and that the script works. However, when I upload this plist with the script, it constantly fails to run and reports an "127" error.

I'm relatively new to launchd for launching scripts, has anyone seen this error or is there a glaring issue with this plist I've missed?

<?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>com.redacted.delayscript</string>
    <key>ProgramArguments</key>
    <array>
        <string>/Users/Shared/.restartscript.sh</string>
    </array>
    <key>StartInterval</key>
    <integer>60</integer>
    <key>RunAtLoad</key>
    <true/>
</dict>
</plist>
1 ACCEPTED SOLUTION

mm2270
Legendary Contributor III

OK, it's not what I was thinking it was, which is that the plist was being formed incorrectly when it was echoed into place. But you're doing it the right way, or at least one way that works correctly.

I did notice two other things though. For one, your script that the launchd is calling is using a jamf policy -event command, which must be run as root. But you're asking a LaunchAgent to run it, which can't run things as root. So right off, that isn't going to work.
You will need to create a LaunchDaemon in order to run a jamf policy command. Can't be done from a LaunchAgent since those run as the user and can't elevate to root privileges on their own.
Of course since LaunchDaemon's can run when no-one is logged in, that creates some complications you may need to overcome, such as designing the script to exit if it's sitting at the login window and run again later, each time checking to make sure a user is logged into the Mac.

Second, even when you do run it from a LaunchDaemon, I would strongly suggest putting the full path to the jamf binary in the script, since oftentimes many of us have found that launchd jobs don't seem to resolve the jamf binary path correctly. So make the script doing the reboot look like this:

restart_script="#!/bin/sh
/usr/local/bin/jamf policy -event RestartScript
exit"

That actually could be why you're getting the 127: The specified service did not ship with the operating system error. It may not understand what jamf resolves to.

View solution in original post

12 REPLIES 12

mm2270
Legendary Contributor III

Not a lot of people know this, but launchctl has an error code subcommand, that let's you see a human readable output of error codes.

$ /bin/launchctl error 127
$ 127: The specified service did not ship with the operating system

So code 127 means what it states above, whatever that actually means. Can you possibly post the script it's calling? Maybe there's an issue in the script that's causing the launchd job to fail to run.

SimonLovett
New Contributor III

Alas, so many things can kill a launchd script :(

Things to check - Ownership and permissions on the launchdaemon (or launch agent) plist file. Should belong to root/wheel and users should only be able to read.
Ownership and permissions on the script itself. Again should belong to root/wheel, but permissions may be slightly different in that anyone can run the script, but the write permission must only lie with root.
Also, I have found any space before the hashbang, or no hashbang at all (e.g. #!/bin/bash or appropriate as your first line) can cause random results depending on the OS version, as can any rubbish at the end - I try and terminate all mine now with an exit 0.
Finally, a new one on me recently, you may want to put a sleep of 10 seconds or so in somewhere as, if a script runs for less than ten seconds, launchd may quarantine it, so you might see it run once, but then it may never run again... puzzled the heck out of me recently, that one!

Not sure quite what a launchd process will make of a hidden script either - might be worth trying it with a visible script if all else fails.

https://developer.apple.com/library/content/documentation/MacOSX/Conceptual/BPSystemStartup/Chapters/CreatingLaunchdJobs.html

:)

mm2270
Legendary Contributor III

@SimonCU actually makes several good points. I was also thinking of asking how you crafted the launchd plist. Was it manually in a plain text editor, or with a program like Lingon or LaunchControl? The latter apps really take the guesswork out of things, so you may want to look at those if you're not already using one of them.
Other than recreating it in one of those apps, check the owner/group and permissions on the plist. It must be root:wheel for owner:group and 644 for the POSIX permissions. If not, I can almost guarantee it won't start. Launchd plist jobs are very finicky that way.

Also, I didn't notice it until it was mentioned, but I agree the hidden script file (starting with a period) may be causing a problem. I don't know if I've ever tried that with a launchd job so I don't know how it handles it. Consider instead using the chflags command to make it hidden without needing to name it with a leading period.

chflags hidden /Users/Shared/restartscript.sh

That will make it invisible in the Finder, but still visible in Terminal when doing ls -al on the directory.

s_mcc
New Contributor II

So this is the part of the script pertaining to the injection, and launching of the plist. I've checked the permissions and owner of all the scripts and plist, and they're all coming back as 644 with root:wheel (The exception being the execute on the script.) I've also tried pulling the currently logged in (Admin, god help me) user and injecting this into their LaunchAgent as well as altering ownership of all files to reflect that current user. Still no good. As I put early, a sample Spotify plist I wrote to test it with worked correctly with this injection script, so it seems to lie with the script portion.

I've also tried this without hidden files, moving them around into different folders with different owners, without any change to the error.

##############################################
## Local Call to Restart Invertval Script located on JSS ##
###########################################################

restart_script="#!/bin/sh
jamf policy -event RestartScript
exit"

#####################################################################
## Execution Code below, alter variables above to change behavior. ##
#####################################################################

while [ "$restart_prompt" != 0 ] && [ "$restart_prompt" != 2 ]; do
    echo "Prompt exited, reopening..."
    restart_prompt=$(/Library/Application Support/JAMF/bin/jamfHelper.app/Contents/MacOS/jamfhelper -windowType hud -title "$title" -heading "$heading" -alignHeading "$alignHeading" -icon "$icon" -button1 "$button1" -button2 "$button2" -description "$description")
done

if [ $restart_prompt -eq 0 ]; then
    osascript -e 'tell app "loginwindow" to «event aevtrrst»'
elif [ $restart_prompt -eq 2 ]; then
    echo "$restart_script" > /Users/Shared/.restartscript.sh
    chmod +x /Users/Shared/.restartscript.sh
    echo "5" > /Users/Shared/.delaycounter.txt
    echo "$launch_delay" > /Library/LaunchAgents/edu.duq.restartdelay.plist
    chmod 644 /Library/LaunchAgents/edu.duq.restartdelay.plist
    launchctl load /Library/LaunchAgents/edu.duq.restartdelay.plist

fi

mm2270
Legendary Contributor III

So, where is launch_delay coming from? I see in this line:

echo "$launch_delay" > /Library/LaunchAgents/edu.duq.restartdelay.plist

you're trying to echo a new plist into place, but I don't see the launch_delay variable defined earlier in the script? Or is this not the complete script you posted?
If you have a variable somewhere else in the script that defines the launchd plist, can you post that? I think I may know what the issue is.

s_mcc
New Contributor II

@mm2270

Sorry, forgot to include that in my snippet.

################################
## Restart Delay Launch Agent ##
################################

launch_delay='<?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>edu.duq.delayscript</string>
    <key>ProgramArguments</key>
    <array>
        <string>/Users/Shared/.restartscript.sh</string>
    </array>
    <key>StartInterval</key>
    <integer>60</integer>
    <key>RunAtLoad</key>
    <true/>
</dict>
</plist>'

PeterClarke
Contributor II

Try escaping the dot file.

i.e. Change: <string>/Users/Shared/.restartscript.sh</string>

To: <string>/Users/Shared/.restartscript.sh</string>

Does that help ?

PeterClarke
Contributor II

Well the back-slash that I added in front of the .restartscript.sh got stripped out of that message..

So I was saying try adding a backslash in front, ( points the other way around to a forward slash / )
or try quoting the string.

Personally I would put scripts in some other location then in: /Users/Shared/

mm2270
Legendary Contributor III

OK, it's not what I was thinking it was, which is that the plist was being formed incorrectly when it was echoed into place. But you're doing it the right way, or at least one way that works correctly.

I did notice two other things though. For one, your script that the launchd is calling is using a jamf policy -event command, which must be run as root. But you're asking a LaunchAgent to run it, which can't run things as root. So right off, that isn't going to work.
You will need to create a LaunchDaemon in order to run a jamf policy command. Can't be done from a LaunchAgent since those run as the user and can't elevate to root privileges on their own.
Of course since LaunchDaemon's can run when no-one is logged in, that creates some complications you may need to overcome, such as designing the script to exit if it's sitting at the login window and run again later, each time checking to make sure a user is logged into the Mac.

Second, even when you do run it from a LaunchDaemon, I would strongly suggest putting the full path to the jamf binary in the script, since oftentimes many of us have found that launchd jobs don't seem to resolve the jamf binary path correctly. So make the script doing the reboot look like this:

restart_script="#!/bin/sh
/usr/local/bin/jamf policy -event RestartScript
exit"

That actually could be why you're getting the 127: The specified service did not ship with the operating system error. It may not understand what jamf resolves to.

s_mcc
New Contributor II

@mm2270

Son of a... I always knew it was going to be a cardinal sin somewhere. That fixed it. When in doubt, full qualified paths.

Interestingly though, that's all I changed. So, it shouldn't work but does? Lots of testing before piloting this is in order.

mm2270
Legendary Contributor III

@mccreedys So it's doing the jamf reboot policy even though it's being run without root? Wow, that's... interesting. I'm really surprised honestly, but maybe there's something about running jamf commands from a launchd job that I don't know about. I didn't expect that to work.

Anyway, glad that fixed the error.

s_mcc
New Contributor II

@mm2270 I'm sure there's an XKCD somewhere about being right, followed by an immediate "I'm right?". I added a little check-in proof to add to that lingering sense of annoyance. Thanks again.

eb3901a9263341649344aee6e9fe92d5