Helping jamfHelper

Helping jamfHelper

helping-jamfhelper

General Overview


jamfHelper is a nifty little utility included with Jamf’s Casper Suite that can be used to display information, get user input, or even trigger custom events all from the command line or your own custom scripts.

There are a number of useful options that can be used to customize the look, feel and functionality of the jamfHelper window. From heads-up notifications, to user selected delay options, to locking the screen, I will do my best to demonstrate some of these uses as well as how each of jamfHelper’s options work.

The jamfHelper executable can be found inside of the jamfHelper.app at:

/Library/Application Support/JAMF/bin/jamfHelper.app/Contents/MacOS/jamfHelper

This executable can be called directly from the command line making it ideal for use in custom scripts. Calling it with the
-help flag will print out a brief help message, but my goal here is to give a more complete description of how to use jamfHelper than is available in its help documentation.

Window Types


At its core jamfHelper is designed to display information to the user and it does this by opening a display window. It has three distinct types of windows: the Utility window, the Heads-Up Display window, and the Full Screen window. There are a few similarities between each type of window, but its their differences that make jamfHelper such a unique and viable tool. Because jamfHelper’s only required option is -windowType, it makes the most sense to cover each type of window and it capabilities.

Utility Windows

The Utility window is a highly customizable window type and can be used to produce dialog windows that look professional.

utility windowType

UTILITY – Making things look official.

Utility windows can be customized with buttons, a dropdown menu, timeout features, and more. It is displayed with:
-windowType utility

Full Screen Windows

By far the most unique of the three window types. The Full Screen window will cover the entire screen and block all user input, making very useful if you need to keep a user from interrupting a critical update.

fullscreen windowType

FULL SCREENGetting things done behind the scenes.

⚠ WARNING ⚠: Once this mode is activated, it will either have to be terminated via a remote command, put into the background and killed later, or via proper threading. (^C will not save you, and you WILL have to reboot your computer).

Fullscreen windows are created by specifying: -windowType fs

Heads-Up Display Windows

The most versatile of all three window types, with a darker look and feel, the HUD window can be configured in every way a Utility window can (and then some).

hud-example

HUD – For everything else.

The HUD window is also the only window that has a close button, which makes it ideal for notification messages that don’t require any user input. They are displayed by specifying: -windowType hud

The Basics


There are a couple key options in jamfHelper that you will find yourself using in almost every instance:

-title, -heading, -description, -icon, -button1 and -button2.

jamfHelper example

Each option labeled as itself, and a Casper Suite icon.

These particular options are pretty straight forward, and may be all you need to introduce this UI into your scripts. Taken directly from -help:

-title "string"
    Sets the window's title to the specified string

-heading "string"
    Sets the heading of the window to the specified string

-description "string"
    Sets the main contents of the window to the specified string

-icon path
    Sets the windows image filed to the image located at the specified path

-button1 "string"
    Creates a button with the specified label

-button2 "string"
    Creates a second button with the specified label

The example above would be called from terminal as:

/Library/Application\ Support/JAMF/bin/jamfHelper.app/Contents/MacOS/jamfHelper -windowType utility -icon /Library/Application\ Support/JAMF/bin/Management\ Action.app/Contents/Resources/Self\ Service.icns -title “Title” -heading “Heading” -description “Description” -button1 “button1” -button2 “button2”

As you can see, jamfHelper commands tend to get pretty verbose, pretty quickly. The main perpetrators being the path to the executable itself, and the absolute path to the icon. I’ll be truncating these two items for future examples.

Button Basics


Buttons are the only method for user interaction with jamfHelper windows. If pressed, -button1 will return 0 , and
-button2 will return 2 (except when -showDelayOptions is also specified, but we’ll get to that later).

By capturing jamfHelper’s output, you can dictate what your scripts will do. For example:

#!/bin/bash

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

# Get the user's selection
RESULT=`"$JAMFHELPER" -windowType utility -title "Example" -description "Press a button!" -button1 "OK" -button2 "Cancel"`

if [ $RESULT == 0 ]; then
    # do button1 stuff
    echo "OK was pressed!"
elif [ $RESULT == 2 ]; then
    # do button2 stuff
    echo "Cancel was pressed!"
