Update: Installomator v10.1

Minor update to Installomator, which brings it to version 10.1. Added and updated a bunch of labels. Many thanks to all who contributed!

  • updated Jamf/Dialog scripts icon handling (#778)
  • Readme Updates (#744)
  • new labels:
    • amazoncorretto11jdk (#721)
    • amazoncorretto17jdk (#721)
    • bbeditpkg (#720)
    • boop (#781)
    • camtasia2021, camtasia2022 (#730)
    • jamfcpr (#753)
    • jetbrainsrider
    • lgcalibrationstudio (#763)
    • mendeleyreferencemanager (#713)
    • microsoftofficefactoryreset (#751)
    • microsoftofficeremoval (#755)
    • mist-cli (#733)
    • mist (#732)
    • mobiletolocal (#752)
    • netiquette (#770)
    • todoist (#769)
    • transfer (#773)
    • vpntracker365 (#760)
    • zerotier (#785)
  • updated labels:
    • 1password8 (#759)
    • amazoncorretto8jdk (#721)
    • camtasia (#730)
    • citrixworkspace (#731)
    • code42 (#766)
    • drawio (#725)
    • duodevicehealth (#761)
    • idrive (#726)
    • idrivethin (#727)
    • macfuse (#714)
    • microsoftazuredatastudio (#788)
    • nudge (#754)
    • prism9 (#746)
    • skype (#762)
    • synologydriveclient (#789)
    • ultimakercura (#740)

Installomator v10.0

After three betas and a lot of testing, we have (finally) released Installomator v10.

If you haven’t been following the betas, then you should really read the release notes, as well as those of the previous betas.

Major new feature is integration with Bart Reardon’s excellent Swift Dialog. Installomator can now send download and install progress to be displayed with Swift Dialog. There are several examples made by Soren Theilgaard and myself in the repo.

This update brings Installomator to more than 500 applications. And there are several more queued up in pull requests that we need to test and merge. The feedback from the community has been amazing. Many thanks to everyone who contributed and helped!

Also, many thanks to Søren, Adam, and Isaac, who are co-managing the project with me, I couldn’t do it without them!

macOS 12.4 and iOS 15.5

The updates for macOS 12.4, iOS 15.5 and all the siblings dropped yesterday. Usually I would gather a list of links for these updates in the news summary on Friday, but since I will be on a vacation break and they will seem stale in two weeks, you will get them now. Enjoy!

Update 2022-05-19: added Apple Business and School Manager User Guides.

macOS Monterey 12.4

iOS 15.5 and iPadOS 15.5

watchOS 8.6

tvOS 15.5

Other Updates

User Guides

Community

Support

Update Installomator: v9.2

We have updated Installomator. This brings Installomator to 465(!) applications! Many thanks to everyone who contributed.

Note: Both Google and Mozilla recommend using the pkg installers instead of the dmg downloads for managed deployments. So far, Installomator has provided labels for both. (googlechrome and googlechromepkgor firefox and firefoxpkg, respectively) Since there are problems with the dmg downloads, a future release of Installomator will disable the firefox and googlechrome dmg labels. You should switch to using the firefoxpkg or googlechromepkg labels instead.

  • bug and documentation fixes
  • 40 new, and 26 updated labels

You can find more details in the release notes.

Update: Installomator 9.1

We have updated Installomator. This brings Installomator to 407(!) applications! Many thanks to everyone who contributed.

Note: Both Google and Mozilla recommend using the pkg installers instead of the dmg downloads for managed deployments. So far, Installomator has provided labels for both. (googlechrome and googlechromepkg or firefox and firefoxpkg, respectively) Since there are problems with the dmg downloads, a future release of Installomator will disable the firefox and googlechrome dmg labels. You should switch to using the respective pkg labels instead.

  • added option for Microsoft Endpoint Manager (Intune) to LOGO
  • minor fixes
  • the googlechrome label now always downloads the universal version
  • 16 new labels
  • 6 updated labels

Full release notes in the repo.

Update: Installomator 9.0.1

We found a bug had snuck in to Installomator 9.0 which broke applications that download as pkgs wrapped in dmgs, so we have a bug fix update. While we were at it, there were a few other minor changes as well:

  • improved logging levels throughout the script
  • fixed a bug for pkgindmg style labels
  • changed the criteria used to locate an app in the case the it cannot be found in the default locations, this should help with some apps with similar name (Virtual Box and Box Drive)
  • new label: WhiteBox Packages (packages)
  • modified label: loom (added Apple silicon download)

You can get more details and download the pkg installer from the Installomator repo’s release page.

Update: Installomator v9.0

We have updated Installomator to version 9.0. This brings Installomator to 404 labels (390 apps). This update also brings with it many changes in behavior:

  • We have moved the root check to the beginning of the script, and improved DEBUG handling with two different modes. DEBUG=0 is still for production, and 1 is still for the DEBUG we previously knew downloading to the directory it is running from, but 2 will download to temporary folder, will detect updates, but will not install anything, but it will notify the user (almost as running the script without root before).
  • Added option to not interrupt Do Not Disturb full screen apps like Keynote or Zoom with INTERRUPT_DND="no". Default is "yes" which is how it has worked until now.
  • pkgName in a label can now be searched for. An example is logitechoptions, where only the name of the pkg is given, and not the exact file path to it.
  • LSMinimumSystemVersion will now be honered, if the Info.plist in the app is specifying this. That means that an app that has this parameter in that file and it shows that the app requires a newer version of the OS than is currently installed, then we will not install it.
  • New variable RETURN_LABEL_NAME. If given the value 1, like RETURN_LABEL_NAME=1 then Installomator only returns the name of the label. It makes for a better user friendly message for displaying in DEPNotify if that is integrated.
  • Changed logic if IGNORE_APP_STORE_APPS=yes. Before this version a label like microsoftonedrive that was installed from App Store, and that we want to replace with the “ordinary” version, Installomator would still use updateTool, even though IGNORE_APP_STORE_APPS=yes. So we would have to have INSTALL=force in order to have the app replaced, as updateTool would be used. But now if IGNORE_APP_STORE_APPS=yes then updateTool will be not set, and the App Store app will be replaced. BUT if the installed software was not from App Store, then updateTool will not be used, and it would be a kind of a forced install (in the example of microsoftonedrive), except if the version is the same (where installation is skipped).
  • Added variable SYSTEMOWNER that is used when copying files when installing. Default 0 is to change owner of the app to the current user on the Mac, like this user was installing this app themselves. When using 1 we will put “root:wheel” on the app, which can be useful for shared machines.
  • Added option curlOptions to the labels. It can be filled with extra headers need for downloading the specific software. It needs to be an array, like curlOptions=( ). See “mocha”-software-labels.

Logging

  • Introducing variable LOGGING, that can be either of the logging levels
  • Logging levels:
    0: DEBUG Everything is logged
    1: INFO Normal logging behavior
    2: WARN
    3: ERROR
    4: REQ
  • External logging to Datadog
  • A function to shorten duplicate lines in installation logs or output of longer commands
  • Ability to extract install.log in the time when Installomator was running, if further investigations needs to be done to logs

Fixes

  • Fixed a problem with pkgs: If they were mounted with .pkg in the name, then we would find the directory and not the pkg file itself.
  • Minor fix for a check for a pkgName on a DMG. We used ls that would throw an error when not found, so the check was corrected.

Many thanks to Søren Theilgaard who did most, if not all, of the heavy lifting for this release. Also many thanks to everyone else who contributed a new label, issue or other help.

You can get Installomator from the GitHub repo.

Update: pkgcheck

The macOS Monterey 12.3 beta release notes say that the Python 2.7 binary (located in /usr/bin/python) will be removed. Since you follow this blog, this should not come as a surprise. We have been warned about this since Catalina. (Or longer)

That said, the removal of Python 2 in a minor macOS release is surprising. Minor updates should not have breaking changes or removals. Admins and developers may not expect removals and other breaking changes in a minor update and therefore not be paying as much attention to changes. Also, the time a minor update is in beta is usually 6-8 weeks, which leaves us and developers much less time to find and fix problems than a major update beta phase, which is usually 4-5 months.

Nevertheless, we have to work with what Apple deals to us. MacAdmins have been investigating their own tools and scripts since the Monterey release or earlier to avoid the prompts. But when you get vendor pkgs, these might contain anything. While you can inspect pkgs with tools like pkgutil, Pacifist or Suspicious Package, it can get tedious with many packages.

A while back I built a script called pkgcheck to automate this check. Since I (and many others) have started using it again in the recent days, I have added a few more checks to it.

The earlier version would flag files in the installer’s resources that had a /bin/bash, /usr/bin/python, /usr/bin/ruby, or /usr/bin/perl shebang. (the first line with the #!) I have now also added check for a shebang with /usr/bin/env [python|ruby|perl] because when run from an installer pkg, this will also resolve tousing the built-in, deprecated runtimes. Also, using python in the shebang will now be shown as a red error, rather than a yellow warning.

The script will now also grep for use of python in installation scripts and show those scripts. This might generate a few false positives. You will have to use your judgement. For example using python3 in an installation script will also trigger this. But then, it probably should, since python3 is not installed on macOS by default. (What you see in /usr/bin/python3 is a shim that prompts you to install the Command Line Developer Tools, unless they or Xcode are already installed.)

I hope this is useful!

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.