Custom "EULA" agreement at login for first time and collect info.

Johnny_Kim
Contributor II

Hey all,
Here is something I am wondering if it exists. I have checked googled and got nothing.

One process when we issue a Macbook Air to staff members is they sign an agreement form(physical paper).

I am wondering if there is a 3rd party app where at first time logging in, the Agreement form is prompted, forcing them to check mark the box'es and type in their name and collect this data to a central location.

1 ACCEPTED SOLUTION

mm2270
Legendary Contributor III

Here's a script that you can use to get you going in the right direction I think. I haven't thoroughly tested all aspects of this, but quick tests show that it works. There are several important part of this all working.
One is putting an Extension Attribute script together to capture the information in the exported file. A simple cat /path/to/file as its result should be enough generally speaking.
Two is deploying cocoaDialog to your Macs (this could be contained in the same pkg that deploys the rest of the items)
Three is a LaunchDaemon. See below for the explanation on that.

I added a lot of notes in here on what's going on and added spaces between lines to make things a little easier to pick apart, since I'm not sure what your scripting skills are like.

You will need to create a LaunchDaemon to fire this all off. I would use a StartInterval with some number of seconds so it kicks off periodically until it runs successfully.
Couple of points on this: Launchd jobs can't run any more frequently than every 10 seconds. The OS throttles them back if they try to run any sooner and you get a bunch of entries in the system.log about it. I would choose a timeframe of maybe every 30 seconds, but of course choose whatever you feel comfortable with. Something soon enough to kick in shortly after they log in, but not so soon that it will end up tripping over itself. It has to run as a LaunchDaemon since there's a jamf recon in the script, which requires root. A LaunchAgent wouldn't be able to do that part. But since its a Daemon, it means it will run even when no-one is logged in so it compensates by detecting the owner of console and exiting if its "root" (login window)

Again, not thoroughly tested, but this should at least be a start.

#!/bin/bash

## Put full path to coocoaDialog here, wherever you deploy it to systems. Note the path all the way to the executable inside "MacOS"
CDPATH="/Library/Application Support/JAMF/cocoaDialog.app/Contents/MacOS/cocoaDialog"

## Capture the logged in user short name
loggedInUser=$( ls -l /dev/console | awk '{print $3}' )

## Get the full user name from dscl, if applicable for your environment (may need adjustment; test this separately)
fullName=$( dscl . read /Users/$loggedInUser RealName | awk -F, '{getline; print $2$1}' | sed 's/^ *//' )

## Get the computer name
compName=$( scutil --get ComputerName )

## Path to EULA file with data on disk
eulaFILE="/private/var/eula_agreement"

## Edit this text below. Note that you may need to adjust the --height integer in the cocoaDialog call to accommodate longer text
MsgText="Below is the company's End User License Agreement.

Please read it carefully, then check the "I agree" checkbox below, and finally click "OK".

Full EULA text goes here."

## Main script starts here

## See who's logged in. If its not "root" we aren't sitting at the login screen.
if [[ "$loggedInUser" != "root" ]]; then

    echo "A user is logged in"

    ## Check to see if a eula_agreement file is already on disk, just in case it ran already

    if [[ ! -e "$eulaFILE" ]]; then

        echo "No previous eula_agreement file found on disk. Continuing..."

        # Display the dialog. Notes:
        # 1. The "--value-required" flag forces them to check the checkbox before the dialog can be dismissed.
        # 2. If you want a custom icon, use --icon-file instead of --icon below and enter the full path to the icon file (icns, png, jpg, etc)
        # 3. Adjustment of the --width and --height integers may be necessary. Occasionally CD doesn't rescale correctly based on the text.    
        # 4. Enter a title after --title if you want the dialog to have one. Use "" to use a blank title

        EULADialog=$( "$CDPATH" checkbox --title "" --label "$MsgText" 
        --items "I agree to these terms" --button1 "   OK   " --value-required 
        --icon info --width 400 --height 220 )

        ## Now detect the response.
        if [[ $( echo "$EULADialog" | awk 'NR>1{print $0}' ) == "1" ]]; then

            echo "The dialog exited with the Agree button checked"
            AgreeChecked="Yes"

## Export the settings to a file on disk that can be picked up by recon later

