I’ve been absent from the blog for a while with my time being pretty split between work and family. I thought I’d drop a quick update on some things I’m meaning to get posted and also get some small ones out of the way. I also wanted to switch out of my minimalist theme and into something a bit easier on the eyes.
Updated script for listing Mac App Store apps in Self Service
The original version of this script I posted here attempted to capture inventory data from a user installing an App Store app just as thought they were installing software published by the IT department. Back in 10.8 the script worked, but as of 10.9 timeouts and errors with various AppleScript actions happened randomly and it was no longer reliable.
Also, come VPP v2 and I had a philosophical shift when it came to having accurate inventory. As I no longer could be sure when users were claiming the apps we were provisioning from the VPP portal it made no sense to rewrite my original App Store workflow I’m treating my Macs more and more like I would iPads when it comes to provisioning.
I trimmed out most of the code and was left with this:
#!/bin/bash # The 'loggertag' is used as a tag for all entries in the system.log loggertag="selfservice-macappstore" log() { # This log() function writes messages to the system.log and STDOUT echo "${1}" /usr/bin/logger -t "${loggertag}: ${policy}" "${1}" } # The iTunes address for the App (can be grabbed from its App Store page) is passed # from the JSS into the 'App Store URL (itunes.apple.com/...)' variable from parameter 4. # Example: itunes.apple.com/app/ibooks-author/id490152466 appAddress="${4}" log "Mac App Store URL: ${appAddress}" # The App Store is opened to the specified app. log "Opening the Mac App Store" /usr/bin/osascript -e "tell application \"System Events\" to open location \"macappstore://${appAddress}\"" if [ $? != 0 ]; then log "Script error $?: There was en error opening the Mac App Store" exit 1 fi exit 0
The script takes only the iTunes/App Store URL and opens the Mac App Store to that item and nothing more. Its simpler, an overall better experience, and I am still able to present a quick shortcut to common Mac App Store apps in Self Service just as I would on any of my users’ iOS devices.
New script for downloading Box Sync 4 via Self Service
Prior to the release of Box Sync 4 I had written a new stand-alone Self Service install script for the app. The script at this earlier post still works for loading the Box Edit plugin, and I still use it, but the script below downloads the Box Sync 4 DMG, mounts it, copies the app and then remove all traces of the process.
#!/bin/bash # The 'policytag' is used for log entries policytag="BoxSync4" # The 'loggertag' is used as a tag for all entries in the system.log loggertag="boxsync4install" log() { # This log() function writes messages to the system.log and STDOUT /usr/bin/logger -t "${loggertag}: ${policytag}" "${1}" echo "${1}" } mountCheck() { if [ -d /Volumes/boxsync4 ]; then log "/Volumes/boxsync4/ directory exists" if [[ $(/sbin/mount | /usr/bin/awk '/boxsync4/ {print $3}') == "/Volumes/boxsync4" ]]; then log "/Volumes/boxsync4/ is a mounted volume: unmounting" /usr/bin/hdiutil detach /Volumes/boxsync4 if [ $? == 0 ]; then log "/Volumes/boxsync4/ successfully unmounted" else log "hdiutil error $?: /Volumes/boxsync4/ failed to unmount" exit 1 fi fi log "Deleting /Volumes/boxsync4/ directory" /bin/rm -rf /Volumes/boxsync4 fi } cleanup() { # The cleanup() function handles clean up tasks when 'exit' is called log "Cleanup: Starting cleanup items" mountCheck if [ -f /tmp/boxsync4.dmg ]; then log "Deleting /tmp/boxsync4.dmg" /bin/rm -rf /tmp/boxsync4.dmg fi log "Cleanup complete." } # This 'trap' statement will execute cleanup() once 'exit' is called trap cleanup exit log "Beginning installation of ${policytag}" # Check for the expected size of the downloaded DMG webfilesize=$(/usr/bin/curl box.com/sync4mac -ILs | /usr/bin/tr -d '\r' | /usr/bin/awk '/Content-Length:/ {print $2}') log "The expected size of the downloaded file is ${webfilesize}" # Download the Box Sync Installer DMG log "Downloading the Box Sync Installer DMG" if [ -f /tmp/boxsync4.dmg ]; then # If there's another copy of the DMG inside /tmp/ it is deleted prior to download /bin/rm /tmp/boxsync4.dmg log "Deleted an existing copy of /tmp/boxsync4.dmg" fi /usr/bin/curl -Ls box.com/sync4mac -o /tmp/boxsync4.dmg if [ $? == 0 ]; then log "The Box Sync Installer DMG successfully downloaded" else log "curl error $?: The Box Sync Installer DMG did not successfully download" exit 1 fi # Check the size of the downloaded DMG dlfilesize=$(/usr/bin/cksum /tmp/boxsync4.dmg | /usr/bin/awk '{print $2}') log "The size of the downloaded file is ${dlfilesize}" # Compare the expected size against the downloaded size if [[ $webfilesize -ne $dlfilesize ]]; then echo "The file did not download properly" exit 1 fi # Check if the /Volumes/boxsync4 directory exists and is a mounted volume mountCheck # Mount the /tmp/boxsync4.dmg file /usr/bin/hdiutil attach /tmp/boxsync4.dmg -mountpoint /Volumes/boxsync4 -nobrowse -noverify if [ $? == 0 ]; then log "/tmp/boxsync4.dmg successfully mounted" else log "hdiutil error $?: /tmp/boxsync4.dmg failed to mount" exit 1 fi if [ -e /Applications/Box\ Sync.app ]; then /bin/rm -rf /Applications/Box\ Sync.app log "Deleted an existing copy of /Applications/Box\ Sync.app" fi log "Copying /Volumes/boxsync4/Box\ Sync.app to /Applications" /bin/cp -R /Volumes/boxsync4/Box\ Sync.app /Applications/Box\ Sync.app if [ $? == 0 ]; then log "The file copied successfully" else log "cp error $?: The file did not copy successfully" exit 1 fi # Open /Applications/Box\ Sync.app # This will also begin the migration from Box Sync 3 to Box Sync 4 #/usr/bin/open /Applications/Box\ Sync.app # Run a recon to update the JSS inventory log "Postinstall for ${policytag} complete. Running Recon." /usr/sbin/jamf recon if [ $? == 0 ]; then log "Recon successful." else log "jamf error $?: There was an error running Recon." fi exit 0
Promoter
I haven’t abandoned Promoter. Work is still progressing, but I stopped during my reorganization of the code into classes/modules when I broke off into a tangent that ended up becoming a full blow JSS REST API Python library. I’ll dive into that next, but I have made a couple of changes to my goals with Promoter:
- I’m dropping file migration support – for now
There are a number of reasons behind this. The bigger piece of this is there’s no API to the JSS for uploading packages/scripts. The other part is that because I have been writing Promoter as a tool to be integrated into scripted workflows it occurs to me that most IT admins will already have their own methods of transferring files between different shares and such functionality would be redundant and possibly less robust. In the end, upon a successful migration, you would want to write in the code that would migrate your files into your production environment (if that would even be required). - Including more controls
Based upon some feedback from Tobias and others, I’m going to be expanding the command line options to allow some limited transformation of the XML before it is posted to the destination JSS mainly in the form of swapping out values (e.g. setting a Site, adding in computer groups for scope that are present on the destination, forcing the policy to be enabled by default). - Support for importing as a Python module
Once I have finished cleaning up the code I’ll be posting the raw source online so everyone can grab it and modify it freely, but also so the code can be easily imported into other Python scripts instead of being a command line utility. That said, I’ll still post compiled versions of the binaries as I did before so they can be run on systems their either do not have the third-partly libraries or don’t have Python installed.
jsslib – A Python library for the JSS REST API
As I mentioned above, I’ve been working on a Python library for making scripts interacting with the JSS REST API easy and quick to write. The project took off when I decided to rewrite the existing JSS class I had written for Promoter and then began expanding it to cover the full API.
The syntax for the new library looks like this:
>>> import jsslib >>> myjss = jsslib.JSS('https://myjss.com', 'myuser', 'mypass') >>> results = myjss.computers() <Response [200]> https://jss.jamfcloud.com/bryson/JSSResource/computers
The above line would return the list of all computers in the JSS (you can see the URL in the output). While that’s easy to write its not very special. Any kind of library should be doing work for you and making things easier for you. That’s why I decided to write the new API to handle a lot of tasks for the end user. The returned objects from these calls all result in “auto-parsed” attributes:
>>> results.id_list [1, 2] >>> results.size '2' >>> results.data '<?xml version="1.0" encoding="UTF-8"?><computers><size>2</size><computer><id>1</id><name>USS-Voyager</name></computer><computer><id>2</id><name>Starship Enterprise</name></computer></computers>'
In addition to auto-parsing, the library also allows for any and all ‘simple’ objects in the JSS to be created or updated using parameters instead of passing XML:
>>> results = myjss.buildings_create("Minneapolis") <Response [201]> https://jss.jamfcloud.com/bryson/JSSResource/buildings/id/0 >>> results.id '5' >>> myjss.buildings_update(5, "St Paul") <Response [201]> https://jss.jamfcloud.com/bryson/JSSResource/buildings/id/5 <jsslib.JSSObject instance at 0x103629dd0> >>> myjss.buildings(5) <Response [200]> https://jss.jamfcloud.com/bryson/JSSResource/buildings/id/5 >>> myjss.buildings(5).name <Response [200]> https://jss.jamfcloud.com/bryson/JSSResource/buildings/id/5 'St Paul'
The API will also be ‘smart’ in that it will use order of priority to test your input for certain API calls. The best example of this is with computers and mobile devices. Here is a series of API calls to look up a computer. Take a note of the URLs in each of the outputs.
>>> results = myjss.computers(1) <Response [200]> https://jss.jamfcloud.com/bryson/JSSResource/computers/id/1 >>> results.udid 'ZZZZ0000-ZZZZ-1000-8000-001B639ABA8A' >>> myjss.computers(results.udid).name <Response [200]> https://jss.jamfcloud.com/bryson/JSSResource/computers/udid/ZZZZ0000-ZZZZ-1000-8000-001B639ABA8A 'USS-Voyager' >>> myjss.computers("USS-Voyager").serial_number <Response [200]> https://jss.jamfcloud.com/bryson/JSSResource/computers/name/USS-Voyager 'QP8ZZZZHX85'
You’ll notice again I’m making use of the auto-parsing features that are built into the library. Computer and mobile device records all return the following attributes already parsed and readable: UDID, serial number, MAC address, ID, model and computer name.
You might be wondering how you’re going to figure out how all of this works as the JSS API returns a wide variety of objects with different sets of data. A huge point of this to me was to make sure anyone could start working with the library without hand holding, and so I’m making sure all of the documentation if baked into the code just like any other Python library:
>>> help(jsslib.JSS.advancedcomputersearches) Help on method advancedcomputersearches in module jsslib: advancedcomputersearches(self, identifier=None) unbound jsslib.JSS method Returns a JSSObject for the /advancedcomputersearches resource Example Usage: myjss.advancedcomputersearches() -- Returns all advanced computer searches ('None' request) myjss.advancedcomputersearches(1) -- Returns an advanced computer search by ID myjss.advancedcomputersearches('name') -- Returns an advanced computer search by name Keyword Arguments: identifier -- The ID or name of the advanced computer search Returned Values for a 'None' request: JSSObject.data -- XML from response JSSObject.size -- Total number of all advanced computer searches JSSObject.id_list -- List of all advanced computer search IDs (int) from the response Returned Values for ID or name requests: JSSObject.data -- XML from response JSSObject.id -- The ID of the advanced computer search (END)
I presented jsslib at an internal company event not long ago and I was about 50% done at that point. This library will end up being used by Promoter for all of its interactions with the JSS, and I will be posted the library somewhere once it is ready. I’m also still continuing to flesh out some of the features (I’m considering adding a .search() method to returned objects for searching the XML data without having to pipe it into an XML parser).
You can expect to see jsslib popping up on here again in the near future.
As always, if you have any comments or questions just reach out to me here or anywhere else I linger.
This sounds awesome. Thanks so much for making my life easier!
LikeLike