fi

-button1 will always appear on the right, and -button2 on the left (even if -button1 isn’t specified)

That’s pretty much all to it. You could start using jamfHelper with these options alone and it would be a useful tool. However, there’s a little more to explore.

Advanced Usage


Now, if you’ve ever used jamfHelper before, you’ll know that it has some… erhm… quirks to it. Everything I’ve described thus far has been pretty intuitive, but there are also some oddities about jamfHelper that can make it frustrating to use at times. The general way jamfHelper operates can make it somewhat difficult to determine what each flag does. Sometimes, you can specify a flag that won’t appear to do anything at all, or maybe, you’ll use a flag that should work but doesn’t.  The rest of this post was meant to clear up some of these “gotchas” as much as I was able.

jamfHelper does not complain or exit if given incorrect flags or improper arguments. It simply works with what you give it and tries to do its best with what it has, but (in my opinion) this lack of feedback makes it somewhat difficult to isolate bugs or improper use. For example:

jamfHelper -windowType utility -description "Please make a selection" -button1 OK -showDelayOptions "0, 300, 86400"

 

selection-example

Now, if we were to change the command slightly, we get:

jamfHelper -windowType utility -description "Please make a selection" -button1 OK -showDelayOptions "-1, 0, 300, 86400"

selection-example-bug

Wait… Where’s the dropdown menu!?

The dropdown menu that -showDelayOptions is supposed to display is missing entirely. Which is odd, because the last command we ran is almost identical to the first one.

What actually happened is that “-1, 0, 300, 86400” was interpreted by jamfHelper as an option, causing -showDelayOptions to be called without any parameters and thusly seen as a malformed option. Instead of quitting immediately with an error message, jamfHelper goes on with what it has and displays the description text and the button.

This is not the only way this can happen either, as not every option works with every -windowType, and some options are reliant on the use of others (e.g. -countdown requires -timeout). If an option is not meant to work with the specified -windowType, is dependent on additional options, or an option is given incorrect parameters, it will be ignored entirely by jamfHelper without any feedback whatsoever.

Full Screen Windows


Full Screen windows are, by far, the most limiting window. They cannot have buttons or dropdown menus, and even -timeout doesn’t work.

The only options available with -windowType fs are: -icon, -heading, -description, and -fullScreenIcon.

⚠ WARNING ⚠: Full Screen Windows will lock you out of your computer unless used properly. If you REALLY need to see how it works, use this script:

#!/bin/bash

PATH=$PATH:/Library/Application\ Support/JAMF/bin/jamfHelper.app/Contents/MacOS

# Run a fullscreen jamfHelper window and put it into the background
jamfHelper -windowType fs -heading "This is a Full Screen Window" -description "Please wait 30 seconds" &
PID=$!      # Get the pid of the jamfHelper command
sleep 30
kill $PID
-fullScreenIcon
    Scales the "icon" to the full size of the window
    Note: Only available in full screen mode

-fullScreenIcon requires -icon. It also disables -heading and -description.

Now if you were to use a general icon with -fullScreenIcon, all you would see is a big black screen with a giant icon. This option is best used when you have very specific formatting needs or more elements than are allowed in a jamfHelper window. You can make a custom image to display whatever information you want, and then block out the entire screen.

Display Text


Some of this was covered above, but for the sake of completeness I have included it here as well with some additional information.

All text flags appear to accept any form of printable characters including unicode (I have not tried control characters besides newline).

-title "string"
    Sets the window's title to the specified string

There doesn’t appear to be any character limit for this string, but will trail off with an ellipsis if the title is too long for the window.

-heading "string"
    Sets the heading of the window to the specified string

Also doesn’t appear to have any character limits, and jamfHelper will resize the window (as well as it can) to accommodate the entire heading.

-description "string"
    Sets the main contents of the window to the specified string

While -title and -heading behave very uniformly, I have noticed some odd things about -description. Because jamfHelper automatically resizes the window based upon various factors (-iconSize, -heading, etc.) text specified in with -description can display haphazardly at times.