echo -e "EULA agreement status:

Username:   $loggedInUser
Full Name:  $fullName
Computer Name:  $compName
User Agreed?:   $AgreeChecked
Agreement Date: $(date +"%b %d, %Y, %T")" > "$eulaFILE"

            ## Run a recon to suck up the EULA file. You will need an Extension Attribute designed to capture the contents of this file.
            echo "Gathering new inventory"
            jamf recon

            ## If the recon was successful
            if [[ "$?" == "0" ]]; then

                ## Now, clean up
                ## Unload the LaucnhDaemon that triggers the script
                /bin/launchctl unload /Library/LaunchDaemons/com.nameoflaunchdamon.plist
                ## Delete the LaunchDaemon
                /bin/rm -f "/Library/LaunchDaemon/com.nameoflaunchdaemon.plist"
                ## Delete the eula file
                /bin/rm "$eulaFILE"
                ## Delete the script last
                /bin/rm -f "$0"

            else

                echo "Recon failed. Let's not delete anything until we can capture the file. Exit until next run..."
                exit 0

            fi

        else

            echo "Somehow the 'I agree' box wasn't checked. Exit until next run..."
            exit 0

        fi

    else

        echo "An existing eula_agreement file was found. Run recon (just in case) and then delete the file..."
        jamf recon
        /bin/rm "$eulaFILE"
        exit 0

    fi

else

    ## If the logged in user is root, the Mac is still sitting at the login window. Exit and wait until the next run
    echo "There is no logged in user. Exit until next run..."
    exit 0

fi

Also for the LaunchDaemon, I recommend creating it in either LaunchControl or Lingon. You can find either ones URL in the Third party Products section here. When you make the Daemon, the ProgramArguments needs to be the full path to the script, like /private/var/scripts/scriptname.sh for example. The keys I'd add are RunAtLoad (makes it load from startup and ongoing) and StartInterval (add the seconds between each run, like 30)

Good luck and let me know if you have any questions. Hopefully its pretty self explanatory.

View solution in original post

25 REPLIES 25

mm2270
Legendary Contributor III

Are you looking for this to come up over the login screen, i.e, before they get to their Desktop, or after they've successfully logged into the Mac?

With the former, nothing like that exists as far as I know. You can have a EULA with custom text and images and a simple "Accept" button on it by dropping a formatted RTF file into /Library/Security/ on the Mac, but it doesn't have checkboxes or a place to type in their name on it.
If you're looking to have them do this after they log in, you can probably come up with an app/script using cocoaDialog to simulate something to that affect. I'm just not sure of the specifics of what you're looking for.

If you can post a generic version of your agreement form, I can probably help you come up with a process.

Johnny_Kim
Contributor II

Hey MM2270, thanks for the reply.

I would prefer after they login so it can capture who is 'submitting' the form. I do know about the /library/security...but need something that would capture the form.

The agreement form states they are responsible for the MacBook and we have been doing this on paper. Things can get out of hand when there are thousands of paper to keep track (We did start scanning and linking it to the asset to organizie it using our asset management). We would need this document if they decided to challenge us for anything.

It doesn't need to have a check boxes or a line to fill in their names as long as we can capture the logged in user who is clicking "Agree" button at the bottom.

mm2270
Legendary Contributor III

Would something like an agreement form that has text plus a single checkbox in it that is required to be checked for them to click the "Accept" button do the trick? Then write out the response to a file that can get picked up either in a recon and either pulled into an Extension Attribute field, or, perhaps some way to upload the saved file to the computer record in the JSS? I'm actually not sure if the latter option is possible at all. It may be something we can do in the API, if not in version 8.x then perhaps in version 9 now. If not, the contents of the file can at least be pulled into an EA.

As a bonus, you can also have a "I disagree" button in the dialog, in case someone really doesn't agree to the terms, which could log them out immediately, or shut the system down.

My thinking is, if they agree and the form is saved to disk, the script, called by a LaunchDaemon could then clean itself up by deleting the script and the daemon, but initiating a full inventory scan to capture the file contents before dong that. That way it won't run again. If they click Disagree, then it remains on disk, but logs them out, ready to run again at next login.

Would that work for you?

Johnny_Kim
Contributor II

