I could not find a way of executing a script for the Guest account during login which executes every minute. They say using cron daemon is deprecated so it looks like I will be using launchd with .plist files.

Scenario: I have a public iMac I want to allow the general public to use the guest account and have it force log out every half-hour. I wrote a ruby script to check the login time and figure out time remaining. I can have it display a banner notification every 10 minutes using osascript and then make it log off my account. Problem is when I try to implement it for the Guest account it doesn't work.

The problem is when I am placing the .plist file inside /Library/LaunchDaemons since it runs after login and also runs as root. Running as root is important since I can have the privilege to shut down processes when time runs out. I need it to execute once every minute. This it the current plist file that does work when I login as my own username "owner" but not guest. Using org.user.plist

My original .plist file looked something like this

<?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.user</string> 
    <key>Program</key> 
        <string>/usr/local/bin/notify-custom</string> 
    <key>RunAtLoad</key> 
        <true/> 
</dict> 
</plist>

Update 1 (Not a solution yet) .plist file which runs every 10 seconds for both Guest and my username

<?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.user</string>
    <key>ProgramArguments</key>
        <string>/usr/local/bin/notify-custom</string>
    <key>WatchPaths</key>
        <array>        
            <string>/Users/Guest/Library</string>
            <string>/Users/owner/Library</string>
        <array>
    </integer>
</dict>
</plist>

As a test to make sure the osascript notification banner pops up I have this code inside /usr/local/bin/notify-custom

#/bin/bash

#Using whoami would have shown me logged in as root under LaunchDaemon .plist
loggedinUser=`finger | awk 'NR==3{print $1}'`
#I need to manually run terminal and type sudo as guest for nextline to work
sudo -u $loggedinUser /usr/bin/osascript -e 'display notification "Test" with title "Banner Notification"'

Solution is below.

  • The .plist file you're showing couldn't possibly load as there is neither a Program nor a ProgramArguments key specified. – user3439894 Aug 11 '16 at 2:09
  • Sorry about that I know I forgot to paste that section. See revision above. (Making me think maybe I should try using the key ProgramArguments instead followed by just a single array element being the program name since it takes no other argument.) – Mickey D Aug 11 '16 at 2:11
  • What is notify-custom and is it a binary or a script, and if the latter what's the contents of the script? – user3439894 Aug 11 '16 at 2:14
  • It's a ruby script. It's at work. Will post it here tomorrow if you think it will help. – Mickey D Aug 11 '16 at 3:07
  • I just tested with my own .plist file, owned by root in the wheel group with 0644 for permissions in /Library/LaunchAgents and it ran when logged in as Guest. With the exception of the string for the Program key the rest of the file was the same. It worked as expected. I also look at the other LaunchAgents in the same location and checking in Activity Monitor their processes started too. So without knowing what notify-custom is and its contents there's not much else I can offer at the moment other that to say .plist files in /Library/LaunchAgents work in the Guest Account. – user3439894 Aug 11 '16 at 3:08

In my opinion the following should work - it does in my VM! - launched as /Library/LaunchDaemons/org.user.plist:

<?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.user</string>
    <key>ProgramArguments</key>
    <array>
        <string>/usr/bin/touch</string>
        <string>-f</string>
        <string>/Users/Guest/Desktop/test.txt</string>
    </array>
    <key>UserName</key>
    <string>Guest</string>
    <key>GroupName</key>
    <string>_guest</string>
    <key>InitGroups</key>
    <true/>
    <key>WatchPaths</key>
    <array>
        <string>/Users/Guest/Library</string>
    </array>
</dict>
</plist>

As example task I use /usr/bin/touch -f /Users/Guest/Desktop/test.txt.

The trick here is that the complete content of the Guest folder is deleted after a log out. After a new Guest logs in the whole content is recreated from scratch. As soon as the folder /Users/Guest/Library is created, the example task (touch ...) is started due to the WatchPaths key.

Since the task/script/app has to be executed as guest you can't use launch agents because the path /Users/Guest/Library/LaunchAgents/ simply doesn't exist.

Use a launch daemon instead and run it as Guest/_guest. Your ruby script /usr/local/bin/notify-custom has to be world readable/executable? of course.


I also tried to run the task every 60 seconds - which works properly but throws some errors after the guest's log out. Probably it's better to implement the whole thing in the ruby script. Depending on your script your mileage may vary though.

