Leveraging Patch Management feature for Jamf Pro apps (Jamf Admin.app, Recon.app, etc.)

Valued Contributor

I have been trying to figure out how to upgrade older versions of the Casper Suite/Jamf Pro on client systems that have the apps installed.

I could package the entire suite, then scope to a smart group with criteria matching a certain version of a specific Jamf app. I'd have to package the suite every time, and correct the permissions on the package (composer seems to apply weird permissions to the Jamf Pro apps). I'd also have to update the version in the smart group every time as well. Additionally, the apps would need to be closed prior to upgrading them which could be accomplished by a preinstall script with a little more trouble.

I could also add it to my on-premise Kinobi Patch Server, but then I'd have build out the patch versions and if I wanted to include the ability to kill the apps before installation (recommended), then that would be a lot of work to build out the "Kill Applications" section every time I wanted to get out a new version of the entire suite.

However, since Jamf provides a nice mechanism to patch apps and includes the Jamf Pro apps as an available Software Title, I decided to leverage that. The Patch Management feature accounts for known/unknown versions, so the patch policies would only run on machines where the apps were out of date or older than Jamf's posted definitions. This would require a separate package for each app, which is a bother if done manually every time, and I assume many people don't use this feature due to this requirement.

Consequently, I decided to script out a way to automagically create individual app packages from the installed Jamf Pro version on a Mac. I needed it to do the following:
1. Create individual packages for each app (Jamf Admin.app, Jamf Imaging.app, Jamf Remote.app, Composer.app, and Recon.app)
2. Remove the corresponding Casper Suite apps if installed
3. Disable the automatic relocation of the Jamf apps to the /Applications/Casper Suite/ directory
4. Remove the /Applications/Casper Suite/ directory if no more .apps are in it
5. Build in a mechanism to apply specific permissions to the app for my institution so that only admins can read/execute the app, and also allow for just setting typical permissions (which will also fix the permissions issue you get when packaging with composer where icons don't show up for an installed Jamf app).

I came up with this:

# Written by Ryan Ball

# The below variable can be set to false to restrict non-admin users from reading or executing the application
# Otherwise you can leave blank or set to true to allow all users to read and execute the application

    "Jamf Admin"
    "Jamf Remote"
    "Jamf Imaging"

# Make sure we run as root so we can chown to root:admin
if [[ "$EUID" -ne 0 ]]; then
  echo "Please run as root with sudo."
  echo "Example: sudo sh $(basename "$0")"
  echo "Example: sudo ./$(basename "$0")"
  exit 0

# Loop through the applications array to package each Jamf Pro app individually
for application in "${applications[@]}"; do
    # If the Jamf Pro app does not exist on the system we are building the packages on, skip it and move on to the next one
    if [[ ! -e "/Applications/Jamf Pro/$application.app" ]]; then
        echo "Error, $application.app does not exist at /Applications/Jamf Pro/$application.app; skipping."
    identifier=$(defaults read "/Applications/Jamf Pro/$application.app/Contents/Info.plist" CFBundleIdentifier)
    version=$(defaults read "/Applications/Jamf Pro/$application.app/Contents/Info.plist" CFBundleShortVersionString)

    mkdir -p "$workingDir/files"
    mkdir -p "$workingDir/scripts"
    mkdir -p "$tempDir/build"

    echo "Staging $application.app for packaging..."
    rsync -aX "/Applications/Jamf Pro" "$workingDir/files/" --include "$application.app" --exclude '*.app'

# # Create the preinstall script for the PKG to ensure that Casper Suite app counterparts are removed
cat << EOF > "$workingDir/scripts/preinstall"

/bin/rm -Rf "/Applications/Casper Suite/$oldAppName.app"

appsRemain=$(/usr/bin/find "/Applications/Casper Suite" -name "*.app")
if [[ -z "$appsRemain" ]]; then
    /bin/rm -Rf "/Applications/Casper Suite"

exit 0

# Create the PKG Component Plist to ensure that new Jamf Pro apps are not installed in place of the Casper Suite apps
cat << EOF > "$workingDir/${application}_Component.plist"
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
    <string>/Jamf Pro/$application.app</string>

    # Set permissions on files
    chown -R root:admin "$workingDir/files/Jamf Pro"
    if [[ "$allowNonAdminToReadOrExecute" == "false" ]]; then
        echo "Setting permissions to restrict non-admin users from reading and executing $application.app."
        chmod -R 750 "$workingDir/files/Jamf Pro"
        echo "Setting permissions to allow non-admin users to read and execute $application.app."
        chmod -R 755 "$workingDir/files/Jamf Pro"
    chmod +x "$workingDir/scripts/preinstall"

    # Create the package
    echo "Packaging $identifier version $version..."
    pkgbuild --quiet --root "$workingDir/files/" 
        --component-plist "$workingDir/${application}_Component.plist" 
        --install-location "/Applications/" 
        --scripts "$workingDir/scripts/" 
        --identifier "$identifier" 
        --version "$version" 
        --ownership preserve 

    echo " "

open "$tempDir/build" &>/dev/null

exit 0

If you don't change anything your apps will be packaged in /private/tmp/jamfpro/build directory (which opens up after the script runs). This process takes less than a minute to create a package for each Jamf app you have installed in the /Applications/Jamf Pro/ directory.


If you only want admins to read/execute the installed application you can do the following:

# Change this line
# to

I hope this helps anybody else who is interested.