I have noticed scenarios where a few words are left off the end, or a sentence is cut off after a newline. Although, this can usually be fixed by padding -heading  with a few spaces or adding 5-10 pixels to -iconSize. jamfHelper does, however, appear to be consistant with -description, meaning, if it appears correctly given a static set of options, it will always appear the same way across any set of machines or times it is executed. So check your output, tweak a few things (-iconSize is my go-to) and if you are happy with the way it appears, you can be confident that it will continue to appear uniformly.

NOTE: -description could also be considered a “required” argument due to the fact that there is a weird bug where if -description is omitted or left blank, it will default to the last -description used by the last instance of jamfHelper.

Text Alignment


These arguments are meant for aligning text as desired. They are pretty straight forward given that you also specify -description, -heading, and -countdown.

-alignDescription [right | left | center | justified | natural]
    Aligns the description to the specified alignment

-alignHeading [right | left | center | justified | natural]
    Aligns the heading to the specified alignment

-alignCountdown [right | left | center | justified | natural]
    Aligns the countdown to the specified alignment

NOTE: -countdown is dependent on -timeout, making -alignCountdown dependent on both.

Icons


Although they aren’t necessary, icons are a nice addition to any dialog window. jamfHelper supports a number of image types.

-icon path
    Sets the windows image filed to the image located at the specified path

As long as the path to an image is valid, jamfHelper can typically use that image as an icon. Icons work with every -windowType. I tested a number of image types and jamfHelper displayed almost all of them. The image types I tested were:

Supported:         ICNS, PNG, JPG/JPEG, TIFF, PDF, and GIF (although GIFs are not animated).

Unsupported:    SVG

This isn’t an exhaustive list, and there may be additional supported/unsupported image formats.

-iconSize pixels
    Changes the image frame to the specified pixel size.

pixels must be in the form of an integer, (e.g. -iconSize 120) and can be no smaller than 15 (The system will throw a CALayerInvalidGeometry exception). I’ve had issues where -iconSize will modify the size of the jamfHelper window, but not the image itself. Because jamfHelper automatically resizes the window based on several factors, I have used -iconSize to fix odd formatting bugs with -description.

Dropdown Menu


-showDelayOptions adds a dropdown menu to the jamfHelper window that will give a user the option to select an amount of time.

-showDelayOptions "int, int, int,..." 
    Enables the "Delay Options Mode". The window will display a dropdown with the values passed through the string

Arguments supplied to this flag should be in the form of seconds and jamfHelper will automatically convert them to a human readable format (i.e. 300 becomes “5 minutes”, 3600 becomes “1 hour”, etc.) The selection is then pre-appended (in seconds) to the return value of the button pressed (without delineation).

The way buttons work is slightly changed as well. Where -button1 would normally return 0 , now it returns XX1 (where XX is the user selection in seconds). The same notation goes for -button2, which returns XX2 .

So assuming jamfHelper was called with the following options:

jamfHelper -windowType utility -button1 "button1" -button2 "button2" -showDelayOptions "0, 1, 1200, 3600, 12345, 214748364"

The result would be:

showdelayoptions-window

Depending on the selection, and which button was pressed, the return values would be:

showdelayoptions

The last example 214748364 is actually the upper limit of -showDelayOptions, any number that is larger will return unpredictably. The numbers given do not have to be in any specific order, but it’s typically good form to go from least to greatest. If text is given (instead of a number) -showDelayOptions replaces it with 0  (a.k.a “Start now”). Specifying negative numbers will display properly in the dropdown menu, but will not return a predictable value.

Default Buttons


jamfHelper is also able to map the return (enter) and escape (esc) keys to certain buttons. These options do not affect -windowType fs.

-defaultButton [1 | 2]
    Sets the default button of the window to the specified button. The Default Button will respond to "return"

This works as expected and in the case of both the hud and utility  -windowType. The default button will be highlighted.

-cancelButton [1 | 2]
    Sets the cancel button of the window to the specified button. The Cancel Button will respond to "escape"

