Archive OS X Installer application workflow

I created an automated workflow to help me archive the OS X Installer application when I download updates from the Mac App Store. The release of OS X Yosemite and all these inadequately-labeled installer apps I have lying around at home spurred me on.

Each archived installer is saved as a disk image to /Users/Shared and named with its OS X version number and build.

I thought that it might be of general interest, so you can see more at Bitbucket or Github. The workflow is automated courtesy of Noodlesoft Hazel.

In a whole different Size Class

The march towards responsive design — now with Size Classes — is a watershed moment for iOS. I can’t help but think that some of the moves Apple has made — and might make at its October 2014 announcement tomorrow — are centered around screens. This is all speculation on my part, but it’s been rolling around my head for a while now.

What if there’s no 5-inch iPhone 6 because Apple wants to move developers to update for Size Classes? No longer having the same iPhone screen as last year in the flagship phones is strong encouragement; both the iPhone 6 and 6 Plus demand that apps be updated. The big screens are a marquee feature of both new phones. Adding Notification Center interfaces is probably another part of this.

I could certainly see more encouragement coming from an expansion of the iPad line, adding a larger model alongside the existing 8 and 10 inch devices. Split screen display of multiple apps? More encouragement.

My conceit for some time has been that the establishment of the iPad Mini would eventually free Apple to increase the resolution and screen density of the iPad Air. There’s not necessarily a reason for the Mini and the Air to share the same resolution. Bumping up the resolution and density on the Air this year would give one more reason to throw out the pixel-perfect designs of the past. (I just hope the GPUs can handle such a move.)

Penn State MacAdmins Conference 2014 Extending management systems with scripting talk

Here are links to the resources related to my talk with Rich Trouton on Friday, July 11, 2014, at the Penn State MacAdmins Conference 2014.

Dynamically integrate other tools with Munki using Facter

I've been thinking about how to integrate commonly-used tools, like DeployStudio and JAMF Casper, with Munki. I think one way to do that is with a third tool, Puppet Labs' Facter.

If one of the management tools at hand is more server-driven, like DeployStudio or Casper, but can scope and tell clients to execute scripts, this integration may work. Casper can do this with policies that can be scoped to groups or smart groups — or even directory service groups.

However, Munki is client-driven. Its smart client needs to make decisions using information on the local system, interpreting instructions pulled from a server. A lot of those instructions are pretty static, but Munki has more tricks available. How can we make that smart client that depends upon local decisions work with a server-driven system?

Casper is extensible; its policies can run scripts, and there's nothing I'm aware of stopping a policy script from writing out a Facter fact. A DeployStudio script can do it, too. Specifically, a type of external fact called a structured data fact.