That would work for me depending on how the output file looks like (legal issues). The "I disagree" buttom would not be necessary. What would be the next step?

mm2270
Legendary Contributor III

Give me a little time and I'll put a template together for you. Unless you want to do it yourself. Have you used cocoaDialog before? That was the app I was thinking about, since I don't know of too many tools out there that would allow you to have both text and a checkbox in a single dialog. You could create a custom app in Xcode with Applescript code behind it that would look more polished, but that's not quite as straight forward.

As for the output file, it could be formatted almost any way you want, as long as we're not talking about styling the text (bold, italic, etc) since it would need to be a plain text file. But new lines, tabs and such for spacing would be possible.

Johnny_Kim
Contributor II

Mike,
I have not use cocoaDiaglog before. If you can create a template for me, I would really appreciate it. I'm going to look into cocoaDialog in the mean time.

Thank you so much for you help!

mm2270
Legendary Contributor III

Here's a script that you can use to get you going in the right direction I think. I haven't thoroughly tested all aspects of this, but quick tests show that it works. There are several important part of this all working.
One is putting an Extension Attribute script together to capture the information in the exported file. A simple cat /path/to/file as its result should be enough generally speaking.
Two is deploying cocoaDialog to your Macs (this could be contained in the same pkg that deploys the rest of the items)
Three is a LaunchDaemon. See below for the explanation on that.

I added a lot of notes in here on what's going on and added spaces between lines to make things a little easier to pick apart, since I'm not sure what your scripting skills are like.

You will need to create a LaunchDaemon to fire this all off. I would use a StartInterval with some number of seconds so it kicks off periodically until it runs successfully.
Couple of points on this: Launchd jobs can't run any more frequently than every 10 seconds. The OS throttles them back if they try to run any sooner and you get a bunch of entries in the system.log about it. I would choose a timeframe of maybe every 30 seconds, but of course choose whatever you feel comfortable with. Something soon enough to kick in shortly after they log in, but not so soon that it will end up tripping over itself. It has to run as a LaunchDaemon since there's a jamf recon in the script, which requires root. A LaunchAgent wouldn't be able to do that part. But since its a Daemon, it means it will run even when no-one is logged in so it compensates by detecting the owner of console and exiting if its "root" (login window)

Again, not thoroughly tested, but this should at least be a start.

#!/bin/bash

## Put full path to coocoaDialog here, wherever you deploy it to systems. Note the path all the way to the executable inside "MacOS"
CDPATH="/Library/Application Support/JAMF/cocoaDialog.app/Contents/MacOS/cocoaDialog"

## Capture the logged in user short name
loggedInUser=$( ls -l /dev/console | awk '{print $3}' )

## Get the full user name from dscl, if applicable for your environment (may need adjustment; test this separately)
fullName=$( dscl . read /Users/$loggedInUser RealName | awk -F, '{getline; print $2$1}' | sed 's/^ *//' )

## Get the computer name
compName=$( scutil --get ComputerName )

## Path to EULA file with data on disk
eulaFILE="/private/var/eula_agreement"

## Edit this text below. Note that you may need to adjust the --height integer in the cocoaDialog call to accommodate longer text
MsgText="Below is the company's End User License Agreement.

Please read it carefully, then check the "I agree" checkbox below, and finally click "OK".

Full EULA text goes here."

## Main script starts here

## See who's logged in. If its not "root" we aren't sitting at the login screen.
if [[ "$loggedInUser" != "root" ]]; then

    echo "A user is logged in"

    ## Check to see if a eula_agreement file is already on disk, just in case it ran already

    if [[ ! -e "$eulaFILE" ]]; then

        echo "No previous eula_agreement file found on disk. Continuing..."

        # Display the dialog. Notes:
        # 1. The "--value-required" flag forces them to check the checkbox before the dialog can be dismissed.
        # 2. If you want a custom icon, use --icon-file instead of --icon below and enter the full path to the icon file (icns, png, jpg, etc)
        # 3. Adjustment of the --width and --height integers may be necessary. Occasionally CD doesn't rescale correctly based on the text.    
        # 4. Enter a title after --title if you want the dialog to have one. Use "" to use a blank title

        EULADialog=$( "$CDPATH" checkbox --title "" --label "$MsgText" 
        --items "I agree to these terms" --button1 "   OK   " --value-required 
        --icon info --width 400 --height 220 )

        ## Now detect the response.
        if [[ $( echo "$EULADialog" | awk 'NR>1{print $0}' ) == "1" ]]; then

            echo "The dialog exited with the Agree button checked"
            AgreeChecked="Yes"

