The unexpected return of JavaScript for Automation

Monterey has deprecated the pre-installed python on macOS. To be precise, built-in python has been deprecated since macOS Catalina, but Monterey will now throw up dialogs warning the user that an app or process using built-in python needs to be updated.

I and others have written about this before:

So far, I have recommended to build native Swift command line tools to replace python calls. However, from discussions in MacAdmins Slack, a new option has emerged. Most of the credit for popularizing and explaining this goes to @Pico (@RandomApps on Twitter) in the #bash and #scripting channels.

(Re-)Introducing JavaScript for Automation

AppleScript has been part of macOS since System 7.1. In the late nineties, there was concern that it wouldn’t make the transition to Mac OS X, but AppleScript made the jump and has happily co-existed with the Terminal and shell scripting as an automation tool on macOS. AppleScript has a very distinct set of strengths (interapplication communication) and weaknesses (awkward syntax and inconsitent application functionality and dictionaries) but it has been serving its purpose well for many users.

With Mac OS X 10.4 Tiger, Apple introduced Automator, which provided a neat UI to put together workflows. Much of Automator was based on AppleScript and users expected a more and improved AppleScript support because of that going forward. Instead, we saw AppleScript’s support from Apple and third parties slowly wane over the years.

AppleScript is stil very much present and functional in recent versions of macOS. It just seems like it hasn’t gotten much love over the last decade or so. Now that Shortcuts has made the jump from iOS, there may be hope for another revival?

The last major changes to AppleScript came with Mavericks and Yosemite. Mavericks (10.9) included a JavaScript syntax for the Open Scripting Architecture (OSA), which is the underlying framework for all AppleScript functionality. Apple called this “JavaScript for Automation.” Because this is a mouthful, it often abbreviated as JXA.

The JavaScript syntax and structure is more like a “real” programming language, than the “english language like” AppleScript. Once again this raised hopes that this could attract more scripters to AppleScript and thus encourage Apple and third party developers to support more AppleScript. But unfortunately, this positive re-inforcement did not take off.

Then Yosemite (10.10) made the AppleScript-Objective-C bridge available everywhere in AppleScript. Previously, the Objective-C bridge was only available when you built AppleScript GUI applications using AppleScript Studio in Xcode. The Objective-C bridge allows scripters to access most of the functionality of the system frameworks using AppleScript or JXA.

The coincidence of these two new features might be the reason that the ObjC bridge works much better using JXA than it does with the native AppleScript syntax.

JXA and Python

What does JXA and the AppleScriptObjC bridge have to do with the Python deprecation in modern macOS?

One reason python became so popular with MacAdmins, was that the pre-installed python on Mac OS X, also came with PyObjC, the Objective-C bridge for python. This allowed python to build applications with a native Cocoa UI, such as AutoDMG and Munki’s Managed Software Center. It also allowed for short python scripts or even one-liners to access system functionality that was otherwise unavailable to shell scripts.

For example, to determine if a preference setting in macOS is enforced with a configuration profile, you can use CFPreferences or NSUserDefaults.

Objective-C/C:

BOOL isManaged =CFPreferencesAppValueIsForced("idleTime", "com.apple.screensaver")

Swift:

let isManaged = CFPreferencesAppValueIsForced("idleTime", "com.apple.screensaver")

The Objective-C bridge allows to use this call from python, as well:

from Foundation import CFPreferencesAppValueIsForced
isManaged=CFPreferencesAppValueIsForced("idleTime", "com.apple.screensaver")

With JXA and the AppleScriptObjC bridge, this will look like this:

ObjC.import('Foundation');
$.CFPreferencesAppValueIsForced(ObjC.wrap('idleTime'), ObjC.wrap('com.apple.screensaver'))

Now, this looks really simple, but working with any Objective-C bridge is always fraught with strange behaviors, inconsistencies and errors and the JXA ObjC implementation is no different.

For example, I wanted to change the code above to return the value of the setting instead of whether it is managed. The CFPreferences function for that is called CFPreferencesCopyAppValue and it works fine in Swift and Python, but using JXA it only ever returned [object Ref]. The easiest solution was to switch from the CFPreferences functions to using the NSUserDefaults object:

ObjC.import('Foundation');
ObjC.unwrap($.NSUserDefaults.alloc.initWithSuiteName('$1').objectForKey('$2'))

(Once again many thanks to @Pico on the MacAdmins Slack for helping me and everyone else with this and also pointing out, that there is a different, somewhat complicated, solution to the object Ref problem. I will keep that one bookmarked for situations where there is no alternative Cocoa object.)

We used this to remove the python dependency from Mischa van der Bent’s CIS-Scripts.

JXA in shell scripts

To call JXA from a shell script, you use the same osascript command as for normal AppleScript, but add the -l option option to switch the language to JavaScript:

osascript -l JavaScript << EndOfScript
     ObjC.import('Foundation');
    ObjC.unwrap($.NSUserDefaults.alloc.initWithSuiteName('idleTime').objectForKey('com.apple.screensaver'))
EndOfScript

For convenience, you can wrap calls like this in a shell function:

function getPrefValue() { # $1: domain, $2: key
      osascript -l JavaScript << EndOfScript
     ObjC.import('Foundation');
    ObjC.unwrap($.NSUserDefaults.alloc.initWithSuiteName('$1').objectForKey('$2'))
EndOfScript
}

