LaunchAgent to shutdown idle Macs

t_bissinger
New Contributor II

Hi there,

I'm struggling. We are a vocational school (~1000 devices) and some of our students don't shutdown their Macs correctly (every day). They are still logged in or just did a logout but didn't shut down the machine.

So I decided to script against that problem to save energy. My solution is:

  • a script is invoked by an LaunchAgent every 5 Minutes   
  • the script checks the iMacs idle status 
  • if it is more than 20 min a jamfhelper dialog is shown to inform the user (with a timer to react)
  • if the dialog it not canceled it (should) shutdown the Mac

I decided to use an LaunchAgent, because the dialog should also be shown if nobody is logged in (loginwindow).

The problem is: shutdown has to be invoked with root privileges and LaunchAgents runs in user-context.

Does anybody have an idea to get around this without giving all students root privileges (they aren't in the soduers file)

 

 

8 REPLIES 8

jamf-42
Valued Contributor II

t_bissinger
New Contributor II

Thanks a lot, but I think: Unfortunately, no. 

pmset is only active if a user is logged in. But I need a solution for the loginwindow too.

 

AJPinto
Honored Contributor II

 

Deleted

AJPinto
Honored Contributor II

You can have your LaunchDaemon call a script, and within the script have admin creds (hopefully salted) to run the shutdown command. You can also adjust sudoers to allow shutdown to be run without admin access.

 

I would just use a configuration profile to turn off the Macs at a given time, and another one to turn them back on. For example shut the macs down at 9pm and turn them back on at 8am. The configuration profile is the "Apple Way" and most tamper resistant for sure as well as being easily backed off if needed.

 

Though, truth be told. How much power are we really saving? At idle these Macs draw 43w which is around $40 a year if the device is up 24/7. To me not being able to run OS updates which apple choses to install overnight with no way to change that behavior will mitigate the majority of potential savings, especially with the slue of 0 days we have seen from Apple of late.

 

AJPinto_0-1676466787322.png

 

t_bissinger
New Contributor II

The launchDeamon can not start a GUI in the Loginwindow. I tried to trigger an launchDeamon event through an launchAgent (via WatchPaths) but didn't get far. 

ok. I'm not able to argue against the 0-days. Till now we didn't have any incidents. 

To shutdown the machines at a given time is - from an energy saving perspective - not an option. There are ~50 Macs which are not shutdown ... every day.  This will sum up.

But thanks for your suggestions. Maybe a configuration profile will be an option if I don't find an other solution.

theandyhamilton
New Contributor

I have an interest in this exact capacity (context: public library Macs, must be restarted/logged off between users for privacy) and tried to script my own solution, but to no avail. Closest I got was:

#!/bin/bash
exec 2>&1

# Get MacOSX idletime. Shamelessly stolen from http://bit.ly/yVhc5H
idleTime=$(/usr/sbin/ioreg -c IOHIDSystem | /usr/bin/awk '/HIDIdleTime/ {print int($NF/1000000000); exit}')

echo "Idle Time is idleTime seconds."

if (( idleTime > 600 )); then
echo "Your computer has been idle for 10 minutes. Please move the mouse or press a button on your keyboard to avoid an automatic shutdown."
fi

sleep 95s;
idleTime=$(/usr/sbin/ioreg -c IOHIDSystem | /usr/bin/awk '/HIDIdleTime/ {print int($NF/1000000000); exit}')
if (( idleTime > 90 )); then
sudo shutdown -r now
else exit 0
fi

and... it wasn't that close. It would flash the warning, but then nothing further would happen. If you solve this, I'd love to see what you came up with!

Hi.

Well, there is some progress. I decided to leave launchAgents and launchDeamons behind me and tried it the Jamf way.

The idea is:

  • a ongoing Jamf policy which starts a script with recurring checkin
  • it checks if the idle time is over max and if a user is logged in
  • is no user logged in and the max idle is hit ->  shutdown without user interaction
  • if there is a user and the max idle is hit -> show a jamfHelper with an timeout where the user can cancel the shutdown process
  • if there is no user interaction with the dialog -> shutdown if the countdown is 0

My first tests seems to work. But: there is a drawback!

If the user locked the Mac and leaves for some time, he is still logged on and the dialog will not be visible in the lock .. but functional. So it will shutdown ... not good. For this particular scenario I'm still searching a solution.

here is my script so far. (some parts are also shameless stolen, but I didn't find the authors --- shame on me)

Hope this helps.

#!/bin/bash
jamfHelper="/Library/Application Support/JAMF/bin/jamfHelper.app/Contents/MacOS/jamfHelper"
idleTimeCurrent=$(/usr/sbin/ioreg -c IOHIDSystem | /usr/bin/awk '/HIDIdleTime/ {print int($NF/1000000000); exit}')
userList=`/usr/bin/w | grep console | awk '{print $1}'`

action="shutdown -h now"
#action="$jamfHelper -windowType utility -heading 'Fahre runter!' -button1 'OK'"
#cancel="$jamfHelper -windowType utility -heading 'Abgebrochen' -button1 'OK'"

#Set "Window Type" Note: Your choices include utility, hud or fs
windowType="utility"
#Set to "Logo". Note: This can be BASE64, a Local File or a URL. If a URL the file needs to curl down prior to jamfHelper
icon="/System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/AlertStopIcon.icns"


# This script will call the jamfHelper to prompt a user to shutdown their computer if idle. The Parameters will be set in the Policy.
#Set Parameter 4 to "Idle Time". Note: Sets the Time in Minutes before the Window goes up
IDLE_M="$4"
# convert to seconds
idleTime=$(($IDLE_M * 60)) 
#Set Parameter 5 to "Timeout" - Note: Sets the timeout of the script (seconds)
TIMEOUT_M="$5"
# convert to seconds
timeout=$(($TIMEOUT_M * 60)) 
#Set Parameter 6 to "Window Title"
title="$6"
#Set Parameter 7 to "Window Heading"
heading=$7
#Set Parameter 10 to "Button2"
#Note: Double digit Parameters should be enclosed with {}
button2="$8"
# This will set a variable for the jamfHelper
description="Der Computer ist seit über $IDLE_M Minuten unbenutzt. Er wird in $TIMEOUT_M Minuten automatisch heruntergefahren. Ungesicherte Daten gehen dabei verloren. Wähle Abbrechen um dies zu verhindern."



####### JUST TESTING VALUES ... didnt want to wait ####
#idleTime=20
#timeout=15
################################

# shutdown if no user is logged in within a specified time
if (("$idleTimeCurrent" >= "$idleTime" )) && test -z "$userList"
then
	echo "No user, $idleTimeCurrent idle seconds hit its max of $idleTime seconds. shutting down "
	$action
    exit 0
fi

# if a User is logged in AND the max idle is hit -> show Dialog
if (("$idleTimeCurrent" >= "$idleTime" )) && test -n "$userList"
then
	echo "we have a user and $idleTimeCurrent idle Seconds hit its max $idleTime. Waking monitor and show dialog"
	# wake monitor
    #/usr/bin/caffeinate -u -t 300
    # Show User Dialog
    # This will set a variable for jamfHelper to do an action...
	userChoice=$("$jamfHelper" -windowType "$windowType" -icon "$icon" -timeout "$timeout" -countdown -title "$title" -heading "$heading" -description "$description" -button2 "$button2" -defaultButton 1 -cancelButton 2)
	echo "user has ended with code $userChoice"
else
	echo "we have a user, but $idleTimeCurrent idle Seconds did not hit its max $idleTime. Stop the script "
	exit 0
fi

# if the timeout has gone jamfhelper will exit with 0 ... so we ara shutting down
if [[ "$userChoice" == "0" ]]; then
#PUT YOUR ACTION HERE - THIS IS JUST AN EXAMPLE
#$ok=$("$jamfHelper" -windowType utility -heading 'Fahre runter!' -button1 'OK')
 echo "we have a user and $idleTimeCurrent Idle seconds hit its max $idleTime seconds and timout is gone. shutting down"
 $action
else
	echo "something went wrong. jamfHelper exitcode $userChoice"
	exit 0
fi
exit 0

 

 

Awesome! I am testing this in my test environment and will let you know how it works. Thank you!