## Export the settings to a file on disk that can be picked up by recon later

echo -e "EULA agreement status:

Username:   $loggedInUser
Full Name:  $fullName
Computer Name:  $compName
User Agreed?:   $AgreeChecked
Agreement Date: $(date +"%b %d, %Y, %T")" > "$eulaFILE"

            ## Run a recon to suck up the EULA file. You will need an Extension Attribute designed to capture the contents of this file.
            echo "Gathering new inventory"
            jamf recon

            ## If the recon was successful
            if [[ "$?" == "0" ]]; then

                ## Now, clean up
                ## Unload the LaucnhDaemon that triggers the script
                /bin/launchctl unload /Library/LaunchDaemons/com.nameoflaunchdamon.plist
                ## Delete the LaunchDaemon
                /bin/rm -f "/Library/LaunchDaemon/com.nameoflaunchdaemon.plist"
                ## Delete the eula file
                /bin/rm "$eulaFILE"
                ## Delete the script last
                /bin/rm -f "$0"

            else

                echo "Recon failed. Let's not delete anything until we can capture the file. Exit until next run..."
                exit 0

            fi

        else

            echo "Somehow the 'I agree' box wasn't checked. Exit until next run..."
            exit 0

        fi

    else

        echo "An existing eula_agreement file was found. Run recon (just in case) and then delete the file..."
        jamf recon
        /bin/rm "$eulaFILE"
        exit 0

    fi

else

    ## If the logged in user is root, the Mac is still sitting at the login window. Exit and wait until the next run
    echo "There is no logged in user. Exit until next run..."
    exit 0

fi

Also for the LaunchDaemon, I recommend creating it in either LaunchControl or Lingon. You can find either ones URL in the Third party Products section here. When you make the Daemon, the ProgramArguments needs to be the full path to the script, like /private/var/scripts/scriptname.sh for example. The keys I'd add are RunAtLoad (makes it load from startup and ongoing) and StartInterval (add the seconds between each run, like 30)

Good luck and let me know if you have any questions. Hopefully its pretty self explanatory.

mm2270
Legendary Contributor III

So one other thing on this that's pretty cool. The API does in fact have a way to attach a local file from the Mac to its own record in the JSS. Works in version 8.7 and should also work in version 9 I assume, though I can't thoroughly test that at the moment. This would shpw up as an attachment in the Attachments section of the Mac's detailed record. It requires having an API account created for your JSS with the proper API privileges.

Here's an idea on how to get that to work

MACaddress=$( networksetup -getmacaddress en0 | awk '{print $3}' | sed 's/:/./g' )
jssAPIURL="https://your.jss.address:8443/JSSResource/fileuploads/computers/macAddress"
apiUsername="apiusername"
apiPassword="apipassword"
fileUpload="/private/var/eula_agreement"

curl -k -s -u "$apiUsername:$apiPassword" "$jssAPIURL/$MACaddress" -F name=@"$fileUpload"

The above code would use your API account username and password, pull the Mac's own MAC address for en0, which exists on any Mac, then upload the eula_agreement file to its record. You could place all of this into its own function block that the script calls when needed.

I tested this on my own system and it works nicely. Adds the attachment right in and can be downloaded from the JSS. This could be a nice extra piece for you since it would be stored as a physical file along with the computer.

jhbush
Valued Contributor II

mm2270, thanks for posting lots of great tips in the last two posts. One additional note I believe you must use the 3.0 beta version of Cocoa Dialog for this script to work.

jhbush
Valued Contributor II

mm2270, I'm having a little trouble with the upload using V9. It looks like the MAC address is not recognized so I used ID to test. When I uploaded the document it removed all of the particular machines information. I had to re-enroll to get the info back as a recon would not work.

curl -k -u user:password https://my.jss:8443/JSSResource/fileuploads/computers/id/2 -F name=@/Users/admin/Documents/Sample.doc -X POST

