Last active
September 15, 2021 02:15
-
-
Save haircut/4f0c1d94f5f164d67f40a4f1c8b0dc15 to your computer and use it in GitHub Desktop.
Spiffy GUI for Jamf Pro workflows
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/python | |
""" | |
Collect Info | |
To be used in a Jamf Pro workflow to prompt a user/tech for info | |
Heavily cribbed from Jamf's iPhone ordering script: | |
https://github.com/jamfit/iPhone-Ordering | |
""" | |
import AppKit | |
import sys | |
import os | |
import Tkinter | |
import tkFont | |
import tkMessageBox | |
import subprocess | |
import plistlib | |
# Path to Jamf binary | |
JAMF = "/usr/local/bin/jamf" | |
# base64-encoded GIF for "icon" at the top of the GUI | |
# MUST BE A GIF! | |
mbp_icon = ''' | |
R0lGODdh+gCWAMQAAAAAAElJSVJSUlxcXGFhYW5ubnNzc3x8fIWFhY+Pj6WlpampqbKysry8vMbGxsjI | |
yNTU1Nzc3OHh4enp6fLy8v///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAkKABYALAAA | |
AAD6AJYAAAX/oCWOZGmeaKqubOu+cCzPdG3feK7vfO//wKBwSCwaj8ikcslsOp/QqHRKrVqv2Kx2y+16 | |
v+CweEwum8/otHrNbrvf8Lh8Tq/b7/i8fs/PGQ6AgYKDhIWGh4iJiouMjY6GBn0pCAwRE5eYmZqbnJ2e | |
n6ChoqOkpZ0RDAiSJQkSFa+wsbKztLW2t7i5uru8vbkTCasiBxO+xsfHEg/IzM25EgerBAzO1davEAsU | |
19zNDASSBRHd5OXm568RBZLE6O7v8LsT0X3tsBIQ+fr7/P3+/wADChxIsKDBg/1cwZrHrhisCA4iSpxI | |
saLFixgzatzIsaPHjxXHLaTHx94rCQ4e/6hcybKly5cwY8qcSbOmzZs4WToQ+YphPYcnU+YcSrSo0aNH | |
d8byWRJoBZRIo0qdSvWm0pENY0GtyrWrV6NXe5LcY/Kp0K9o06rVybMCU7JOt66dS5dqWLdj9ZSVW7ev | |
35x33+qNe/av4cMwA+fNs7cw4seIFWe95xiy5b6Sf2qtfLmz2sxNN3sePRc0XNGkU381PRi16td22wpm | |
TBi2bamsabu+zXtobjyNewsHLHsx8NrDk9P8fSe48ueJi08OCr16S+Z2nFu3jr2O9u3Qu9P5Dl65+Dnk | |
yw8/Lye9+t7s47h/fzs+nPn0Ydt/gz+/6v1u9OcfaQC2IeCAnhXIxv+BCF6m4BoMNgjZg2pEKGFk0mlG | |
2YW2UZiGhRz+5SEaIIaIWYahbWhiaiOeUeKKpaF4moowJihjazTW6OCNuuWo44Q8Hrfbj4+1aMaLRHpl | |
ZBlIJsnVkmQ06WRsSxnXHHJTHgblGFJmGdWWYnTpZVJBXjnkmHSBGYaYaBKlJhhstklcldOZJeeJdGpI | |
3Z1plpkdlnx+5qd3gAaK1ptfxGnoTIh6oeiiMTXaxaOQviQpF5RWet2g4xWqKZVY6Wnnp11dukWmpD5g | |
qhaokrpqFq1++ioWsWo66xW1VnqrFblCumsVvS76KxXBGjrsFMUGeqwUyfK5bBTN3vksFNHKOe3/E9W2 | |
ea0T2aK5bRPdjvktE+F6Oe4S5WZ5rhLpTrluEu06+S4S8SY57xH1EnmvEfn+uG8R/er4LxEB1zjwEAXD | |
eLAQZUEE0sMQRyzxxBQvHERZE0ig8cYcd+zxxyCHLPLIJJds8skoe+zUbELG4/LL8bBsJsw019yNzH/a | |
rPPOyOBMKM9AB42Lz50KbfTRYoXTAEJMN/1PBFBHLfXUVFft9NVXN7AOlw5gnc9wChRgwNhkl2322WYX | |
oMBwXkPggJUMO4W0OeqgbffdY4sztztE/1DW3uU0gMDghBdu+OGGNwA4On378Pfi3VAg+eSUV2555ZCf | |
03gPCMid+eegNzOBoCrwKrDA6ainrvrqrLfu+uuwxy777LTXbvvtuM+uANxCiF222L7jLfzwxBdv/PHI | |
J6/88swDnza2B/wRCPPUV2/99dhnn7wg0gP7x9iAaC/++OSXP3743/M+BdnRm+/++/DH334kwtRv//34 | |
56///vz37///AAygAAdIwAIa8IAITKACF8jABjrwgRCMoAQnSMEKWvCCGMygBjfIwQ56MAQAOw== | |
''' | |
class App: | |
def __init__(self, master): | |
"""Main GUI window""" | |
self.master = master | |
self.master.resizable(False, False) | |
self.master.title("Assign User Information") | |
self.master.protocol("WM_DELETE_WINDOW", self.cancel) | |
self.master.call('wm', 'attributes', '.', '-topmost', True) | |
x = (self.master.winfo_screenwidth() - self.master.winfo_reqwidth()) / 2 | |
y = (self.master.winfo_screenheight() - self.master.winfo_reqheight()) / 3 | |
self.master.geometry("+{0}+{1}".format(x, y)) | |
# w, h = self.master.winfo_screenwidth(), self.master.winfo_screenheight() | |
# self.master.overrideredirect(1) | |
# self.master.geometry("%dx%d+0+0" % (w, h)) | |
bgcolor = '#F0F0F0' | |
self.master.tk_setPalette(background=bgcolor, | |
highlightbackground=bgcolor) | |
font = tkFont.nametofont('TkDefaultFont') | |
font.config(family='system', | |
size=14) | |
self.master.option_add("*Font", font) | |
menu_bar = Tkinter.Menu(self.master) | |
self.master.config(menu=menu_bar) | |
print('Starting app') | |
# Input variables | |
self.input_assigned_user = Tkinter.StringVar() | |
self.input_assigned_dept = Tkinter.StringVar() | |
self.input_asset_tag = Tkinter.StringVar() | |
# Get icon | |
self.icon_data = Tkinter.PhotoImage(data=mbp_icon) | |
# Icon Frame | |
self.frame1 = Tkinter.Frame(self.master) | |
self.photo_canvas = Tkinter.Canvas(self.frame1, width=250, height=150) | |
self.photo_canvas.pack() | |
self.icon = self.photo_canvas.create_image(0, 0, anchor="nw", image=self.icon_data) | |
self.frame1.pack(padx=40, pady=(30, 5)) | |
# Title Frame | |
self.frame2 = Tkinter.Frame(self.master) | |
title_label = Tkinter.Label(self.frame2, text="Assign Device") | |
title_label.grid(row=0, column=0) | |
self.frame2.pack(padx=40, pady=(10,5)) | |
# Inputs frame | |
self.frame3 = Tkinter.Frame(self.master) | |
user_label = Tkinter.Label(self.frame3, text="Assigned User:") | |
user_label.pack() | |
self.entry_assigned_user = Tkinter.Entry(self.frame3, | |
background='white', | |
textvariable=self.input_assigned_user, | |
width=30) | |
self.entry_assigned_user.pack(pady=(0, 20)) | |
dept_label = Tkinter.Label(self.frame3, text="Department Code:") | |
dept_label.pack() | |
self.entry_assigned_dept = Tkinter.Entry(self.frame3, | |
background='white', | |
textvariable=self.input_assigned_dept, | |
width=30) | |
self.entry_assigned_dept.pack(pady=(0, 20)) | |
asset_label = Tkinter.Label(self.frame3, text="Asset Tag:") | |
asset_label.pack() | |
self.entry_asset_tag = Tkinter.Entry(self.frame3, | |
background='white', | |
textvariable=self.input_asset_tag, | |
width=30) | |
self.entry_asset_tag.pack(pady=(0, 20)) | |
self.frame3.pack(padx=40, pady=5) | |
# Buttons | |
self.frame5 = Tkinter.Frame(self.master) | |
submit = Tkinter.Button(self.frame5, text='Assign', height=1, width=8, command=self.submit) | |
submit.pack(side='right') | |
cancel = Tkinter.Button(self.frame5, text='Cancel', height=1, width=8, command=self.cancel) | |
cancel.pack(side='right') | |
self.frame5.pack(padx=40, pady=(5, 30)) | |
# Add GUI padding | |
def cancel(self): | |
"""Exit the GUI""" | |
print('User has closed the app') | |
self.master.destroy() | |
def submit(self): | |
""" | |
Do something with the data submitted | |
You can do...well, anything you want here. | |
I use the gathered data to set the computer's name to conform with | |
our naming convention and submit the asset tag and end user's username | |
to the JSS. | |
""" | |
print('User has submitted') | |
# Our naming convention is: | |
# [department code]-[M for Mac][L for latop or D for desktop][asset tag] | |
# i.e. IT-ML00000 | |
# | |
# Fun Fact! Our asset tags are prepended with the year the device was | |
# purchased, so a quick glance at a hostname tells us where it goes, | |
# the platform and form factor, and an approximate age. We order them | |
# from myassettag.com each year this way. | |
# Clean up the submitted end username and make it lowercase | |
# Splitting each character of the input with split(), then re-joining | |
# with ''.join() strips all whitespace as opposed to strip() which | |
# just cleans the head and tail | |
i_user = ''.join(self.input_assigned_user.get().lower().split()) | |
# Clean up the department code and make it uppercase | |
i_dept = ''.join(self.input_assigned_dept.get().upper().split()) | |
# Just strip whitespace from the asset tag | |
i_atag = ''.join(self.input_asset_tag.get().split()) | |
# Determine model for assigning name | |
# https://github.com/gregneagle/psumac2014_python/blob/master/4_3_0_machine_info.py | |
cmd = ['/usr/sbin/system_profiler', 'SPHardwareDataType', '-xml'] | |
output = subprocess.check_output(cmd) | |
info = plistlib.readPlistFromString(output) | |
hardware_info = info[0]['_items'][0] | |
if "Book" in hardware_info['machine_model']: | |
model_id = "L" | |
else: | |
model_id = "D" | |
# Assemble hostname | |
hostname = "{}-M{}{}".format(i_dept, model_id, i_atag) | |
print "Hostname: {}".format(hostname) | |
# Rename the computer | |
cmd = [JAMF, 'setComputerName', '-name', hostname] | |
rename = subprocess.Popen(cmd, stdout=subprocess.PIPE, | |
stderr=subprocess.PIPE) | |
(out, err) = rename.communicate() | |
if rename.returncode == 0: | |
print "Set computer name to {}".format(hostname) | |
else: | |
print "Rename failed!" | |
sys.exit(1) | |
# Submit new inventory | |
cmd = [JAMF, 'recon', '-endUsername', i_user, '-assetTag', i_atag] | |
inventory = subprocess.Popen(cmd, stdout=subprocess.PIPE, | |
stderr=subprocess.PIPE) | |
(out, err) = inventory.communicate() | |
if inventory.returncode == 0: | |
print "Submitted inventory to JSS" | |
else: | |
print "Inventory update failed!" | |
sys.exit(1) | |
self.master.destroy() | |
def main(): | |
# Prevent the Python app icon from appearing in the Dock | |
info = AppKit.NSBundle.mainBundle().infoDictionary() | |
info['CFBundleIconFile'] = u'PythonApplet.icns' | |
info['LSUIElement'] = True | |
root = Tkinter.Tk() | |
app = App(root) | |
# Have the GUI appear on top of all other windows | |
AppKit.NSApplication.sharedApplication().activateIgnoringOtherApps_(True) | |
rdata = app.master.mainloop() | |
sys.exit(0) | |
if __name__ == '__main__': | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
A 👍 GUI example using Tkinter to prompt for multiple user inputs without doing successive AppleScript/cocoaDialog/etc popups.
Heavily cribbed from Jamf's iPhone ordering script https://github.com/jamfit/iPhone-Ordering
Requires at least a bit of familiarity with python and tkinter. Batteries not included.
The meat of what this script does is found in the
submit()
function starting at L147.@haircut on macadmins slack if you want to chat