315 lines
10 KiB
Python
315 lines
10 KiB
Python
#!/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'\u2705') # 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() |