autopkg-conductor updated to support reporting to Slack and Microsoft Teams

When the autopkg-conductor tool was first written, one of its primary functions was to send the output of JSSImporter to a Slack channel. With JSSImporter being deprecated in favor of JamfUploader, I’ve decided to do the following:

  1. Drop support for JSSImporter.
  2. Add additional reporting options for JamfUploader.

As of the current version to the tool, autopkg-conductor can send output from JamfUploader to the following:

For more details, please see below the jump.

autopkg-conductor can now be configured to send output to Slack, Teams or to both Slack and Teams. The following AutoPkg processors are being leveraged for this:

To configure autopkg-conductor to send to Slack using the JamfUploaderSlacker AutoPkg processor, the following variables need to be configured:

  • slack_post_processor
  • slack_webhook

To configure autopkg-conductor to send to Teams using the JamfUploaderTeamsNotifier AutoPkg processor, the following variables need to be configured:

  • teams_post_processor
  • teams_webhook

To configure autopkg-conductor to send to both Slack and Teams, all four variables need to be configured:

  • slack_post_processor
  • slack_webhook
  • teams_post_processor
  • teams_webhook

Screenshot 2022 11 19 at 3 35 15 PM

Both the JamfUploaderSlacker and the JamfUploaderTeamsNotifier AutoPkg processors should be included with JamfUploader. The message which appears in Slack should look similar to what is shown below:

Screenshot 2022 11 19 at 2 50 14 PM

The message which appears in Teams should look similar to what is shown below:

Screenshot 2022 11 19 at 2 46 31 PM

Error logs should also be sent to Slack and/or Teams. There will be differences in appearance, as the script sends the error log one line at a time. Slack and Teams handle this differently in terms of formatting, so the error logs should appear similar to what’s shown below:

Slack:

Screenshot 2022 11 19 at 2 54 09 PM

Teams:

Screenshot 2022 11 19 at 2 53 37 PM

The autopkg-conductor script is available below. It’s also available from GitHub using the following link:

https://github.com/rtrouton/autopkg-conductor


