MacOS – Run Script on Logout Without Use of Logout Hook

launchdmacosscript

I've been looking into running a few cleanup commands when a user logs out, however the old logout hook feature, although still functioning, has been deprecated for some time now so may not be with us for much longer.

Unfortunately, although launchd provides a convenient alternative to login hooks, there is no such obvious replacement for logout hooks.

I've already experimented with creating a shell script that is launched on login, and simply sleeps until a kill signal is received, however this doesn't seem to work (the script never receives the signal during normal operation).

Otherwise I'm not sure what the best way to run a quick command on logout would be? I know there are some third party utilities that can do it, but is there a "correct" way to do this anymore?

Best Answer

It seems that Apple isn't interested in a logout hook replacement, as they closed my issue inquiring about one.

However, one of the improvements in Yosemite is that launchd now properly sends signals down to shell scripts. What this means is that you can now do a log-out task like so:

Here's an example logout.sh:

#!/bin/sh
onLogout() {
    echo 'Logging out' >> ~/Logs/logout.sh.log
    exit
}

trap 'onLogout' SIGINT SIGHUP SIGTERM
while true; do
    sleep 86400 &
    wait $!
done

This will simply sleep (asynchronously, doing it synchronously without the ampersand doesn't seem to work) until it receives one of the trapped signals, at which point it will execute the onLogout function.

All you need to do is launch that script using a RunAtLoad launch agent or launch daemon and it will run at log-out or shutdown, though it's important to bear in mind that tasks only have a limited amount of time to complete before they are killed instead, so this shouldn't be used to run anything that takes a long time, or requires a network connection that could be delayed etc.

Of course this is of no use to anyone on Mavericks or earlier, but under Yosemite this now seems to work as expected; so I was actually doing it right in the first place, launchd just wasn't sending the signals properly :)

NOTE: For this to work the shell scripts seems to need to be executed directly by launched, i.e - it shouldn't invoked via sh. So if it were placed in ~/Library/Scripts/foo.sh your program arguments might look like:

<key>ProgramArguments</key>
<array>
    <string>~/Library/Scripts/foo.sh</string>
    <string>bar</string>
</array>
<key>EnableGlobbing</key>
<true/>