Popups Script with 30 Day Counter

bmarks
Contributor II

I searched but I couldn't find much info. Basically, I want to create a policy that once per day pops up a message letting a user know they have 30 days, 29 days, etc. to upgrade to a new version of macOS before something draconian happens. It could be a macOS reminder or it could use either the jamf or jamfhelper binaries, that part I know how to do. It's the countdown part that I don't know how to do. Has anyone done this? I've seen examples of timers for minutes to delay a patch, but nothing like this.

7 REPLIES 7

mm2270
Legendary Contributor II

The simplest way to achieve this is to write/update a value into a local hidden file, like something inside /Library/Application Support/JAMF/ for example, or maybe /private/var/ if you really want to make it a little more obscure.
Each time the user dismisses your dialog, in your script you would capture their dismissal, typically by which button they clicked, and have the script increment that counter down by one.
How you would do this is have the script look for the file at the start, before it pops up a dialog. If it exists, read the value from the file first and store it in a variable that would get used in the dialog, for example to tell them "20 days left" before xyz happens.
When they dismiss the warning, do some math to change the value down by 1, then write that new number into the local file, making sure to overwrite the old number.
The next time the script runs it will use that new value (like 19, following the example from above) and tell them they have "19 days left"

It will keep doing this until it hits 0. At that point, in your script you will need to have a line that, if the value is 0, offer no more deferrals and just do whatever it is you need to do, like update the OS or something.

Does that help? If you need more specific examples of how to do the above, post back and I can post up a basic code example.

tlarkin
Honored Contributor

This is a good time to look at using epoch date stamps. You can get the current epoch date stamp by running date +%s in terminal. This should spit out an integer date stamp in epoch time. You can use an epoch date stamp to easily count days, weeks, months, years, etc with simple math.

here is what my sample looks like as a test

cat /tmp/datestamp.txt 
date = 1502051408

I did adjust the epoch date stamp to make it different than today's date for testing purposes. Here is what I whipped up in Python really quick to demonstrate how to do this.

#!/usr/bin/python

# import modules

import time

date_file = '/tmp/datestamp.txt'

current_time_epoch = int(time.time())

with open(date_file, 'r') as f:
    date_stamp_file = f.readline()
    date_stamp = int(date_stamp_file.split('=')[1])
    time_diff = (current_time_epoch - date_stamp)
    days_passed = (time_diff / 86400)

print '%s days have passed' % days_passed

sample output:

/usr/bin/python /Users/tlarkin/IdeaProjects/python/date_counter.py
23 days have passed

Here would be a sample in bash:

#!/bin/bash

date_file='/tmp/datestamp.txt'
original_date="$(awk -F= '/date/ { print $2 }' $date_file | tr -d ' ')"
current_epoch_time=$(date +%s)
date_diff=$(expr ${current_epoch_time} - ${original_date})
days_ran=$(expr ${date_diff} / 86400)

echo "it has been ${days_ran}"

sample output:

/bin/bash -x /Users/tlarkin/IdeaProjects/bash/epoch_diff.sh
it has been 23 days
+ date_file=/tmp/datestamp.txt
++ awk -F= '/date/ { print $2 }' /tmp/datestamp.txt
++ tr -d ' '
+ original_date=1502051408
++ date +%s
+ current_epoch_time=1504072983
++ expr 1504072983 - 1502051408
+ date_diff=2021575
++ expr 2021575 / 86400
+ days_ran=23
+ echo 'it has been 23 days'
+ exit 0

So you could write to a plist file locally when the policy first runs, just build some logic in to see if the file exists or not, if not create the file with the epoch date stamp and then every time it runs after that if the file is present it will pass that part of the code and read in the date. You can pretty much choose any language you want to script in and this should be pretty simple. For this example I just edited my date stamp so it was much older than the date stamp today to get the value of 23 days.

bmarks
Contributor II

@mm2270 If you could post an example, that'd be great. All this info is greatly helpful.

mm2270
Legendary Contributor II