By default, -windowType hud will respond to the esc key as a shortcut to closing the window (returning code 239-lockHUD can be used to disable this functionality. -cancelButton overwrites this default.

Both -defaultButton and -cancelButton are dependent on -button1 and/or -button2.

Miscellaneous


-timeout int
    Causes the window to timeout after the specified amount of seconds
    Note: The timeout will cause the default button, button 1 or button 2 to be selected (in that order)
-countdown
    Displays a string notifying the user when the window will time out

NOTE: -countdown can only be used in conjunction with -timeout.

-lockHUD
    Removes the ability to exit the HUD by selecting the close button

-lockHUD can only be used with -windowType hud. Apart from removing the close button, it also removes the esc key functionality (unless -cancelButton is specified).

Speculation


-startlaunchd
    Starts the JAMF Helper as a launchd process

Does nothing as far as I am able to tell.

-kill
    Kills the JAMF Helper when it has been started with launchd

If used, jamfHelper immediately exits with 3 … That’s it.

This option is very confusing as it seems to immediately (and only) kill the instance of jamfHelper that uses it. As far as I was able to tell, it seems to leave all other instances of jamfHelper alone. Because you are immediately killing the jamfHelper window that uses this flag, it essentially makes the flag as useful as not calling jamfHelper at all.

It is pretty obvious that these two flags are tied to each other in some way, but how they are meant to work is a mystery to me.

I originally assumed that you could use -startlaunchd to start jamfHelper in the background, and continue on with a script. Then afterwards, you could use -kill to reap the original jamfHelper window. I tested this for the better portion of 2 days before I finally gave up and called JAMF support, but they were not able to provide any answers or additional documentation.

Real World Examples


What good is all of this information if you can’t think of any way to use it Well my friend, let me share with you the way I originally used jamfHelper. Cool thing about jamfHelper windows is that they can work on top of the login window. If you were to make your own LaunchAgent, maybe something to the tune of:

<?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>example.loginwindow.softwareupdate</string>
	<key>LimitLoadToSessionType</key>
	<string>LoginWindow</string>
	<key>ProgramArguments</key>
	<array>
		<string>/Library/Application Support/JAMF/bin/jamfHelper.app/Contents/MacOS/jamfHelper</string>
		<string>-windowType</string>
		<string>fs</string>
		<string>-icon</string>
		<string>/System/Library/CoreServices/Software Update.app/Contents/Resources/SoftwareUpdate.icns</string>
		<string>-fullScreenIcon</string>
	</array>
	<key>KeepAlive</key>
	<dict>
		<key>PathState</key> 
		<dict>
			<key>/path/to/some/triggerfile</key>
			<true/>
		</dict>
	</dict>
</dict>
</plist>

You could essentially block a user from login as long as /path/to/some/triggerfile existed. If you replace the icon with something institutionally relevant, have a script touch that triggerfile at startup, and then delete the file after finishing. You have one a very viable use of jamfHelper.

Here at the Marriott Library, we used jamfHelper to notify consultants about maintenance errors on checkout laptops for students before the laptop was checked out. So if a maintenance error occurred the login window would appear as:

loginwindow

This would give a consultant the ability to quickly identify an issue and choose another laptop to checkout. Beyond just the notification, we wanted to give consultants the ability to re-run maintenance right from the jamfHelper window. This was easy, because we could just add a button! However, there were some complications. Because our maintenance process requires a reliable connection to the network, and can take a very long time to run, we needed some additional functionality to test that maintenance could be run properly.

We created some functions that could be used to test if the power adapter was connected and if the system was currently using an ethernet connection.

Because we could add whatever text we wanted (including unicode), we came up with a script that would dynamically create a jamfHelper window as the system power or ethernet connection changed. When “Connect” was pressed, two more windows were spawned in place of the one above:

waiting-for-power

This window stays open until a power adapter is plugged in.

First the system would wait for power, then for an ethernet based connection. Each window would stay open as long the service was not detected. If the user pressed cancel, it would cycle back to the original window with the “Connect” button.

waiting-for-ethernet

This window stays open until a Thunderbolt Ethernet connection is detected.

Once both system states were detected, the user was then finally presented the original window but with a different option:

With this window, a user could run maintenance right from the login window.

This would then kick off our maintenance program. The LaunchAgent used to start the script was tied to the login window and to a specific error log that was only created if maintenance failed. If a user logged in, the jamfHelper windows would disappear, and the dialog windows would not display if maintenance succeeded. This script has allowed us to rely on our consultants to fix a multitude of common issues, saving time for all of us, all while keeping our laptop checkouts moving smoothly.

I included the basic forms of the code below.

Presentation


Here is a presentation from the University of Utah’s October MacAdmin Meeting:

helping-jamfhelper-presentation

 

More


Here’s a decently sanitized version of the script I was describing above, I had to change some of the paths and supporting files so that it would run on any computer with jamfHelper installed, but you can go through and modify what you need to if you want to play around with it.

#!/usr/bin/python

################################################################################
# Copyright (c) 2017 University of Utah Student Computing Labs.
# All Rights Reserved.
#
# Author: Sam Forester sam.forester@utah.edu
# 
# Permission to use, copy, modify, and distribute this software and
# its documentation for any purpose and without fee is hereby granted,
# provided that the above copyright notice appears in all copies and
# that both that copyright notice and this permission notice appear
# in supporting documentation, and that the name of The University
# of Utah not be used in advertising or publicity pertaining to
# distribution of the software without specific, written prior
# permission. This software is supplied as is without expressed or
# implied warranties of any kind.
################################################################################

import sys
import subprocess
import plistlib
import SystemConfiguration as sc
import threading
import time
import re
import os

'''
Author's Notes: 

Here's the basic idea of my script described in the blog: https://apple.lib.utah.edu/jamfhelper

The script on it's own doesn't do much as I stripped out the functional maintenance portions,
but it should illustrate how I used the jamfHelper command.

It is designed to work on Mac laptops, but functionally works on any mac with JAMF
installed. On desktops, power connectivity isn't much of an issue for obvious
reasons.

I've wanted to make this script better for a while.

Currently it spawns a jamfHelper window with the status of both power and ethernet
connections, but if the jamfHelper window is just left there without interaction while
the ethernet and power adapters states are changed, the jamfHelper window is static
and doesn't update.

The script goes through and is pretty thorough about checking the status of power and
ethernet after each interaction, but I would still like main window to dynamically update
on the change of status.

The Success and Failure classes were fun to make, python has some pretty cool features
they probably aren't necessary, but I was all about having a variable to test as well as
print.

I have two versions of check_power() both work the exact same but one uses another framework.

This was my first attempt with threading so bear that in mind, it's no fun to code if you
can't try something new and/or learn something.

Use the snippets or extend it to your needs. You can comment below if you have any questions. 
'''

class Success(object):
    '''
    simple class that tests true and prints as unicode checkmark
    '''
    def __str__(self):
#         return u" {} ".format(u'\u2713')   # checkmark
        return u"{}".format(u'\u2705')       # White Heavy Check Mark

    def __bool__(self):
        return True

    __nonzero__ = __bool__

 
class Failure(object):
    '''
    simple class that tests False and prints as unicode X
    '''
    def __str__(self):
        return u"{}".format(u'\u274C')       # Cross Mark (i.e. red X))

    def __bool__(self):
        return False

    __nonzero__ = __bool__


def check_ethernet():
    '''
    checks active network interface hardware, if Ethernet, returns success()
    '''

    dynamicStoreRef = sc.SCDynamicStoreCreate(None, sys.argv[0], None, None)

    active_key = 'State:/Network/Global/IPv4'
    activeService = sc.SCDynamicStoreCopyValue(dynamicStoreRef, active_key)
    
    if activeService:
        serviceUUID = activeService['PrimaryService']
        key = 'Setup:/Network/Service/{}/Interface'.format(serviceUUID)
        service = sc.SCDynamicStoreCopyValue(dynamicStoreRef, key)
        hardware = service['Hardware']
    else:
        # if no network services are active, then obviously ethernet isn't connected
        return Failure()

    # return the class with which we can both test and print its unicode character
    if 'Ethernet' in hardware:
        return Success()
    else:
        return Failure()

def check_power():
    '''
    Simple command to check for a power adapter on a Mac laptop.
    
    If connected to power, returns success()
    '''
    out = subprocess.check_output(['pmset', '-g', 'ps'])
    if 'AC' in out:
        return Success()
    else:
        return Failure()

def check_power2():
    '''
    Does the exact same thing as check_power(), but using the SystemConfiguration
    framework instead.
    '''
    dynamicStoreRef = sc.SCDynamicStoreCreate(None, sys.argv[0], None, None)
    power_key = 'State:/IOKit/PowerAdapter'
    power_active = sc.SCDynamicStoreCopyValue(dynamicStoreRef, power_key)
    
    # return the class with which we can both test and print its unicode character
    if power_active:
        return Success()
    else:
        return Failure()

def main_display_window():
    '''
    The main jamfHelper window display.
    
    We are just dynamically building the jamfHelper window as events happen.
    
    Automatically changing the description as tests succeed/fail and changing the text
    '''
    # get the initial status of the ethernet and power connections
    ethernet = check_ethernet()
    power = check_power()
    
    # because of the Success() and Failure() classes we can use them to print out a 
    # unicode character as well as test them for True|False
    desc = "Before running maintenance make sure the computer is:\n\n" + \
           u"{}  Connected to Power\n".format(power) + \
           u"{}  Connected to Ethernet\n".format(ethernet)
    
    # If both services are up then we can just run maintenance (otherwise we'll have to wait)
    if ethernet and power:
        button = 'Run'
    else:
        button = 'Connect'
    
    # default icon located in the jamfHelper.app (we use other images)
    icon = '/Library/Application Support/JAMF/bin/jamfHelper.app/Contents/Resources/Message.png'

    # jamfHelper command
    helper_cmd = [
        '/Library/Application Support/JAMF/bin/jamfHelper.app/Contents/MacOS/jamfHelper', 
        '-windowType', 'utility', 
        '-windowPosition', 'lr',
        '-iconSize', '145',
        '-icon', icon,  # requires a valid path to image file
        '-title', 'Error',
        '-description', desc,
        # change the heading by a few characters and see the wildly different
        # window sizing issues I was talking about
        '-heading', 'An error was encountered...', 
        '-button1', button,
        '-defaultButton', '1',
    ]
    
    pipe = subprocess.PIPE
    p = subprocess.Popen(helper_cmd, stdout=pipe, stderr=pipe)

    # this isn't really necessary in this script, but if you had more than one button
    # you would want to test this variable to see what button was pressed
    retcode, err = p.communicate()  # retcode returned as string
    return int(retcode)             # easier to test an int
    
def detect_ethernet_window():
    '''
    Similar to detect_power_window(), we are threading out a jamfHelper window and 
    checking the status of an ethernet network conenction.
        
    Once the network connection is connected, it automatically closes the jamfHelper window
    '''
    # default icon located in the jamfHelper.app (we use other images)
    icon = '/Library/Application Support/JAMF/bin/jamfHelper.app/Contents/Resources/Message.png'

    # jamfHelper command
    helper_cmd = [
        '/Library/Application Support/JAMF/bin/jamfHelper.app/Contents/MacOS/jamfHelper', 
        '-windowType', 'utility', 
        '-windowPosition', 'lr',
        '-title', 'Connecting...',
        '-heading', 'Waiting for Ethernet...',
        '-description', 'Please plug in Thunderbolt Ethernet adapter (this may take a moment)',
        '-icon', icon,
        '-iconSize', '170',
        '-button1', 'Cancel',
        '-defaultButton', '1',
    ]

    # get current status of ethernet network connection
    ethernet = check_ethernet()

    # Here we thread out a jamfHelper window and check the status of ethernet every second
    # but only spawn a thread if we need to (i.e. ethernet isn't already connected)
    if not ethernet:
        p = subprocess.Popen(helper_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        # create a new thread for the helper window
        t = threading.Thread(target=p.communicate)  
        t.start()

        # while the status of check_ethernet() fails or the thread lives
        while not ethernet and t.isAlive():
            ethernet = check_ethernet()
            time.sleep(1)
        
        # kill the jamfHelper thread if it's still running
        if t.isAlive():
            subprocess.check_call(['kill', str(p.pid)])     

        # bring the threads back together
        t.join()                           

    return ethernet

def detect_power_window():
    '''
    Similar to detect_ethernet_window(), we are threading out a jamfHelper window and 
    checking the status of the power adapter.
        
    Once the power adapter is connected, it automatically closes the jamfHelper window
    '''
    # default icon located in the jamfHelper.app (we use other images)
    icon = '/Library/Application Support/JAMF/bin/jamfHelper.app/Contents/Resources/Message.png'

    # jamfHelper command
    helper_cmd = [
        '/Library/Application Support/JAMF/bin/jamfHelper.app/Contents/MacOS/jamfHelper', 
        '-windowType', 'utility', 
        '-windowPosition', 'lr',
        '-title', 'Connecting...',
        '-heading', 'Waiting for Power...',
        '-description', 'Please plug in AC Power adapter',
        '-icon', icon,
        '-iconSize', '150',
        '-button1', 'Cancel',
        '-defaultButton', '1',
    ]

    # get current status of power adapter
    power = check_power()

    # Here we thread out a jamfHelper window and check the status of power every second
    # only spawn a thread if we need to (i.e. power isn't already connected)
    if not power:
        p = subprocess.Popen(helper_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        # create a new thread for the helper window
        t = threading.Thread(target=p.communicate)  
        t.start()

        # while the status of check_power() fails or the thread lives
        while not power and t.isAlive():
            power = check_power()
            time.sleep(1)
        
        # kill the jamfHelper thread if it's still running
        if t.isAlive():
            subprocess.check_call(['kill', str(p.pid)])     

        # bring the threads back together
        t.join()                           

    return power

def run_maintenance():
    '''
    This is where we kick off our mainentenance program at the Marriott Library, but 
    because this is so institutionally specific, I modified the actual code for the blog
    post.
    '''
    
    # Do something interesting here!
    print('running maintenance!')
    sys.exit()


def main():
    while True:
        main_display_window()
        if check_ethernet() and check_power():
            run_maintenance()
        else:
            detect_ethernet_window()
            detect_power_window()
    

if __name__ == '__main__':
    main()

 

10 Comments
  • Ross Derewianko (@PingRD)
    Posted at 16:51h, 10 October Reply

    Awesome writeup! The one other thing thats slowly been worked on is, Someone Over at JAMF is building a gui to build your helper.
    https://github.com/JAMFSupport/JAMF-Helper-GUI

    • Sam Forester
      Posted at 17:48h, 10 October Reply

      Thanks, I’m glad you enjoyed it. I’ll take a look at the gui builder. Thanks for the link.

  • uraqt
    Posted at 01:38h, 24 March Reply

    Hi,

    Could you share your jamfHelper scripts in github? I am trying to get a jamfHelper to show the jamf log live.

    Thanks
    C

    • Richard Glaser
      Posted at 20:59h, 28 March Reply

      Hello Chris:

      Thanks for the comment. We will work on sanitizing our jamfHelper scripts and posting either to our github repository, this blog post or both.

    • Sam Forester
      Posted at 22:03h, 31 March Reply

      Hi Chris,

      I added a version of the script to the post, play around with it and tell me what you think.

  • QRAQT
    Posted at 22:31h, 29 March Reply

    Thank you !!!

  • Kavan Joshi
    Posted at 10:59h, 21 February Reply

    Hi Sam,

    Awesome write up. Just wondering if you know a way to add ‘InputBox(s)’ to let user enter their data?

    • Sam Forester
      Posted at 17:52h, 21 February Reply

      Kavan,

      Unfortunately there is not a textfield option with jamfHelper. There isn’t even an option for a text based dropdown menu.

      However, there are other tools such as cocoadialog which has a lot of options including `inputbox`, but it isn’t built into macOS and you would need to distribute it yourself. Here’s a link to their GitHub

      If you need something simple, you can always use osascript.

      $ osascript -e 'display dialog "Enter your name" default answer "" buttons {"Cancel", "OK"} default button "OK"'
      button returned:OK, text returned:Sam

      I usually switch between the two depending on my needs. Hope that helps!

      • KAVAN JOSHI
        Posted at 07:58h, 16 June Reply

        Thanks SAM.
        Visited this site after long time, just noticed your response.

  • Pingback:Dad Jokes as a (Self) Service (DJaaS?) – Sound Mac Guy
    Posted at 07:52h, 19 May Reply

    […] those amongst us using Jamf, we’ve got a binary called jamfHelper. Learn more about that, here. Here’s an example of how you might use jamfHelper in a […]

Leave a Reply to Sam ForesterCancel reply