macOS user privilege, admin or not?
For years we have been locking our users on Windows devices out from being administrators, and for a good reason. For macOS, this is a different story. Many tasks on macOS requires the user to be an admin to be able to do their job, especially when talking about developers. But do they need to be an admin non stop 24/7?
Today we’re going to have a look at enrolling our mac users as standard users, but giving them the ability to elevate their privileges when needed.
To do this, we’ll walk through how to deploy an app called Privileges in Microsoft Endpoint Manager.
What is Privileges?
Developed by the great guys at SAP, and which were nice enough to release it as open source, Privileges is an app that allows your users to work as standard users performing their day-to-day tasks and provide an easy way to request admin privileges when needed. All you have to do is click an icon in the dock.
We believe all users, including all developers, can benefit from using Privileges.app. Working as a standard user instead of an administrator adds another layer of security to your Mac and is considered a security best practice. Privileges.app helps enable users to act as administrators of the system only when required.
Requirements
Privileges supports the following macOS versions:
- macOS 10.12.x
- macOS 10.13.x
- macOS 10.14.x
- macOS 10.15.x
- macOS 11.x
Deploy Privileges with MEM
As Privileges comes as a .app file, we have to install it using a scripted method instead of wrapping it. This script is provided by Microsoft and includes good logging which you can collect from the MEM console, we’re going to have a look at that when the script has run. Below you can find the script I modified to download Privileges directly from SAP’s GitHub repo.
**Note: **at the time of writing, the version is 1.5.2
To use this script, first do the following
- Copy the script
- Save it as installprivileges.sh
In the terminal on a mac, run this command to make it executable
chmod +x /path/installprivileges.sh#!/bin/bash #set -x
############################################################################################ ## ## Script to install the latest GNU Imagine Manipulation client ## ############################################################################################
## Copyright (c) 2020 Microsoft Corp. All rights reserved. ## Scripts are not supported under any Microsoft standard support program or service. The scripts are provided AS IS without warranty of any kind. ## Microsoft disclaims all implied warranties including, without limitation, any implied warranties of merchantability or of fitness for a ## particular purpose. The entire risk arising out of the use or performance of the scripts and documentation remains with you. In no event shall ## Microsoft, its authors, or anyone else involved in the creation, production, or delivery of the scripts be liable for any damages whatsoever ## (including, without limitation, damages for loss of business profits, business interruption, loss of business information, or other pecuniary ## loss) arising out of the use of or inability to use the sample scripts or documentation, even if Microsoft has been advised of the possibility ## of such damages. ## Feedback: neiljohn@microsoft.com
# User Defined variables weburl=”https://github.com/SAP/macOS-enterprise-privileges/releases/download/1.5.2/Privileges.zip” # What is the Azure Blob Storage URL? appname=”Privileges” # The name of our App deployment script (also used for Octory monitor) app=”Privileges.app” # The actual name of our App once installed logandmetadir=”/Library/Logs/Microsoft/IntuneScripts/installPrivileges” # The location of our logs and last updated data processpath=”/Applications/Privileges.app/Contents/MacOS/Privileges” # The process name of the App we are installing terminateprocess=”false” # Do we want to terminate the running process? If false we’ll wait until its not running autoUpdate=”false” # Application updates itself, if already installed we should exit
# Generated variables tempdir=$(mktemp -d) log=”$logandmetadir/$appname.log” # The location of the script log file metafile=”$logandmetadir/$appname.meta” # The location of our meta file (for updates)
# function to delay script if the specified process is running waitForProcess () {
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
################################################################################################################# ################################################################################################################# ## ## Function to pause while a specified process is running ## ## Functions used ## ## None ## ## Variables used ## ## $1 = name of process to check for ## $2 = length of delay (if missing, function to generate random delay between 10 and 60s) ## $3 = true/false if = "true" terminate process, if "false" wait for it to close ## ############################################################### ############################################################### processName=$1 fixedDelay=$2 terminate=$3 echo "$(date) | Waiting for other [$processName] processes to end" while ps aux | grep "$processName" | grep -v grep &>/dev/null; do if [[ $terminate == "true" ]]; then echo "$(date) | + [$appname] running, terminating [$processpath]..." pkill -f "$processName" return fi # If we've been passed a delay we should use it, otherwise we'll create a random delay each run if [[ ! $fixedDelay ]]; then delay=$(( $RANDOM % 50 + 10 )) else delay=$fixedDelay fi echo "$(date) | + Another instance of $processName is running, waiting [$delay] seconds" sleep $delay done echo "$(date) | No instances of [$processName] found, safe to proceed"}
# function to check if we need Rosetta 2 checkForRosetta2 () {
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
################################################################################################################# ################################################################################################################# ## ## Simple function to install Rosetta 2 if needed. ## ## Functions ## ## waitForProcess (used to pause script if another instance of softwareupdate is running) ## ## Variables ## ## None ## ############################################################### ############################################################### echo "$(date) | Checking if we need Rosetta 2 or not" # if Software update is already running, we need to wait... waitForProcess "/usr/sbin/softwareupdate" processor=$(/usr/sbin/sysctl -n machdep.cpu.brand_string) if [[ "$processor" == *"Intel"* ]]; then echo "$(date) | [$processor] found, Rosetta not needed" else echo "$(date) | [$processor] founbd, is Rosetta already installed?" # Check Rosetta LaunchDaemon. If no LaunchDaemon is found, # perform a non-interactive install of Rosetta. if [[ ! -f "/Library/Apple/System/Library/LaunchDaemons/com.apple.oahd.plist" ]]; then /usr/sbin/softwareupdate --install-rosetta --agree-to-license if [[ $? -eq 0 ]]; then echo "$(date) | Rosetta has been successfully installed." return else echo "$(date) | Rosetta installation failed!" fi else echo "$(date) | Rosetta is already installed. Nothing to do." fi fi}
# Function to update the last modified date for this app fetchLastModifiedDate() {
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
################################################################################################################# ################################################################################################################# ## ## This function takes the following global variables and downloads the URL provided to a temporary location ## ## Functions ## ## none ## ## Variables ## ## $logandmetadir = Directory to read nand write meta data to ## $metafile = Location of meta file (used to store last update time) ## $weburl = URL of download location ## $tempfile = location of temporary DMG file downloaded ## $lastmodified = Generated by the function as the last-modified http header from the curl request ## ## Notes ## ## If called with "fetchLastModifiedDate update" the function will overwrite the current lastmodified date into metafile ## ############################################################### ############################################################### ## Check if the log directory has been created if [[ ! -d "$logandmetadir" ]]; then ## Creating Metadirectory echo "$(date) | Creating [$logandmetadir] to store metadata" mkdir -p "$logandmetadir" fi # generate the last modified date of the file we need to download lastmodified=$(curl -sIL "$weburl" | grep -i "last-modified" | awk '{$1=""; print $0}' | awk '{ sub(/^[ \t]+/, ""); print }' | tr -d '\r') if [[ $1 == "update" ]]; then echo "$(date) | Writing last modifieddate [$lastmodified] to [$metafile]" echo "$lastmodified" > "$metafile" fi}
function downloadApp () {
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90
################################################################################################################# ################################################################################################################# ## ## This function takes the following global variables and downloads the URL provided to a temporary location ## ## Functions ## ## waitForCurl (Pauses download until all other instances of Curl have finished) ## downloadSize (Generates human readable size of the download for the logs) ## ## Variables ## ## $appname = Description of the App we are installing ## $weburl = URL of download location ## $tempfile = location of temporary DMG file downloaded ## ############################################################### ############################################################### echo "$(date) | Starting downlading of [$appname]" # wait for other downloads to complete waitForProcess "curl -f" #download the file updateOctory installing echo "$(date) | Downloading $appname" cd "$tempdir" curl -f -s --connect-timeout 30 --retry 5 --retry-delay 60 -L -J -O "$weburl" if [ $? == 0 ]; then # We have downloaded a file, we need to know what the file is called and what type of file it is tempSearchPath="$tempdir/*" for f in $tempSearchPath; do tempfile=$f done case $tempfile in *.pkg|*.PKG) packageType="PKG" ;; *.zip|*.ZIP) packageType="ZIP" ;; *.dmg|*.DMG) packageType="DMG" ;; *) # We can't tell what this is by the file name, lets look at the metadata echo "$(date) | Unknown file type [$f], analysing metadata" metadata=$(file "$tempfile") if [[ "$metadata" == *"Zip archive data"* ]]; then packageType="ZIP" mv "$tempfile" "$tempdir/install.zip" tempfile="$tempdir/install.zip" fi if [[ "$metadata" == *"xar archive"* ]]; then packageType="PKG" mv "$tempfile" "$tempdir/install.pkg" tempfile="$tempdir/install.pkg" fi if [[ "$metadata" == *"bzip2 compressed data"* ]] || [[ "$metadata" == *"zlib compressed data"* ]] ; then packageType="DMG" mv "$tempfile" "$tempdir/install.dmg" tempfile="$tempdir/install.dmg" fi ;; esac if [[ ! $packageType ]]; then echo "Failed to determine temp file type" rm -rf "$tempdir" else echo "$(date) | Downloaded [$app] to [$tempfile]" echo "$(date) | Detected install type as [$packageType]" fi else echo "$(date) | Failure to download [$weburl] to [$tempfile]" updateOctory failed exit 1 fi}
# Function to check if we need to update or not function updateCheck() {
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
################################################################################################################# ################################################################################################################# ## ## This function takes the following dependencies and variables and exits if no update is required ## ## Functions ## ## fetchLastModifiedDate ## ## Variables ## ## $appname = Description of the App we are installing ## $tempfile = location of temporary DMG file downloaded ## $volume = name of volume mount point ## $app = name of Application directory under /Applications ## ############################################################### ############################################################### echo "$(date) | Checking if we need to install or update [$appname]" ## Is the app already installed? if [ -d "/Applications/$app" ]; then # App is installed, if it's updates are handled by MAU we should quietly exit if [[ $autoUpdate == "true" ]]; then echo "$(date) | [$appname] is already installed and handles updates itself, exiting" exit 0 fi # App is already installed, we need to determine if it requires updating or not echo "$(date) | [$appname] already installed, let's see if we need to update" fetchLastModifiedDate ## Did we store the last modified date last time we installed/updated? if [[ -d "$logandmetadir" ]]; then if [ -f "$metafile" ]; then previouslastmodifieddate=$(cat "$metafile") if [[ "$previouslastmodifieddate" != "$lastmodified" ]]; then echo "$(date) | Update found, previous [$previouslastmodifieddate] and current [$lastmodified]" update="update" else echo "$(date) | No update between previous [$previouslastmodifieddate] and current [$lastmodified]" echo "$(date) | Exiting, nothing to do" exit 0 fi else echo "$(date) | Meta file [$metafile] not found" echo "$(date) | Unable to determine if update required, updating [$appname] anyway" fi fi else echo "$(date) | [$appname] not installed, need to download and install" fi}
## Install PKG Function function installPKG () {
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
################################################################################################################# ################################################################################################################# ## ## This function takes the following global variables and installs the PKG file ## ## Functions ## ## isAppRunning (Pauses installation if the process defined in global variable $processpath is running ) ## fetchLastModifiedDate (Called with update flag which causes the function to write the new lastmodified date to the metadata file) ## ## Variables ## ## $appname = Description of the App we are installing ## $tempfile = location of temporary DMG file downloaded ## $volume = name of volume mount point ## $app = name of Application directory under /Applications ## ############################################################### ############################################################### # Check if app is running, if it is we need to wait. waitForProcess "$processpath" "300" "$terminateprocess" echo "$(date) | Installing $appname" # Update Octory monitor updateOctory installing # Remove existing files if present if [[ -d "/Applications/$app" ]]; then rm -rf "/Applications/$app" fi installer -pkg "$tempfile" -target /Applications # Checking if the app was installed successfully if [ "$?" = "0" ]; then echo "$(date) | $appname Installed" echo "$(date) | Cleaning Up" rm -rf "$tempdir" echo "$(date) | Application [$appname] succesfully installed" fetchLastModifiedDate update updateOctory installed exit 0 else echo "$(date) | Failed to install $appname" rm -rf "$tempdir" updateOctory failed exit 1 fi}
## Install DMG Function function installDMG () {
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
################################################################################################################# ################################################################################################################# ## ## This function takes the following global variables and installs the DMG file into /Applications ## ## Functions ## ## isAppRunning (Pauses installation if the process defined in global variable $processpath is running ) ## fetchLastModifiedDate (Called with update flag which causes the function to write the new lastmodified date to the metadata file) ## ## Variables ## ## $appname = Description of the App we are installing ## $tempfile = location of temporary DMG file downloaded ## $volume = name of volume mount point ## $app = name of Application directory under /Applications ## ############################################################### ############################################################### # Check if app is running, if it is we need to wait. waitForProcess "$processpath" "300" "$terminateprocess" echo "$(date) | Installing [$appname]" updateOctory installing # Mount the dmg file... volume="$tempdir/$appname" echo "$(date) | Mounting Image" hdiutil attach -quiet -nobrowse -mountpoint "$volume" "$tempfile" # Remove existing files if present if [[ -d "/Applications/$app" ]]; then echo "$(date) | Removing existing files" rm -rf "/Applications/$app" fi # Sync the application and unmount once complete echo "$(date) | Copying app files to /Applications/$app" rsync -a "$volume"/*.app/ "/Applications/$app" # Unmount the dmg echo "$(date) | Un-mounting [$volume]" hdiutil detach -quiet "$volume" # Checking if the app was installed successfully if [[ -a "/Applications/$app" ]]; then echo "$(date) | [$appname] Installed" echo "$(date) | Cleaning Up" rm -rf "$tempfile" echo "$(date) | Fixing up permissions" sudo chown -R root:wheel "/Applications/$app" echo "$(date) | Application [$appname] succesfully installed" fetchLastModifiedDate update updateOctory installed exit 0 else echo "$(date) | Failed to install [$appname]" rm -rf "$tempdir" updateOctory failed exit 1 fi}
## Install ZIP Function function installZIP () {
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110
################################################################################################################# ################################################################################################################# ## ## This function takes the following global variables and installs the DMG file into /Applications ## ## Functions ## ## isAppRunning (Pauses installation if the process defined in global variable $processpath is running ) ## fetchLastModifiedDate (Called with update flag which causes the function to write the new lastmodified date to the metadata file) ## ## Variables ## ## $appname = Description of the App we are installing ## $tempfile = location of temporary DMG file downloaded ## $volume = name of volume mount point ## $app = name of Application directory under /Applications ## ############################################################### ############################################################### # Check if app is running, if it is we need to wait. waitForProcess "$processpath" "300" "$terminateprocess" echo "$(date) | Installing $appname" updateOctory installing # Change into temp dir cd "$tempdir" if [ "$?" = "0" ]; then echo "$(date) | Changed current directory to $tempdir" else echo "$(date) | failed to change to $tempfile" if [ -d "$tempdir" ]; then rm -rf $tempdir; fi updateOctory failed exit 1 fi # Unzip files in temp dir unzip -qq -o "$tempfile" if [ "$?" = "0" ]; then echo "$(date) | $tempfile unzipped" else echo "$(date) | failed to unzip $tempfile" if [ -d "$tempdir" ]; then rm -rf $tempdir; fi updateOctory failed exit 1 fi # If app is already installed, remove all old files if [[ -a "/Applications/$app" ]]; then echo "$(date) | Removing old installation at /Applications/$app" rm -rf "/Applications/$app" fi # Copy over new files rsync -a "$app/" "/Applications/$app" if [ "$?" = "0" ]; then echo "$(date) | $appname moved into /Applications" else echo "$(date) | failed to move $appname to /Applications" if [ -d "$tempdir" ]; then rm -rf $tempdir; fi updateOctory failed exit 1 fi # Make sure permissions are correct echo "$(date) | Fix up permissions" sudo chown -R root:wheel "/Applications/$app" if [ "$?" = "0" ]; then echo "$(date) | correctly applied permissions to $appname" else echo "$(date) | failed to apply permissions to $appname" if [ -d "$tempdir" ]; then rm -rf $tempdir; fi updateOctory failed exit 1 fi # Checking if the app was installed successfully if [ "$?" = "0" ]; then if [[ -a "/Applications/$app" ]]; then echo "$(date) | $appname Installed" updateOctory installed echo "$(date) | Cleaning Up" rm -rf "$tempfile" # Update metadata fetchLastModifiedDate update echo "$(date) | Fixing up permissions" sudo chown -R root:wheel "/Applications/$app" echo "$(date) | Application [$appname] succesfully installed" exit 0 else echo "$(date) | Failed to install $appname" exit 1 fi else # Something went wrong here, either the download failed or the install Failed # intune will pick up the exit status and the IT Pro can use that to determine what went wrong. # Intune can also return the log file if requested by the admin echo "$(date) | Failed to install $appname" if [ -d "$tempdir" ]; then rm -rf $tempdir; fi exit 1 fi }function updateOctory () {
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
################################################################################################################# ################################################################################################################# ## ## This function is designed to update Octory status (if required) ## ## ## Parameters (updateOctory parameter) ## ## notInstalled ## installing ## installed ## ############################################################### ############################################################### # Is Octory present if [[ -a "/Library/Application Support/Octory" ]]; then # Octory is installed, but is it running? if [[ $(ps aux | grep -i "Octory" | grep -v grep) ]]; then echo "$(date) | Updating Octory monitor for [$appname] to [$1]" /usr/local/bin/octo-notifier monitor "$appname" --state $1 >/dev/null fi fi}
function startLog() {
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
################################################### ################################################### ## ## start logging - Output to log file and STDOUT ## #################### #################### if [[ ! -d "$logandmetadir" ]]; then ## Creating Metadirectory echo "$(date) | Creating [$logandmetadir] to store logs" mkdir -p "$logandmetadir" fi exec &> >(tee -a "$log")}
# function to delay until the user has finished setup assistant. waitForDesktop () { until ps aux | grep /System/Library/CoreServices/Dock.app/Contents/MacOS/Dock | grep -v grep &>/dev/null; do delay=$(( $RANDOM % 50 + 10 )) echo “$(date) | + Dock not running, waiting [$delay] seconds” sleep $delay done echo “$(date) | Dock is here, lets carry on” }
################################################################################### ################################################################################### ## ## Begin Script Body ## ##################################### #####################################
# Initiate logging startLog
echo “” echo “##############################################################” echo “# $(date) | Logging install of [$appname] to [$log]” echo “############################################################” echo “”
# Install Rosetta if we need it checkForRosetta2
# Test if we need to install or update updateCheck
# Wait for Desktop waitForDesktop
# Download app downloadApp
# Install PKG file if [[ $packageType == “PKG” ]]; then installPKG fi
# Install PKG file if [[ $packageType == “ZIP” ]]; then installZIP fi
# Install PKG file if [[ $packageType == “DMG” ]]; then installDMG fi
Now that the script is prepared, it can be added to MEM. To upload the script, follow the steps below.
- Sign in to the MEM console
- Go to Devices -> macOS -> Shell scripts and click Add
- Give your script a Name, then click Next
- Upload the script annd configure script settings, I will be using the defaults from Microsofts example
- Run script as signed-in user: No
- Hide script notifiations on devices: Yes
- Script frequency: Every 1 day
- Max number of times to retry if the script fails: 3 times
- Click Next, assign the script to a test device then click Next and Add
Troubleshooting
If the script fails, you can see additional details on the script result and also collect logs by going to **Device status **in the script details.
- To collect logs, click Collect Logs and provide the following path, /Library/Logs/Microsoft/IntuneScripts/installPrivileges/Privileges.log
- If you click on Show details, the last output from the script is displayed
- Once the logs has been collected, you can now download the logs by clicking Download logs. Inside the zip file will be the requested file, and the two Intune script agent (user) and daemon (root) logs, which are always returned.
Configuring Privileges
To configure Privileges we’re going to use a custom configuration profile. If you are fine with the user only having to click the app to be an administrator for 20 minutes you don’t have to change anything. I’m going to set 3 additional settings,
DockToggleMaxTimeoutwhen this key is configured, it allows the user to choose timeout values to something shorter than 20 minutes but 20 will always be the maximum amout of time they can be an admin before it reverts back to a standard user.RequireAuthenticationRequires the user to enter their password before admin rights are grantedReasonRequiredRequires the user to enter a reason why they need admin rights. The given reason is logged.ReasonMinLengthmust also be set which specifies the minimum amount of characters the user has to enter as the reason. Maximum number of characters are 100.
Besides the additional keys I’m configuring, you can also set these configurations,
DockToggleTimeoutsets a fixed timeout before the user is reverted to a standard userEnforcePrivilegesAllows you to enforce certain privileges- admin: administrator rights always set by Privileges.
- user: standard user rights are always set by Privileges.
none: Privileges.app and the PrivilegesCLI command line tool are disabled and it is not possible to change user privileges using these tools.
LimitToGrouplimit the use of Privileges to a specified user groupLimitToUserlimit the use of Privileges to a specified userRemoteLoggingused to send the logging for Privileges.app to a remote syslog server
If you want to test the application with the same settings I’m using, copy the profile below and save it as a .mobileconfig file.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
<?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>PayloadContent</key>
<dict>
<key>corp.sap.privileges</key>
<dict>
<key>Forced</key>
<array>
<dict>
<key>mcx_preference_settings</key>
<dict>
<key>DockToggleMaxTimeout</key>
<integer>20</integer>
<key>RequireAuthentication</key>
<true/>
<key>ReasonRequired</key>
<true/>
<key>ReasonMinLength</key>
<integer>5</integer>
</dict>
</dict>
</array>
</dict>
</dict>
<key>PayloadDescription</key>
<string/>
<key>PayloadDisplayName</key>
<string>Privileges configuration</string>
<key>PayloadEnabled</key>
<true/>
<key>PayloadIdentifier</key>
<string>com.apple.ManagedClient.preferences.AEC544EE-591D-4834-900E-4C94858E7871</string>
<key>PayloadOrganization</key>
<string>DemoOrg</string>
<key>PayloadType</key>
<string>com.apple.ManagedClient.preferences</string>
<key>PayloadUUID</key>
<string>AEC544EE-591D-4834-900E-4C94858E7871</string>
<key>PayloadVersion</key>
<integer>1</integer>
</dict>
</array>
<key>PayloadDescription</key>
<string>Configures the Privileges app.</string>
<key>PayloadDisplayName</key>
<string>Privileges configuration</string>
<key>PayloadEnabled</key>
<true/>
<key>PayloadIdentifier</key>
<string>82A2ED21-27DA-428E-8516-446A52169C71</string>
<key>PayloadOrganization</key>
<string>SAP SE</string>
<key>PayloadRemovalDisallowed</key>
<true/>
<key>PayloadScope</key>
<string>System</string>
<key>PayloadType</key>
<string>Configuration</string>
<key>PayloadUUID</key>
<string>C2F39834-001F-4930-AC7D-E5BA0DE82529</string>
<key>PayloadVersion</key>
<integer>1</integer>
</dict>
</plist>
Once you have the file saved follow the process below.
- Open the MEM console
- Go to Devices -> Configuration profile and click Create, choose macOS for Platform and Profile type as Template then, choose Custom and click Create
- Give your profile a Name and click Next
- Upload the .mobileconfig file, set the Configuration profile name and click Next
- Assign the profile to your test device and click Next
- Click Create
End user experience
This is how the process looks for the end user. All they have to do is click the Privileges app, provide a reason, enter their password and they’re admin. A visual indication shows if they are admin or not, if the Privileges icon is green, the user is a standard user. If the icon is orange, the user is an admin. 


