Check Machine Uptime and Bug Users

stevewood
Honored Contributor II
Honored Contributor II

I've posted a version of this script before but did some updates and wanted to re-post. This script will grab the uptime of a machine, and if past a set minimum number of days, will use cocoaDialog to bug the user to restart. If the machine is up past a set maximum number of days, the script will bug them with cocoaDialog and will start sending emails to them.

The script uses the JSS API (thanks to @brysontyrrell for his article on the JSS API) to grab the user's real name and email address to send them a message.

You can find the script on Github here: https://github.com/stevewood-tx/CasperScripts-Public/tree/master/checkUpTime

The link to Bryson's article: http://bryson3gps.wordpress.com/2014/03/30/the-jss-rest-api-for-everyone/

And here's the script:

#!/bin/sh

# Name: checkUpTime.sh
# Date:  19 Aug 2014
# Author:  Steve Wood (swood@integer.com)
# Purpose:  look for machines that have not been restarted in X number of days.
# Requirements:  cocoaDialog on the local machine
#
# How To Use:  create a policy in your JSS with this script set to run once every day.

## Global Variables and Stuff
logPath='/path/to/store/log/files'  ### <--- enter a path to where you store log files locally
if [[ ! -d "$logPath" ]]; then
    mkdir $logPath
fi
set -xv; exec 1> $logPath/checkUpTime.txt 2>&1
version=1.0
CD="/path/to/cocoaDialog.app/Contents/MacOS/cocoaDialog" ### <--- path to where you store cocoDialog on local machine
NC='/Library/Application Support/JAMF/bin/Management Action.app/Contents/MacOS/Management Action'
jssURL='https://YOUR.JSSSERVER.COM:8443' ### <--- enter your JSS URL
apiUser="<APIREADUSER>"   ### <--- enter your API user
apiPass="<APIREADUSERPASS>"  ### <--- enter your API user password
serNum=$(ioreg -l | grep IOPlatformSerialNumber | awk '{print $4}'| sed 's/"//g')
cdTitle="Machine Needs A Restart"
loggedInUser=`/bin/ls -l /dev/console | /usr/bin/awk '{ print $3 }'`

## set minDays - we start bugging users at this level with just a dialog box
minDays=7

## set maxDays - after we reach maxDays we bug with dialog box AND email
maxDays=15

## Grab user info ##
### Thanks to Bryson Tyrrell (@bryson3Gps) for the code to parse
info=$(curl -s -k -u $apiUser:$apiPass $jssURL/JSSResource/computers/match/$serNum)
email=$(echo $info | /usr/bin/awk -F'<email>|</email>' '{print $2}')
realName=$(echo $info | /usr/bin/awk -F'<realname>|</realname>' '{print $2}')

#### MAIN CODE ####
days=`uptime | awk '{ print $4 }' | sed 's/,//g'`  # grabs the word "days" if it is there
num=`uptime | awk '{ print $3 }'`  # grabs the number of hours or days in the uptime command

## set the body of the email message
message1="Dear $realName"
message1b="Your computer has now been up for $num days.  It is important for you to restart your machine on a regular"
message2="basis to help it run more efficiently and to apply updates and patches that are deployed during the login or logout"
message3="process."
message3a="Please restart your machine ASAP.  If you do not restart, you will continue to get this email and the pop-up"
message4="dialog box daily until you do."
message5="FROM THE IT STAFF"  ### <---  change this to whomever you want

## now the logic

if [ $loggedInUser != "root" ]; then
    if [ $days = "days" ]; then

        if [ $num -gt $minDays ]; then

            if [ $num -gt $maxDays ]; then

                cdIcon="/private/var/inte/icons/redX.icns"
                cdText="Your computer has not been restarted in more than $maxDays days.  Please restart ASAP.  Thank you."

                bubble=`$CD bubble --title "$cdTitle" --no-timeout --text "$cdText" --icon-file $cdIcon`

                if [ $email != "" ]; then

                    echo "$message1

$message1b
$message2
$message3

$message3a
$message4


$message5" | mail -s "URGENT: Restart Your Machine" $email

                fi
            else

                cdIcon="/private/var/inte/icons/ProblemReporter.icns"
                cdText="Your computer has not been restarted in $num days.  Please restart ASAP.  Thank you."
                bubble=`$CD bubble --title "$cdTitle" --no-timeout --text "$cdText" --icon-file $cdIcon`

            fi
        fi
    fi
fi


exit 0
20 REPLIES 20

perrycj
Contributor III

Good stuff man, thanks for posting.

ToriAnneke
Contributor II

Hey all,

This is pretty cool.

I just ran a test and went to check the log file the script creates and there in plain text is the API user name and password.

Is there a way to stifle this? Is the log file necessary?

Thanks as always,
-pat

-pat

ToriAnneke
Contributor II

Ok. Was able to comment out lines in order to stop the log file.

### logPath='/Library/Logs'  ### <--- enter a path to where you store log files locally
### if [[ ! -d "$logPath" ]]; then
###     mkdir $logPath
### fi
### set -xv; exec 1> $logPath/checkUpTime.txt 2>&

Which is fine for me cause I really don't need to the log file. At least I don't think I do.

Then another error pops up, which has no bearing on the log file. Cause I was seeing the error before I commented the above lines:

Script result: /Library/Application Support/JAMF/tmp/checkUptime.sh: line 66: [: !=: unary operator expected

Which points to:

if [ $email != "" ]; then     
echo "$message1

$message1b
$message2
$message3

$message3a
$message4


$message5" | mail -s "URGENT: Restart Your Machine" $email
fi

I can comment the above lines out and the error goes away. But I do like the idea of email the user.

Thanks again!!
-pat

mm2270
Legendary Contributor III

Just FYI, you don't need to hardcode the API username and password into a script. You can assign them to parameters, like $4 and $5 respectively, and pass that down to the script from the JSS when it runs. That's always what I do. That way, if someone actually gets their hands on the script they still wouldn't see a username and password in it.

Regarding the unary operator error, there are two things you can try changing to fix that.
The first if to use double brackets, like so

if [[ $email != "" ]]; then

or, quote the $email variable

if [ "$email" != "" ]; then

Either one may help stop that error.

stevewood
Honored Contributor II
Honored Contributor II

@mm2270 you're absolutely right. I didn't pass it as a $ variable for no real reason. Just being lazy, I guess. :-)

As for the unary operator error, I'll probably just change it to this:

if [ $email ]; then

Since really all I am testing for is if the email address is blank.

ToriAnneke
Contributor II

I just tried using the $4 and $5 and yes, it does work!!

For the email part, I've tried both ways:

if [[ $email != "" ]]; then

or

if [ "$email" != "" ]; then

And both no longer produce the error I posted.

But I don't receive the email even though my machine is 32 days uptime.

-pat

stevewood
Honored Contributor II
Honored Contributor II

@pvader I'm assuming your email address is filled out in the JSS for your computer record, correct? You can troubleshoot by checking the mail log on your machine. It is located in /var/log/mail.log. Check to see if the email is getting sent out or if an error is popping up.

ToriAnneke
Contributor II

@stevewood

I do have a correct email entry that computer record. So that is good.
I do not have /var/log/mail.log on the client machine nor the JSS server (which is an Ubuntu 12.04 flavour.)

-pat

mm2270
Legendary Contributor III

@pvader - you should probably throw an extra echo in there to print out what email address the script is pulling. Are you certain you have your API account set up correctly? You can easily test it in a browser with the same credentials ahead of time to be sure there are no problems with that.

As an alternative, if your user accounts are all primarily AD based, you could simply grab the email address from the locally cached account, based on either who is logged in at the moment, or who's the most frequently logged in account. The 'EMailAddress' attribute gets stored in AD based accounts on the local system and can be pulled pretty easily with dscl.

ToriAnneke
Contributor II

I'll check it soon. Busy with Universal Type Client 4 and Adobe CC roll out these past few days.

Thanks all as always!
-p

jchin-ro
New Contributor III

Can it pop up a message window instead of sending e-mail?

wildfrog
Contributor II

@jchin-ro - We use an EA to tell us Days Since Last Reboot.
- We then create a smart group for machines with 14+ days since last reboot. In the criteria, we excluded servers.
- We then created a script using displayMessage that will put up a dialog that says "Just a friendly reminder that your Mac hasn't been restarted in a while. Please restart when you get a chance." - and requires the user click "OK" to acknowledge.
- We then ccreated a policy that calls the script and scoped it to the above smart group and set it to trigger on recurring checkin once a week.

So once a machine hasn't been rebooted in 14 days, the user will get a weekly reminder that they have to acknowledge until they reboot - causing their machine to leave the smart group.

jchin-ro
New Contributor III

I would love to do exactly the same thing. Can you shed a little more detail on how to do that display message setup? I am new to Jamf and any pointers would really help. Thanks.

lkrasno
Contributor II

@jchin-ro

Have a look at :

https://www.jamf.com/jamf-nation/feature-requests/751/jamfhelper-make-it-awesome

https://github.com/haircut/better-jamf-policy-deferral

I might suggest running such a policy after hours initially, or at least detect Power Point or Keynote running. (this can be complicated if they're set to launch on startup)

Also be sure to check you're collecting inventory sufficiently, keeping in mind that inventory collection doesn't always succeed on startup, you can end up with a client that doesn't leave the smart group, even after a reboot. To be safe, script uptime "precheck" before you kick off notify as in the original post.

wildfrog
Contributor II

@jchin-ro Here's the EA for days since last reboot.

#!/bin/bash
# Commands required by this script
# credit to acidprime on jamfnation
declare -x awk="/usr/bin/awk"
declare -x sysctl="/usr/sbin/sysctl"
declare -x perl="/usr/bin/perl"


declare -xi DAY=86400
declare -xi EPOCH="$($perl -e "print time")"
declare -xi UPTIME="$($sysctl kern.boottime |
$awk -F'[= ,]' '/sec/{print $6;exit}')"


declare -xi DIFF="$(($EPOCH - $UPTIME))"


if [ $DIFF -le $DAY ] ; then
echo "<result>1</result>"
else
echo "<result>$(($DIFF / $DAY))</result>"
fi

Here's the script for pushing the reminder message to the user -

#!/bin/sh
/usr/local/jamf/bin/jamf displayMessage -message "Just a friendly reminder that your Mac hasn't been restarted in a while. Please restart when you get a chance."

We collect inventory once a day for all endpoints.
We scope the policy for the notification message to run once a week, and exclude servers.
So given these two things, it's very unlikely (but not impossible) that we'll encounter a situation where a user just rebooted right before getting the notification.

If this is something you want to do, it's easy enough to test the EA and the script to push the notification separately.

All that said. . .if you wanted to go nuts you could probably use the same EA, smart groups, and logic and do a more elegant Notification Center notification using Yo (https://github.com/sheagcraig/yo). We just haven't gone there yet and implemented this more quickly.

lkrasno
Contributor II

easier to get uptime in days with this one liner :

#!/bin/sh
uptime | cut -d "," -f 1 | awk '{print $3;}'

I might make the message less arbitrary and include something along the lines "In order to ensure installation of security/application updates and to improve performance, please reboot as soon as convenient"

Maybe start turning this on for clients that haven't rebooted in 30, and evaluate from there. Starting at even 14 will likely cause disruption and push back from a large group of users.

wildfrog
Contributor II

@lkrasno Your uptime script is definitely simpler. How does it handle displaying a value of less than one?
One of the reasons we use this particular methodology is that it shares a foundation with a few scripts/EAs we have that monitor things that use epoch time.

As for the message, the verbiage can definitely be whatever suits the personality and culture of your org. We've had this workflow in place for over a year across all of our clients (we're an MSP) and we've heard no complaints from anyone regarding frequency.

stevewood
Honored Contributor II
Honored Contributor II

@jchin-ro if you look at the original post, I was using cocoaDialog to pop a bubble (Notification Center) dialog. If you have cocoaDialog installed and do not want to have an EA and a policy, you can use just the script in a policy. And the reason I was grabbing the uptime this way:

days=`uptime | awk '{ print $4 }' | sed 's/,//g'`  # grabs the word "days" if it is there
num=`uptime | awk '{ print $3 }'`  # grabs the number of hours or days in the uptime command

Was because if the machine had been up for 7 hours the script would see that as 7 days unless you checked for the word 'days' and would alert the user.

Cleaned up without email or logging:

#!/bin/sh

CD="/usr/local/bin/cocoaDialog.app/Contents/MacOS/cocoaDialog"
cdTitle="Machine Needs A Restart"
loggedInUser=`/bin/ls -l /dev/console | /usr/bin/awk '{ print $3 }'`

## set minDays - we start bugging users at this level with just a dialog box
minDays=7

## set maxDays - after we reach maxDays we bug with dialog box AND email
maxDays=15

#### MAIN CODE ####
days=`uptime | awk '{ print $4 }' | sed 's/,//g'`  # grabs the word "days" if it is there
num=`uptime | awk '{ print $3 }'`  # grabs the number of hours or days in the uptime command

## now the logic

if [ $loggedInUser != "root" ]; then
    if [ $days = "days" ]; then

        if [ $num -gt $minDays ]; then

            if [ $num -gt $maxDays ]; then

                cdIcon="/System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/AlertStopIcon.icns"
                cdText="Your computer has not been restarted in more than $maxDays days.  Please restart ASAP.  Thank you."

                bubble=`$CD bubble --title "$cdTitle" --no-timeout --text "$cdText" --icon-file $cdIcon`

            else

                cdIcon="/System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/AlertCautionIcon.icns"
                cdText="Your computer has not been restarted in $num days.  Please restart ASAP.  Thank you."
                bubble=`$CD bubble --title "$cdTitle" --no-timeout --text "$cdText" --icon-file $cdIcon`

            fi
        fi
    fi
fi

exit 0

amartin253
New Contributor III

Thanks for creating this thread @stevewood . I am going to use this as a baseline for what my original Google search was (which lead me here) to be able to email users who's Mac hasn't checked in to JAMF in over 30+ days. I know there has got to be a way!

tunberg
New Contributor

@amartin253, you can use mailsend-go for that. smtp command line tool.