@bmarks Sure, here is a quick and dirty example I just wrote on the fly. I tried to comment it sufficiently to show how it's doing what it's doing, but let me know if you have any questions on it. Obviously, you will need to add/customize this as needed.
FYI, I changed it to use a hidden plist file instead of a hidden file, so it will live in the standard /Library/Preferences/ location in this example, but starts with a period, so it's not visible in the Finder.

#!/bin/bash

jhPath="/Library/Application Support/JAMF/bin/jamfHelper.app/Contents/MacOS/jamfHelper"

## This is the default number of deferrals we want to start with. Adjust this value as needed
defaultDeferrals="10"

## The path to a plist that will store how many deferrals are left
deferralPlist="/Library/Preferences/.com.policyname.deferralCount.plist"

## Here we check for the existence of the plist. If it's there, capture how many deferrals remain
if [ -e "$deferralPlist" ]; then
    deferralsRemaining=$(defaults read "$deferralPlist" deferralsLeft)
else
    ## If no plist is present, use our default amount to start with
    deferralsRemaining="$defaultDeferrals"
fi

descriptionText="Install [some update] now. You can defer this installation $deferralsRemaining times before it will become mandatory."

if [ "$deferralsRemaining" -gt 0 ]; then
    ## If there are more than 0 deferrals left, show the jamfHelper dialog
    prompt=$("$jhPath" -windowType utility 
    -title "Some title" 
    -heading "Some header if needed" 
    -description "$descriptionText" 
    -button1 "Install Now" 
    -button2 "Defer" 
    -defaultButton 1)

    if [ "$prompt" == "0" ]; then
        echo "Install Now chosen"
        ## User chose to Install Now. Do something here to trigger a policy, run a command or whatever
    elif [ "$prompt" == "2" ]; then
        echo "User chose to defer until later"
        ## Here we calculate a new deferment value and send that back to the plist
        newDeferralsLeft=$((deferralsRemaining-1))
        defaults write "$deferralPlist" deferralsLeft -int $newDeferralsLeft
        exit 0
    fi
else
    ## If 0 deferrals are left, we move on displaying a final dialog instead (no deferral button)
    echo "No deferrals remain."
    "$jhPath" -windowType utility 
    -title "Some title" 
    -heading "Some header if needed" 
    -description "You have no deferrals left to install [some update]. Installation will start momentarily." 
    -timeout 60 
    -countdown 
    -button1 "Start Now" 
    -defaultButton 1

    echo "Dialog dismissed. Beginning install"
fi

In addition to the above, I would also look at what @tlarkin wrote above. Using epoch time and doing calculations on when something last ran can also be useful. It prevents odd cases of machines running a policy more than once per day, which probably wouldn't be desirable in a case like this.
But, in my experience, as long as the policy execution frequency is set to "Once per day", it really should only run once per day at max. It would only fire off more frequently if you go in and manually flush a policy log for a machine or the entire policy. As long as you don't do that, it should be fine.

jameson
Contributor II

I tried used the above from @mm2270 - but the last part does not seems to work if no deferals are left - anyone has an idea what is wrong ?

#!/bin/sh
    ## If 0 deferrals are left, we move on displaying a final dialog instead (no deferral button)
    echo "No deferrals remain."
    "$jhPath" -windowType utility 
    -title "xxx - Software Update" 
    -icon "/System/Library/CoreServices/Software Update.app/Contents/Resources/SoftwareUpdate.icns" 
    -heading "Mac OS Software Updates are pending" 
    -description "You can not delay pending updates anymore. They will be auto installed, so please save you work! " 
    -timeout 300 
    -countdown 
    -button1 "Start Now" 
    -defaultButton 1
    echo "Dialog dismissed. Beginning install"
    if [ "$jhPath" == "0" ]; then
        echo "Install Now chosen"
        ## User chose to Install Now. Do something here to trigger a policy, run a command or whatever
   jamf policy -trigger event43
fi

dan_gregson
New Contributor II

How would you set up the policy to run the package for event43?

Nix4Life
Valued Contributor

currently looking at nudge. seems like it was built for this. also look at umad for non-DEP stuff