If you have two different tasks to execute (e.g show a banner every 10 minutes with Ruby and a timer to force log out after 30 minutes) it's probably better to create two different launch daemons.

  • You said, "Since the task/script/app has to be executed as guest you can't use launch agents because the path /Users/Guest/Library/LaunchAgents/ simply doesn't exist." and while that's true nonetheless my .plist ran just fine from within the Guest Account when run from /Library/LaunchAgents and without the use of WatchPaths key as well as other LaunchAgents did from the same location. – user3439894 Aug 11 '16 at 3:59
  • @user3439894 Hmm I'm very keen to see notify-custom... ;-) – klanomath Aug 11 '16 at 4:05
  • @user3439894 An additional problem of using a launch agent is that the agent/script is also executed logging in as normal user - which is probably unwanted. – klanomath Aug 11 '16 at 16:22
  • On the machine I tested this on I have 3 LaunchAgents in /Library/LaunchAgents, the one I created for this test and 2 existing ones that were added by applications running on the system, e.g. Little Snitch. So, in general I do not see an issue using a LaunchAgents to take action in the Guest Account. That said, it certainly may depend on exactly what one is trying to do that may make using a LaunchAgents in /Library/LaunchAgents in this use case scenario not ideal. I can only go by my testing and it worked for me in the manner I tested. Don't know what else I can say. – user3439894 Aug 11 '16 at 16:29
up vote 0 down vote accepted

Solved. I have been working on this for a while. My solution finally does as I need and that is it starts up during login for the user Guest (and as an option I also have it starting up for myself user iMac1 just to display logged in time). I didn't see a simple way of putting the org.user.plist file in /Users/Guest/Library/LaunchAgents which would have theoretically launched it when the Guest logged in and the reason I gave up on that situation is since that folder isn't created until login.

What I did was put my .plist file it inside /Library/LaunchAgents/ which gets run for every user. That's fine since my code will distinguish the Guest user and take action (in this case log them out after set time.)

The final .plist file:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
        <string>org.user</string>
    <key>ProgramArguments</key>
    <array>
        <string>/usr/local/bin/notify-custom</string>
    </array>
    <key>RunAtLoad</key>
        <true/>
    <key>StartInterval</key>
        <integer>60</integer>
</dict>
</plist>

Notice I added a RunAtLoad key since without it the script did run but waited a minute to fire it's first event. If instead I used WatchPaths key as @klanomath noted in his comment, then the script would run every 10 seconds since their must be activity happening in that folder regularly. I just wanted it to run every 60 seconds for now. I might change that timer later when I clean up the whole procedure with some more colorful warning dialogs written in Python.

Here is the ruby code inside /usr/local/bin/notify-custom that gets executed for every user login:

#!/usr/bin/ruby -w

require 'time'
require 'FileUtils'

loggedinUser=`finger|awk 'END{print $1}'`.strip
getloginTime=`finger|awk 'END{print}'|cut -c49-53`
getnowTime=`date|awk 'NR==1{print $4}'`[0..4]
loginTime=(Time.parse(getloginTime).to_i)
nowTime=(Time.parse(getnowTime).to_i)
diffSec=(nowTime-loginTime)
diffMin=(diffSec/60)
timeRemain=30-diffMin

#To see some console output while debugging
puts "getloginTime      =#{getloginTime}"
puts "getnowTime        =#{getnowTime}"
puts "loginTime=#{loginTime}"
puts "nowTime  =#{nowTime}"
puts "timeRemain=#{timeRemain}"

if loggedinUser == "Guest"
        open("/Users/#{loggedinUser}/Desktop/30 Minutes Max Use Per Day",'a'){|f| f.puts "With this new iMac, you are limited to a maximum of 1/2 hour use per day"}
        if timeRemain < 0
            `/usr/bin/osascript -e 'tell application "Finder" to set desktop picture to POSIX file "/Library/Desktop Pictures/Earth Horizon.jpg"'`
            `/usr/bin/osascript -e 'display notification "SHUTTING DOWN! Now= #{getnowTime}   LoggedInAt=#{getloginTime}   TimeRemain=#{timeRemain}" with title "Guest SHUTTING DOWN" sound name "Glass"'`
            `/usr/bin/osascript -e 'tell app "Terminal" to do script "sudo shutdown -h now"'`
        else
            `/usr/bin/osascript -e 'display notification "Now= #{getnowTime}     TimeRemain=#{timeRemain}" with title "#{loggedinUser} TIME LOGGED IN= #{getloginTime}" subtitle "User= #{loggedinUser}"'`
        end
else
        `/usr/bin/osascript -e 'display notification "Now= #{getnowTime}     TimeRemain=#{timeRemain}" with title "#{loggedinUser} TIME LOGGED IN= #{getloginTime}" subtitle "User= #{loggedinUser}"'`
end

Again note that if you use the LaunchDaemons instead, they run under the system root account while the Agents run on the logged in users account. Using the 2nd option I had to give the Guest user permission to run sudo shutdown as @klanomath mentioned below. This was done with running the command: $sudo visudo and adding the following to the end of the file:

Guest ALL=NOPASSWD: /sbin/shutdown

Also I wanted to only show the Guest account on the log on page so I hid my account from that screen with this command:

sudo dscl . create /Users/hiddenuser IsHidden 1

and if you change your mind you can bring it back with :

sudo dscl . create /Users/hiddenuser IsHidden 0

Thanks @klanomath and @user3439894

Your Answer

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Not the answer you're looking for? Browse other questions tagged or ask your own question.