Forked from talkingmoose/Manage App Notifications.bash
Last active
February 26, 2020 20:43
-
-
Save Paul-Benton/ed0fcdbbccaa7fa87184082ed441e549 to your computer and use it in GitHub Desktop.
Similar to User Approved Kernel Extensions (UAKEL) and Privacy Preferences Policy Control (PPPC) in earlier macOS versions, Catalina will now prompt users to allow Notifications from each app. Administrators can manage these prompts using a Configuration Profile. These profiles do not require User Approved MDM.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/zsh | |
<<ABOUT_THIS_SCRIPT | |
----------------------------------------------------------------------- | |
Written by:William Smith | |
Professional Services Engineer | |
Jamf | |
[email protected] | |
https://gist.github.com/talkingmoose/9faf50deaaefafa9a147e48ba39bb4b0 | |
Reference: https://developer.apple.com/documentation/devicemanagement/notifications/notificationsettingsitem | |
Originally posted: October 5, 2019 | |
Last updated: January 30, 2020 - Kyle Benton | |
Purpose: Create configuration profiles to manage app notifications. | |
Upload the configuration profile to Jamf Pro or save to your desktop. | |
Instructions: Run the script with help, -h or --usage for help. | |
2020-01-30 By Kyle Benton | |
- converted to zsh | |
- added request for accepting default for all | |
- can now read a text file list of App paths (one per line) or single app path | |
Except where otherwise noted, this work is licensed under | |
http://creativecommons.org/licenses/by/4.0/ | |
"Fortune makes promises to many, keeps them to none. | |
Live for each day, live for the hours, since nothing is forever yours." | |
----------------------------------------------------------------------- | |
ABOUT_THIS_SCRIPT | |
# Jamf Pro URL and credentials | |
URL="https://jamfproserver.domain.net:8443" | |
userName="API-Editor" | |
password="P@55w0rd" | |
organizationName="Forsman & Bodenfors Canada" | |
# -- set some variables for the rest of the script ---------------------------- | |
# read a textfile of app paths or just use passed apps | |
mode="default" | |
# regular expression for "help" and "usage" | |
regHelp="^-?-?[Hh]([Ee][Ll][Pp])?|[Uu]([Ss][Aa][Gg][Ee])?$" | |
# regular expressions for "true" and "false" | |
regTrue="^[Tt]([Rr][Uu][Ee])?|[Yy]([Ee][Ss])?$" | |
regFalse="^[Ff]([Aa][Ll][Ss][Ee])?|[Nn]([Oo])?$" | |
# regular expressions for "upload" and "save" | |
regUpload="^[Uu]([Pp][Ll][Oo][Aa][Dd])?$" | |
regSave="^[Ss]([Aa][Vv][Ee])?$" | |
regBoth="^[Bb]([Oo][Tt][Hh])?$" | |
# regular expressions for "none", "banners" and "alerts" | |
regNone="^[Nn]([Oo][Nn][Ee])?$" | |
regBanners="^[Bb]([Aa][Nn][Nn][Ee][Rr][Ss])?$" | |
regAlerts="^[Aa]([Ll][Ee][Rr][Tt][Ss])?$" | |
# generate two UUIDs for configuration profile payload identifiers | |
PayloadUUID1=$(/usr/bin/uuidgen) | |
PayloadUUID2=$(/usr/bin/uuidgen) | |
appDicts="" | |
# -- ask if defaults should be used | |
while [[ ! "$useDefaults" =~ ${regTrue} && ! "$useDefaults" =~ ${regFalse} ]]; do | |
echo | |
read "useDefaults?Would you like to use your defaults? ( [Yes] No ): " # true, false, yes or no; case insensitive, first letter accepted, return to accept default | |
useDefaults=${useDefaults:-true} | |
done | |
if [[ "$useDefaults" =~ ${regTrue} ]]; then | |
useDefaults="true" | |
else | |
useDefaults="false" | |
fi | |
# -- display usage information or parse app path for information -------------- | |
appPath="" | |
inPath="$1" | |
if [[ "$inPath" =~ ${regHelp} ]]; then | |
echo | |
echo "Manage App Notifications | |
Purpose: Create configuration profiles to manage app notifications | |
Upload the configuration profile to Jamf Pro or save to your desktop | |
Configuration: To upload to your Jamf Pro server, edit these lines before running the script. | |
The API-Editor account needs the Create privilege for macOS Configuration Profiles in Jamf Pro | |
URL=\"https://jamfproserver.domain.net:8443\" | |
userName=\"API-Editor\" | |
password=\"P@55w0rd\" | |
organizationName=\"Talking Moose Industries\" | |
Usage: \"$0\" [/path/to/application] | |
or | |
Usage: \"$0\" [/path/to/application_list.txt] | |
Questions are followed by allowed responses with [default] responses in brackets. | |
Responses are case insensitive and accept the first letter or entire word. | |
Press return to accept the default response. | |
Example: $ \"$0\" | |
Path to managed app (drag app into this Terminal window): /Applications/FaceTime.app | |
... | |
or | |
$ \"$0\" /Applications/FaceTime.app | |
Allow Notifications from FaceTime ( [Yes] No ): Yes | |
FaceTime alert style ( None [Banners] Alerts ): A | |
Show notifications on lock screen ( [Yes] No ): n | |
Show in Notification Center ( [Yes] No ): true | |
Badge app icon ( [Yes] No ): | |
Play sound for notifications ( [Yes] No ): NO | |
Critical Alerts Enabled ( Yes [No] ): FALSE | |
Add another app ( [Yes] No ): n | |
Upload to Jamf Pro or Save to Desktop? ( Both [Upload] Save ): U | |
Your new Notifications configuration profile for FaceTime was uploaded to Jamf Pro and is ready for scoping. | |
Would you like to view the profile now? ( [Yes] No ): yes" | |
echo | |
exit 0 | |
else | |
# if its a file (not a directory) | |
if [[ -f $inPath ]]; then | |
mode="list" | |
else | |
mode="default" | |
appPath=$inPath | |
appBundleID=$(/usr/bin/defaults read "$appPath/Contents/Info.plist" CFBundleIdentifier 2>/dev/null) | |
appExecutable=$(/usr/bin/defaults read "$appPath/Contents/Info.plist" CFBundleExecutable 2>/dev/null) | |
appExecutableString=$(/usr/bin/sed -e 's/ /./g' <<<"$appExecutable") | |
fi | |
fi | |
function getApp() { | |
appBundleID=$(/usr/bin/defaults read "$appPath/Contents/Info.plist" CFBundleIdentifier 2>/dev/null) | |
appExecutable=$(/usr/bin/defaults read "$appPath/Contents/Info.plist" CFBundleExecutable 2>/dev/null) | |
appExecutableString=$(/usr/bin/sed -e 's/ /./g' <<<"$appExecutable") | |
if [[ "$appExecutable" = "" ]]; then | |
echo "failed geting metadata on \"$appPath\"" | |
#exit 1 | |
fi | |
} | |
function getFile() { | |
# -- request path to managed app if no app path provided ---------------------- | |
if [[ ! $mode == "list" ]]; then | |
inPath="" | |
while [[ ! -e $inPath ]]; do | |
echo | |
read "inPath?Path to managed app or list of paths (drag app/file into this Terminal window): " | |
done | |
mode="default" | |
if [[ -f $inPath ]]; then | |
mode="list" | |
else | |
appPath=$inPath | |
fi | |
fi | |
} | |
function getNotificationsEnabled() { | |
# -- choose notificationsEnabled ---------------------------------------------- | |
while [[ ! "$notificationsEnabled" =~ ${regTrue} && ! "$notificationsEnabled" =~ ${regFalse} ]]; do | |
echo | |
read "notificationsEnabled?Allow Notifications from $appExecutable ( [Yes] No ): " # true, false, yes or no; case insensitive, first letter accepted, return to accept default | |
notificationsEnabled=${notificationsEnabled:-true} | |
done | |
if [[ "$notificationsEnabled" =~ ${regTrue} ]]; then | |
notificationsEnabled="true" | |
else | |
notificationsEnabled="false" | |
fi | |
} | |
function getAlertType() { | |
# -- choose alertType --------------------------------------------------------- | |
while [[ ! "$alertType" =~ ${regNone} && ! "$alertType" =~ ${regBanners} && ! "$alertType" =~ ${regAlerts} && "$notificationsEnabled" = "true" ]]; do | |
read "alertType?Alert style ( None [Banners] Alerts ): " # none, banners or alerts, case insensitive, first letter accepted, return to accept default | |
alertType=${alertType:-Banners} | |
done | |
if [[ "$alertType" =~ ${regNone} ]]; then | |
alertType="0" | |
elif [[ "$alertType" =~ ${regBanners} ]]; then | |
alertType="1" | |
else | |
alertType="2" | |
fi | |
} | |
function getShowInLockScreen() { | |
# -- choose showInLockScreen -------------------------------------------------- | |
while [[ ! "$showInLockScreen" =~ ${regTrue} && ! "$showInLockScreen" =~ ${regFalse} && "$notificationsEnabled" = "true" ]]; do | |
read "showInLockScreen?Show notifications on lock screen ( [Yes] No ): " # true, false, yes or no; case insensitive, first letter accepted, return to accept default | |
showInLockScreen=${showInLockScreen:-true} | |
done | |
if [[ "$showInLockScreen" =~ ${regTrue} ]]; then | |
showInLockScreen="true" | |
else | |
showInLockScreen="false" | |
fi | |
} | |
function getShowInNotificationCenter() { | |
# -- choose showInNotificationCenter ------------------------------------------ | |
while [[ ! "$showInNotificationCenter" =~ ${regTrue} && ! "$showInNotificationCenter" =~ ${regFalse} && "$notificationsEnabled" = "true" ]]; do | |
read "showInNotificationCenter?Show in Notification Center ( [Yes] No ): " # true, false, yes or no; case insensitive, first letter accepted, return to accept default | |
showInNotificationCenter=${showInNotificationCenter:-true} | |
done | |
if [[ "$showInNotificationCenter" =~ ${regTrue} ]]; then | |
showInNotificationCenter="true" | |
else | |
showInNotificationCenter="false" | |
fi | |
} | |
function getBadgesEnabled() { | |
# -- choose badgesEnabled ----------------------------------------------------- | |
while [[ ! "$badgesEnabled" =~ ${regTrue} && ! "$badgesEnabled" =~ ${regFalse} && "$notificationsEnabled" = "true" ]]; do | |
read "badgesEnabled?Badge app icon ( [Yes] No ): " # true, false, yes or no; case insensitive, first letter accepted, return to accept default | |
badgesEnabled=${badgesEnabled:-true} | |
done | |
if [[ "$badgesEnabled" =~ ${regTrue} ]]; then | |
badgesEnabled="true" | |
else | |
badgesEnabled="false" | |
fi | |
} | |
function getSoundsEnabled() { | |
# -- choose soundsEnabled ----------------------------------------------------- | |
while [[ ! "$soundsEnabled" =~ ${regTrue} && ! "$soundsEnabled" =~ ${regFalse} && "$notificationsEnabled" = "true" ]]; do | |
read "soundsEnabled?Play sound for notifications ( [Yes] No ): " # true, false, yes or no; case insensitive, first letter accepted, return to accept default | |
soundsEnabled=${soundsEnabled:-true} | |
done | |
if [[ "$soundsEnabled" =~ ${regTrue} ]]; then | |
soundsEnabled="true" | |
else | |
soundsEnabled="false" | |
fi | |
} | |
function getCriticalAlertsEnabled() { | |
# -- choose criticalAlertsEnabled (does not appear in GUI) -------------------- | |
while [[ ! "$criticalAlertsEnabled" =~ ${regTrue} && ! "$criticalAlertsEnabled" =~ ${regFalse} && "$notificationsEnabled" = "true" ]]; do | |
read "criticalAlertsEnabled?Critical Alerts Enabled ( Yes [No] ): " # true, false, yes or no; case insensitive, first letter accepted, return to accept default | |
criticalAlertsEnabled=${criticalAlertsEnabled:-false} | |
done | |
if [[ "$criticalAlertsEnabled" =~ ${regTrue} ]]; then | |
criticalAlertsEnabled="true" | |
else | |
criticalAlertsEnabled="false" | |
fi | |
} | |
function uploadProfile() { | |
# upload to Jamf Pro | |
profilePayload=$(/usr/bin/xmllint --noblanks - <<<"$mobileconfig" | /usr/bin/sed -e 's/</\</g' -e 's/>/\>/g') | |
profileXML="<os_x_configuration_profile> | |
<general> | |
<name>Managed Notifications</name> | |
<description> | |
<string>Manage Notifications settings.</string> | |
</description> | |
<site/> | |
<category/> | |
<distribution_method>Install Automatically</distribution_method> | |
<user_removable>false</user_removable> | |
<level>computer</level> | |
<uuid>$PayloadUUID2</uuid> | |
<payloads>$profilePayload</payloads> | |
</general> | |
</os_x_configuration_profile>" | |
# flatten the XML for the configuration profile to upload to Jamf Pro | |
flatXML=$(/usr/bin/xmllint --noblanks - <<<"$profileXML") | |
# upload and create configuration profile | |
result=$(/usr/bin/curl "$URL/JSSResource/osxconfigurationprofiles/id/0" \ | |
--silent \ | |
--insecure \ | |
--write-out "%{http_code}" \ | |
--user "$userName":"$password" \ | |
--header "Content-Type: text/xml" \ | |
--request POST \ | |
--data "$flatXML" 2>&1) | |
# evaluate HTTP status code | |
resultStatus=${result: -3} | |
if [[ $resultStatus = 201 ]]; then # 201 is successful | |
echo | |
echo "Your new Notifications configuration profile was uploaded to Jamf Pro and is ready for scoping." | |
# -- offer to open configuration profile in Jamf Pro ------------------ | |
while [[ ! "$openProfile" =~ ${regTrue} && ! "$openProfile" =~ ${regFalse} ]]; do | |
echo | |
read "openProfile?Would you like to view the profile now? ( [Yes] No ): " # true, false, yes or no; case insensitive, first letter accepted, return to accept default | |
openProfile=${openProfile:-true} | |
done | |
if [[ "$openProfile" =~ ${regTrue} ]]; then | |
resultXML=${result%???} | |
profileID=$(/usr/bin/xpath '/os_x_configuration_profile/id/text()' <<<"$resultXML" 2>/dev/null) | |
/usr/bin/open "$URL/OSXConfigurationProfiles.html?id=$profileID&o=r" | |
fi | |
else | |
echo | |
echo "Unable to upload your new Notifications configuration profile [Response code: $resultStatus]." | |
echo | |
echo "CODE DESCRIPTION | |
000 Check server URL in script | |
200 Request successful | |
201 Request to create or update object successful | |
400 Bad request. Verify the syntax of the request specifically the XML body. | |
401 Authentication failed. Verify the credentials being used for the request. | |
403 Invalid permissions. Verify the account being used has the proper permissions for the object/resource you are trying to access. | |
404 Object/resource not found. Verify the URL path is correct. | |
409 Conflict. Delete existing profile \"Set $appExecutable notifications\" first. | |
500 Internal server error. Retry the request or contact Jamf support if the error is persistent." | |
fi | |
} | |
function saveProfile() { | |
echo | |
echo "$mobileconfig" >"$HOME/Desktop/Managed Notifications.mobileconfig" | |
echo "Your new Notifications configuration profile was saved to your desktop." | |
} | |
function appendApp() { | |
if [[ "$useDefaults" = "true" ]]; then | |
notificationsEnabled="true" | |
alertType="1" | |
showInLockScreen="true" | |
showInNotificationCenter="true" | |
badgesEnabled="true" | |
soundsEnabled="true" | |
criticalAlertsEnabled="false" | |
else | |
echo | |
echo | |
echo "Preferences for $appExecutable" | |
getNotificationsEnabled | |
getAlertType | |
getShowInLockScreen | |
getShowInNotificationCenter | |
getBadgesEnabled | |
getSoundsEnabled | |
getCriticalAlertsEnabled | |
fi | |
appDicts="$appDicts | |
<dict> | |
<key>AlertType</key> | |
<integer>$alertType</integer> | |
<key>BadgesEnabled</key> | |
<$badgesEnabled/> | |
<key>BundleIdentifier</key> | |
<string>$appBundleID</string> | |
<key>CriticalAlertEnabled</key> | |
<$criticalAlertsEnabled/> | |
<key>NotificationsEnabled</key> | |
<$notificationsEnabled/> | |
<key>ShowInLockScreen</key> | |
<$showInLockScreen/> | |
<key>ShowInNotificationCenter</key> | |
<$showInNotificationCenter/> | |
<key>SoundsEnabled</key> | |
<$soundsEnabled/> | |
</dict>" | |
appList="$appList $appBundleID" | |
appExecutable="" | |
notificationsEnabled="" | |
alertType="" | |
showInLockScreen="" | |
notificationsEnabled="" | |
showInNotificationCenter="" | |
badgesEnabled="" | |
soundsEnabled="" | |
criticalAlertsEnabled="" | |
} | |
function readList() { | |
while read -u 9 "appPath"; | |
do | |
getApp | |
if [[ $appList = *"$appBundleID"* ]]; then | |
echo | |
echo "$appExecutable is already added to the list." | |
else | |
appendApp | |
fi | |
done 9< "$inPath" | |
if [[ $useDefaults == "false" ]]; then | |
echo | |
echo | |
echo "----- DONE INPUTING PREFRENCES FOR LIST -----" | |
echo | |
fi | |
} | |
# -- ask for another app -------------------------------------------------- | |
while [[ ! "$addApp" =~ ${regFalse} ]]; do | |
getFile | |
if [[ $mode == "list" ]]; then | |
echo "you gave me a list!" | |
readList | |
mode="default" | |
else | |
getApp | |
if [[ $appList = *"$appBundleID"* ]]; then | |
echo | |
echo "$appExecutable is already added to the list." | |
else | |
appendApp | |
fi | |
fi | |
addApp="" | |
while [[ ! "$addApp" =~ ${regTrue} && ! "$addApp" =~ ${regFalse} ]]; do | |
echo | |
read "addApp?Add another app/list ( [Yes] No ): " # true, false, yes or no; case insensitive, first letter accepted, return to accept default | |
addApp=${addApp:-true} | |
done | |
if [[ "$addApp" =~ ${regTrue} ]]; then | |
addApp="true" | |
mode="default" | |
else | |
addApp="false" | |
fi | |
done | |
# -- use this template XML to create a .mobileconfig file -------------------------- | |
mobileconfig="<?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\"> | |
<dict> | |
<key>PayloadContent</key> | |
<array> | |
<dict> | |
<key>NotificationSettings</key> | |
<array>$appDicts | |
</array> | |
<key>PayloadDescription</key> | |
<string>Managed Notifications</string> | |
<key>PayloadDisplayName</key> | |
<string>Managed Notifications</string> | |
<key>PayloadEnabled</key> | |
<true/> | |
<key>PayloadIdentifier</key> | |
<string>$PayloadUUID1</string> | |
<key>PayloadOrganization</key> | |
<string>$organizationName</string> | |
<key>PayloadType</key> | |
<string>com.apple.notificationsettings</string> | |
<key>PayloadUUID</key> | |
<string>$PayloadUUID1</string> | |
<key>PayloadVersion</key> | |
<integer>1</integer> | |
</dict> | |
</array> | |
<key>PayloadDescription</key> | |
<string>Managed Notifications</string> | |
<key>PayloadDisplayName</key> | |
<string>Managed Notifications</string> | |
<key>PayloadEnabled</key> | |
<true/> | |
<key>PayloadIdentifier</key> | |
<string>$PayloadUUID2</string> | |
<key>PayloadOrganization</key> | |
<string>$organizationName</string> | |
<key>PayloadRemovalDisallowed</key> | |
<false/> | |
<key>PayloadScope</key> | |
<string>System</string> | |
<key>PayloadType</key> | |
<string>Configuration</string> | |
<key>PayloadUUID</key> | |
<string>$PayloadUUID2</string> | |
<key>PayloadVersion</key> | |
<integer>1</integer> | |
</dict> | |
</plist>" | |
# -- choose to upload to Jamf Pro or save to Desktop -------------------------- | |
while [[ ! "$chooseOutput" =~ ${regUpload} && ! "$chooseOutput" =~ ${regSave} && ! "$chooseOutput" =~ ${regBoth} ]]; do | |
echo | |
read "chooseOutput?Upload to Jamf Pro or Save to Desktop? ( Both [Upload] Save ): " # upload, save or both; case insensitive, first letter accepted, return to accept default | |
chooseOutput=${chooseOutput:-upload} | |
done | |
if [[ "$chooseOutput" =~ ${regUpload} ]]; then | |
uploadProfile | |
elif [[ "$chooseOutput" =~ ${regSave} ]]; then | |
saveProfile | |
else | |
uploadProfile | |
saveProfile | |
fi | |
exit 0 |
Author
Paul-Benton
commented
Jan 31, 2020
- converted to zsh
- added request for accepting default for all
- can now read a text file list of App paths (one per line) or single app path
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment