Launch script once via Launchd Library\LaunchAgent\ and remove

New Contributor III

During imaging with DP we are copying a launchd agent .plist to /Library/LaunchAgents that runs a script on user login. After the script is complete we would like it to remove the launchd agent .plist and remove itself (/usr/bin/srm "$0"). The problem is that the script fails to remove the launchd agent due to permissions even though the loged in user is an administrator. Changing permissions on the .plist results in a "dubious permissions on file (skipping) error. Anyone have recommendations?


Valued Contributor III

Our firstrun script when it's complete executes the following code:

rm -f /Library/LaunchAgents/com.ual.casperfirstrun.plist
rm -rf /firstrun

The firstrun folder and all the files within are owned by root / wheel. Works ok for us.

Legendary Contributor III

I have seen cases where, despite the fact that LaunchAgents should be running as root or admin, you need to preface certain commands in the script with sudo. It won't or shouldn't require any authentication to do this. I believe it just escalates its privileges automatically to run the command as root. That may allow it to delete itself successfully.

Something else - there is a key called LaunchOnlyOnce you can add to the LaunchAgent, uses a boolean YES/NO string. When set to YES, it prevents it from running again until a full reboot, which may or may not be helpful in your case. Just thought I'd mention it in case.

Valued Contributor III

Ahh .. LaunchAgents do NOT run as admin or root. Only LaunchDaemons can run as that, and they're restricted from being GUI processes or running on the LoginWindow. I learnt this one the hard way. That's why you need sudo for root level tasks.

Legendary Contributor III

Yeah franton, you're probably right. I thought I had read somewhere that they ran as root as well, but I believe you are correct on this based on my own experiences with them. It would explain my experiences and also explain why the OP's script isn't able to delete itself, since it would likely need to do the srm command using sudo.

Esteemed Contributor III

@mm2270 wrote:

Something else - there is a key called LaunchOnlyOnce you can add to the LaunchAgent, uses a boolean YES/NO string. When set to YES, it prevents it from running again until a full reboot, which may or may not be helpful in your case. Just thought I'd mention it in case.

I believe Launch Agents run at login, so LaunchOnlyOnce ensures the agent only runs once for that login.

It'll run (only)once again on the next login.

Creating Launch Daemons and Agents



Legendary Contributor III

@Don, I suppose that might be possible., but the description of the key I had read said it would ensure the job only ran again after a full reboot. Maybe not though. I have never actually used that key since I haven't had a need for it.

Honored Contributor

Hey Everyone,

Launch Agents run as the user, so even an admin user would get permission errors when trying to write to something located in the /Library folder, since it is outside their home folder, and thus get prompted to enter admin credentials to modify anything in the /Library folder. This is standard Unix permissions I would think.

So, my question is, does this have to be a Launch Agent (runs as user) or can this be a Launch Daemon (run as system)? So, if a Launch Agent runs the script, it runs the script as that user, and unless that user is root they would need sudo rights to run srm $0 on anything in /Library.

A few options I can see for testing are available here, and this is what I would try

1) See if a Launch Daemon can be used
2) Move the script into the user's home folder, so they can delete it
3) Change permissions on the script so everyone can write to it (delete it)
4) Don't use a script and put all of your commands into the .plist file itself.

Would any of this scenarios work for you? I hope this helps in some capacity.



owner/group/permissions for your launchD items are KEY:

launchdaemons: 644
scripts: 777

here's a nifty image to help out if you use composer. you don't have to use composer, by the way, but it's got its fun uses....

external image link

New Contributor III

Thanks for all the suggestions! In this particular case it needs to be a LaunchAgent (or maybe a Loginhook?). We are deploying a new emergency notification application (alertus), which requires a user to manually launch the application once to be properly added to the login items for all users in 10.8 In 10.7.5 the application works just fine. I am not sure what is going on in 10.8, but it seems like the application is blocked from creating or editing the and loginwindow.plist. I even tried pre-creating the .plist settings using plistbuddy (Tom - is this what you meant in suggestion 4?), but that just confused the installer and didn't force the application to launch at login in 10.8. Lately I have had to sign a few apps with a dev cert to resolve installation issues on 10.8 even when gatekeeper is disabled. And this application doesn't appear to be written well.

During imaging we have a first boot daemon that configuring a number of settings and works well, but it currently clears out the files in the local administrative account. So while adding the launch agent and script to the administrative account's home folder did work in testing some changes will need to be made to prevent it from being deleted during the first boot process.

Honored Contributor

Hi Imperatives,

I am not familiar with this product, and was suggesting that if the script was the issue, you can actually put shell commands into the plist file itself. If you just need to it launch at login, have you looked at added Login Items via a configuration profile in the JSS? You can have a configuration profile launch apps at login if that is your desired result.

Have you tried that?


Contributor III

I also prefer the simplicity of a script that modifies the login items directly, or a configuration profile. However, if that is not possible, there are a couple other options.

You can make a LaunchDaemon *act* like a LaunchAgent and interact with the user space. You will still need to determine if someone is logged into the machine before doing anything. You could either do this check on an interval or using "WatchPaths" and watching directories/files that are modified when someone logs in and then do a check to see if there is a console user logged in. Then, run the following to start an application.

consoleUser="$(who | awk -F ' ' ' /console/ { print $1 } ')"
consolePID=$(ps -axj | awk "/^$consoleUser/ && / {print $2;exit}")
launchctl bsexec "${consolePID}" sudo -u "${consoleUser}" open /path/to/application

You can also use both a LaunchAgent, to do what you need to do, and then a LaunchDaemon to "clean up". Just have your LaunchAgent "touch" a flag file somewhere and define "WatchPaths" for your LaunchDaemon to flag off of. Verify that the flag file actually exists as the first part of your script and then have it delete everything.

LaunchAgent script:

touch /path/to/flag/file
launchctl remove

LaunchDaemon script:

if [ -f "/path/to/flag/file" ]; then
    rm /path/to/flag/file
    rm /Library/LaunchDaemons/
    rm /Library/LaunchAgents/
    launchctl remove

It's a little complex, but it should work. I glossed over a couple things, including creating the actual plist files. Let me know if you need this more explicitly laid out.

Valued Contributor

This could be simplified. Create a script to run at logout which removes both the launchd file if it exists and removes itself in the process. I've not tested the following. You may need to attempt to unload the daemon first.


if [ -f "$launchdFile" ]
    rm "$launchdFile

/usr/bin/srm "$0"

Honored Contributor

I was going to post something similar to Sean's approach. Have Launchd kick off a script, then have the last lines of the script unload and remove the launchd item, then use srm $0 to self-destruct the script as the very last line.

I am just not sure if all of this trouble is needed, but I also do not know the product we are dealing with here. Is there a way to package these things in Composer?


Contributor III

I am facing a similar dilemma, different scenario. I think I am going to take a different direction for the solution though, using Casper.

I plan on having the policy trigger at login, and having it launch the application/script. When the application/script is complete it will run a recon that will recognize the script/application has run properly through an Extension Attribute.

This policy will be set to ongoing and scoped to a smart group that has the EA marked as 'missing' or 'not installed'. So if for some reason the machine falls out of compliance, it will run again. (for instance if you are looking at something the NVRAM, a purge would clear it out and this would run again after the next recon recognizes the info is no longer there).

Hope this helps. I'm still trying to figure out how to have this only run on new machines, not on machines already in the wild. But for your scenario, it should work (running a script on everyone's machine).

New Contributor III

Thanks for all of your suggestions. We ended up packaging and deploying a complete file during the imaging process. Adding these entries alone ensures that the application launches during first login and without dealing with the launchd issues I was running into. For existing machines we are planning on pushing out the application and then a script to create the information, which would ensure that we don't overwrite a user's existing data. I'm using PlistBuddy to create the nested structure and that has gone relatively well, but I have hit a roadblock on the data items . I cannot seem to figure out how to properly write data values in the .plist such as the alias and icon data. Unfortunately it seems like without these entries the application will not launch on login. From what I understand the data items are base64-coded and even when I decode the data and add it in it never shows up properly on the target machine.

