├── images ├── success.png ├── invalid_tag.png └── existing_tag.png ├── README.md └── AssetTag.py /images/success.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamf/Mac-Asset-Tag/HEAD/images/success.png -------------------------------------------------------------------------------- /images/invalid_tag.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamf/Mac-Asset-Tag/HEAD/images/invalid_tag.png -------------------------------------------------------------------------------- /images/existing_tag.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamf/Mac-Asset-Tag/HEAD/images/existing_tag.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Mac Asset Tag 2 | 3 | ## What were we trying to solve? 4 | 5 | There are a lot of simple solutions out there for prompting a Mac user for the asset tag to their computer and submitting that value to the JSS. In fact, the jamf binary handles this using the recon option: 6 | 7 | ``` 8 | ~$ sudo jamf recon -assetTag XXXXXX 9 | ``` 10 | 11 | A solution for prompting a user can be as simple as using a call to osascript in a shell script or writing it entirely in AppleScript and submitting the value input by the user. 12 | 13 | Instead, the asset tag script written for use at JAMF Software became a test ground for what could be done for user interaction through Self Service. 14 | 15 | ## What does it do? 16 | 17 | The script, written in Python, generates a GUI using the Tkinter framework and submits the entered asset tag value to the computer record via the JSS REST API. 18 | 19 | The custom GUI that is generated from the script contains an embedded graphic and displays feedback to the user when they click the Submit button. A regular expression is used to ensure what the user enters into the input field matches what we expect for an asset tag. 20 | 21 | Message for an invalid asset tag value: 22 | 23 | ![Screenshot](/images/invalid_tag.png) 24 | 25 | Message for an existing asset tag on the record: 26 | 27 | ![Screenshot](/images/existing_tag.png) 28 | 29 | Message for successful update of the asset tag: 30 | 31 | ![Screenshot](/images/success.png) 32 | 33 | If you want to dive into how the GUI is created and defined, you will find the code in the script is heavily commented and should be able to experiment with altering the appearance or crafting your own. 34 | 35 | ## How to deploy this script in a policy 36 | 37 | Upload the script to your JSS and create a policy. Enter the JSS API username into parameter 4 and the password into parameter 5. 38 | 39 | This user account will need read/update permissions to **computer** objects and update permissions to **user** objects. 40 | 41 | ## License 42 | 43 | ``` 44 | JAMF Software Standard License 45 | 46 | Copyright (c) 2015, JAMF Software, LLC. All rights reserved. 47 | 48 | Redistribution and use in source and binary forms, with or without modification, are permitted 49 | provided that the following conditions are met: 50 | 51 | * Redistributions of source code must retain the above copyright notice, this list of 52 | conditions and the following disclaimer. 53 | * Redistributions in binary form must reproduce the above copyright notice, this list of 54 | conditions and the following disclaimer in the documentation and/or other materials 55 | provided with the distribution. 56 | * Neither the name of the JAMF Software, LLC nor the names of its contributors may be 57 | used to endorse or promote products derived from this software without specific prior 58 | written permission. 59 | 60 | THIS SOFTWARE IS PROVIDED BY JAMF SOFTWARE, LLC "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, 61 | INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 62 | PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JAMF SOFTWARE, LLC BE LIABLE FOR ANY 63 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 64 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 65 | BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 66 | STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 67 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 68 | ``` -------------------------------------------------------------------------------- /AssetTag.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python2.7 2 | from AppKit import NSBundle 3 | import base64 4 | import re 5 | import subprocess 6 | import sys 7 | import syslog 8 | import tkFont 9 | import Tkinter 10 | import urllib2 11 | import xml.etree.cElementTree as etree 12 | 13 | # This script logs actions to both STDOUT and system.log 14 | # You can set your own 'tag' for the log entries here 15 | syslog.openlog("jamfsw-it-logs") 16 | 17 | 18 | def log(msg): 19 | """Logging function to write to system.log and STDOUT""" 20 | print(msg) 21 | syslog.syslog(syslog.LOG_ALERT, "AssetTag: " + str(msg)) 22 | 23 | log("Starting Submit Mac Asset Tag") 24 | 25 | # Prevent the Python app icon from appearing in the Dock 26 | info = NSBundle.mainBundle().infoDictionary() 27 | info['LSUIElement'] = True 28 | 29 | 30 | def get_uuid(): 31 | """This will obtain the UUID of the current Mac using system_profiler""" 32 | mac_uuid = subprocess.check_output(["/usr/sbin/system_profiler", "SPHardwareDataType"]) 33 | return mac_uuid[mac_uuid.find("Hardware UUID:"):].split()[-1] 34 | 35 | # The UUID is used for API calls to the JSS 36 | macUUID = get_uuid() 37 | log("Mac UUID: {0}".format(macUUID)) 38 | 39 | # The JSS URL and API credentials are assigned here (credentials passed as parameters) 40 | jssURL = "https://yourjss.jamfcloud.com/JSSResource/computers/udid/{}".format(macUUID) 41 | jssAuth = base64.b64encode(sys.argv[4] + ':' + sys.argv[5]) 42 | 43 | # This is a base64 encoded GIF that will be displayed in the GUI 44 | gifBase = """ 45 | R0lGODlhgACAAPcAAO3t7SMfICklJiwoKSsnKDMvMDQwMTczND87PEM/QDg1NkE+P2BdXm1qa2hlZn16e01LTEtJSmVjZIB+f3l3eJ2bnIyKi4mHiIiGhyUk 46 | JVRTVFxbXL69vra1tqCfoJ6dnpeWlwEBAzw8PRUbMQoRJyM3exs5gho1ewYLGCVBhSM8fiU/gSZFigEDBx1CiihHhm5vcSRKkRctUylNjBtHkSJPmCdUnSla 47 | oi5bnigqLS1gpzJmqjFioyxVjylOfjdmoztsqzI1OTU4PDc6Pjs+QjNrrjlrpkFFSjhxsTBglzVooz9xqihFZlaOylmRzTx2swMGCUJ7tUd+tkR3q0t9r2id 48 | 0SwvMjAzNiMlJxYXGDU3ORscHWttb0iEukqCt0yFuVCIvFCHuVCEtFWLvViPwVWJuGCazl2TxFiMujxdfDVSbmOVxGeZx0yJvFOLvFWNvlqSwl+YylWJtV2V 49 | w1uQvl+XxV2TwVqNuGOayGWdyl6Ru2KVv2aZw0ZphmqdyG2fy3iv3nWq13Gkzlx+mk9rg2GCnlGOvVWSv1iVwl2YxGuo2GegzEVfc1+cxSUoKiotLy4xM2Kg 50 | yGqmzW1wcAgJCCcoJwYGBQ0NDAMCAAQDAQUEAhgXFQ0KBwsHBBQRDzEuLjw6OiMiIkpISFlXV399fW9tbWpoaHp5eXd2dnNycnFwcG1sbGJhYeDf39TT08jH 51 | x8TDw7q5ubSzs66tra2srKqpqainp6KhoZybm5qZmZWUlJKRkZCPj4aFhYWEhPX19fDw8Ozs7Ovr6+np6efn5+Xl5eLi4t7e3t3d3dvb29ra2tfX19DQ0M3N 52 | zcrKysXFxcLCwsDAwL6+vry8vLu7u7e3t7KysrCwsK6urqurq6WlpaOjo46OjoyMjIqKioODg4KCgoCAgH5+fnx8fHt7e29vb2tra2lpaV9fX1paWlZWVlFR 53 | UU9PT0NDQz8/Pzk5OTY2NjIyMi8vLywsLCoqKiAgIBgYGBUVFRISEhAQEAoKCgQEBAICAgEBAQAAAP///yH5BAEAAP8ALAAAAACAAIAAAAj/AAEIHEiwoMGD 54 | CBMqXMiwocOHECNKnEixosWLGDNq3Mixo8ePIEOKHEmypMmTEYVRK6UuncuXMGPKnEmzpstRDiZx2cmzp7lz3pihpAgNHiVK+pIqXcq0qdOkSJ82pZQvCJEh 55 | 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 124 | fnvuu/f+e/DDF5/cgAAAOw== 125 | """ 126 | 127 | 128 | class App: 129 | # This class will generate the GUI and all of its elements 130 | # It also contains all of the functions used when one of the buttons are clicked 131 | 132 | # Regular expression to match asset tag pattern 133 | # This regex will match inputs to "JSXXXXXX" where 'X' is a number 134 | assetTag = re.compile('^JS[0-9]{6}$') 135 | 136 | def __init__(self): 137 | # The first lines create the GUI object and set it to stay on top of all other windows 138 | self.root = Tkinter.Tk() 139 | self.root.withdraw() 140 | self.root.protocol('WM_DELETE_WINDOW', self.clicked_exit) 141 | self.root.call('wm', 'attributes', '.', '-topmost', True) 142 | self.root.title("Submit Your Asset Tag") 143 | 144 | # Set the default background colors for the window elements 145 | bgcolor = "#EDEDED" 146 | self.root.tk_setPalette(highlightbackground=bgcolor, background=bgcolor) 147 | 148 | # Setup the default font for widgets 149 | font = tkFont.nametofont('TkDefaultFont') 150 | font.config(family='Helvetica Neue', size=14) 151 | self.root.option_add("*Font", font) 152 | 153 | # This will prevent menu bar options from appearing when the window is selected 154 | menubar = Tkinter.Menu(self.root) 155 | self.root.config(bg=bgcolor, menu=menubar) 156 | 157 | # Here we load the data of our GIF image above and place it in the window 158 | # The '.grid()' method allows you to place elements in the window according to XY coordinates 159 | gif = Tkinter.PhotoImage(data=gifBase) 160 | Tkinter.Label(self.root, image=gif, borderwidth=10).grid(row=0, rowspan=5) 161 | 162 | # Tkinter labels display text or images within a window and can have unique styles for each 163 | Tkinter.Label(self.root, text="Enter the asset tag number for your Mac:").grid(padx=15, row=0, column =1) 164 | Tkinter.Label(self.root, text="(Your asset tag is located beneath your MacBook)", 165 | font=("Helvetica Neue", 10)).grid(row=1, column =1) 166 | 167 | # Here is another Tkinter variable that will store the value of what is in our entry box 168 | self.input_variable = Tkinter.StringVar() 169 | self.input_variable.set("JSxxxxxx") 170 | # The entry box is a field where a user can type in 171 | Tkinter.Entry(self.root, width=8, bg='white', textvariable=self.input_variable).grid(row=2, column=1) 172 | 173 | # These variables are for the message line in our GUI that changes both text 174 | # and color based upon success or error messages 175 | self.messageColor = Tkinter.StringVar() 176 | self.messageColor.set("red") 177 | 178 | self.messageVar = Tkinter.StringVar() 179 | self.messageVar.set("") 180 | 181 | self.messageLabel = Tkinter.Label(self.root, textvariable=self.messageVar, 182 | font=("Helvetica Neue", 10, "italic"), fg=self.messageColor.get()) 183 | self.messageLabel.grid(row=3, column=1) 184 | 185 | # To have the 'Exit' and 'Submit' buttons display nicely in the lower-right corner I am 186 | # 'packing' them inside of a widget called a frame that is place into the grid 187 | button_frame = Tkinter.Frame(self.root) 188 | Tkinter.Button(button_frame, text="Exit", command=self.clicked_exit, height=1).pack(side=Tkinter.RIGHT) 189 | self.submitButton = Tkinter.Button(button_frame, text="Submit", command=self.clicked_submit, height=1) 190 | self.submitButton.pack(side=Tkinter.RIGHT) 191 | button_frame.grid(row=4, column=1, padx=10, pady=(0, 5), sticky='e') 192 | 193 | # These last lines will position the GUI slightly above the center of the screen 194 | # when it is finally drawn on the 'self.root.mainloop()' line 195 | x = (self.root.winfo_screenwidth() - self.root.winfo_reqwidth()) / 2 196 | y = (self.root.winfo_screenheight() - self.root.winfo_reqheight()) / 3 197 | self.root.geometry("+{0}+{1}".format(x, y)) 198 | self.root.resizable(False,False) 199 | self.root.deiconify() 200 | self.root.mainloop() 201 | 202 | def display_error(self, msg): 203 | # This error function writes a log entry and also displays the same text on the 204 | # message line in the GUI 205 | log("Message displayed to user: " + str(msg)) 206 | self.messageVar.set(str(msg)) 207 | 208 | def clicked_exit(self): 209 | # This will close the window and exit the script 210 | log("Exit button has been clicked") 211 | self.root.destroy() 212 | 213 | def clicked_submit(self): 214 | # This function will validate the entered asset tag by the user and also verify 215 | # that there is not already an asset tag in the inventory record (that issue can 216 | # also be addressed via smart computer group scoping) 217 | input_value = self.input_variable.get().upper() 218 | if not self.assetTag.match(input_value): 219 | self.display_error("An invalid asset tag was entered") 220 | return 221 | 222 | # XML string for the PUT to the JSS 223 | xml = '' \ 224 | '{}'.format(input_value) 225 | 226 | # If 'self.checkForExistingTag()' returns 'True' then the API update is performed 227 | if self.check_for_existing_asset_tag(): 228 | self.update_asset_tag(xml) 229 | 230 | def check_for_existing_asset_tag(self): 231 | # This function checks the JSS for an existing asset tag in the inventory record 232 | log("Checking the JSS for an existing asset tag in the record") 233 | request = urllib2.Request(jssURL + '/subset/general') 234 | request.add_header('Authorization', 'Basic ' + jssAuth) 235 | 236 | # If the API call is successful it will check for an asset tag and return 'True' if 237 | # there is none or 'False' if a value is present ('False' will prevent the follow- 238 | # -up API update from occurring) 239 | try: 240 | response = urllib2.urlopen(request) 241 | except Exception as e: 242 | # If an error is encountered it is logged and a message is displayed to the user 243 | log("There was an error with the API call:") 244 | log("API error: {0}".format(e)) 245 | self.messageVar.set("There was a problem communicating with the JSS.") 246 | else: 247 | existing_tag = etree.fromstring(response.read()).find('general/asset_tag').text 248 | if existing_tag: 249 | self.display_error("There is an existing asset tag for this Mac: {}".format(existing_tag)) 250 | self.submitButton['state'] = 'disabled' 251 | return False 252 | else: 253 | return True 254 | 255 | def update_asset_tag(self, xml): 256 | # This function will update the inventory record in the JSS with the new asset tag 257 | log("Updating the asset tag in the JSS") 258 | request = urllib2.Request(jssURL) 259 | request.add_header('Authorization', 'Basic ' + jssAuth) 260 | request.add_header('Content-Type', 'text/xml') 261 | request.get_method = lambda: 'PUT' 262 | 263 | # The below is very similar to the 'checkForExistingTag()' function above 264 | # If the API call is successful the message color is set to blue with a success 265 | # message displayed to the user and the 'Submit' button is disabled to they 266 | # cannot perform the action again 267 | try: 268 | urllib2.urlopen(request, xml) 269 | except Exception as e: 270 | log("There was an error with the API call:") 271 | log("API error: {}".format(e)) 272 | log("API error: {}".format(e.read())) 273 | self.messageVar.set("There was a problem submitting the asset tag to the JSS.") 274 | else: 275 | log("Message displayed to user: The asset tag has been submitted to the JSS.") 276 | self.messageColor.set("blue") 277 | self.messageLabel.configure(fg=self.messageColor.get()) 278 | self.messageVar.set("Success! The asset tag has been submitted to the JSS.") 279 | self.submitButton['state'] = 'disabled' 280 | 281 | 282 | # Execute the GUI by calling the class 283 | if __name__ == '__main__': 284 | App() 285 | --------------------------------------------------------------------------------