#!/bin/bash
# AutoPkg automation script
# Adjust the following variables for your particular configuration.
#
# autopkg_user_account – This should be the user account you're running AutoPkg in.
# autopkg_user_account_home – This should be the home folder location of the AutoPkg user account
#
# Note: The home folder location is currently set to be automatically discovered
# using the autopkg_user_account variable.
#
# recipe_list – This is the location of the plain text file being used to store
# your list of AutoPkg recipes. For more information about this list, please see
# the link below:
#
# https://github.com/autopkg/autopkg/wiki/Running-Multiple-Recipes
#
# log_location – This should be the location and name of the AutoPkg run logs.
#
# Note: The location is currently set to be automatically discovered
# using the autopkg_user_account_home variable.
autopkg_user_account="username_goes_here"
autopkg_user_account_home=$(/usr/bin/dscl . -read /Users/"$autopkg_user_account" NFSHomeDirectory | awk '{print $2}')
recipe_list="/path/to/recipe_list.txt"
log_location="$autopkg_user_account_home/Library/Logs/autopkg-run-for-$(date +%Y-%m-%d-%H%M%S).log"
# If you're using Jamf Upload, the URL of your Jamf Pro server should be populated into the jamfpro_server variable automatically.
#
# If you're not using Jamf Upload, this variable will return nothing and that's OK.
jamfpro_server=$(/usr/bin/defaults read "$autopkg_user_account_home"/Library/Preferences/com.github.autopkg JSS_URL)
# Optional variables
# This script supports using either Jamf Upload's JamfUploaderSlacker or Jamf Upload's JamfUploaderTeamsNotifier processors
# JamfUploaderSlacker – used with Jamf Upload
#
# To use the JamfUploaderSlacker post-processor, you'll need to use add Graham Pugh's
# Autopkg repo by running the command below:
#
# autopkg repo-add grahampugh-recipes
#
# The slack_post_processor variable should look like this:
# slack_post_processor="com.github.grahampugh.jamf-upload.processors/JamfUploaderSlacker"
slack_post_processor=""
# JamfUploaderTeamsNotifier – used with Jamf Upload
#
# To use the JamfUploaderTeamsNotifier post-processor, you'll need to use add Graham Pugh's
# Autopkg repo by running the command below:
#
# autopkg repo-add grahampugh-recipes
#
# The teams_post_processor variable should look like this:
# teams_post_processor="com.github.grahampugh.jamf-upload.processors/JamfUploaderTeamsNotifier"
teams_post_processor=""
# If you're sending the results of your AutoPkg run to Slack, you'll need to set up
# a Slack webhook to receive the information being sent by the script.
# If you need help with configuring a Slack webhook, please see the links below:
#
# https://api.slack.com/incoming-webhooks
# https://get.slack.help/hc/en-us/articles/115005265063-Incoming-WebHooks-for-Slack
#
# Once a Slack webhook is available, the slack_webhook variable should look similar
# to this:
# slack_webhook="https://hooks.slack.com/services/XXXXXXXXX/YYYYYYYYY/ZZZZZZZZZZ"
slack_webhook=""
# If you're sending the results of your AutoPkg run to Teams, you'll need to set up
# a Teams webhook to receive the information being sent by the script.
# If you need help with configuring a Slack webhook, please see the links below:
#
# https://learn.microsoft.com/en-us/microsoftteams/platform/webhooks-and-connectors/how-to/add-incoming-webhook
#
# Once a Teams webhook is available, the teams_webhook variable should look similar
# to this:
# teams_webhook="https://companyname.webhook.office.com/webhookb2/7ce853bd-a9e1-462f-ae32-d3d35ed5295d@7c155bae-5207-4bb5-8b58-c43228bc1bb7/IncomingWebhook/8155d8581864479287b68b93f89556ae/651e63f8-2d96-42ab-bb51-65cb05fc62aa"
teams_webhook=""
# don't change anything below this line
# Set script exit status
exit_error=0
# Define logger behavior
ScriptLogging(){
DATE=$(date +%Y-%m-%d\ %H:%M:%S)
LOG="$log_location"
echo "$DATE" " $1" >> $LOG
}
# Function for sending multi-line output to a Slack webhook. Original script from here:
#
# http://blog.getpostman.com/2015/12/23/stream-any-log-file-to-slack-using-curl/
SendToSlack(){
cat "$1" | while read LINE; do
(echo "$LINE" | grep -e "$3") && curl -X POST –silent –data-urlencode "payload={\"text\": \"$(echo $LINE | sed "s/\"/'/g")\"}" "$2";
done
}
# Function for sending multi-line output to a Teams webhook.
SendToTeams(){
while read LINE; do
(echo "${LINE}" | grep -e "$3") && curl -X POST -H 'Content-Type: application/json' –silent -d "{\"text\": \"${LINE//\"/\'/}\"}" "$2";
done < "$1"
}
# Function for AutoPkg runs
RunAutoPkg(){
if [[ ! -z "$slack_autopkg_report" ]] && [[ -z "$teams_autopkg_report" ]]; then
/usr/local/bin/autopkg run –recipe-list="${recipe_list}" –post=${slack_post_processor} –key ${slack_autopkg_postprocessor_key}=${slack_webhook} >> /tmp/autopkg.out 2>>/tmp/autopkg_error.out
elif [[ -z "$slack_autopkg_report" ]] && [[ ! -z "$teams_autopkg_report" ]]; then
/usr/local/bin/autopkg run –recipe-list="${recipe_list}" –post=${teams_post_processor} –key ${teams_autopkg_postprocessor_key}=${teams_webhook} >> /tmp/autopkg.out 2>>/tmp/autopkg_error.out
elif [[ ! -z "$slack_autopkg_report" ]] && [[ ! -z "$teams_autopkg_report" ]]; then
/usr/local/bin/autopkg run –recipe-list="${recipe_list}" –post=${slack_post_processor} –key ${slack_autopkg_postprocessor_key}=${slack_webhook} –post=${teams_post_processor} –key ${teams_autopkg_postprocessor_key}=${teams_webhook} >> /tmp/autopkg.out 2>>/tmp/autopkg_error.out
else
/usr/local/bin/autopkg run –recipe-list="${recipe_list}" >> /tmp/autopkg.out 2>>/tmp/autopkg_error.out
fi
}
# The key used by the JamfUploaderSlacker and JamfUploaderTeamsNotifier AutoPkg processors is slightly different
# so the right one needs to be used when running AutoPkg.
#
# JamfUploaderSlacker: slack_webhook_url
#
# JamfUploaderTeamsNotifier: teams_webhook_url
#
# The slack_autopkg_postprocessor and teams_autopkg_postprocessor variables will enable
# the script to identify the correct key for the processor.
slack_autopkg_postprocessor=${slack_post_processor#*/}
teams_autopkg_postprocessor=${teams_post_processor#*/}
# If the AutoPkg run's log file is not available, create it
if [[ ! -r "$log_location" ]]; then
touch "$log_location"
fi
# If the AutoPkg recipe list is missing or unreadable, stop the script with an error.
if [[ ! -r "$recipe_list" ]]; then
ScriptLogging "Error Detected. Unable to start AutoPkg run."
echo "" > /tmp/autopkg_error.out
if [[ "$jamfpro_server" = "" ]]; then
echo "AutoPkg run failed" >> /tmp/autopkg_error.out
else
echo "AutoPkg run for $jamfpro_server failed" >> /tmp/autopkg_error.out
fi
echo "$recipe_list is missing or unreadable. Fix immediately." >> /tmp/autopkg_error.out
echo "" > /tmp/autopkg.out
# If a Slack webhook is configured, send the error log to Slack.
if [[ ! -z "$slack_webhook" ]]; then
SendToSlack /tmp/autopkg_error.out ${slack_webhook}
fi
cat /tmp/autopkg_error.out >> "$log_location"
ScriptLogging "Finished AutoPkg run"
exit_error=1
fi
# If the the AutoPkg recipe list is readable and AutoPkg is installed,
# run the recipes stored in the recipe list.
if [[ -x /usr/local/bin/autopkg ]] && [[ -r "$recipe_list" ]]; then
ScriptLogging "AutoPkg installed at $(which autopkg)"
ScriptLogging "Recipe list located at $recipe_list and is readable."
echo "" > /tmp/autopkg.out
if [[ "$jamfpro_server" = "" ]]; then
echo "Starting AutoPkg run" >> /tmp/autopkg_error.out
else
echo "Starting AutoPkg run for $jamfpro_server" >> /tmp/autopkg.out
fi
echo "" >> /tmp/autopkg.out
echo "" > /tmp/autopkg_error.out
if [[ "$jamfpro_server" = "" ]]; then
echo "Error log for AutoPkg run" >> /tmp/autopkg_error.out
else
echo "Error log for AutoPkg run to $jamfpro_server" >> /tmp/autopkg_error.out
fi
echo "" >> /tmp/autopkg_error.out
/usr/local/bin/autopkg repo-update all 2>&1 >> /tmp/autopkg.out 2>>/tmp/autopkg_error.out
cat /tmp/autopkg.out >> "$log_location" && cat /tmp/autopkg_error.out >> "$log_location"
# If a webhook for Slack is configured, send output to Slack
if [[ ! -z "$slack_webhook" ]]; then
if [[ ! -z "$slack_post_processor" ]] && [[ ! -z "$slack_autopkg_postprocessor" ]]; then
if [[ ${slack_autopkg_postprocessor} = "JamfUploaderSlacker" ]]; then
# If both a post-processor to post to Slack and a Slack webhook are configured and nothing is configured for Teams,
# the Jamf Upload recipes should have their outputs posted to Slack using the post-processor, while all other
# output should go to /tmp/autopkg.out. All standard error output should go to /tmp/autopkg_error.out
if [[ ${slack_autopkg_postprocessor} = "JamfUploaderSlacker" ]]; then
slack_autopkg_postprocessor_key="slack_webhook_url"
slack_autopkg_report=1
fi
fi
fi
fi
# If a webhook for Teams is configured, send output to Teams
if [[ ! -z "$teams_webhook" ]]; then
if [[ ! -z "$teams_post_processor" ]] && [[ ! -z "$teams_autopkg_postprocessor" ]]; then
if [[ ${teams_autopkg_postprocessor} = "JamfUploaderTeamsNotifier" ]]; then
# If both a post-processor to post to Teams and a Teams webhook are configured and nothing is configured for Slack,
# the Jamf Upload recipes should have their outputs posted to Teams using the post-processor, while all other
# output should go to /tmp/autopkg.out. All standard error output should go to /tmp/autopkg_error.out
if [[ ${teams_autopkg_postprocessor} = "JamfUploaderTeamsNotifier" ]]; then
teams_autopkg_postprocessor_key="teams_webhook_url"
teams_autopkg_report=1
fi
fi
fi
fi
# Run AutoPkg with the configured reporting options for Slack and/or Teams
RunAutoPkg
if [[ "$jamfpro_server" = "" ]]; then
echo "Finished with AutoPkg run" >> /tmp/autopkg.out
else
echo "Finished with AutoPkg run for $jamfpro_server" >> /tmp/autopkg.out
fi
echo "" >> /tmp/autopkg.out && echo "" >> /tmp/autopkg_error.out
cat /tmp/autopkg.out >> "$log_location"
cat /tmp/autopkg_error.out >> "$log_location"
ScriptLogging "Finished AutoPkg run"
echo "" >> /tmp/autopkg_error.out
echo "End of error log for AutoPkg run" >> /tmp/autopkg_error.out
echo "" >> /tmp/autopkg_error.out
if [[ -z "$slack_post_processor" ]] && [[ ! -z "$slack_webhook" ]]; then
# If the AutoPkg post-processor for posting to Slack is
# not configured but we do have a Slack webhook set up,
# all standard output should be sent to Slack.
ScriptLogging "Sending AutoPkg output log to Slack"
SendToSlack /tmp/autopkg.out ${slack_webhook}
ScriptLogging "Sent AutoPkg output log to $slack_webhook."
fi
if [[ -z "$teams_post_processor" ]] && [[ ! -z "$teams_webhook" ]]; then
# If the AutoPkg post-processor for posting to Teams is
# not configured but we do have a Teams webhook set up,
# all standard output should be sent to Teams.
ScriptLogging "Sending AutoPkg output log to Teams"
SendToTeams /tmp/autopkg.out ${teams_webhook}
ScriptLogging "Sent AutoPkg output log to $teams_webhook."
fi
if [[ ! -z "$slack_webhook" ]]; then
# If using a Slack webhook, at the end of the AutoPkg run all standard
# error output logged to /tmp/autopkg_error.out should be output to Slack,
# using the SendToSlack function.
if [[ $(wc -l </tmp/autopkg_error.out) -gt 7 ]]; then
ScriptLogging "Sending AutoPkg error log to Slack"
SendToSlack /tmp/autopkg_error.out ${slack_webhook}
ScriptLogging "Sent autopkg log to $slack_webhook. Ending run."
else
ScriptLogging "Error log was empty. Nothing to send to Slack."
fi
fi
if [[ ! -z "$teams_webhook" ]]; then
# If using a Teams webhook, at the end of the AutoPkg run all standard
# error output logged to /tmp/autopkg_error.out should be output to Teams,
# using the SendToTeams function.
if [[ $(wc -l </tmp/autopkg_error.out) -gt 7 ]]; then
ScriptLogging "Sending AutoPkg error log to Teams"
SendToTeams /tmp/autopkg_error.out ${teams_webhook}
ScriptLogging "Sent autopkg log to $teams_webhook. Ending run."
else
ScriptLogging "Error log was empty. Nothing to send to Teams."
fi
fi
fi
exit "$exit_error"

Downloading macOS IPSW files for use with Mac virtual machines on Apple Silicon Macs

A change between creating Mac virtual machines on Intel Macs and creating them on Apple Silicon Macs is that virtualization on Apple Silicon Macs often assumes that the virtual machine is built using a macOS restore image . These restore images are files with an .ipsw file extension and are commonly referred to as IPSW files.

Apple publishes the download links for macOS restore images via the following URL:

https://mesu.apple.com/assets/macos/com_apple_macOSIPSW/com_apple_macOSIPSW.xml

If you look at the XML file from the link above, it provides download links for the current version of macOS for the various Mac models which support running that version of macOS.

Among the various models listed is the model identifier for Mac virtual machines (VirtualMac2,1) created using Apple’s Virtualization framework. This means that we should be able to identify and download the appropriate IPSW file for use when building Mac virtual machines.

Screenshot 2022-11-16 at 7.33.48 PM

Using this information, I’ve written a script to download the appropriate IPSW file for building macOS virtual machines by checking the file linked above for the download URL associated with the VirtualMac2,1 Mac model. For more details, please see below the jump.

The script checks Apple’s IPSW feed to get the appropriate IPSW file for the current release of macOS used by the VirtualMac2,1 Mac model. If it finds a matching IPSW download URL, it will take the following actions:

  1. Download the IPSW file to a temp directory.
  2. If the download succeeds, a message is displayed notifying the user that the download has completed and where the IPSW file is stored.
  3. If the download fails, a message is displayed notifying the user that the download failed and the script exits with an error.

Usage:

./download_latest_macOS_ipsw_for_virtualization.sh

Screenshot 2022 11 16 at 10 39 10 PM

Screenshot 2022 11 16 at 10 42 14 PM

This script is available below and also from GitHub at the following location:

https://github.com/rtrouton/rtrouton_scripts/tree/main/rtrouton_scripts/download_latest_macOS_ipsw_for_virtualization


#!/bin/bash
# This script checks Apple's IPSW feed to get the appropriate IPSW file
# for the current release of macOS used by the VirtualMac2,1 virtualization
# Mac model.
clear
exitCode=0
Apple_macOS_IPSW_Download_Directory=$(mktemp -d)
Apple_macOS_IPSW_Feed="https://mesu.apple.com/assets/macos/com_apple_macOSIPSW/com_apple_macOSIPSW.xml"
Apple_macOS_IPSW_XML=$(/usr/bin/curl -s "$Apple_macOS_IPSW_Feed" | xmllint –format –)
Apple_macOS_IPSW_Download_URL=$(/usr/libexec/PlistBuddy -c 'print ":MobileDeviceSoftwareVersionsByVersion:1:MobileDeviceSoftwareVersions:VirtualMac2,1"' /dev/stdin <<< "$Apple_macOS_IPSW_XML" | awk '/FirmwareURL/ {print $3}')
Apple_macOS_IPSW_Filename=$(echo "$Apple_macOS_IPSW_Download_URL" | awk -F / '{print $NF}')
# Verify that the IPSW download URL contains a filename which ends in .ipsw
if [[ -n $(echo "$Apple_macOS_IPSW_Download_URL" | grep -o ".ipsw") ]]; then
# If the IPSW download URL contains a filename which ends in .ipsw,
# download the IPSW file and store it in a temp directory.
echo "Downloading $Apple_macOS_IPSW_Filename"
echo "From: $Apple_macOS_IPSW_Download_URL"
echo "To: $Apple_macOS_IPSW_Download_Directory/$Apple_macOS_IPSW_Filename"
echo ""
/usr/bin/curl -L "$Apple_macOS_IPSW_Download_URL" -o "$Apple_macOS_IPSW_Download_Directory"/"$Apple_macOS_IPSW_Filename" && download_success=1
# If the download succeeds, display a message notifying the user that the
# download has completed and where the IPSW file is stored.
#
# If the download fails, display a message notifying the user that the download failed
# and exit with an error.
if [[ -n "$download_success" ]] && [[ -f "$Apple_macOS_IPSW_Download_Directory"/"$Apple_macOS_IPSW_Filename" ]]; then
echo ""
echo "$Apple_macOS_IPSW_Filename has been downloaded to the following location:"
echo "$Apple_macOS_IPSW_Download_Directory/$Apple_macOS_IPSW_Filename"
else
echo "Download of $Apple_macOS_IPSW_Filename from $Apple_macOS_IPSW_Download_URL has failed. Exiting."
exitCode=1
fi
else
# If the IPSW download URL does not contain a filename which ends in .ipsw,
# display a message notifying the user that an IPSW file was not found and
# exit with an error.
echo "Unable to detect macOS IPSW file to download. Exiting."
exitCode=1
fi
exit "$exitCode"

Apple Device Management Second Edition book coming soon

Three years back, both Charles Edge and I wrote a book together:

We decided to put the band back together for a Second Edition, which I’m pleased to say is far enough along in the publishing process that it’s been assigned an ISBN number and a listing on Amazon.

While it’s not yet available for pre-order, hopefully you’ll be able to add a pre-order for it to your Christmas shopping list!

Charles and I were also on the Mac Admins Podcast to discuss the new book so if you’re interested in learning more, please see the link below:

https://podcast.macadmins.org/2022/09/27/episode-284-rich-trouton-on-apple-device-management-2nd-edition/

Downloading macOS Monterey from the App Store

Now that macOS Ventura has been released, it’s become more difficult to access the macOS Monterey installer for those who still need it. Fortunately, macOS Monterey has not been removed from the App Store and it is still available for download. Apple has a KBase article that shows how to access the macOS Monterey page in the App Store, available via the link below:

https://support.apple.com/HT211683

Screenshot 2022 11 11 at 5 24 32 PM

To access the macOS Monterey page directly, please click on the link below:

https://apps.apple.com/us/app/macos-monterey/id1576738294?mt=12

That link should open the App Store and take you to the macOS Monterey download page.

Screenshot 2022 11 11 at 5 26 18 PM

 

In the event that you’re blocked from downloading macOS Monterey, you should be able to download it in a virtual machine. I have a post on how to do this, available via the link below:

https://derflounder.wordpress.com/2017/02/21/downloading-older-os-installers-on-incompatible-hardware-using-vms/

Adding hidden Login Items on macOS Ventura

One of the changes made between macOS Monterey’s System Preferences and macOS Ventura’s System Settings is that the Hide checkbox in System Preferences’ Login Items has disappeared from System Settings’ Login Items.

Login Items in System Preferences

Screen Shot 2022 10 27 at 2 25 28 PM

Login Items in System Settings

Screenshot 2022 10 27 at 2 40 18 PM

Fortunately for those who want to continue being able to launch applications on login and automatically hide them, it’s still possible to do so on macOS Ventura from the command line using osascript.

To do this, run a command similar to the one shown below using the logged-in user’s privileges:

/usr/bin/osascript -e 'tell application "System Events" to make login item at end with properties {path:"/path/to/itemname", hidden:true}'

For example, if you want Safari to launch at login with its windows automatically hidden, run the command below using the logged-in user’s privileges:

/usr/bin/osascript -e 'tell application "System Events" to make login item at end with properties {path:"/Applications/Safari.app", hidden:true}'

Safari will appear in the Login Items list without any sign that it’s launching as hidden, but the application behavior on login will be just like it would be on earlier versions of macOS where the Hide checkbox was checked.

Opening macOS Ventura’s System Settings to desired locations via the command line

With the release of macOS Ventura, the System Preferences application has been replaced with the System Settings application.

macOS Monterey System Preferences:

Screen Shot 2022 10 25 at 3 08 11 PM

macOS Ventura System Settings:

Screenshot 2022 10 25 at 3 10 04 PM

Along with this change, a number of previously-known commands for opening individual System Preferences preference panes from the command line no longer work with System Settings.

However, it looks like the underlying command line functionality wasn’t changed by Apple. You just need to know what the new options are to enter. For more details, please see below the jump.

I’ve put together a list of the ones I’ve found to work, which is available below. Find any more? Please let me know in the comments:


Open Storage, in System Settings: General:
open x-apple.systempreferences:com.apple.settings.Storage
Open Software Update, in System Settings: General:
open x-apple.systempreferences:com.apple.Software-Update-Settings.extension
Open General, in System Settings:
open x-apple.systempreferences:com.apple.systempreferences.GeneralSettings
Open Privacy & Security, in System Settings:
open x-apple.systempreferences:com.apple.preference.security
Open Privacy & Security, in System Settings:
open x-apple.systempreferences:com.apple.settings.PrivacySecurity.extension
Open Startup Disk, in System Settings: General:
open x-apple.systempreferences:com.apple.preference.startupdisk
Open Startup Disk, in System Settings: General:
open x-apple.systempreferences:com.apple.Startup-Disk-Settings.extension
Open Displays, in System Settings:
open x-apple.systempreferences:com.apple.preference.displays
Open Wallpaper, in System Settings:
open x-apple.systempreferences:com.apple.Wallpaper-Settings.extension
Open Network, in System Settings:
open x-apple.systempreferences:com.apple.preference.network
Open Network, in System Settings:
open x-apple.systempreferences:com.apple.Network-Settings.extension
Open Profiles, in System Settings: Privacy & Security:
open x-apple.systempreferences:com.apple.Profiles-Settings.extension
Open Transfer or Reset, in System Settings: General:
open x-apple.systempreferences:com.apple.Transfer-Reset-Settings.extension
Open Date & Time, in System Settings: General:
open x-apple.systempreferences:com.apple.Date-Time-Settings.extension
Open About, in System Settings: General:
open x-apple.systempreferences:com.apple.SystemProfiler.AboutExtension
Open Language & Region, in System Settings: General:
open x-apple.systempreferences:com.apple.Localization-Settings.extension
Open Login Items, in System Settings: General:
open x-apple.systempreferences:com.apple.LoginItems-Settings.extension
Open Sharing, in System Settings: General:
open x-apple.systempreferences:com.apple.Sharing-Settings.extension
Open AirDrop & Handoff, in System Settings: General
open x-apple.systempreferences:com.apple.AirDrop-Handoff-Settings.extension
Open Time Machine, in System Settings: General
open x-apple.systempreferences:com.apple.Time-Machine-Settings.extension
Open Appearance, in System Settings:
open x-apple.systempreferences:com.apple.Appearance-Settings.extension
Open Apple ID, in System Settings:
open x-apple.systempreferences:com.apple.preferences.AppleIDPrefPane

Comment bloquer macOS Ventura ?

Apple vient de lancer macOS 13 alias Ventura, la nouvelle fournée annuelle de notre système d’exploitation préférée (enfin, celui de nos Mac). Je passe outre les annonces des fonctionnalités majeures ou mineures, pour me concentrer sur un aspect qui intéresse surtout les administrateurs informatiques : comment bloquer l’installation de macOS Ventura ? Parce que mine de rien, vous allez voir que c’est pas de la tarte. C’est expliqué ici par Apple, c’est pas encore traduit, j’essaie d’éclaircir un peu le truc.

Nouveauté : macOS Ventura est une mise à jour… mineure !

Commençons par la nouveauté la plus importante : désormais, Apple distribue macOS Ventura comme une mise à jour mineure de macOS. Cela veut dire concrètement qu’un utilisateur pourra basculer de macOS Big Sur vers macOS Ventura simplement en utilisant la mise à jour de logiciels, sans même télécharger l’application de mise à jour depuis le Mac App Store.

Avantages :

  • L’installation est beaucoup plus légère (on passe de 12 à 5 Go environ) ;
  • L’utilisateur peut installer lui-même la mise à jour, sans avoir besoin de compte d’administrateur, et simplement en tapant son mot de passe.

Inconvénients :

  • L’utilisateur peut installer lui-même la mise à jour, sans avoir besoin de compte d’administrateur, et simplement en tapant son mot de passe.

Et là vous vous dites « mais dis donc GG, tu vieillis, tu radotes, t’as fait un mauvais copier-coller, t’as mis la même ligne dans les avantages et les inconvénients !

Et bien oui. Et en fait, c’est totalement volontaire, car si il peut être considéré comme bénéfique le fait de pousser les utilisateurs à installer la dernière version du système, cela peut être délicat si, par exemple, vos logiciels ne sont pas prêts pour la dernière version (au hasard, pour toutes les solutions de sécurité informatique, antivirus/anti-malware, etc).

Mais alors, que faire ? Et bien, coup de chance, vous pouvez bloquer la mise à jour via le canal mineur… si vos postes sont équipés de macOS 12.6.1. En effet, un bug dans les versions précédentes de macOS empêche le blocage du téléchargement de macOS si le poste utilise une version inférieure de macOS.

Oui, mais Guillaume, t’es fou ou quoi, macOS 12.6.1 est sorti ce soir, donc c’est trop tard ! On ne peut pas déployer cette version alors que Ventura est désormais dispo !

C’est vrai, et du coup, les ingénieurs d’Apple ont prévu une parade. Si vos postes sont intégrés dans une solution MDM et considérés comme gérés, la mise à jour de Ventura ne sera pas visible dans le canal de mise à jour mineure durant au moins 30 jours. Ce qui vous donnera le temps de déployer macOS 12.6.1.

Ah c’est cool, Merci Guillaume ! Mais comment on fait pour déployer la 12.6.1 ?

Plusieurs méthodes :

  • Envoyer une commande MDM de mise à jour mineure, mais ça marche pas super ;
  • Installer et configurer Nudge pour inciter vos utilisateurs à télécharger eux-même et installer la mise à jour dès que possible, en leur donnant un peu de temps (genre une dizaine de jours) ;
  • Utiliser une autre solution, comme Erase-install, qui comme son nom ne l’indique pas, permet aussi de mettre à jour macOS en téléchargeant au préalable la version de son choix, sans forcément effacer le disque.

Bloquer l’application d’installation de macOS Ventura

Si vous souhaitez aussi bloquer l’installation de macOS depuis l’app d’installation, vous pouvez télécharger et déployer le package Ventura Blocker, qui fera très bien le boulot. Ensuite, le script fourni sur la page devra être déployé pour permettre à nouveau l’installation de Ventura.

En résumé : c’est encore un peu le bordel

Amis admins, vous avez donc désormais au moins un mois pour mettre à jour vers macOS 12.6.1, puis vous pourrez ensuite bloquer l’installation de Ventura pour 90 jours (délai max pour bloquer via un profil de configuration). Ensuite… bonne chance !

Creating AWS S3 buckets for webpage redirection

I recently had an issue where I needed to solve a particular problem:

1. I had a DNS domain name

dns.name.here

2. I needed to point it to a HTTPS URL hosted on another domain:

https://other.dns.name.here/path/to/site/goes/here

3. The DNS server for dns.name.here does not support HTTP Redirect records.

To address this, I decided to use S3 buckets hosted on Amazon Web Services to handle the redirection to the HTTPS URL. In this scenario, what I’m doing is pointing the relevant dns.name.here domain name at the S3 bucket’s AWS domain name. The S3 bucket is performing a HTTP 301 redirect, which sends the requesting web browser the URL of the site I want to connect to. For those interested, Amazon’s documentation of how to use an S3 bucket for URL redirection is linked below:

https://docs.aws.amazon.com/AmazonS3/latest/userguide/how-to-page-redirect.html

After doing it the first time manually, I decided to see if anyone had scripted this task. It turns out the answer is “no”, at least for what I wanted to do, so I’ve written a script which handles this task. For more details, please see below the jump.

The script I’ve developed sets up S3 buckets in Amazon Web Services for use with redirecting DNS or URL requests to an alternative HTTP or HTTPS URL.

Pre-requisites:

The AWS CLI tool must be installed.
The AWS CLI tool must be configured to use AWS programmatic user credentials with the permissions to do the following:

  • Create an S3 bucket
  • Set permissions on the newly-created S3 bucket
  • Apply an S3 bucket policy to the newly-created S3 bucket
  • Apply a website configuration to the newly-created S3 bucket

Usage:

./s3_website_redirection_creator.sh

Once the pre-requisites are met, this script performs the following actions:

  1. Requests a name for an S3 bucket, which should be the DNS name that you want to set up a redirection for.
  2. Requests the AWS region that the S3 bucket should be created in.
  3. Requests the HTTP or HTTPS URL of the website that the redirection is being set up for.

Once the user-requested information is provided, this script performs the following actions:

  1. Creates an S3 bucket using the name supplied by user input
  2. Set permissions on the newly-created S3 bucket so that no public access is permitted.
  3. Set the default encryption behavior for the newly-created S3 bucket to be enabled and to use Amazon S3-managed encryption keys.
  4. Sets an S3 bucket policy which blocks non-SSL connections to the contents of the newly-created S3 bucket.
  5. Set the website configuration for the desired URL redirection for the newly-created bucket.

Screen Shot 2022 10 18 at 4 41 10 PM

Once the S3 bucket is created, you should be able to verify that accessing the HTTP address of the S3 bucket results in your browser being automatically forwarded to the desired website address.

Screen Shot 2022 10 18 at 4 39 25 PM

Screen Shot 2022 10 18 at 4 39 38 PM

This script is available below and also from GitHub at the following location:

https://github.com/rtrouton/aws_scripts/tree/main/s3_website_redirection_creator


#!/bin/bash
# This script sets up S3 buckets in Amazon Web Services for use with redirecting
# DNS or URL requests to an alterative HTTP or HTTPS URL.
#
# The following pre-requisites are needed:
#
# * The AWS CLI tool must be installed
# * The AWS CLI tool must have access to AWS programmatic user credentials with the
# permissions to do the following:
#
# * Create an S3 bucket
# * Set permissions on the newly-created S3 bucket
# * Apply an S3 bucket policy to the newly-created S3 bucket
# * Apply a website configuration to the newly-created S3 bucket
#
#
# Once the pre-requisites are met, this script performs the following actions:
#
# A. Requests a name for an S3 bucket, which should be the DNS name that you want to set up a redirection for.
# B. Requests the AWS region that the S3 bucket should be created in.
# C. Requests the HTTP or HTTPS URL of the website that the redirection is being set up for.
#
# Once the user-requested information is provided, this script performs the following actions:
#
# 1. Creates an S3 bucket using the name supplied by user input
# 2. Set permissions on the newly-created S3 bucket so that no public access is permitted.
# 3. Set the default encryption behavior for the newly-created S3 bucket to be enabled and to use Amazon S3-managed encryption keys.
# 4. Sets an S3 bucket policy which blocks non-SSL connections to the contents of the newly-created S3 bucket.
# 5. Set the website configuration for the desired URL redirection for the newly-created bucket.
# Set exit code
exitCode=0
clear
echo "This script sets up S3 buckets in Amazon Web Services for website redirection."
echo "You will need to enter the following information:"
echo ""
echo "A. The name of the new S3 bucket, which should be the DNS name that you want to set up a redirection for."
echo "B. The Amazon Web Services region that you want to create the S3 bucket in."
echo "C. The address of the website address you want to redirect to."
echo ""
read -p "Please enter the name of the new S3 bucket: " s3_bucket_name
read -p "Please enter the AWS region that the new S3 bucket should be created in: " s3_bucket_region
read -p "Please enter the website address you want to redirect to : " website_url
# Figure out if an HTTP or HTTPS URL is being used.
http_protocol=${website_url%://*}
# Get the website URL and split it as necessary for use with the redirection rules.
site=${website_url#*//}
if [[ "$site" == *\/* ]]; then
site=${site%%/*}
site_path=${website_url#*//}
site_path=${site_path#*/}
else
site=${site%%/*}
site_path=""
fi
# Verify that the URL begins with either 'http' or 'https'. If it doesn't, the script
# will display an error message and exit.
if [[ ${http_protocol} != "http" ]] && [[ ${http_protocol} != "https" ]] && [[ ${http_protocol} != "HTTP" ]] && [[ ${http_protocol} != "HTTPS" ]]; then
echo "ERROR – ${website_url} URL begins with $http_protocol, which means it is not a valid HTTP or HTTPS URL. Script will now exit."
exitCode=1
exit "$exitCode"
fi
# The redirection rule Protocol entries need to be lower-case, so
# set HTTP and HTTPS entries to lower-case if needed.
if [[ ${http_protocol} == "HTTP" ]]; then
http_protocol="http"
elif [[ ${http_protocol} == "HTTPS" ]]; then
http_protocol="https"
fi
# Create an S3 website configuration for the desired URL redirection.
if [[ -n ${site_path} ]]; then
read -r -d '' redirectionJSON <<AWS_S3_REDIRECTION_POLICY
{
"IndexDocument": {
"Suffix": "index.html"
},
"ErrorDocument": {
"Key": "error.html"
},
"RoutingRules": [
{
"Redirect": {
"HostName": "$site",
"HttpRedirectCode": "301",
"Protocol": "$http_protocol",
"ReplaceKeyPrefixWith": "$site_path"
}
}
]
}
AWS_S3_REDIRECTION_POLICY
else
read -r -d '' redirectionJSON <<AWS_S3_REDIRECTION_POLICY
{
"IndexDocument": {
"Suffix": "index.html"
},
"ErrorDocument": {
"Key": "error.html"
},
"RoutingRules": [
{
"Redirect": {
"HostName": "$site",
"HttpRedirectCode": "301",
"Protocol": "$http_protocol"
}
}
]
}
AWS_S3_REDIRECTION_POLICY
fi
redirectionJSON_file=$(mktemp)
echo "$redirectionJSON" > "$redirectionJSON_file"
# Create an S3 bucket policy which blocks non-SSL connections to the contents
# of the S3 bucket.
read -r -d '' bucketpolicyJSON <<AWS_S3_BUCKET_POLICY
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowSSLRequestsOnly",
"Effect": "Deny",
"Principal": "*",
"Action": "s3:*",
"Resource": [
"arn:aws:s3:::$s3_bucket_name",
"arn:aws:s3:::$s3_bucket_name/*"
],
"Condition": {
"Bool": {
"aws:SecureTransport": "false"
}
}
}
]
}
AWS_S3_BUCKET_POLICY
bucketpolicyJSON_file=$(mktemp)
echo "$bucketpolicyJSON" > "$bucketpolicyJSON_file"
# Create the S3 bucket.
if [[ ${s3_bucket_region} = "us-east-1" ]]; then
aws s3api create-bucket –bucket ${s3_bucket_name} –region ${s3_bucket_region} 2>&1 > /dev/null
else
aws s3api create-bucket –bucket ${s3_bucket_name} –region ${s3_bucket_region} –create-bucket-configuration LocationConstraint=${s3_bucket_region} 2>&1 > /dev/null
fi
# Set permissions on the newly-created S3 bucket so that no public access is permitted.
aws s3api put-public-access-block –bucket ${s3_bucket_name} –public-access-block-configuration "BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true" 2>&1 > /dev/null
# Set the default encryption behavior for the newly-created S3 bucket to be enabled and to use Amazon S3-managed encryption keys.
aws s3api put-bucket-encryption –bucket ${s3_bucket_name} –server-side-encryption-configuration '{"Rules": [{"ApplyServerSideEncryptionByDefault": {"SSEAlgorithm": "AES256"}}]}' 2>&1 > /dev/null
# Sets an S3 bucket policy which blocks non-SSL connections to the contents of the newly-created S3 bucket.
aws s3api put-bucket-policy –bucket ${s3_bucket_name} –policy file://$bucketpolicyJSON_file 2>&1 > /dev/null
# Set the website configuration for the desired URL redirection for the newly-created bucket.
aws s3api put-bucket-website –bucket ${s3_bucket_name} –website-configuration file://$redirectionJSON_file 2>&1 > /dev/null
echo "New S3 bucket is available from the address below:"
echo ""
echo "S3 bucket name: ${s3_bucket_name}"
echo "S3 bucket location: ${s3_bucket_region}"
echo "S3 bucket website URL: http://${s3_bucket_name}.s3-website.${s3_bucket_region}.amazonaws.com"
echo ""
echo "Going to http://${s3_bucket_name}.s3-website.${s3_bucket_region}.amazonaws.com in a web browser should automatically redirect the browser to the address below: "
echo ""
echo "${website_url}"
exit "$exitCode"

Using the Jamf Pro API to report on Self Service policies

Every so often, it may be necessary to generate a report from Jamf Pro of which policies are available in Self Service. To assist with this task, I’ve written a script which uses the Jamf Pro Classic API to search through the policy records and generate a report in .tsv format.

For more details, please see below the jump.

Pre-requisites:

If setting up a specific Jamf Pro user account for this purpose with limited rights, here are the required API privileges for the account on the Jamf Pro server:

Jamf Pro Server Objects:

  • Policies: Read

For authentication, the script can accept manual input or values stored in a ~/Library/Preferences/com.github.jamfpro-info.plist file.

The plist file can be created by running the following commands and substituting your own values where appropriate:

To store the Jamf Pro URL in the plist file:

defaults write com.github.jamfpro-info jamfpro_url https://jamf.pro.server.goes.here:port_number_goes_here

To store the account username in the plist file:

defaults write com.github.jamfpro-info jamfpro_user account_username_goes_here

To store the account password in the plist file:

defaults write com.github.jamfpro-info jamfpro_password account_password_goes_here

Usage:

./Generate_Self_Service_Policy_Report.sh

Screen Shot 2022 10 14 at 5 29 53 PM

The script takes the following actions:

  1. Uses the Jamf Pro Classic API to download the Jamf Pro IDs of all computer policies.
  2. Checks which policies are Self Service policies.
  3. Uses the Jamf Pro Classic API to download all information about matching Self Service policies.
  4. Pulls the following information out of the policy record data:
  • Jamf Pro ID
  • If the policy is enabled in the Jamf Pro admin console
  • The policy’s name in the Jamf Pro admin console
  • The name of the policy’s category
  • The name displayed in Self Service for the policy

Create a report in tab-separated value (.tsv) format which contains the following information about the Self Service policies.

  • Jamf Pro ID
  • If it’s a Self Service policy
  • If the policy is enabled in the Jamf Pro admin console
  • The policy’s name in the Jamf Pro admin console
  • The name of the policy’s category
  • The name displayed in Self Service for the policy
  • Jamf Pro admin console URL for the Self Service policy

The report generated by script should appear similar to what is shown below:



Jamf Pro ID Number Self Service Policy Policy Enabled Policy Name Category Self Service Display Name Jamf Pro URL
105 TRUE TRUE Check for Apple Software Updates First Aid Check for Apple Software Updates https://server.name.here/policies.html?id=105
107 TRUE FALSE Get Logs Utilities Get Logs https://server.name.here/policies.html?id=107
94 TRUE TRUE Microsoft Office 365 Microsoft Microsoft Office 16.47.0 https://server.name.here/policies.html?id=94
23 TRUE TRUE Microsoft OneNote Microsoft Microsoft OneNote https://server.name.here/policies.html?id=23
22 TRUE TRUE Microsoft Remote Desktop Remote Access Microsoft Remote Desktop https://server.name.here/policies.html?id=22
104 TRUE TRUE Slack Communication Slack https://server.name.here/policies.html?id=104

This script is available below and also from GitHub at the following location:

https://github.com/rtrouton/rtrouton_scripts/tree/main/rtrouton_scripts/Casper_Scripts/Generate_Self_Service_Policy_Report


#!/bin/bash
# This script uses the Jamf Pro Classic API to detect Jamf Pro policies are
# Self Service policies and generates a report with information about those
# policies.
# Set default exit code
exitCode=0
# Create report file
report_file="$(mktemp).tsv"
# If you're on Jamf Pro 10.34.2 or earlier, which doesn't support using Bearer Tokens
# for Classic API authentication, set the NoBearerToken variable to the following value
# as shown below:
#
# yes
#
# NoBearerToken="yes"
#
# If you're on Jamf Pro 10.35.0 or later, which does support using Bearer Tokens
# for Classic API authentication, set the NoBearerToken variable to the following value
# as shown below:
#
# NoBearerToken=""
NoBearerToken=""
GetJamfProAPIToken() {
# This function uses Basic Authentication to get a new bearer token for API authentication.
# Use user account's username and password credentials with Basic Authorization to request a bearer token.
if [[ $(/usr/bin/sw_vers -productVersion | awk -F . '{print $1}') -lt 12 ]]; then
api_token=$(/usr/bin/curl -X POST –silent -u "${jamfpro_user}:${jamfpro_password}" "${jamfpro_url}/api/v1/auth/token" | python -c 'import sys, json; print json.load(sys.stdin)["token"]')
else
api_token=$(/usr/bin/curl -X POST –silent -u "${jamfpro_user}:${jamfpro_password}" "${jamfpro_url}/api/v1/auth/token" | plutil -extract token raw –)
fi
}
APITokenValidCheck() {
# Verify that API authentication is using a valid token by running an API command
# which displays the authorization details associated with the current API user.
# The API call will only return the HTTP status code.
api_authentication_check=$(/usr/bin/curl –write-out %{http_code} –silent –output /dev/null "${jamfpro_url}/api/v1/auth" –request GET –header "Authorization: Bearer ${api_token}")
}
CheckAndRenewAPIToken() {
# Verify that API authentication is using a valid token by running an API command
# which displays the authorization details associated with the current API user.
# The API call will only return the HTTP status code.
APITokenValidCheck
# If the api_authentication_check has a value of 200, that means that the current
# bearer token is valid and can be used to authenticate an API call.
if [[ ${api_authentication_check} == 200 ]]; then
# If the current bearer token is valid, it is used to connect to the keep-alive endpoint. This will
# trigger the issuing of a new bearer token and the invalidation of the previous one.
if [[ $(/usr/bin/sw_vers -productVersion | awk -F . '{print $1}') -lt 12 ]]; then
api_token=$(/usr/bin/curl "${jamfpro_url}/api/v1/auth/keep-alive" –silent –request POST –header "Authorization: Bearer ${api_token}" | python -c 'import sys, json; print json.load(sys.stdin)["token"]')
else
api_token=$(/usr/bin/curl "${jamfpro_url}/api/v1/auth/keep-alive" –silent –request POST –header "Authorization: Bearer ${api_token}" | plutil -extract token raw –)
fi
else
# If the current bearer token is not valid, this will trigger the issuing of a new bearer token
# using Basic Authentication.
GetJamfProAPIToken
fi
}
# If you choose to hardcode API information into the script, set one or more of the following values:
#
# The username for an account on the Jamf Pro server with sufficient API privileges
# The password for the account
# The Jamf Pro URL
# Set the Jamf Pro URL here if you want it hardcoded.
jamfpro_url=""
# Set the username here if you want it hardcoded.
jamfpro_user=""
# Set the password here if you want it hardcoded.
jamfpro_password=""
# Read the appropriate values from ~/Library/Preferences/com.github.jamfpro-info.plist
# if the file is available. To create the file, run the following commands:
#
# defaults write $HOME/Library/Preferences/com.github.jamfpro-info jamfpro_url https://jamf.pro.server.here
# defaults write $HOME/Library/Preferences/com.github.jamfpro-info jamfpro_user API_account_username_goes_here
# defaults write $HOME/Library/Preferences/com.github.jamfpro-info jamfpro_password API_account_password_goes_here
#
if [[ -f "$HOME/Library/Preferences/com.github.jamfpro-info.plist" ]]; then
if [[ -z "$jamfpro_url" ]]; then
jamfpro_url=$(defaults read $HOME/Library/Preferences/com.github.jamfpro-info jamfpro_url)
fi
if [[ -z "$jamfpro_user" ]]; then
jamfpro_user=$(defaults read $HOME/Library/Preferences/com.github.jamfpro-info jamfpro_user)
fi
if [[ -z "$jamfpro_password" ]]; then
jamfpro_password=$(defaults read $HOME/Library/Preferences/com.github.jamfpro-info jamfpro_password)
fi
fi
# If the Jamf Pro URL, the account username or the account password aren't available
# otherwise, you will be prompted to enter the requested URL or account credentials.
if [[ -z "$jamfpro_url" ]]; then
read -p "Please enter your Jamf Pro server URL : " jamfpro_url
fi
if [[ -z "$jamfpro_user" ]]; then
read -p "Please enter your Jamf Pro user account : " jamfpro_user
fi
if [[ -z "$jamfpro_password" ]]; then
read -p "Please enter the password for the $jamfpro_user account: " -s jamfpro_password
fi
echo
# Remove the trailing slash from the Jamf Pro URL if needed.
jamfpro_url=${jamfpro_url%%/}
# If configured to get one, get a Jamf Pro API Bearer Token
if [[ -z "$NoBearerToken" ]]; then
GetJamfProAPIToken
fi
# The following function downloads individual Jamf Pro policy as XML data
# then mines the policy data for the relevant information.
CheckSelfServicePolicies(){
local PolicyId="$1"
if [[ -n "$PolicyId" ]]; then
if [[ -z "$NoBearerToken" ]]; then
CheckAndRenewAPIToken
local DownloadedXMLData=$(/usr/bin/curl -s –header "Authorization: Bearer ${api_token}" -H "Accept: application/xml" "${jamfpro_url}/JSSResource/policies/id/$PolicyId")
else
local DownloadedXMLData=$(/usr/bin/curl -su "${jamfpro_user}:${jamfpro_password}" -H "Accept: application/xml" "${jamfpro_url}/JSSResource/policies/id/$PolicyId")
fi
local PolicyName=$( echo "$DownloadedXMLData" | xmllint –xpath '/policy/general/name/text()'2>/dev/null)
local SelfServicePolicyCheck=$(echo "$DownloadedXMLData" | xmllint –xpath '/policy/self_service/use_for_self_service/text()'2>/dev/null)
# If a policy is detected as being a Self Service policy, specified information is extracted from the downloaded data
# and added to a report in .tsv format.
if [[ "$SelfServicePolicyCheck" = "true" ]]; then
if [[ ! -f "$report_file" ]]; then
touch "$report_file"
printf "Jamf Pro ID Number\tSelf Service Policy\tPolicy Enabled\tPolicy Name\tCategory\tSelf Service Display Name\tJamf Pro URL\n" > "$report_file"
fi
JamfProID=$(echo "$DownloadedXMLData" | xmllint –xpath '//policy/general/id/text()'2>/dev/null)
PolicyEnabled=$(echo "$DownloadedXMLData" | xmllint –xpath '//policy/general/enabled/text()'2>/dev/null)
PolicyName=$(echo "$DownloadedXMLData" | xmllint –xpath '//policy/general/name/text()'2>/dev/null)
PolicyCategory=$(echo "$DownloadedXMLData" | xmllint –xpath '//policy/general/category/name/text()'2>/dev/null)
SelfServiceDisplayName=$(echo "$DownloadedXMLData" | xmllint –xpath '//policy/self_service/self_service_display_name/text()'2>/dev/null)
JamfProURL=$(echo "$jamfpro_url"/policies.html?id="$JamfProID")
if [[ $? -eq 0 ]]; then
printf "$JamfProID\t$SelfServicePolicyCheck\t$PolicyEnabled\t$PolicyName\t$PolicyCategory\t$SelfServiceDisplayName\t${JamfProURL}\n" >> "$report_file"
else
echo "ERROR! Failed to read policy record with ID $JamfProID"
fi
fi
fi
}
progress_indicator() {
spinner="⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏"
while :
do
for i in $(seq 0 7)
do
echo -n "${spinner:$i:1}"
echo -en "\010"
sleep 0.10
done
done
}
echo "Report being generated. File location will appear below once ready."
progress_indicator &
SPIN_PID=$!
trap "kill -9 $SPIN_PID" $(seq 0 15)
# Download all Jamf Pro policy ID numbers
if [[ -z "$NoBearerToken" ]]; then
CheckAndRenewAPIToken
PolicyIDList=$(/usr/bin/curl -s –header "Authorization: Bearer ${api_token}" -H "Accept: application/xml" "${jamfpro_url}/JSSResource/policies" | xmllint –xpath '//id'2>/dev/null)
else
PolicyIDList=$(/usr/bin/curl -su "${jamfpro_user}:${jamfpro_password}" -H "Accept: application/xml" "${jamfpro_url}/JSSResource/policies" | xmllint –xpath '//id'2>/dev/null)
fi
PolicyIDs=$(echo "$PolicyIDList" | grep -Eo "[0-9]+")
PoliciesCount=$(echo "$PolicyIDs" | grep -c ^)
echo "Checking $PoliciesCount policies for Self Service policies …"
echo
# Generate report of Self Service policies.
for anID in ${PolicyIDs}; do
CheckSelfServicePolicies $anID
done
kill -9 "$SPIN_PID"
if [[ -f "$report_file" ]]; then
echo "Report on Self Service policies available here: $report_file"
else
echo "ERROR! Report on Self Service policies not found."
exitCode=1
fi
exit $exitCode

Building Jamf Pro smart groups for Ventura-compatible and Ventura-incompatible Mac models

As part of preparing for macOS Ventura, it may be useful to have a way to easily distinguish between the Macs in your fleet which can run macOS Ventura and those which can’t. Apple has published the following list of Macs which are compatible with Ventura, which will help with both identitying the compatible Mac models as well as the incompatible Mac models.

  • iMac: 2017 and later models
  • iMac Pro: All models
  • MacBook: 2017 and later models
  • MacBook Pro: 2017 and later models
  • MacBook Air: 2018 and later models
  • Mac Mini: 2018 or later models
  • Mac Pro: 2019 or later models
  • Mac Studio: All models

From there, here’s the list of Mac models which are compatible with macOS Ventura:


Mac13,1
Mac13,2
Mac14,2
Mac14,7
MacBook10,1
MacBookAir10,1
MacBookAir8,1
MacBookAir8,2
MacBookAir9,1
MacBookPro14,1
MacBookPro14,2
MacBookPro14,3
MacBookPro15,1
MacBookPro15,2
MacBookPro15,3
MacBookPro15,4
MacBookPro16,1
MacBookPro16,2
MacBookPro16,3
MacBookPro16,4
MacBookPro17,1
MacBookPro18,1
MacBookPro18,2
MacBookPro18,3
MacBookPro18,4
MacPro7,1
Macmini8,1
Macmini9,1
VirtualMac2,1
iMac18,1
iMac18,2
iMac18,3
iMac19,1
iMac19,2
iMac20,1
iMac20,2
iMac21,1
iMac21,2
iMacPro1,1
iSim1,1

We can use this information to build smart groups which can help identify which Macs are compatible with Ventura and which are not. For more details, see below the jump:

Using the information mentioned above, I was able to build two smart groups, one which displays compatible Macs and the other which displays incompatible Macs.

The compatible Macs’ smart group checks for if the Mac in question’s model identifier is any of the model identifiers which are compatible with Ventura:


<?xml version="1.0" encoding="UTF-8"?>
<computer_group>
<name>Macs compatible with macOS Ventura</name>
<is_smart>true</is_smart>
<criteria>
<size>40</size>
<criterion>
<name>Model Identifier</name>
<priority>0</priority>
<and_or>or</and_or>
<search_type>is</search_type>
<value>Mac13,1</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>1</priority>
<and_or>or</and_or>
<search_type>is</search_type>
<value>Mac13,2</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>2</priority>
<and_or>or</and_or>
<search_type>is</search_type>
<value>Mac14,2</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>3</priority>
<and_or>or</and_or>
<search_type>is</search_type>
<value>Mac14,7</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>4</priority>
<and_or>or</and_or>
<search_type>is</search_type>
<value>MacBook10,1</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>5</priority>
<and_or>or</and_or>
<search_type>is</search_type>
<value>MacBookAir10,1</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>6</priority>
<and_or>or</and_or>
<search_type>is</search_type>
<value>MacBookAir8,1</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>7</priority>
<and_or>or</and_or>
<search_type>is</search_type>
<value>MacBookAir8,2</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>8</priority>
<and_or>or</and_or>
<search_type>is</search_type>
<value>MacBookAir9,1</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>9</priority>
<and_or>or</and_or>
<search_type>is</search_type>
<value>MacBookPro14,1</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>10</priority>
<and_or>or</and_or>
<search_type>is</search_type>
<value>MacBookPro14,2</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>11</priority>
<and_or>or</and_or>
<search_type>is</search_type>
<value>MacBookPro14,3</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>12</priority>
<and_or>or</and_or>
<search_type>is</search_type>
<value>MacBookPro15,1</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>13</priority>
<and_or>or</and_or>
<search_type>is</search_type>
<value>MacBookPro15,2</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>14</priority>
<and_or>or</and_or>
<search_type>is</search_type>
<value>MacBookPro15,3</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>15</priority>
<and_or>or</and_or>
<search_type>is</search_type>
<value>MacBookPro15,4</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>16</priority>
<and_or>or</and_or>
<search_type>is</search_type>
<value>MacBookPro16,1</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>17</priority>
<and_or>or</and_or>
<search_type>is</search_type>
<value>MacBookPro16,2</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>18</priority>
<and_or>or</and_or>
<search_type>is</search_type>
<value>MacBookPro16,3</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>19</priority>
<and_or>or</and_or>
<search_type>is</search_type>
<value>MacBookPro16,4</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>20</priority>
<and_or>or</and_or>
<search_type>is</search_type>
<value>MacBookPro17,1</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>21</priority>
<and_or>or</and_or>
<search_type>is</search_type>
<value>MacBookPro18,1</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>22</priority>
<and_or>or</and_or>
<search_type>is</search_type>
<value>MacBookPro18,2</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>23</priority>
<and_or>or</and_or>
<search_type>is</search_type>
<value>MacBookPro18,3</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>24</priority>
<and_or>or</and_or>
<search_type>is</search_type>
<value>MacBookPro18,4</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>25</priority>
<and_or>or</and_or>
<search_type>is</search_type>
<value>MacPro7,1</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>26</priority>
<and_or>or</and_or>
<search_type>is</search_type>
<value>Macmini8,1</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>27</priority>
<and_or>or</and_or>
<search_type>is</search_type>
<value>Macmini9,1</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>28</priority>
<and_or>or</and_or>
<search_type>is</search_type>
<value>VirtualMac2,1</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>29</priority>
<and_or>or</and_or>
<search_type>is</search_type>
<value>iMac18,1</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>30</priority>
<and_or>or</and_or>
<search_type>is</search_type>
<value>iMac18,2</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>31</priority>
<and_or>or</and_or>
<search_type>is</search_type>
<value>iMac18,3</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>32</priority>
<and_or>or</and_or>
<search_type>is</search_type>
<value>iMac19,1</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>33</priority>
<and_or>or</and_or>
<search_type>is</search_type>
<value>iMac19,2</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>34</priority>
<and_or>or</and_or>
<search_type>is</search_type>
<value>iMac20,1</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>35</priority>
<and_or>or</and_or>
<search_type>is</search_type>
<value>iMac20,2</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>36</priority>
<and_or>or</and_or>
<search_type>is</search_type>
<value>iMac21,1</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>37</priority>
<and_or>or</and_or>
<search_type>is</search_type>
<value>iMac21,2</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>38</priority>
<and_or>or</and_or>
<search_type>is</search_type>
<value>iMacPro1,1</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>39</priority>
<and_or>or</and_or>
<search_type>is</search_type>
<value>iSim1,1</value>
</criterion>
</criteria>
<computers/>
</computer_group>

Screen Shot 2022 10 12 at 1 20 52 PM

The incompatible Macs’ smart group checks for if the Mac in question’s model identifier is not any of the model identifiers which are compatible with Ventura:


<?xml version="1.0" encoding="UTF-8"?>
<computer_group>
<name>Macs incompatible with macOS Ventura</name>
<is_smart>true</is_smart>
<criteria>
<size>40</size>
<criterion>
<name>Model Identifier</name>
<priority>0</priority>
<and_or>and</and_or>
<search_type>is not</search_type>
<value>Mac13,1</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>1</priority>
<and_or>and</and_or>
<search_type>is not</search_type>
<value>Mac13,2</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>2</priority>
<and_or>and</and_or>
<search_type>is not</search_type>
<value>Mac14,2</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>3</priority>
<and_or>and</and_or>
<search_type>is not</search_type>
<value>Mac14,7</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>4</priority>
<and_or>and</and_or>
<search_type>is not</search_type>
<value>MacBook10,1</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>5</priority>
<and_or>and</and_or>
<search_type>is not</search_type>
<value>MacBookAir10,1</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>6</priority>
<and_or>and</and_or>
<search_type>is not</search_type>
<value>MacBookAir8,1</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>7</priority>
<and_or>and</and_or>
<search_type>is not</search_type>
<value>MacBookAir8,2</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>8</priority>
<and_or>and</and_or>
<search_type>is not</search_type>
<value>MacBookAir9,1</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>9</priority>
<and_or>and</and_or>
<search_type>is not</search_type>
<value>MacBookPro14,1</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>10</priority>
<and_or>and</and_or>
<search_type>is not</search_type>
<value>MacBookPro14,2</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>11</priority>
<and_or>and</and_or>
<search_type>is not</search_type>
<value>MacBookPro14,3</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>12</priority>
<and_or>and</and_or>
<search_type>is not</search_type>
<value>MacBookPro15,1</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>13</priority>
<and_or>and</and_or>
<search_type>is not</search_type>
<value>MacBookPro15,2</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>14</priority>
<and_or>and</and_or>
<search_type>is not</search_type>
<value>MacBookPro15,3</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>15</priority>
<and_or>and</and_or>
<search_type>is not</search_type>
<value>MacBookPro15,4</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>16</priority>
<and_or>and</and_or>
<search_type>is not</search_type>
<value>MacBookPro16,1</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>17</priority>
<and_or>and</and_or>
<search_type>is not</search_type>
<value>MacBookPro16,2</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>18</priority>
<and_or>and</and_or>
<search_type>is not</search_type>
<value>MacBookPro16,3</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>19</priority>
<and_or>and</and_or>
<search_type>is not</search_type>
<value>MacBookPro16,4</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>20</priority>
<and_or>and</and_or>
<search_type>is not</search_type>
<value>MacBookPro17,1</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>21</priority>
<and_or>and</and_or>
<search_type>is not</search_type>
<value>MacBookPro18,1</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>22</priority>
<and_or>and</and_or>
<search_type>is not</search_type>
<value>MacBookPro18,2</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>23</priority>
<and_or>and</and_or>
<search_type>is not</search_type>
<value>MacBookPro18,3</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>24</priority>
<and_or>and</and_or>
<search_type>is not</search_type>
<value>MacBookPro18,4</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>25</priority>
<and_or>and</and_or>
<search_type>is not</search_type>
<value>MacPro7,1</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>26</priority>
<and_or>and</and_or>
<search_type>is not</search_type>
<value>Macmini8,1</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>27</priority>
<and_or>and</and_or>
<search_type>is not</search_type>
<value>Macmini9,1</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>28</priority>
<and_or>and</and_or>
<search_type>is not</search_type>
<value>VirtualMac2,1</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>29</priority>
<and_or>and</and_or>
<search_type>is not</search_type>
<value>iMac18,1</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>30</priority>
<and_or>and</and_or>
<search_type>is not</search_type>
<value>iMac18,2</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>31</priority>
<and_or>and</and_or>
<search_type>is not</search_type>
<value>iMac18,3</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>32</priority>
<and_or>and</and_or>
<search_type>is not</search_type>
<value>iMac19,1</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>33</priority>
<and_or>and</and_or>
<search_type>is not</search_type>
<value>iMac19,2</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>34</priority>
<and_or>and</and_or>
<search_type>is not</search_type>
<value>iMac20,1</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>35</priority>
<and_or>and</and_or>
<search_type>is not</search_type>
<value>iMac20,2</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>36</priority>
<and_or>and</and_or>
<search_type>is not</search_type>
<value>iMac21,1</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>37</priority>
<and_or>and</and_or>
<search_type>is not</search_type>
<value>iMac21,2</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>38</priority>
<and_or>and</and_or>
<search_type>is not</search_type>
<value>iMacPro1,1</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>39</priority>
<and_or>and</and_or>
<search_type>is not</search_type>
<value>iSim1,1</value>
</criterion>
</criteria>
<computers/>
</computer_group>

Screen Shot 2022 10 12 at 1 21 30 PM

To upload these smart group XML files to a Jamf Pro server server using the API, download the XML file to a convenient location, then run the commands shown below (substituting your Jamf Pro server and Jamf Pro user account information as appropriate):

Note: You will first need to get an API bearer token for authentication. Use the commands below (depending on which OS you’re using) to obtain a bearer token from your JAMF Pro server:

Get API bearer token on macOS Monterey and later:


curl -X POST -u username:password -s https://server.name.here/api/v1/auth/token | plutil -extract token raw –

view raw

gistfile1.txt

hosted with ❤ by GitHub

Get API bearer token on macOS Big Sur and earlier:


curl -X POST -u username:password -s https://server.name.here/api/v1/auth/token | python -c 'import sys, json; print json.load(sys.stdin)["token"]'

view raw

gistfile1.txt

hosted with ❤ by GitHub

The bearer token should look something like this:


eyJhbGciOiJIUzI1NiJ9.eyJhdXRoZW50aWNhdGVkLWFwcCI6IkdFTkVSSUMiLCJhdXRoZW50aWNhdGlvbi10eXBlIjoiSlNTIiwiZ3JvdXBzIjpbXSwic3ViamVjdC10eXBlIjoiSlNTX1VTRVJfSUQiLCJ0b2tlbi11dWlkIjoiODQwNzBjZjctOGYwNS00N2NhLTliNWItZjU3YzYwYTY2ZGIwIiwibGRhcC1zZXJ2ZXItaWQiOi0xLCJzdWIiOiIxMSIsImV4cCI6MTY2NTYwMTUzMX0.R8grAtlzG1raZw95HJqiLyxZavf03SwFqbgfb3eVSgg

view raw

gistfile1.txt

hosted with ❤ by GitHub

Once you have the bearer token, use the token to authenticate the API command as shown below:


curl -sf https://jamfpro.server.here/JSSResource/computergroups/id/0 -T /path/to/filename.xml -X POST -H "Authorization: Bearer API_Bearer_Token_Goes_Here";

view raw

gistfile1.txt

hosted with ❤ by GitHub

If the API bearer token is the one shown above, the API command should look something like this:


curl -sf https://jamfpro.server.here/JSSResource/computergroups/id/0 -T /path/to/filename.xml -X POST -H "Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJhdXRoZW50aWNhdGVkLWFwcCI6IkdFTkVSSUMiLCJhdXRoZW50aWNhdGlvbi10eXBlIjoiSlNTIiwiZ3JvdXBzIjpbXSwic3ViamVjdC10eXBlIjoiSlNTX1VTRVJfSUQiLCJ0b2tlbi11dWlkIjoiODQwNzBjZjctOGYwNS00N2NhLTliNWItZjU3YzYwYTY2ZGIwIiwibGRhcC1zZXJ2ZXItaWQiOi0xLCJzdWIiOiIxMSIsImV4cCI6MTY2NTYwMTUzMX0.R8grAtlzG1raZw95HJqiLyxZavf03SwFqbgfb3eVSgg";

view raw

gistfile1.txt

hosted with ❤ by GitHub

If the smart group was successfully uploaded, you should next see output similar to that shown below:


<?xml version="1.0" encoding="UTF-8"?><computer_group><id>95</id></computer_group>

view raw

gistfile1.txt

hosted with ❤ by GitHub

The new smart group should now be present on the Jamf Pro server.

Screen Shot 2022 10 12 at 1 20 52 PM