mm2270
Legendary Contributor III

@jhbush1973, Wow, sounds like a bug to me. I can't imagine how uploading an attachment via API could wipe out all information on the enrolled system.

Sorry that happened, but I guess thanks for the heads up. We have a dev server at the moment running version 9.22, so I'll need to test this out to see what the story is. I also can't imagine why the MAC address wouldn't be recognized since it still gathers that information. Even with the switch to Hardware UUID I thought MAC address was still one of the methods you could use to target a system in the API.

Also, thanks for posting that bit on the beta version of cocoaDialog. I meant to mention that the checkbox window style only exists in that version.

Johnny_Kim
Contributor II

Wow! Mike,
I will start testing/working on this. Much appreciated!

mm2270
Legendary Contributor III

@jhbush1973 - I was able to confirm the defect you ran into in your testing against our 9.22 test environment. Uploading an attachment using the outlined method direct from the Casper API documentation wipes out much of the information on the system. The attachment appears, but in my case, it seems everything from General down to Purchasing Information remained intact. Everything else from that point downwards,like Storage, Extension Attributes, Disk Encryption, Applications, Receipts and the rest are all blanked out. That's a pretty nasty bug!

Do you want to do the honors of reporting this to JAMF, assuming they don't already know about it? If not, I'll gladly submit this one. Uploading an attachment should never be doing that, so I'm sure they're going to want to know about that and work on fixing it.

BTW, what version of the JSS are you running? It would be good to know how many versions this issue appears in.

@Johnny.Kim - let me know how it works out for you.
One thing I realized yesterday is that since my script deletes the EULA file, the next time the EA script runs at next inventory submission, if the file isn't there and its just trying to cat a non existent file, it may return a blank result and end up blanking out the EA field for the inventory record. Not what you would want of course.

Two ways around that. One is just leave the EULA file in place. There's no real necessity in deleting it. I was just doing that to be tidy, but you could easily leave it there and let the EA pick it up during each recon.
Two would be to put some logic into the EA script to first look for the file, then cat it. If its not there, use an exit to leave the script without submitting anything.

I think the more reliable method will be to just leave it on disk. I've had mixed success with getting EA scripts to exit without returning something as a result.

jhbush
Valued Contributor II

@mm2270, I'm using 9.23 currently. Please submit the bug if you don't mind doing it. Thanks for providing some frameworks very helpful.

FritzsCorner
Contributor III

Just wanted to chime in and say I like what I am seeing here.. Tried it out here and it works great. I am hoping I can get this reviewed by our Security team and remove the nagging EULA we display prior to logon that pops up every 60 seconds.

mm2270
Legendary Contributor III

Hi folks, For anyone that might care, I just wanted to update this thread with some additional information regarding the issue posted by @jhbush1973 above. As mentioned I was able to reproduce the issue self in 9.22. The specific issue is that using the API to upload an attachment seems to remove a lot of the captured data on the target system in the JSS. This is only a Casper Suite 9 defect. If you're using version 8.x like we still are, this defect does not exist in that version as far as I can see in my own testing.
I submitted this to our JAMF TAM and he was very proactive in quickly working to reproduce the same issue in his test environment, so this is now a confirmed defect. Defect # D-006330 for anyone that wants to track it. Although our TAM was able to recon the affected system to get the data back, he also noted that the Mac went into an unmanaged and MDM capable - no state, so this is kind of serious in that respect. JAMF will likely address this in the next release.

For anyone that would like to use something like the above to capture a digitally 'signed" EULA that's running a version 9 JSS, don't use the API method of uploading the file until this is resolved. You'll need to use an Extension Attribute to capture it for now.

jhbush
Valued Contributor II

@mm2270 I'm guessing JAMF doesn't post it's bugs so is there any interest from community to setup something like Open Radar to act as a clearing house for bugs. It would seem that this could save new and old JAMF users lots of time.

mm2270
Legendary Contributor III

@jhbush1973 - yeah, there's already a Feature Request for customers to be able to view outstanding known issues and defects, to save time in troubleshooting. I don't have the exact feature request link handy, but I know its there, and I think JAMF has commented that they're looking at such a process.