function getPrefIsManaged() { # $1: domain, $2: key
     osascript -l JavaScript << EndOfScript
     ObjC.import('Foundation')
     $.CFPreferencesAppValueIsForced(ObjC.wrap('$1'), ObjC.wrap('$2'))
EndOfScript
}

echo $(getPrefValue "com.apple.screensaver" "idleTime")
# -> actual value
echo $(getPrefIsManaged "com.apple.screensaver" "idleTime")
# -> true/false

Note that the $ character does a lot of work here. It does the shell variable substitution for the function arguments in the case of $1 and $2. These are substituted before the here doc is piped into the osascript command. The $. at the beginning of the command is a shortcut where $ stands in for the current application and serves as a root for all ObjC objects.

There is also a $(…) function in JXA which is short for ObjC.unwrap(…) but I would recommend against using that in combination with shell scripts as shell’s command substitution has the same syntax and would happen before the JavaScript is piped into osascript.

There is a GitHub wiki with more detailed documentation on using JXA, and the JXA Objective-C bridge in particular.

JXA for management tasks

I’ll be honest here and admit that working with JXA seems strange, inconsistent, and — in weird way — like a step backwards. Putting together a Command Line Tool written in Swift feels like a much more solid (for lack of a better word) way of solving a problem.

However, the Swift binary command line tool has one huge downside: you have to install the binary on the client before you can use it in scripts and your management system. Now, as MacAdmins, we usually have all the tools and workflows available to install and manage software on the client. That’s what we do.

On the other hand, I have encountered three situations (set default browser, get free disk space, determine if a preference is managed) where I needed to replace some python code in the last few months and I would have no trouble finding a few more if I thought about it. Building, maintaining, and deploying a Swift CLI tool for each of these small tasks would add up to a lot of extra effort, both for me as the developer and any MacAdmin who wants to use the tools.

Alternatively, you can deploy and use a Python 3 runtime with PyObjC, like the MacAdmins Python and continue to use python scripts. That is a valid solution, especially when you use other tools built in python, like Outset or docklib. But it still adds a dependency that you have to install and maintain.

In addition to being extra work, it adds some burden to sharing your solutions with other MacAdmins. You can’t just simply say “here’s a script I use,” but you have to add “it depends on this runtime or tool, which you also have to install.

Dependencies add friction.

This is where JXA has an advantage. Since AppleScript and its Objective-C bridge are present on every Mac (and have been since 2014 when 10.10 was released) there is no extra tool to install and manage. You can “just share” scripts you build this way, and they will work on any Mac.

For example, I recently built a Swift command line tool to determine the free disk space. You can download the pkg, upload it to your management system, deploy it on your clients and then use a script or extension attribute or fact or something like to report this value to your management system. Since there is a possibility that the command line tool is not yet installed when the script runs, you need to add some code to check for that. All-in-all, nothing here is terribly difficult or even a lot of work, but it adds up.

Instead you can use this script (sample code for a Jamf extension attribute):

#!/bin/sh

freespace=$(/usr/bin/osascript -l JavaScript << EndOfScript
    ObjC.import('Foundation')
    var freeSpaceBytesRef=Ref()
    $.NSURL.fileURLWithPath('/').getResourceValueForKeyError(freeSpaceBytesRef, 'NSURLVolumeAvailableCapacityForImportantUsageKey', null)
    ObjC.unwrap(freeSpaceBytesRef[0])
EndOfScript
)

echo "<result>${freespace}</result>"

Just take this and copy/paste it in the field for a Jamf Extension Attribute script and you will get the same same free disk space value as the Finder does. If you are running a different management solution, it shouldn’t be too difficult to adapt this script to work there.

The Swift tool is nice. Once it is deployed, there are some use cases where it could be useful to have a CLI tool available. But most of the time, the JXA code snippet will “do the job” with much less effort.

Note on Swift scripts

Some people will interject with “but you can write scripts with a swift shebang!” And they are correct. However, scripts with a swift shebang will not run on any Mac. They will only run with Xcode, or at least the Developer Command Line Tools, installed. And yes, I understand this is hard for developers to wrap their brains around, but most people don’t have or need Xcode installed.

When neither of these are installed yet, and your management system attempts to run a script with a swift shebang, it will prompt the user to install the Developer command line tools. This is obviously not a good user experience for a managed deployment.

As dependencies go, Xcode is a fairly gigantic installation. The Developer Command Line Tools much less so, but we are back in the realm of “install and manage a dependency.”

Parsing JSON

Another area where JXA is (not surprisingly) extremely useful is JSON parsing. There are no built-in tools in macOS for this so MacAdmins either have to install jq or scout or fall back to parsing the text with sed or awk. Since JSON is native JavaScript, JXA “just works” with it.

For example the new networkQuality command line tool in Monterey has a -c option which returns JSON data instead of printing a table to the screen. In a shell script, we can capture the JSON in a variable and substitute it into a JXA script:

#!/bin/sh

json=$(networkQuality -c)

osascript -l JavaScript << EndOfScript
    var result=$json
    console.log("Download:  " + result.dl_throughput)
    console.log("Upload:    " + result.ul_throughput)
EndOfScript

Update: (2021-11-24) Paul Galow points out that this syntax might allow someone to inject code into my JavaScript. This would be especially problematic with MacAdmin scripts as those often run with root privileges. The way to avoid this injection is too parse the JSON data with JSON.parse :