The structured data fact file can be written to a plain text file in /private/etc/facter/facts.d/ (for the open source version of Facter). (The plain text file could also be JSON data or a YAML file, if we'd rather use those.) No matter what format, the file contains one or more key-value pairs.

Casper is interesting here because it has policies whose scripts could write out Facter facts automatically, while also supporting Self Service policies whose scripts could do the same when the user selects them.

This wouldn't work if Munki didn't have a means to use Facter facts, of course. Would I have lead us down this path if it didn't? Out of the box, that isn't there, but it can be extended with administrator-provided conditional scripts. Luckily, Tim Sutton comes to the rescue (again!) with a Munki condition script for facts from Facter. Add this, and the way is open for us to build Munki conditional phrases to scope deployments based on the values of these facts.

So here's a rough outline of the process.

  1. Add a computer record to a group or a smart group. (Adding to a smart group in Casper probably requires completion of a "recon" run.)
  2. A client workstation executes a policy scoped to that group or smart group, of which it is now a member.
  3. The policy script writes out a Facter structured data fact, like "sample_group_membership=True", in a file. The key is "sample_group_membership" and its value is the string "True." A script could write multiple facts like this, or one fact at a time. Different policies could write or append facts to the same file.
  4. Set up a software deployment in Munki, making it a conditional item in a manifest. Use a conditional like "sample_group_membership == 'True'" to limit the scope of the deployment to only cases where the key "sample_group_membership" matches the desired value.
  5. The Managed Software Update process runs and the Munki client builds data for the ConditionalItems.plist.
  6. The client evaluates the conditional items in manifests, where "sample_group_membership == 'True'" evaluates as true, so the managed installs (or other manifest keys) under it are interpreted as being available to the client.
  7. Stuff gets installed.

Roll this all back a step to deployment time, since we may want this to start as far upstream as possible. A tool like DeployStudio could run scripts that write facts, or copy whole structured data fact files, during a deployment workflow. This could be entirely automated or perhaps based on entries in the computer record in the DeployStudio database.

There, we just connected different systems and scoped a Munki deployment based on external information.

I haven’t heard of anyone using this technique, so if this is old hat to you, I’d love to hear how it has worked out.

A year of change and a change of year

It has now been one year since I started my new position with Tamman Technologies and moved my family to the Greater Philadelphia area. It feels like everything has changed, but everything has stayed the same. Plus ça change, plus c'est la même chose, as the saying goes.

It has been a good change, a happy change. While it was a big move, it felt more dramatic to me a year ago than it does now, when it is just reality. I don’t have all of my thoughts about it collected and organized right now, but I felt that I should at least mark this point in time.

I’d also like to thank all of our family and friends that made the move possible, bearable, and successful.

Repo switcheroo

I am a little late to this, but my AutoPkg recipe repository is now available alongside many others. So, I made the switch:

$ autopkg repo-delete “$HOME/Library/AutoPkg/RecipeRepos/com.github.Jaharmi.autopkg_recipes"
$ autopkg repo-add ''

So, if you’ve been following my whimsical AutoPkg repository (Acorn! Fantastical! LaunchBar! XRG!), I suggest you switch, too.

Glob without further comment

>>> import glob
>>> glob.glob('/Applications/Install<em>OS X</em>.app')
['/Applications/Install Mac OS X', '/Applications/Install OS X', '/Applications/Install OS X Mountain']

Source code update for Penn State MacAdmins Conference 2013 Luggage talk

In case you were still interested, I've updated the source code link for my Penn State MacAdmins Conference 2013 Luggage talk. My examples from the talk are finally on-line, so you can follow along in a little more detail if the information already presented in the slides wasn't enough.

The main repository for the talk contains links to several Mercurial subrepositories, since I tracked each example separately as its own project.

Find the OS X Update in the Munki InstallResults with Python

Munki writes out data into the ManagedInstallReport.plist file when it runs. The InstallResults key in the report shows if anything at all was installed. If something was installed during the last Munki cycle, can we can programmatically filter out just the OS X Update?

The answer is yes! If it weren’t, of course, this would be an even shorter article. No short articles!

Let’s take a look at the Python code to do this. You can enter the following lines of code in the interactive Python interpreter. To get there, type “python” at the command prompt in Terminal on an OS X system.

We’ll start with the assumption that the OS X 10.8.4 update has just been installed. In that case, it would be listed in the current ManagedInstallReport.plist. (This won’t be the case on your own computer, of course, unless the same OS X update was just installed by Munki. For more on other conditions, stay tuned.) Create a variable for the plist file path as follows.

install_report_path = '/Library/Managed Installs/ManagedInstallReport.plist'

Import the Python “sys” module and add “/usr/local/munki” to the sys.path. This tells Python to look for importable modules there. This is roughly akin to adding to the PATH environment variable in a UNIX shell.

Since the Munki tools are installed — otherwise, we wouldn’t be worried about interpreting Munki install results — we can depend upon the availability of the “munkilib” Python module. That happens to include FoundationPlist, which is a handy way to read property lists. (In the following example, I import FoundationPlist as “plistlib” to hearken back to the name of an older Python module that did the same.)

import sys
from munkilib import FoundationPlist as plistlib

Read the Managed Installs Report plist file from the path given earlier. Pull out only the contents of the “InstallResults” array from the property list data.

report_plist = plistlib.readPlist(install_report_path)
install_results = report_plist['InstallResults']

Iterate through the “InstallResults” array to find each dictionary whose name is “OS X Update” and whose “applesus” value is “true.” There should only ever be one result, because only one OS X update should be installed during any given Munki run.

osx_update = [item for item in install_results if \
    (item['name'] == 'OS X Update' and \

Printing the result displays information from the matching dictionary.

    applesus = 1;
    name = "OS X Update";
    productKey = "041-9635";
    status = 0;
    time = "2013-06-13 01:36:38 +0000";
    version = "10.8.4";

Mac developers

Syndicate content