Non-Admin ability to remove Apps

agetz
Contributor

As a school district we have some schools that allow the students to get Apps from the Mac App Store. Because we are a school district, we obviously cannot allow admin access to those machine. Therein lies the rub.

I would love to see if any of you have a graceful solution for non-admins to remove apps from the Applications folder without being an admin.

This is what I have worked out so far:
I KNOW I can use a script to move all apps from the applications folder to the user's applications folder.
From there they should be able to delete them. (To my knowledge, please correct me if I'm wrong.)
I KNOW that I should be able to exclude items from that action, Utilities folder, built-in Mac Apps, etc.

I need some help getting a script together so that we can do this. If anyone has a more graceful method, I am all ears.

I will include what I have so far but am limited by the string length and can only include a few apps.

#!/bin/sh
# current user is $3

shopt -s extglob

cd /Users/$3/desktop/test

mv !('dont move'|Computers.csv|'Asset Tags.csv') /Users/$3/Applications
1 ACCEPTED SOLUTION

mm2270
Legendary Contributor III

@agetz Give the below a try. I haven't been able to do much testing with it, but it may work better than before. It's a bit weird but I've used this technique before, which essentially pipes a script out to tmp and then runs the script itself as the logged in user. The result of the script is captured and passed to the remainder of the script and determines what application to delete. In function, this works the same as before when testing the script locally, but runs as root, then executes the "selection" script as the user using launchctl asuser which I find tends to be more reliable.

Once I have a moment, I'll test this under Sierra 10.12.6 to see if there are still any issues to be addressed. But give it a try.

#!/bin/bash

## Edited to correct an error with variables ##

## We capture the logged in user and the UID to use later when running the local script as the user
loggedInUser=$(stat -f%Su /dev/console)
loggedInUID=$(id -u "$loggedInUser")

## Create a script in /tmp to run as the logged in user
cat << EOS > /private/tmp/del_appstore_user.sh
#!/bin/bash

## Get a list of all App Store apps installed into the main Applications folder
AppStoreAppsRaw=$(mdfind -onlyin /Applications kMDItemAppStoreHasReceipt == 1 | awk -F'/' '{print $NF}' | sort -f)

## Run an Applescript to convert the list into a list for selection and present the list to the user
AppToDelete=$(/usr/bin/osascript << EOD
set theAppsList to paragraphs of (do shell script "echo "$AppStoreAppsRaw"")
tell app "System Events"
activate
choose from list theAppsList with prompt "Choose an application to delete"
end tell
EOD)

## Echo back the selection result
echo "$AppToDelete"

EOS

## Script creation done

## Now make the new script executable
chmod +x /private/tmp/del_appstore_user.sh

## Run the script as the user, capturing the output into a new variable
AppToDeleteResponse=$(/bin/launchctl asuser "$loggedInUID" sudo -iu "$loggedInUser" "/private/tmp/del_appstore_user.sh")

## Now check to see if the response from the selection was something other than 'false',
## which would indicate the user canceled from a selection
if [ "$AppToDeleteResponse" != "false" ]; then
ConfirmDelete=$(/usr/bin/osascript << EOF
set theAppName to do shell script "echo "$AppToDeleteResponse""
tell app "System Events"
activate
set response to button returned of (display dialog "Are you sure you want to delete the application "" & theAppName & "?" buttons {"Yes","No"} default button 2)
end tell
EOF)
else
    echo "User may have canceled. Exiting."
    ## Clean up the script in /tmp
    rm -f /private/tmp/del_appstore_user.sh
    exit 0
fi

## If the button returned from the confirmation was Yes, then delete the application using rm
if [ "$ConfirmDelete" == "Yes" ]; then
    echo "User confirmed. Deleting $AppToDeleteResponse"
    rm -rf "/Applications/$AppToDeleteResponse"
    ## Finally, clean up the script in /tmp
    rm -f /private/tmp/del_appstore_user.sh
    exit 0
else
    ## If not, cancel since the user chose No
    echo "User may have canceled. Exiting."
    ## Clean up the script in /tmp
    rm -f /private/tmp/del_appstore_user.sh
    exit 0
fi

View solution in original post

24 REPLIES 24

mm2270
Legendary Contributor III

I'm not entirely sure what you're looking to do, but it sounds like you want Mac App Store apps to get moved into ~/Applications/ instead of the default /Applications/ I'm not sure that's entirely supported, but I do know that the App Store.app will still recognize the applications as being from the App Store even if they are moved into another path. I've seen that myself with something I moved that was installed from there. The App Store application was still showing an available update for the program even though it wasn't in the main Applications folder.

If this is what you're after, I would recommend using mdfind to locate all App Store apps in a nice neat list in your script, then simply loop over that list, copying them or moving them into the new location.

The following will grab a list of MAS applications living in /Applications/

mdfind -onlyin /Applications/ -name kMDItemAppStoreHasReceipt == 1

Try using that in your script to determine what needs to be copied. It will naturally exclude things like the built in apps that come with the OS, since those aren't App Store apps.
Note that If you don't get the full results you expect, do an mdls on a known Mac App Store app to see some of the other App Store distinct items in its record. For example, you could also use kMDItemAppStoreIsAppleSigned == 1

Hope that helps.

mm2270
Legendary Contributor III

Another thought I just had, which may make this much easier, though initially more work to develop. Think about putting together a script that can be run from a Self Service policy that could list out the apps from the App Store in Applications, just like I show above, put that into a list in an Applescript 'choose from list' style dialog, and let them select the app they want to delete, then do the rm with root privileges.

That way, they can still remove them by running something via Self Service and the apps can remain where they get installed by default. No mess needed with moving apps around. This would also allow anyone who logs into the Mac to delete these rather than just a single account on the Mac.

agetz
Contributor

@mm2270 Yes, that would be the best solution if we could get there. The issue is really that eventually many students fill up their drives with app store apps and then our techs have to go and remove them so they can continue working. I was working on just moving the apps to a directory they had rights to but actually giving them a type of uninstaller for only App Store Apps would be a beautiful thing.

I am not a master scripter but I could eventually get something together with a bit of testing. Can you point me in the right direction? Even with getting your first recommendation together into a script.

The mdfind option will work nicely, just have to figure out how to setup the mv option worked out in the script to mv the applications it finds.

Thanks.

mm2270
Legendary Contributor III

Here's a working script that could be run to display an AS dialog with only App Store installed apps that they can select and it will delete the application.

The one potential problem with this is that generally, to get interactive dialogs to show up properly, they usually need to be run as the user, not as the root account. Not the deletion part, but the part where it displays the dialogs. I haven't thrown this into Self Service myself to see if it just works 'as is' You can try, and it may be fine without further modification. I don't know for sure though.
Also, I'm not an expert in Applescript. I have a feeling this could be simplified a bit, but I don't know enough to do that right now. In any case, I tested this locally (not via Self Service) and it works. I downloaded a free App Store app I didn't really want, and used this to delete it from Applications.

#!/bin/bash

AppStoreAppsRaw=$(mdfind -onlyin /Applications kMDItemAppStoreHasReceipt == 1 | awk -F'/' '{print $NF}' | sort -f)

AppToDelete=$(/usr/bin/osascript << EOD
set theAppsList to paragraphs of (do shell script "echo "$AppStoreAppsRaw"")
tell app "System Events"
activate
choose from list theAppsList with prompt "Choose an application to delete"
end tell
EOD)

if [ "$AppToDelete" != "false" ]; then
ConfirmDelete=$(/usr/bin/osascript << EOF
set theAppName to do shell script "echo "$AppToDelete""
tell app "System Events"
activate
set response to button returned of (display dialog "Are you sure you want to delete the application "" & theAppName & "?" buttons {"Yes","No"} default button 2)
end tell
EOF)
else
    echo "User may have canceled. Exiting."
    exit 0
fi

if [ "$ConfirmDelete" == "Yes" ]; then
    echo "User confirmed. Deleting $AppToDelete"
    rm -rf "/Applications/$AppToDelete"
    exit 0
else
    echo "User may have canceled. Exiting."
    exit 0
fi

agetz
Contributor

@mm2270 Thanks for the help, I will do some testing on my end and see how it works. I will post back with the results.

agetz
Contributor

@mm2270 I am seeing errors when running as root in self-service.

Script exit code: 0
Script result: 227:235: execution error: An error of type -10810 has occurred. (-10810)
71:79: execution error: System Events got an error: Application isn’t running. (-600)
User may have canceled. Exiting.

Running locally works until it gets to the rm command, you must be an admin to do it.

mm2270
Legendary Contributor III

OK, that is what I was concerned about. The "error of type -10810" basically means, you can't display this interactive dialog to the logged in user when it's run as root.

In this case, let me see what we can do to get it to work. As I mentioned, we need to run the Applescript call as the logged in user. There are a few ways to do this, but some work better/more reliably than others.

mm2270
Legendary Contributor III

Ok, fairly simple modification. This works, at least for me. I tested running it from Self Service to delete an App Store app. Displays the selection dialog and takes that selection and deletes it.

See if it works for you now.

#!/bin/bash

loggedInUser=$(stat -f%Su /dev/console)

AppStoreAppsRaw=$(mdfind -onlyin /Applications kMDItemAppStoreHasReceipt == 1 | awk -F'/' '{print $NF}' | sort -f)

AppToDelete=$(su "$loggedInUser" -c /usr/bin/osascript << EOD
set theAppsList to paragraphs of (do shell script "echo "$AppStoreAppsRaw"")
tell app "System Events"
activate
choose from list theAppsList with prompt "Choose an application to delete"
end tell
EOD)

if [ "$AppToDelete" != "false" ]; then
ConfirmDelete=$(su "$loggedInUser" -c /usr/bin/osascript << EOF
set theAppName to do shell script "echo "$AppToDelete""
tell app "System Events"
activate
set response to button returned of (display dialog "Are you sure you want to delete the application "" & theAppName & "?" buttons {"Yes","No"} default button 2)
end tell
EOF)
else
    echo "User may have canceled. Exiting."
    exit 0
fi

if [ "$ConfirmDelete" == "Yes" ]; then
    echo "User confirmed. Deleting $AppToDelete"
    rm -rf "/Applications/$AppToDelete"
    exit 0
else
    echo "User may have canceled. Exiting."
    exit 0
fi

agetz
Contributor

@mm2270 Well, a little different this time.

Script result: 227:235: execution error: System Events got an error: Application isn’t running. (-600)
71:79: execution error: System Events got an error: Application isn’t running. (-600)
User may have canceled. Exiting.

Running in self service is hanging but running from casper remote produced this error.

mm2270
Legendary Contributor III

Hmm. Odd. I'm not sure what that error means. Haven't seen that before. Let me do a little more testing if I can. Can I ask what OS you're running it on? Sierra or something else? That might be part of it actually.

agetz
Contributor

@mm2270 Sierra. 10.12.6

Running it locally it wont display the application choices. Just su: Sorry
su: Sorry
User may have canceled. Exiting.

Casper remote:
Script exit code: 0
Script result: 227:235: execution error: An error of type -10810 has occurred. (-10810)
71:79: execution error: An error of type -10810 has occurred. (-10810)
User may have canceled. Exiting.

Self service doesnt even know whats happening and doesnt produce a log, just gathers information and ends. no logs.

mm2270
Legendary Contributor III

@agetz Give the below a try. I haven't been able to do much testing with it, but it may work better than before. It's a bit weird but I've used this technique before, which essentially pipes a script out to tmp and then runs the script itself as the logged in user. The result of the script is captured and passed to the remainder of the script and determines what application to delete. In function, this works the same as before when testing the script locally, but runs as root, then executes the "selection" script as the user using launchctl asuser which I find tends to be more reliable.

Once I have a moment, I'll test this under Sierra 10.12.6 to see if there are still any issues to be addressed. But give it a try.

#!/bin/bash

## Edited to correct an error with variables ##

## We capture the logged in user and the UID to use later when running the local script as the user
loggedInUser=$(stat -f%Su /dev/console)
loggedInUID=$(id -u "$loggedInUser")

## Create a script in /tmp to run as the logged in user
cat << EOS > /private/tmp/del_appstore_user.sh
#!/bin/bash

## Get a list of all App Store apps installed into the main Applications folder
AppStoreAppsRaw=$(mdfind -onlyin /Applications kMDItemAppStoreHasReceipt == 1 | awk -F'/' '{print $NF}' | sort -f)

## Run an Applescript to convert the list into a list for selection and present the list to the user
AppToDelete=$(/usr/bin/osascript << EOD
set theAppsList to paragraphs of (do shell script "echo "$AppStoreAppsRaw"")
tell app "System Events"
activate
choose from list theAppsList with prompt "Choose an application to delete"
end tell
EOD)

## Echo back the selection result
echo "$AppToDelete"

EOS

## Script creation done

## Now make the new script executable
chmod +x /private/tmp/del_appstore_user.sh

## Run the script as the user, capturing the output into a new variable
AppToDeleteResponse=$(/bin/launchctl asuser "$loggedInUID" sudo -iu "$loggedInUser" "/private/tmp/del_appstore_user.sh")

## Now check to see if the response from the selection was something other than 'false',
## which would indicate the user canceled from a selection
if [ "$AppToDeleteResponse" != "false" ]; then
ConfirmDelete=$(/usr/bin/osascript << EOF
set theAppName to do shell script "echo "$AppToDeleteResponse""
tell app "System Events"
activate
set response to button returned of (display dialog "Are you sure you want to delete the application "" & theAppName & "?" buttons {"Yes","No"} default button 2)
end tell
EOF)
else
    echo "User may have canceled. Exiting."
    ## Clean up the script in /tmp
    rm -f /private/tmp/del_appstore_user.sh
    exit 0
fi

## If the button returned from the confirmation was Yes, then delete the application using rm
if [ "$ConfirmDelete" == "Yes" ]; then
    echo "User confirmed. Deleting $AppToDeleteResponse"
    rm -rf "/Applications/$AppToDeleteResponse"
    ## Finally, clean up the script in /tmp
    rm -f /private/tmp/del_appstore_user.sh
    exit 0
else
    ## If not, cancel since the user chose No
    echo "User may have canceled. Exiting."
    ## Clean up the script in /tmp
    rm -f /private/tmp/del_appstore_user.sh
    exit 0
fi

agetz
Contributor

@mm2270

Well, it tried to delete everything in the application folder...

The choices did come up and so did the verification prompt, but doesn't look like it passed the right info back for the removal...

The log file is rather large and the policy in self service did complete so theres that. Let me know if you need that.

mm2270
Legendary Contributor III

@agetz Ouch! Sorry about that! I see now where I messed up when I was editing the script. I sincerely hope that it didn't mess up your computer or whatever you were testing it on. :(

So my mistake was I left the original variable labeled AppToDelete in the line where it deletes the application. It needed to change to AppToDeleteResponse because that gets populated with the user response / selection when it runs the local script. So because AppToDelete was blank, when it ran this line:

rm -rf "/Applications/$AppToDelete"

It was really rm'ing the entire Applications folder! Again, my bad and sorry for not proofing that more carefully.

Please try the edited script above (I changed it so if anyone comes across it they won't try to use the faulty one) Please try on a test Mac, as I'm sure you're already doing. See if that actually works now.

agetz
Contributor

@mm2270 No worries, thankfully it was localized to the Application folder and the vast majority were locked by Apple apparently and couldn't be removed. It was time to upgrade a few apps anyway.

Yes, I noticed the same thing in the script. Commented out the RM and echoed out the other variable to test. Thanks for the reply back to confirm.

Will let you know how this goes, thanks.

agetz
Contributor

@mm2270 It seems to be working. I'm going to have some students test it out and see how it goes.

This process is exactly what I was looking for. Thanks for your time and efforts.

Seems that you have helped me out at least a couple times with script related questions and I owe you. Thanks again.

CapU
Contributor III

I agree.
mm2270 Is very helpful.

mm2270
Legendary Contributor III

@agetz Hey, glad to hear it worked for you! And happy to assist. Hopefully the work done on this will provide a useful example to anyone else that comes across this thread too.

agetz
Contributor

I've gone a step further and used Platypus to create a simple app that will reference the policy in Self Service. End user can then run the app instead of going to self service. My student test group seems to like it this way better. Guess its one less click, I dunno.

#!/bin/bash

#opens self service in hidden mode, then opens the policy URL.

open -j -a /Applications/Self Service.app "selfservice://entity=policy&url=&product=651&recon=false&enableUserLevelMdm=false&installed=false”

mm2270
Legendary Contributor III

@agetz Nice! I was just about to post, why not embed the whole script within a self contained platypus app, but then of course you'd run into the admin privileges issue. So that's a creative way of getting around that.

mucgyver-old
New Contributor III

@mm2270, what would be the modification in the script to not limit this script to App Store applications, but to all (maybe excluding macOS default apps)?

I guess it is in here:

AppStoreAppsRaw=$(mdfind -onlyin /Applications kMDItemAppStoreHasReceipt == 1 | awk -F'/' '{print $NF}' | sort -f)

But I do not have a clue...

Thanks for your help!

Best regards
Chris

tlarkin
Honored Contributor

@cbednarzwd haha I literally was doing this in the #bash channel in the Mac Admin Slack last week. Spotlight is an amazing tool. So, almost everything in macOS will have meta data tags to it, and Spotlight can search for those tags. So, we do know that every App has what is called a Bundle ID and it is unique. We also know that all Apple Apps start with com.apple in the Bundle ID. The meta data tag for this is kMDItemCFBundleIdentifier. We can use this to wild card inclusion or exclusion of data we are searching for.

mdfind -onlyin /Applications "kMDItemCFBundleIdentifier == com.apple.*"

We can wildcard com.apple.* to return a list of all Apps by Apple. I would suggest looking at mdfind, and mdls to shell script using Spotlight. There are a lot of powerful things you can do with them.

mucgyver-old
New Contributor III

Well, I don't get this search query to work, and I cannot figure out the problem. According to all the references I found in WWW; this should basically work:

mdfind -onlyin /Applications "kMDItemCFBundleIdentifier != com.apple.*" && "kMDItemKind = Application" | awk -F'/' '{print $NF}' | sort -f

So, this string should
- just search within Applications folder
- just give out raw Applications (not the other stuff that might reside there)
- limit this just to Applications just to third party apps, NOT from Apple
- format the result accordingly to the script above from mm2270

But it gives this error(s):

-bash: kMDItemKind = Application: command not found
awk: syntax error at source line 1 context is {print >>> <<< $NF}

I played around with the syntax so much that I am completely confused now. Totally lost in this... :-(

Best regards,
Christian

tlarkin
Honored Contributor

@cbednarzwd you have a weird escape in your awk command, but this is how you fix it, and you are quoting the Spotlight meta data arguments twice, so the binary sees that as two arguments (it is a bashism) so try this:

mdfind -onlyin /Applications "kMDItemCFBundleIdentifier != com.apple.* && kMDItemKind = Application" | awk -F'/' '{print $NF}' | sort -f