#!/bin/sh 

json=$(networkQuality -c) 

osascript -l JavaScript << EndOfScript     
  var result=JSON.parse(\`$json\`)     
  console.log("Download:  " + result.dl_throughput)     
  console.log("Upload:    " + result.ul_throughput) 
EndOfScript

(I am leaving the original code up there for comparison.)

Conclusion

After being overlooked for years, JXA now became noticeable again as a useful tool to replace python in MacAdmin scripts, without adding new dependencies. The syntax and implementation is inconsistent, buggy, and frustrating, but the same can be said about the PyObjC bridge, we are just used it. The community knowledge around the PyObjC bridge and solutions goes deeper.

However, as flawed as it is, JXA can be a simple replacement for the classic python “one-liners” to get data out of a macOS system framework. Other interesting use cases are being discovered, such as JSON parsing. As such, JavaScript for Automation or JXA should be part of a MacAdmins tool chest.

Scripting OS X — Weekly News Summary for Admins — 2021-11-12

This week’s newsletter has many links to great posts by MacAdmins. (Thanks to all!) But we also got the surprising announcement that Apple is (re-)entering* the MDM market with “Apple Business Essentials.”


(Sponsor: Mosyle)

The Fusion of Apple MDM, Identity, Patching & Security.

Mosyle Fuse logo

Mosyle Fuse is the first and only product to bring a perfect blend of an Enterprise-grade MDM, an innovative solution for macOS Identity Management, automated application installation and patching, and purpose-built multi-layer endpoint security, all specially designed for Apple devices used at work at a price point that’s almost unexplainable.

Click here to learn more!


First of all, Apple Business Essentials (I am going to risk the scorn of Apple Marketing and abbreviate it as “ABE”) will be in beta until “Spring 2022.” The beta and presumably the release is US only and limited to businesses with less than 500 users, though each user can have up to three Apple devices. The subscription includes extended iCloud storage for the managed Apple IDs (50GB to 2TB) and, after release, can include “prioritized AppleCare support” with onsite repairs. (Prices including the AppleCare support are not known yet.)

The introductory video and page are nice. But there is a lot more information in the Apple Business Essentials User Guide. (You can also find a PDF with some information for the ABE beta program in the AppleSeed for IT downloads.)

Overall, this looks like an interesting new offering from Apple, as long as your business matches the target audience. It looks as if ABE uses MDM commands only, with no local agent other than a “Apple Business Essetials” self-service app. This is standard for iOS and iPadOS, but will make the management options for Macs very limited. For many MacAdmins this will disqualify ABE for “serious” Mac management.

Keep the target audience in mind, though. For many organizations managing iPhones and iPads in business will be the main benefit of ABE and enforcing some management settings on the Macs will be a nice bonus. After all, even the little management possible with MDM commands will be better than no management at all.

From the user guide we can glean a few more interesting facts: the Apple Business Essentials web interface will replace Apple Business Manager for managing business Apple IDs, volume purchase of Apps & Books and assigning devices to MDMs, including MDMs other than Apple Business Manager. It is unclear if all ABM users will get the new interface. I imagine the iCloud storage options for Managed Apple IDs will be available to all ABM accounts, maybe even the business AppleCare subscriptions. In that case, ABE could replace ABM for everyone, even when you use a third party MDM, but the ABE management features will only be unlocked when you get the ABE subscription? We will have to wait and see.

Apple is targeting the “low-end” for device management. They are competing less with Jamf Pro and Workspace One, and more with Jamf Now, SimpleMDM, Mosyle Business, Kandji and Addigy. But when you look at the feature set, Apple’s cannot really compete with any of these, but they provide a minimal or, well, “essential” step up from “no management.” It’ll be up to the vendors to provide features and value above this new, essential, base line.

Overall, I think this is an exciting and promising announcement. There is also the hope, that since Apple is now building and selling their own management system*, this will result in improvements to the MDM protocol and Apple platform management for all. The Spring release of Monterey and iOS 15 should be very interesting.

*Apple has been and still is selling Profile Manager as part of the macOS Server app. Nevertheless, MacAdmins consider this a “reference implementation” at best and Profile Manager is not recommended for use in production at any scale.

Oh yes, we also got new beta2 for macOS 12.1 and iOS 15.2 (and siblings).

If you think your company or product is a good fit to sponsor this newsletter, please contact me!

If you would rather get the weekly newsletter by email, you can subscribe to the Scripting OS X Weekly Newsletter here!! (Same content, delivered to your Inbox once a week.)

Apple Business Essentials

📰News and Opinion

🐟macOS 12 Monterey and iOS 15

⚙️macOS and iOS Updates

🐦MacAdmins on Twitter

  • Mr. Macintosh: “Apple has released a new Intel T2 BridgeOS Update (19.16.10549) The previous version for 11.6.1 and 2021-007 is 19.16.10548. I do not see an associated OS update that goes along with it. Could this BridgeOS update fix the bricking problems for some T2 Macs?”

🔐Security and Privacy

🔨Support and HowTos

🤖Scripting and Automation

🍏Apple Support

♻️Updates and Releases

📺To Watch

🎧To Listen

🎈Just for Fun

📚Support

If you are enjoying what you are reading here, please spread the word and recommend it to another Mac Admin!

If you want to support me and this website even further, then consider buying one (or all) of my books. It’s like a subscription fee, but you also get a useful book or two extra!

Weekly News Summary for Admins — 2021-11-05

The week after a major macOS release, and we got a whole bunch of interesting posts by MacAdmins, clarifying or explaining features and aspects of the the upgrade.

Once again, many thanks to all who write and share their knowledge!


(Sponsor: Mosyle)

Mosyle Fuse logo

The Fusion of Apple MDM, Identity, Patching & Security.

Mosyle Fuse is the first and only product to bring a perfect blend of an Enterprise-grade MDM, an innovative solution for macOS Identity Management, automated application installation and patching, and purpose-built multi-layer endpoint security, all specially designed for Apple devices used at work at a price point that’s almost unexplainable.

Click here to learn more!


If you would rather get the weekly newsletter by email, you can subscribe to the Scripting OS X Weekly Newsletter here!! (Same content, delivered to your Inbox once a week.)

News and Opinion

macOS 12 Monterey and iOS 15

macOS and iOS Updates

  • Mr. Macintosh: “Heads up! Big Sur 11.6.1 & Catalina 2021-007 Security Updates are having problems with the 19.16.10548 T2 BridgeOS Update This affects a small number of Macs (<5%) The BridgeOS update fails = Mac stuck on a black screen” (Thread)

MacAdmins on Twitter

  • Craig Cohen (LinkedIn): Apple: Mac Evaluation Utility 4.0 available in AppleSeed Go to AppleSeed for IT Use your Apple Business Manager or Apple School Manager Managed Apple ID. Head to the downloads tab. Wonderful update Apple.
  • Tim Perfitt: “I saw on Monty that fn-q brings up a new note. So what other shortcuts use the fn key? fn-a: activates item in dock so you can arrow around and space to launch app, fn-n: open sidebar, fn-c: open control center, fn-h: show desktop, fn-q: new note”

Support and HowTos

Scripting and Automation

Apple Support

Updates and Releases

To Watch

To Listen

Just for Fun

Support

If you are enjoying what you are reading here, please spread the word and recommend it to another Mac Admin!

If you want to support me and this website even further, then consider buying one (or all) of my books. It’s like a subscription fee, but you also get a useful book or two extra!

Monterey, python, and free disk space

With Montery, many MacAdmins have been seeing dialogs that state:

“ProcessName” needs to be updated

and often the “ProcessName” is your management system. As others have already pointed out, the process, or scripts this process is calling, is using the pre-installed Python 2.7 at /usr/bin/python.

This is Apple’s next level of warning us that that the pre-installed Python (and Perl and Ruby) is deprecated and going away in “future version of macOS.” I have written about this before.

Even though the management system will be identified as the process that “needs to be updated,” the culprits are scripts and scriptlets that the management system calls for for management tasks (e.g. policies, tasks, scripts) and information gathering (e.g. extension attributes, facts, etc.). Ben Tom’s post above has information on how to identify scripts which may use python in a Jamf Pro server.

You can suppress the warning using a configuration profile. While this a useful measure to avoid confusing users with scary dialogs, you will have to start identifying and fixing scripts that are written entirely in python or just use simple python calls, and replacing them with non-python solutions.

Python 2.7 is not getting any more security patches and I assume Apple is eager to remove it from macOS. The clock is really ticking on this one.

Current User

The most common python call is probably the one which determines the currently logged in user. The python call for this was developed by Mike Lynn and popularized by Ben Toms in this post and has been a reliable MacAdmin tool for years. I have written about this and introduced a shell-based solution discovered by Erik Berglund.

But there are other use cases, where it is not so straight forward to replace the python code. The built-in python is so popular for MacAdmin tasks because it comes with PyObjC which allows access to the macOS system frameworks. With a few python calls you can avoid having to build an Objective-C or Swift command line tool.

Desktop Picture

I built desktoppr for this reason. The standard way to set a desktop picture with locking it down was a line of AppleScript. But, starting in macOS Mojave, sending AppleEvents to another process (in this case Finder) required a PPPC profile. You can also set the desktop picture using a framework call. There were python scripts out there, but the Swift solution will survive them…

Available Disk Space

Yesterday, I came across another such problem. With the recent versions of macOS, getting a value of the available disk space is not as strightforward as it used to be. There are a lot of files and data on the system, which will be cleared out when some process requires more disk space. Most of this is cache data or data that can be restored from cloud storage. But this ‘flexible’ available disk space will not be reported by the traditional tools, such as df or diskutil. The available disk space these tools report will be woefully low.

The available disk space which Finder reports will usually be much higher. There is functionality in the macOS system frameworks where apps can get the values for available that takes the ‘flexible’ files into account. There is even useful sample code!

Starting with this sample code, I built a command line tool that reports the different levels of ‘available’ disk space. When you run diskspace it will list them all. There are raw and ‘human-readable’ formats.

> diskspace                  
Available:      70621810688
Important:      231802051028
Opportunistic:  214051607271
Total:          494384795648
> diskspace -H              
Available:      70.62 GB
Important:      231.8 GB
Opportunistic:  214.05 GB
Total:          494.38 GB

The ‘Available’ value matches the actually unused disk space that df and diskutil will report. The ‘Important’ value matches what Finder will report as available. The ‘Opportunistic’ value is somewhat lower, and from Apple’s documentation on the developer page, that seems to be what we should use for automated background tasks.

For use in scripts, you can get each raw number with some extra flags:

> diskspace --available               
70628638720
> diskspace --important
231808547284
> diskspace --opportunistic
214057661159
> diskspace --total
494384795648

You can get more detail by running diskspace --help.

In Scripts

If you wanted to check if there is enough space to run the macOS Monterey upgrade (26 GB) you could do something like this:

if [[ $(/usr/local/bin/diskspace --opportunistic ) -gt 26000000000 ]]; then
     echo "go ahead"
else
    echo "not enough free disk space"
fi

Jamf Extension Attributes

Or, you can use diskspace in a Jamf Extension Attribute:

#!/bin/sh

diskspace="/usr/local/bin/diskspace"

# test if diskspace is installed
if [ ! -x "$diskspace" ]; then
    # return a negative value as error
    echo "<result>-1</result>"
fi

echo "<result>$($diskspace --opportunistic)</result>"

Since, this extension attribute relies on the diskspace tool being installed, you should have a ‘sanity check’ to see that the tool is there.

Get and install the tool

You can get the tool from the GitHub repo and I have created a (signed and notarized) installer pkg that will drop the tool in /usr/local/bin/diskspace.

Weekly News Summary for Admins — 2021-10-29

The macOS Monterey release is finally here. We also got iOS 15.1 (and siblings) and the security updates for macOS Big Sur (11.6.1) and iOS 14.8.1. And the new round of betas for macOS 12.1 and iOS 15.2 started.


(Sponsor: Mosyle)

The Fusion of Apple MDM, Identity, Patching & Security.

Mosyle Fuse logo

Mosyle Fuse is the first and only product to bring a perfect blend of an Enterprise-grade MDM, an innovative solution for macOS Identity Management, automated application installation and patching, and purpose-built multi-layer endpoint security, all specially designed for Apple devices used at work at a price point that’s almost unexplainable.

Click here to learn more!


While some features shown at WWDC are still missing, this should keep everyone busy for a while. Many MacAdmins were already busy and published posts on how to work with and manage the new systems.

Settle in, because this is a big summary!

If you would rather get the weekly newsletter by email, you can subscribe to the Scripting OS X Weekly Newsletter here!! (Same content, delivered to your Inbox once a week.)

macOS 12 Monterey and iOS 15.1

macOS Monterey 12.0.1

Deployment

iOS 15.1 and siblings

Security Updates

Apple Support

Reactions

MacAdmins

New Apple Silicon MacBooks Pro

macOS and iOS Updates

MacAdmins on Twitter

  • Eric Holtam: “Live Text works to capture the horribly tiny and shiny Serial Numbers from the bottom of Macs. Never question if it’s a Q or 0 again!”

Security and Privacy

Support and HowTos

Scripting and Automation

Updates and Releases

To Listen

Support

If you are enjoying what you are reading here, please spread the word and recommend it to another Mac Admin!

If you want to support me and this website even further, then consider buying one (or all) of my books. It’s like a subscription fee, but you also get a useful book or two extra!

Download Full Installer update

The small tool to download the InstallAssistant.pkg I built a while back, has been working fine, even on Monterey. However, earlier this week some people started noticing that it would not show the 11.6.1 installer. The reason had me stumped, and I was putting together a minor update with UI fixes and an icon for Monterey installers, when I realized what the problem was and was able to fix it. So you will actually find _two_ new releases on the GitHub repo today, but of course, you only need the latest, v1.1.1 aka “The real Monterey Update”.

Save up to 25% pkg file size with this weird Monterey trick!

macOS 12 Monterey brings with it a lot of new features, both for admins and users. You will probably be busy learning and experimenting with them right now, unless you already did that during the beta phase.

But, as usual, there are also many undocumented new changes and features hidden away in the update. I discovered one in the pkgbuild man page. pkgbuild, as the name implies, is used by developers and admins to build installer packages, or pkg files. Even when you are using a tool like munki-pkg or AutoPkg, it is probably using pkgbuild to assemble the installer package.

If you want to learn more about pkgbuild and creating installer packages, read my book: “Packaging for Apple Administrators

Large Payloads and Minimum Versions

“Discovered” is a strong word here. I stumbled over this as I was looking for a different new option for pkgbuild in Monterey. In a converstation with the ever awesome Duncan McCracken, he mentioned that the tool had gained an new option, --large-payload, which allows for individual files in the payload to be larger than 8GB.

This is a significant change to the pkg file format, so pkg installers created with this option will not work on systems older than 12.0. “This option requires the user to pass --min-os-version 12.0 or later to acknowledge this requirement.” (quote from the man page)

This comment led me to look for the description of the --min-os-version option and right between --large-payload and --min-os-version I stumbled over --compression.

There is no indication that these are new options in the man page. There is also no mention of any of these new options in the Developer Release Notes or the AppleSeed for IT release notes.

Payload Compression

The description for the --compression option reads:

--compression compression-mode
Allows control over the compression used for the package. This option does not affect the compression used for plugins or scripts. Not specifying this option will leave the chosen compression algorithm up to the operating system. Two compression-mode arguments are supported:

• legacy forces a 10.5-compatible compression algorithm for the package.

• latest enables pkgbuild to automatically select newer, more efficient compression algorithms based on what is provided to [--min-os-version <version>].

With this new option, in combination with the --min-os-version option, we can influence the compression algorithm used for the payload inside the pkg file. Other than that, we are left in the dark. What kind of compression algorithms? And which minimum macOS versions use which compression algorithms?

The man page is silent on this, so we need to experiment!

Lots of pkgs

After some less organized experimentation, I put together this one-liner:

for x in 10.{5..15} 11 12; do caffeinate time pkgbuild --component /Applications/Numbers.app --min-os-version $x --compression latest Numbers-min$x.pkg; done 

This is very, well, compressed, so I will explain it in steps:

Brace expansion

for x in 10.{5..15} 11 12; do

If I used for x in 10 11 12; do the shell would loop through the list using the values 10, 11, and 12. However, I also need eleven versions starting with 10. so I use the ‘brace expansion:’ 10.{5..15} will expand to 10.5, 10.6, 10.7, … until 10.15.

Brace expansion is rarely used but a very useful shell feature.

This for loop will loop through 10.5 through 10.15, and then 11 and 12, as well.

caffeinate

I did not want the MacBook I was testing on to fall asleep during the test. The caffeinate will prevent a Mac from sleeping. Most people use this command as a standalone command where it will prevent the Mac from sleeping indefinitely. Maybe you have used caffeinate -t 3600 to prevent sleep for a certain time.

But you can also use caffeinate together with a second command, and then caffeinate will prevent sleep for as long as the second command is running. For example:

caffeinate system_profiler

will prevent the Mac from falling asleep while system_profiler does its thing, which always seems like it takes ages.

The default mode of caffeinate will only prevent system sleep. The display may still dim, sleep, or even lock. If you want to prevent that as well, use caffeinate -di.

time

I was interested in the duration each pkgbuild run would take. The time command will give you that information. For example, when you run time system_profiler the system_profiler command will run, showing all its output, but the time command will add this at the end:

system_profiler  11.64s user 7.35s system 53% cpu 35.493 total

The first number (sometimes called the ‘real’ time) is the time that elapsed on the clock from the start to end of the process. The ‘user’ is also interesting as it gives the cpu time the process itself was actively running (and not waiting for other processes). Confusingly, the user time may be larger than the real time. This means the process was running on multiple cpus at once.

pkgbuild

And then we have the actual pkgbuild command using the --compression and the --min-os-version to build a pkg installer from the Numbers application on my system. I chose Numbers.app because it is fairly large (589MB) so the compression algorithm has something to do.

Results

I ran this on a MacBook Air M1 with 8GB of RAM. While pkgbuild was doing its work, I kept doing other tasks on the Mac. You can see that the run times vary by a few seconds. The system was experiencing memory pressure as the commands ran. This test wasn’t really very accurate, but the quality of the output, as you will see, is good enough to yield conclusions.

After running the above command I had 13 pkg files. and I created a chart with the output from the time command and the pkg file sizes.

When you chart the resulting filesize against the --min-os-version you see a distinct change in 10.10:

The filesizes will vary by a few bytes, but I presume that stems from different timestamps and a different minimum OS version set in the metadata of each package.

The same chart with the time required to create the pkg file:

When you use a --min-os-version value of 10.10 or higher, the file size drops by about 24% but the creation time (the ‘real’ value) nearly doubles. The ‘user’ time value increases nearly ten-fold and you may wonder how the user time can be so much higher than the ‘real’ time elapsed. The explanation is that the legacy compression algorithm uses only a single core, while the 10.10+ compression algorithm uses all available cores.

From 10.10 (Yosemite) on, the numbers stay fairly constant, all the way to 12.0, so my assumption is the compression algorithm stays the same.

Compression Algorithm

So which compression algorithms are actually used?

To figure this out, I first wanted to know if the file type of the pkg file file itself changed:

> file *.pkg    
Numbers-min10.5.pkg:   xar archive compressed TOC: 709, SHA-1 checksum, contains zlib compressed data
...
Numbers-min11.pkg:     xar archive compressed TOC: 706, SHA-1 checksum, contains zlib compressed data
Numbers-min12.pkg:     xar archive compressed TOC: 708, SHA-1 checksum, contains zlib compressed data
Numbers-minLegacy.pkg: xar archive compressed TOC: 708, SHA-1 checksum, contains zlib compressed data

As you can see the format of the wrapping archive of the pkg installer remains the same. This is probably necessary so that old versions of macOS can read the metadata inside the pkg.

But inside the pkg, is another compressed archive. You can see this when you run

> pkgutil --expand Numbers-min10.5.pkg 10.5Pkg
> ls 10.5Pkg
Bom         PackageInfo Payload
> file 10.5Pkg/Payload    
10.5Pkg/Payload: gzip compressed data, from Unix, original size modulo 2^32 594074112

Up to and including 10.9 the Payload is a gzip archive. From 10.10 upward the file command returns only data:

> file 10.10Pkg/Payload 
10.10Pkg/Payload: data

On an educated guess, I tried to list the contents of the 10.10 payload with the aa command which reads and writes the poorly documented ‘Apple Archive’ format:

> aa list -i 10.10Pkg/Payload    
.
./Numbers.app
./Numbers.app/Contents
./Numbers.app/Contents/_CodeSignature
./Numbers.app/Contents/_CodeSignature/CodeResources
...

(Note: the aa command is available on macOS Big Sur and higher.)

Expanding

When I saw how much more compute intensive the compression was, I was a bit concerned the decompression might be compute intensive on old hardware. To see if that would be a problem I used my 2012 Mac mini (the server model) running Catalina 10.15.7.

Catalina does not have the aa command line tool, (it was added in Big Sur) but pkgutil has an undocumented --expand-full option which will expand the pkg and the payload. So, I used that as a comparison:

> time pkgutil --expand-full Numbers-min10.10.pkg 10.10ExpandFull/

The M1 MacBook Air took 8.97 seconds for this operation, the 2012 Mac mini took 15.46. While that is slower, it is not a dramatic difference.

For comparison, expanding a legacy compressed file took 1.76 seconds (M1 MacBook Air) and 3.98 seconds (Mac mini 2021). The decompression is about four times slower using the new compression.

But what about productbuild?

For admins, component pkgs built with pkgbuild, are sufficient for most tasks, but sometimes developers require distribution packages. Developers generally prefer to build distribution packages.

For details on the differences of the package types and when you need which type, watch my MacDevOps YVR 2021 presentation: The Encyclopedia of Packages

The productbuild command line tool builds distribution packages. Since distribution pkgs can be far more complex than component packages, this tool has many more options. (Read my Packaging book for details.) But it has a similar mode to quickly build a distribution pkg from an app bundle:

> caffeinate time productbuild --component /Applications/Numbers.app NumbersDistDefault.pkg
productbuild: Adding component at /Applications/Numbers.app
productbuild: Inferred install-location of /Applications
productbuild: Wrote product to NumbersDistDefault.pkg
productbuild: Supported OS versions: [Min: 11.0, Before: None]
       28.85 real        22.43 user         3.42 sys

From the timing, we can guess that is creating a legacy compressed payload.

When we dig into the man page for productbuild on Monterey, we find a --component-compression option, which sounds promising. It has three options: legacy, auto, and default. The man page states that default behaves the same as legacy but that “may change in future releases of OS X.”

> caffeinate time productbuild --component /Applications/Numbers.app --component-compression auto NumbersDist.pkg
productbuild: Adding component at /Applications/Numbers.app
productbuild: Inferred install-location of /Applications
productbuild: Wrote product to NumbersDist.pkg
productbuild: Supported OS versions: [Min: 11.0, Before: None]
       44.87 real       207.40 user         8.70 sys

In this case the time suggests this uses the Apple Archive compression. But we didn’t have to provide a minimum OS version. The trick here is in the last output line of productbuild. There we see that productbuild automatically determined a minimum OS version from the application bundle. It reads the LSMinimumSystemVersion key from the app bundle’s Info.plist for this.

This is even more flexible than generically setting a min OS version of 10.10.

However, this will only work with the --component option of productbuild. Usually you have to build the components individually with pkgbuild and combine them with productbuild and in that workflow you will have to provide the minimum OS version for each component. Or determine it dynamically from the source, which is even more flexible.

Conclusion

We have learned that when you use the --compression latest with a --min-os-version of 10.10 or higher the pkg creation uses the Apple Archive compression for the payload, leading to smaller pkg file sizes. I did a few more tests with some other apps and the file compression improvements were between 20% and 25%.

When I set out to explore this, I did not expect a new compression algorithm to be present except maybe in the lastest macOS releases. This would have meant admins (who usually need to support at least two or three versions back) would have had to wait a few years before we could use a new compression algorithm, unless they were pushing the bleeding edge. However, a minimum macOS version of 10.10 means that a large majority of Apple Admins should be able to use this.

Most of the software deployed will have higher system requirements than OS X Yosemite. The minimum OS version for the package should be determined dynamically from the contents, pkgbuild and productbuild will then use the appropriate compression. The --component-compression auto option for productbuild has this dynamic behavior, but it should not be too complicated to add similar logic to your package creation workflows.

You might ask if a ~20-25% reduction in file size is really worth the extra effort of updating your packaging workflows. Since many management systems are now hosted in the cloud, every bit you can save in up- and download might have a noticeable price, if not in bandwidth costs, then in time saved for user downloading the pkg. The savings will be multiplied by the number of clients, which adds up quickly with large fleets.

I think that most effective application of this knowledge would be to have an option in your packaging workflow to use this better compression. For that, the packaging workflow will have to run on Monterey. AutoPkg is the best example of such a workflow, but there are other tools, like Packages.app or munki-pkg which could profit from this, as well.

News Summary for Admins — 2021-10-22

We finally got those Apple Silicon based MacBooks Pro this week. And they do look very nice! You have probably already read all about them. To prepare for the new M1 Pro and Max goodness, we will also get macOS 12 Monterey (and iOS 15.1 and siblings) on next Monday, October 25. Do you feel ready?

I have decided to call the 16″ a “MacBook Pro Max,” just to sow confusion.


macOS Terminal and Shell Book Cover

Support this weekly news summary

macOS Terminal and Shell:
You have always wanted to ‘learn Terminal,‘ right? This book teaches how (and why) to use the command line on macOS. Get it on Apple Books!

(If you have already bought the book, please leave a review on the Apple Books Store. Thank you!)


If you think your company or product is a good fit to sponsor this newsletter, please contact me!

In preparation for the OS releases next week, we got not just one, but two realease candidates. Unfortunately, it does not look like there will be a full installer of either RC, so no final deployment testing workflows. (There is an IPSW for Apple Configurator, so you can test those.)

If you would rather get the weekly newsletter by email, you can subscribe to the Scripting OS X Weekly Newsletter here!! (Same content, delivered to your Inbox once a week.)

News and Opinion

New MacBooks Pro with M1 Pro and Max

macOS 12 Monterey and iOS 15

macOS and iOS Updates

MacAdmins on Twitter

  • Rich Trouton: “If you want an Intel processor in your new Mac, here are your options: 21.5 inch iMac, 27 inch iMac, Mac Pro. All other Mac models are now Apple Silicon Macs.” (Mac mini added in a later tweet)
  • Brian Stucki: “FYI: There was one small addition in the macOS SLA for Monterey. (highlighted in the image) It’s especially helpful for those who run virtual machines on their Mac. Thanks!” (Highlighted passage: “run up to two (2) additional copies of the Apple Software, or any prior macOS or OS X operation system software or subsequent release of the Apple Software”)

Security and Privacy

Support and HowTos

Scripting and Automation

Apple Support

Updates and Releases

To Watch

To Listen

Support

If you are enjoying what you are reading here, please spread the word and recommend it to another Mac Admin!

If you want to support me and this website even further, then consider buying one (or all) of my books. It’s like a subscription fee, but you also get a useful book or two extra!

Weekly News Summary for Admins — 2021-10-15

Apple has announced the next live event for Monday (Oct 18). The expectation is they will show new Apple Silicon Macs and announce the publishing date for macOS Monterey.


(Sponsor: vast limits)

uberAgent: per-application network monitoring

uberAgent Logo

uberAgent is an innovative user experience monitoring product for macOS and Windows. uberAgent’s highlights include detailed information about application performance, network reliability drill-downs, application usage metering, browser performance, and web app metrics. Try for yourself and get your free 100 user community license at uberagent.com.


We also got a new series of betas for macOS 12 Monterey (beta 10) and iOS 15.1 (beta 4) and siblings.

MacSysAdmin concluded last week, but the sessions are still online. I still have some catching up to do. On top of that, Objective-by-the-Sea have posted their sessions on YouTube, as well!

Watch those sessions quickly, because next week is Jamf Nation User Conference, which will bring even more sessions to feel guilty because you have not watched them yet. You can still register for free. I will be in a Panel on Patch Managemet with Ryan Ball and Ben Toms, and I have a presentation on Installomator.

We also learned some dates for some of the conferences next year. I have updated my conference page, which also has links to session archives (where available).

If you think your company or product is a good fit to sponsor this newsletter, please contact me!

If you would rather get the weekly newsletter by email, you can subscribe to the Scripting OS X Weekly Newsletter here!! (Same content, delivered to your Inbox once a week.)

News and Opinion

macOS 12 Monterey and iOS 15

MacAdmins on Twitter

  • Patrick Wardle: “Really seems that malware (adware) authors have no problem getting their malicious creations notarized by Apple…unless this is really a legitimate/required Flash Player update!?”
  • Patrick Wardle: “~1 hr later (on a Saturday no less), Apple has revoked the notarization/certificate!”
  • William Smith: “Attention MacAdmins: Today’s release of Microsoft Office for Mac 16.54 is the last version to support macOS Mojave 10.14.x. November’s release of 16.55 will require a minimum of macOS Catalina 10.15.”
  • Edward Marczak: “Instead of blocking the Next Major OS: – Prep, test, and be ready to roll it out day 1 – Communicate your upgrade policy If there is some breaking change, communicate this to your user base, and people that upgrade just earn themselves a trip to the re-imaging station.” (Thread)

Security and Privacy

Support and HowTos

Scripting and Automation

Updates and Releases

To Watch

To Listen

Just for Fun

Support

If you are enjoying what you are reading here, please spread the word and recommend it to another Mac Admin!

If you want to support me and this website even further, then consider buying one (or all) of my books. It’s like a subscription fee, but you also get a useful book or two extra!

Installomator update: v0.7

We have published an update for Installomator. It is now at version 0.7 and has over 340 labels!

Here are the changes in detail:

  • default for BLOCKING_PROCESS_ACTIONis now BLOCKING_PROCESS_ACTION=tell_user and not prompt_user. It will demand the user to quit the app to get it updated, and not present any option to skip it. In considering various use cases in different MDM solutions this is the best option going forward. Users usually choose to update, and is most often not bothered much with this information. If it’s absoultely a bad time, then they can move the dialog box to the side, and click it when ready.
  • script is now assembled from fragments. This helps avoid merging conflicts on git and allows the core team to work on the script logic while also accepting new labels. See the “Assemble Script ReadMe” for details.
  • We now detect App Store installed apps, and we do not replace them automatically. An example is Slack that will loose all settings if it is suddenly changed from App Store version to the “web” version (they differ in the handling of settings files). If INSTALL=force then we will replace the App Store app. We log all this.
  • Change in finding installed apps. We now look in /Applications and /Applications/Utilities first. If not found there, we use spotligt to find it. (We discovered a problem when a user has Parallels Windows installed with Microsoft Edge in it. Then Installomator wanted to update the app all the time, becaus spotlight found that Windows version of the app that Parallels created.)
  • Added bunch of new labels, and improved others.
  • Renamed buildCaseStatement.sh to buildLabel.sh and improved it a lot. It is a great start when figuring out how to create a new label for an app, or a piece of software. Look at the tutorials in our wiki.
  • Mosyle changed their app name from Business to Self-Service

I have explained the changes to building the script in the beta release post and in the readme document on the repository. If you want to build your own labels, this is very important, be sure to read that first.