Here is what I have so far:
#!/bin/sh # The plist we are writing to:
# Look for Alertus Desktop in loginitems.plist:
PlistExists=defaults read /Library/Preferences/ | grep "Alertus"
if [ "$PlistExists" = "" ]; then

/usr/libexec/PlistBuddy -c "Add :privilegedlist:Controller string CustomListItems" $LoginItemsPlist
/usr/libexec/PlistBuddy -c "Add :privilegedlist:CustomListItems array" $LoginItemsPlist
/usr/libexec/PlistBuddy -c "Add :privilegedlist:CustomListItems:0:Alias data" $LoginItemsPlist
/usr/libexec/PlistBuddy -c "Add :privilegedlist:CustomListItems:0:CustomItemProperties dict" $LoginItemsPlist
/usr/libexec/PlistBuddy -c "Add data" $LoginItemsPlist
/usr/libexec/PlistBuddy -c "Add bool false" $LoginItemsPlist
/usr/libexec/PlistBuddy -c "Add string /Applications/Alertus" $LoginItemsPlist
/usr/libexec/PlistBuddy -c "Add :privilegedlist:CustomListItems:0:Icon data" $LoginItemsPlist
/usr/libexec/PlistBuddy -c "Add :privilegedlist:CustomListItems:0:Name string Alertus Desktop" $LoginItemsPlist
exit 0

This is what I am attempting to achieve:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "">
<plist version="1.0">
<dict> <key>privilegedlist</key> <dict> <key>Controller</key> <string>CustomListItems</string> <key>CustomListItems</key> <array> <dict> <key>Alias</key> <data> AAAAAAC6AAMAAQAAzWj24gAASCsAAAAAAAAASgAKDWMA AMqYC6wAAAAACSD//gAAAAAAAAAA/////wABAAQAAABK AA4AKAATAEEAbABlAHIAdAB1AHMAIABEAGUAcwBrAHQA bwBwAC4AYQBwAHAADwAaAAwATQBhAGMAaQBuAHQAbwBz AGgAIABIAEQAEgAgQXBwbGljYXRpb25zL0FsZXJ0dXMg RGVza3RvcC5hcHAAEwABLwD//wAA </data> <key>CustomItemProperties</key> <dict> <key></key> <dict> <key>AliasData</key> <data> AAAAAAC6AAMAAQAAzWj24gAASCsA AAAAAAAASgAKDWMAAMqYC6wAAAAA CSD//gAAAAAAAAAA/////wABAAQA AABKAA4AKAATAEEAbABlAHIAdAB1 AHMAIABEAGUAcwBrAHQAbwBwAC4A YQBwAHAADwAaAAwATQBhAGMAaQBu AHQAbwBzAGgAIABIAEQAEgAgQXBw bGljYXRpb25zL0FsZXJ0dXMgRGVz a3RvcC5hcHAAEwABLwD//wAA </data> <key>Hide</key> <false/> <key>Path</key> <string>/Applications/Alertus</string> </dict> </dict> <key>Icon</key> <data> SW1nUgAAAPQAAAAARkJJTAAAAOgAAAACAAAAAAAAAAAA 2AADAAAAAM1o9uIAAEgrAAAAAAAKDWkACg1sAADKXHns AAAAAAkg//4AAAAAAAAAAP////8AAQAQAAoNaQAKDWQA Cg1jAAAASgAOABoADABBAGwAZQByAHQAdQBzAC4AaQBj AG4AcwAPABoADABNAGEAYwBpAG4AdABvAHMAaAAgAEgA RAASAEBBcHBsaWNhdGlvbnMvQWxlcnR1cyBEZXNrdG9w LmFwcC9Db250ZW50cy9SZXNvdXJjZXMvQWxlcnR1cy5p Y25zABMAAS8A//8AAA== </data> <key>Name</key> <string>Alertus Desktop</string> </dict> </array> </dict>