How can I use launchd to run logout script

CapU
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

17 REPLIES 17

bentoms
Release Candidate Programs Tester

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

Pinging him in the hope he picks this up.

Bruienne
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.

LaunchAgent: https://gist.github.com/bruienne/7fb612c413216ccea86b
Logout watch script: https://gist.github.com/bruienne/7fb612c413216ccea86b

Your script should go inside the onLogout() function.

gachowski
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... : )

C

PeterClarke
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 com.apple.loginwindow logoutHook path/to/my/logoutscript.sh

But before you do that use:

sudo defaults read com.apple.loginwindow

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:

MyLogout_Script.sh My_Logout_Stuff JAMF_Logout

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

PeterClarke
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..

haircut
Contributor

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?

Nix4Life
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

fantastic!

LSinNY

talkingmoose
Moderator
Moderator

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.

PeterClarke
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..

calumhunter
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: https://gist.github.com/bruienne/7fb612c413216ccea86b Logout watch script: https://gist.github.com/bruienne/7fb612c413216ccea86b Your script should go inside the onLogout() function.

alex_drinkwater
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?

hodgesji
Contributor

@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?

bkvines
New Contributor III

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

https://gist.github.com/bruienne/0cce2d93687985c8df14

ski
New Contributor II

The one problem with the logoutwatcher.sh 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 logouthook.sh file exists
if [ -e /etc/nsd/files/runlogoutwatcher ]; then
  echo "Touching file to trigger launchdaemon"
  touch /tmp/studentloggedout   
  echo "Done!"
fi
exit   
}

echo "$(date): Watching for student logout. Waiting..."
trap 'onLogout' SIGINT SIGHUP SIGTERM
while true; do
  sleep 86400 & 
  wait $! 
done

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" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>org.nsd.studentsync.plist</string>
    <key>ProgramArguments</key>
    <array>
        <string>/etc/nsd/files/studentsync.sh</string>
    </array>
        <key>WatchPaths</key>
    <array>
        <string>/tmp/studentloggedout</string>
    </array>
</dict>
</plist>

macsadmin
New Contributor

Works on shutdown but not logout

etsang
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.

srhyne
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

        targetUser="${myUser2}"
        reason="logout"

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

        targetUser="${myUser0}"
        reason="crash"

    else
        targetUser=""
    fi

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

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

            # Logout code goes here
    fi

done