How can I use launchd to run logout script

Contributor III

Good afternoon fellow geeks

We need to move away from login/logout hooks. I have been researching using launchd and think I have a good start for using launchd to run a script but now I want to run something when the user logs out ( I want to un-mount some network shares)
Does anyone have any ideas?

Thanks in advance


Release Candidate Programs Tester

@CapU I think @Bruienne has stumbled across a method.

Pinging him in the hope he picks this up.

New Contributor II

We've implemented a logout watcher with a LaunchAgent for cleaning user directories on shared Macs, but you can easily modify the following examples to do $thingyouneed.

Logout watch script:

Your script should go inside the onLogout() function.

Valued Contributor II

I asked the same question a few years ago and there was no answers to you are not going to find a lot of options... : )


Contributor II

One of the easiest ways to run a LogOUT script is via the logout hook..
( - which is not using Launchd.. )

sudo defaults write logoutHook path/to/my/

But before you do that use:

sudo defaults read

and look to see what if any logout hooks are already defined..
- you will need to continue supporting those functions too
for instance JAMF have a logout script

You could do: My_Logout_Stuff JAMF_Logout

So that any JAMF logout items are still called at user logout

Contributor II

I have used a LaunchDaemon, to run a system startup script.
One of whose tasks, is to examine the Login and logout hooks, an exact a repair of them if they have been disturbed by some other install, so that they are returned back to a known state.

I would guess that would be the main reason why you want to "move away from using a logout hook" ?

So although that also does not exactly answer your question..
But I think is a helpful contribution to this area of discussion, and shows one resolution of the issue.

LaunchDaemons don't seem to offer logout options, least not that I have spotted so far..


I'm probably uninformed, but is there a reason not to use Casper's login/logout hooks? Why are you transitioning away – is it specific to your environment or a philosophical choice?

Valued Contributor

@PeterClarke Great tip. i am not using loginhooks now , but good to know, and the comment about the state of the file, just gave me an idea with puppet




Casper already places login and logout hooks on a system. All you need to do is create a policy that triggers at Logout and maintain that in your JSS.

Contributor II

We have had people with admin access manually install things that have as a side effect blown-away the login and logout hooks, so now i detect that condition, and repair the login and logout hooks..

Without that JAMF Logout script would otherwise no longer be called..
It's a bit of an odd case - but that's what you get when you work in education..
People sometimes installing things that could potentially break the system..

Valued Contributor

I don't know why people in this thread have completely missed the logout watcher launch agent that bruienne posted earlier...

It does the job the OP asked for. Run a script at logout, via a launchdaemon.

In case you missed it

Posted: 10/30/15 at 7:05 PM by Bruienne We've implemented a logout watcher with a LaunchAgent for cleaning user directories on shared Macs, but you can easily modify the following examples to do $thingyouneed. LaunchAgent: Logout watch script: Your script should go inside the onLogout() function.

New Contributor

@Bruienne does your solution run successfully at logout if the machine is shut down? I'm imagining in this scenario, the system may go down before the script has finished executing, leaving the machine in a potentially unknown state. Is this actually the case?


@Bruienne Looking to use your method but I can't find a copy of your logout watch script. Can you point me in the right direction please?

New Contributor III

@hodgesji I found @Bruienne's logout watcher script here:

New Contributor II

The one problem with the script is that a long process (e.g. resetting a common student directory by copying over a lot of files) will not finish before the logout process kills the script. the work around is a two step process:

  1. Change the logoutwatcher to touch a file in /tmp:
# Script to touch a file when the student user logs out
# This file is watched by a launchdaemon that then resets the student user
# 170908-ski: initial version
onLogout() {
#Redirect STDOUT and STDERR to the desired logfile
exec > /tmp/nsd.studentlogout.log 2>&1
#Compare command to find if file exists
if [ -e /etc/nsd/files/runlogoutwatcher ]; then
  echo "Touching file to trigger launchdaemon"
  touch /tmp/studentloggedout   
  echo "Done!"

echo "$(date): Watching for student logout. Waiting..."
while true; do
  sleep 86400 & 
  wait $! 

Then set up a LaunchDaemon to look for changes to the /tmp file and run whenever it sees a change. Since Launch daemons are not dependent on the logout process they are not time limited:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "">
<plist version="1.0">

New Contributor

Works on shutdown but not logout

New Contributor

Does the latest method posted by ski still work in Mojave? It seems like launchd stops watching /tmp/studentloggedout after shutdown/restart has begun. touch /tmp/studentloggedout seems to trigger the script only before a shutdown/restart.

New Contributor

Knowing that Login/Logout Hooks would eventually disappear, I pivoted to using LaunchAgents years ago to cover login actions, but have been watching and waiting for an "official" replacement for the Logout Hook. When the Jamf Pro interface started putting a warning icon next to my policies that use a Logout trigger, i.e. that rely on a Logout Hook, I started looking for an alternative, which brought me to this thread.

I tried the method posted here by ski in macOS 10.15 Catalina, but couldn't get it to work, so I came up with my own scheme, and it seems to be working pretty well so far; I'm still testing it. It's a LaunchDaemon that runs a bash script that continuously parses the output of the 'last' command, and takes action when it detects that a user has logged out. The caveats are: (1) Because it's a LaunchDaemon, it runs with root permissions. (2) As written, it won't work if more than one user is logged in at a time (via Fast User Switching), but a better script-writer could probably make that work.

Here's the gist. Can anyone think of any reasons why this is a bad idea? Would any script-writers care to pilfer and improve?:

echo "Watching and waiting for a user to logout..."

while true ; do

    # Case 1: If the first line of 'last' contains "reboot" and the second line contains "crash",
    # then assume the username on the "crash" line inelegantly logged out:

    myTest0=$( last -2 )
    myTest0Line1=$( echo "${myTest0}" | sed -n '1 p' | grep -i "reboot")
    myTest0Line2=$( echo "${myTest0}" | sed -n '2 p' | grep -i "crash")
    myUser0=$( echo "${myTest0Line2}" | awk -F " " '{ print $1 }' )

    # Case 2: Compare the most recent line of 'last' to the same line one second later.
    # If the user names are the same and the status has changed from "still logged in"
    # to a login duration "(HH:MM)", then assume the user just logged out:

    myTest1=$( last -1 -t console )
    myUser1=$( echo "${myTest1}" | awk -F " " '{ print $1 }' )
    sleep 1
    myTest2=$( last -1 -t console )
    myUser2=$( echo "${myTest2}" | awk -F " " '{ print $1 }' )

    # Case 2 Check:
    if [[ $( echo "${myTest1}" | grep -i "still logged in" ) ]] && 
       [[ $( echo "${myTest2}" | grep -i "(.*:.*)" ) ]] && 
       [[ "${myUser1}" = "${myUser2}" ]] ; then


    # Case 1 Check:
    elif [[ "${myTest0Line1}" ]] && [[ "${myTest0Line2}" ]] ; then



    # Take action:
    if [[ "${targetUser}" ]] ; then

        echo "${targetUser} logged out. [Reason: ${reason}]"

            # Logout code goes here