I was a little reluctant to post the information above here on a publicly accessible thread. I understand if JAMF may not want to air their dirty laundry about bugs like this. However, I do think that some way for paying customers to have access to a knowledge base or Open Radar like list would be most helpful and a huge time saver, so I'm all for that.
In the end I decided to post the above because this is more than an annoyance or visual glitch, Potentially un-managing a system by uploading a file via API sounds pretty serious to me, and since I posted the code to do it above, I felt it prudent to let everyone running version 9 know not to use it until further notice.

Johnny_Kim
Contributor II

As JHbush mentioned, I am almost able to get it working using the v3 beta except one issue, It doesn't create the output file. I have changed the path and still nothing. I'm using Lingon3, created the plist in the launchagent.

Also, is there a way to use msgbox and inputbox at the same time? I know in the earlier post I did mention that I didn't need the line to have them fill their name but now I may.

Again, I appreciate your help!

Johnny_Kim
Contributor II

As JHbush mentioned, I am almost able to get it working using the v3 beta except one issue, It doesn't create the output file. I have changed the path and still nothing. I'm using Lingon3, created the plist in the launchagent.

Also, is there a way to use msgbox and inputbox at the same time? I know in the earlier post I did mention that I didn't need the line to have them fill their name but now I may.

Again, I appreciate your help!

mm2270
Legendary Contributor III

@Johnny.Kim, as I might have mentioned, you really want to create a LaunchDaemon, not a LaunchAgent. LaunchDaemon's run jobs (a script in this case) as root by default, which will be needed, mostly for the jamf recon piece, if you're using that, but also in most cases to write out the temp output file. You could change the path to some globally writable location to get around the latter part, but it will not do the recon to pick up the file contents in the Extension Attribute. LaunchControl let's you create LaunchDaemons, but there is a version of Lingon as well that will let you make them. I think Lingon 3 is limited to making LaunchAgents only (a Mac App Store restriction). LaunchAgents run as the user so they can't do anything as root
Here's a link to Lingon 2.1.1, the version that can create Daemons:
http://sourceforge.net/projects/lingon/files/Lingon/2.1.1/Lingon-2.1.1.zip/download

As for having one dialog with a input box and some other controls at the same time, no, there's no way to do this other than making both dialogs show up at the same time (put the first into the background for example), but its clunky. cocoaDialog is designed to only use a single dialog style at any given time. You could sequence them, so they enter their name in one box, then another dialog appears after they click "OK" asking for some other information. Not ideal, but I've used that when needed. As long as you're not talking about a half dozen different screens its usually acceptable.
OTOH, are you just looking for a way to include a large amount of text, like a full EULA, along with the inputbox? If so, I think the --label option for inputbox accepts a fair amount of text, including multiple lines. I've never tested its limits, but try something like this as a test-

/path/to/cocoaDialog.app/Contents/MacOS/cocoaDialog inputbox --label "$(echo -e "This is line one.

This is line two.

This is line three.

Enter your name below:
")" --button1 "OK" --button2 "Cancel"

As part of troubleshooting your script, I would try putting in echo commands liberally, echoing out what its supposed to be capturing. That should show up in the log. If the echo's are empty, you'll know its not actually capturing anything. If it does output something, its working OK, but isn't writing the file out.

Let me know if I can help with anything else on this.

Johnny_Kim
Contributor II

Mike,
I got it to work. I was using Lingon 3, so by default it was launchAgent. Once it was a LaunchDaemon, it worked perfectly. I am using JSS 8.7, created a second script to extract the file and it placed it in the JSS under attachments. I did spend some time playing around with Cocoadiaglog and doesn't seem to hard at all.

Again, I really appreciate your help with this!

jhbush
Valued Contributor II

@mm2270 the bug we were seeing uploading a document has been resolved.
[D-006330] Fixed an issue that caused a computer or mobile device to be unmanaged if an attachment
is added to the computer or mobile device using the JSS API. This also resulted in the removal of most of the computer’s or mobile device’s inventory information from the JSS.

mm2270
Legendary Contributor III

@jhbush1973 - Yes, thanks, I had gotten the same information from my account rep. I think I posted that on one of the other threads where this topic came up.
We're still not on vers 9 of Casper, but will be sometime soon, so good to hear they resolved that one.

Johnny_Kim
Contributor II

Thanks for the heads up!