Remove Specific Login Items for Specific Users

seansb
New Contributor III

Hello All!

I've been banging my head against the keyboard on this one. I need to remove a specific Login Item from a user's Login Items IF they have it. I then need to add an updated version of that application into that user's Login Items (yes, login items... nothing else). In other words, I can't just blanket remove this application from startup items and then place the application into every user's login items. If the user had it in their login items, old one gets removed, new one gets placed in there.

I see people referencing:

osascript -e 'tell application "System Events" to delete login item "itemname"'

quite often, but this works for the currently logged in user. I need to be able to target this to specific, possibly non logged in users. I tried running a variant, something like:

su $user osascript -e 'tell application "System Events" to delete login item "itemname"'

but it simply wasn't executing as the user that had the login item. I'm looking specifically for a way to script this. I've been trying plist buddy but it just hasn't been working the way I expected.

Any help would be greatly appreciated. Please let me know if you need clarification - I know it's a bit confusing.

18 REPLIES 18

Josh_Smith
Contributor III

I haven't done this, but if I needed to I think I would write a bash/python script that cycles through each user's ~/Library/Preferences/com.apple.loginitems.plist on the computer to see if the old item was there, and if it was then remove it and drop in the new item. I would use the policy scope/trigger/frequency to control the who/when/how often.

mm2270
Legendary Contributor III

You can't use an Applescript command like that to work on a non logged in user's preferences. First off, Applescript is pretty user centric and expects to be run in the user space. That's just how Apple designed it and how the OS expects it to be used. Second, calling System Events to do some action against a non logged in account simply won't work.

As @Josh.Smith mentions, you'll need to script deleting and writing back to the com.apple.loginitems.plist file for each account. While PlistBuddy can do this, a python script would probably work better since its better at interacting directly with the CoreFoundation stuff in OS X.

Since I don't know python though, I can offer some help in bash. The only issue with PlistBuddy is that the change may not actually be visible in the GUI until after a logout and login, but maybe that's OK since these are Login Items anyway we're talking about.

So here's an example bash script that, in this case, I'm looking for, and deleting the "WebEx Productivity Tools" Login item out of the logged in account.

#!/bin/bash

## Get the logged in user
loggedInUser=$(ls -l /dev/console | awk '{print $3}')

## Pull a list of login item names for the user
LoginItemNames=$(/usr/libexec/PlistBuddy -c "Print :SessionItems:CustomListItems" /Users/${loggedInUser}/Library/Preferences/com.apple.loginitems.plist | awk -F'= ' '/Name/{print $NF}')

## Put them into an array to loop over
while read loginitem; do
    LoginItemArray+=("$loginitem")
done < <(printf '%s
' "$LoginItemNames")

## Loop over them, locate the item to delete
while read item; do
    if [[ "$item" == "WebEx Productivity Tools" ]]; then
        index=${i}
        break
    fi
    let i+=1
done < <(printf '%s
' "${LoginItemArray[@]}")

## Delete the item if found
if [ ! -z "$index" ]; then
    echo "Found item at index $index"
    /usr/libexec/PlistBuddy -c "Delete :SessionItems:CustomListItems:$index" /Users/${loggedInUser}/Library/Preferences/com.apple.loginitems.plist
else
    echo "Item not found in Login Items"
fi

Now, as I mentioned, until you log out and back in, it won't show as being removed in the GUI in System Preferences. I even tried doing a killall cfprefsd to see if I could get System Prefs to read the new values in, but it didn't work. Once I logged out and back in though, WebEx Productivity Tools did not load and was not present in my Login Items list.

I have not worked out the adding in part just yet, but I'll post back when I have a moment with more on that, once I can run a test.

seansb
New Contributor III

Hi @mm2270 ,

Thanks. I should have edited my original post. I have no problem removing the entries via PListBuddy, the issue is adding items back. Every time i try to manipulate the loginitems plist, I try to log in and I get logged out again. It seems like I end up corrupting the loginitems (I tried rebooting and everything). I guess what I'm looking for is either an example of PListBuddy or defaults write for adding a login item for a specific user. I've been trying this but to no avail:

/usr/libexec/plistbuddy -c "Add :SessionItems:CustomListItems:dict" ~/Library/Preferences/com.apple.loginitems.plist

/usr/libexec/plistbuddy -c "Add :SessionItems:CustomListItems:Alias data $applicationLocation" ~/Library/Preferences/com.apple.loginitems.plist

/usr/libexec/plistbuddy -c "Add :SessionItems:CustomListItems:Name string $appName" ~/Library/Preferences/com.apple.loginitems.plist

jacob_salmela
Contributor II

Using a Python script, which is similar to another thread:

#!/usr/bin/python
import sys,pwd,os
import subprocess
from os import system

u = os.getlogin()
if ( u == '_atsserver') or ( u == 'root'):
    print "no login items for root."
else:
    print "Adding login item..."
    pw = pwd.getpwnam(u)
    os.initgroups(u,pw.pw_gid)
    env={"TERM":"xterm","USER":pw.pw_name,"HOME":pw.pw_dir,"SHELL":pw.pw_shell,"LOGNAME":pw.pw_name,"PATH":"/usr/bin:/bin:/opt/bin"};
    os.setgid(pw.pw_gid);
    os.setuid(pw.pw_uid);
    os.execve('/usr/bin/osascript',["", "-e", 'tell application "System Events" to delete login item "iTunes"'],env);

With the key line being:

os.execve('/usr/bin/osascript',["", "-e", 'tell application "System Events" to delete login item "iTunes"'],env);

Similarly, you can add items:

os.execve('/usr/bin/osascript',["", "-e", 'tell application "System Events" to make new login item at end with properties {path:"/Applications/iTunes.app", name:"iTunes.app", hidden:false}'],env);

mm2270
Legendary Contributor III

Yeah, unfortunately I've had no luck in creating a new entry via PlistBuddy myself. It can be done with the Applescript commands, but as I mentioned, there is no way to tell Applescript calls like that to operate on other user home directories.

Would it possibly work to have a script run on the logout trigger that runs the AS calls to add the item in? I don't know for sure if logout would work since I think the policy won't actually run until after the account is logged out. Anyone know for sure though? I can test it I guess. We don't use logout triggers with any of our policies.

mm2270
Legendary Contributor III

@jacob_salmela I believe the OP wanted to have a script that would add a login item into other user accounts on the Mac that aren't even logged in. As far as I know, osascript calls to "System Events" aren't going to work against anything but the logged in user.

jacob_salmela
Contributor II

I see that I read it wrong. I'm not sure of a way to do that for users that are not logged in...

seansb
New Contributor III

@mm2270 - correct, accounts that aren't logged in. What's bizarre to me is I believe AppleScript can manipulate it for all users or the currently logged in user, but you can't specify a specific user. I also can't seem to find a way to do it programmatically using either defaults write or PListBuddy. With PListBuddy it's easy enough to remove items, but apparently adding items in is a whole different ballgame. I usually just end up breaking the plist.

mm2270
Legendary Contributor III

It is indeed a whole different ballgame to add new entries in. Looking at the existing dictionary items in the array in defaults or PlistBuddy, those entries are pretty complex, and I have a feeling that unless the items are added in exactly the way the OS expects them to be, they aren't going to work.

Could you just have a login script run each time for a user account and check to see if that entry is in their plist, and add it in using the osascript code it its not? I can't think of any other good way to accomplish this.

Josh_Smith
Contributor III

@seansb I know you very specifically asked about Login Items....but could you consider scripting the removal of the login items and adding the new functionality with a LaunchAgent?

nessts
Valued Contributor II

this is how I added Universal Type Client to startup items a while back when it was not auto launching for some users

sudo su $user -c "defaults write loginwindow AutoLaunchedApplicationDictionary -array-add '<dict><key>Hide</key><true/><key>Path</key><string>/Library/PreferencePanes/utcore-prefpane.prefPane/Contents/Resources/UTCoreHelper.app</string></dict>'"

its not pretty but its doable.

eric_wood
New Contributor II

@mm2270

I've been trying to use the wonderful example code provided by @mm2270 to remove items from both the Global or User com.apple.loginitems.plist on various systems from El Capitan through High Sierra.

I picked a low hanging fruit like iTunesHelper to remove from a local user. I used the provided script with only the name of the item changed. I also added a trap for debugging and for learning purposes. The item that I wish to remove from the user's Login Items remains. Anything obvious, or has there been a change in El Capitan, Sierra, and High Sierra, that makes this script need tweaking? I am very novice in my ability to create complex scripts from scratch.

#!/bin/bash

trap "set +x; sleep 3; set -x" DEBUG

## Get the logged in user
loggedInUser=$(ls -l /dev/console | awk '{print $3}')

## Pull a list of login item names for the user
LoginItemNames=$(/usr/libexec/PlistBuddy -c "Print :SessionItems:CustomListItems" /Users/${loggedInUser}/Library/Preferences/com.apple.loginitems.plist | awk -F'= ' '/Name/{print $NF}')

## Put them into an array to loop over
while read loginitem; do
    LoginItemArray+=("$loginitem")
done < <(printf '%s
' "$LoginItemNames")

## Loop over them, locate the item to delete
while read item; do
    if [[ "$item" == "iTunesHelper" ]]; then
        index=${i}
        break
    fi
    let i+=1
done < <(printf '%s
' "${LoginItemArray[@]}")

## Delete the item if found
if [ ! -z "$index" ]; then
    echo "Found item at index $index"
    /usr/libexec/PlistBuddy -c "Delete :SessionItems:CustomListItems:$index" /Users/${loggedInUser}/Library/Preferences/com.apple.loginitems.plist
else
    echo "Item not found in Login Items"
fi

Here is the output of the debug trap.

bash-3.2$ sudo ./Remove_Login_Item_User.sh 
Password:
++ ls -l /dev/console
++ awk '{print $3}'
+ loggedInUser=teacher
++ set +x
++ /usr/libexec/PlistBuddy -c 'Print :SessionItems:CustomListItems' /Users/teacher/Library/Preferences/com.apple.loginitems.plist
++ awk '-F= ' '/Name/{print $NF}'
+ LoginItemNames=iTunesHelper
++ set +x
++ printf '%s
' iTunesHelper
+ read loginitem
++ set +x
+ LoginItemArray+=("$loginitem")
++ set +x
+ read loginitem
++ set +x
++ printf '%s
' iTunesHelper
+ read item
++ set +x
+ [[ iTunesHelper == iTu
esHelpe
 ]]
++ set +x
+ index=
++ set +x
+ break
++ set +x
+ '[' '!' -z '' ']'
++ set +x
+ echo 'Item not found in Login Items'
Item not found in Login Items
bash-3.2$

Here is the contents of the logged in teacher user's com.apple.loginitems.plist

bash-3.2$ sudo defaults read /Users/teacher/Library/Preferences/com.apple.loginitems.plist
{
    SessionItems =     {
        Controller = CustomListItems;
        CustomListItems =         (
                        {
                Alias = <00000000 00d80003 00010000 d374c952 0000482b 00000000 00b6847f 00b68482 0000d764 e01c0000 00000920 fffe0000 00000000 0000ffff ffff0001 001000b6 847f00b6 846900b6 84680000 0047000e 00220010 00690054 0075006e 00650073 00480065 006c0070 00650072 002e0061 00700070 000f001a 000c004d 00610063 0069006e 0074006f 00730068 00200048 00440012 00374170 706c6963 6174696f 6e732f69 54756e65 732e6170 702f436f 6e74656e 74732f4d 61634f53 2f695475 6e657348 656c7065 722e6170 70000013 00012f00 ffff0000>;
                CustomItemProperties =                 {
                    "com.apple.LSSharedFileList.Binding" = <646e6962 00000000 02000000 00000000 00000000 00000000 00000000 40000000 00000000 66696c65 3a2f2f2f 4170706c 69636174 696f6e73 2f695475 6e65732e 6170702f 436f6e74 656e7473 2f4d6163 4f532f69 54756e65 7348656c 7065722e 6170702f 16000000 00000000 636f6d2e 6170706c 652e6954 756e6573 48656c70 65720100 80000030 00008e40 10100200 0000ae1b 131d>;
                    "com.apple.LSSharedFileList.ItemIsHidden" = 1;
                };
                Flags = 1;
                Name = iTunesHelper;
            }
        );
    };
}
bash-3.2$

If I manually open the user's com.apple.loginitems.plist and run the commands against the plist I do receive the desired removal, of course I can't guarantee that index 0 will be the correct index hence my usage of @mm2270 's code.

bash-3.2$ sudo /usr/libexec/PlistBuddy /Users/teacher/Library/Preferences/com.apple.loginitems.plist
Command: Print
Dict {
    SessionItems = Dict {
        Controller = CustomListItems
        CustomListItems = Array {
            Dict {
                CustomItemProperties = Dict {
                    com.apple.LSSharedFileList.Binding = dnib@file:///Applications/iTunes.app/Contents/MacOS/iTunesHelper.app/com.apple.iTunesHelper?0?@?
                   com.apple.LSSharedFileList.ItemIsHidden = true
                }
                Name = iTunesHelper
                Flags = 1
                Alias = ??t?RH+??????d?  ??????????i??hG"iTunesHelper.app
                                                                         Macintosh HD7Applications/iTunes.app/Contents/MacOS/iTunesHelper.app/??
            }
        }
    }
}
Command: Delete :SessionItems:CustomListItems:0
Command: Print
Dict {
    SessionItems = Dict {
        Controller = CustomListItems
        CustomListItems = Array {
        }
    }
}
Command: exit
bash-3.2$

Does anyone see the error that I can't see?

The fact that I see index= rather than index=0 during the debug output makes me believe the looping through the array and counting the items to find the index number of an item is not working, but I don't know how to fix it.

mm2270
Legendary Contributor III

Hi @eric.wood Yes, you actually discovered the issue with my original script, which I somehow never noticed. I'm guessing you might be the first person that actually used it!
I hadn't even read through your whole post as I was looking at my script, and I saw that I left out a crucial part. The index isn't being correctly assigned, because I never told it what $i should start at. See, plist arrays always start at "0". Bash arrays typically always start at that as well, but in this case the i wasn't defined to start at 0, so although it was locating the item you searched for, it wasn't using the correct index number when attempting to remove it. Or possibly it was never assigning anything to i as you saw in your script output.

The fix is pretty simple. In the first loop, just add this to the very top, before the while read loginitem; do line:

i=0

That way, when it begins the loop, it already knows that is should start at 0 and will correctly iterate as it goes, finding the correct index.

Try that and see if it helps. I made that change to the script and tested it on something in my login items plist and it worked.

eric_wood
New Contributor II

Thank you @mm2270. That did it. Everything works as expected. Thank you very much.

I had tried unset i, unset index, and index=0 but I am ashamed to say I never tried to define i=0.

#!/bin/bash

## Get the logged in user
loggedInUser=$(ls -l /dev/console | awk '{print $3}')

## Pull a list of login item names for the user
LoginItemNames=$(/usr/libexec/PlistBuddy -c "Print :SessionItems:CustomListItems" /Users/${loggedInUser}/Library/Preferences/com.apple.loginitems.plist | awk -F'= ' '/Name/{print $NF}')

## Put them into an array to loop over
i=0
while read loginitem; do
    LoginItemArray+=("$loginitem")
done < <(printf '%s
' "$LoginItemNames")

## Loop over them, locate the item to delete
while read item; do
    if [[ "$item" == "LanSchool Teacher" ]]; then
        index=${i}
        break
    fi
    let i+=1
done < <(printf '%s
' "${LoginItemArray[@]}")

## Delete the item if found
if [ ! -z "$index" ]; then
    echo "Found item at index $index"
    /usr/libexec/PlistBuddy -c "Delete :SessionItems:CustomListItems:$index" /Users/${loggedInUser}/Library/Preferences/com.apple.loginitems.plist
else
    echo "Item not found in Login Items"
fi

Here, for someone in the future, is my inexpertly edited version that scans the Global Login Items Plist and removes an item as long as you know it won't exist more than three times. You can adjust by editing j<3 to the loop count you need. I needed this behavior as LanSchool's Teacher Console installer places it in the Global Login Items plist twice and the local user's Login Items plist once. I have yet to combine the User and Global version into a script that will do them all, but when I do I will share in this discussion.

#!/bin/bash

## Loop command three times
for ((j=0;j<3;j++))
do
unset LoginItemNames
unset LoginItemArray
unset loginitem
unset item
unset index
unset i

## Pull a list of Global login items
LoginItemNames=$(/usr/libexec/PlistBuddy -c "Print :privilegedlist:CustomListItems" /Library/Preferences/com.apple.loginitems.plist | awk -F'= ' '/Name/{print $NF}')

## Put them into an array to loop over
i=0
while read loginitem; do
    LoginItemArray+=("$loginitem")
done < <(printf '%s
' "$LoginItemNames")

## Loop over them, locate the item to delete
while read item; do
    if [[ "$item" == "LanSchool Teacher" ]]; then
        index=${i}
        break
    fi
    let i+=1
done < <(printf '%s
' "${LoginItemArray[@]}")

## Delete the item if found
if [ ! -z "$index" ]; then
    echo "Found item at index $index"
    /usr/libexec/PlistBuddy -c "Delete :privilegedlist:CustomListItems:$index" /Library/Preferences/com.apple.loginitems.plist
else
    echo "Item not found in Login Items"
fi

done

marklamont
Contributor III

I'm guessing you're working on older OS versions here as in 10.13 the loginItems.plist has gone and been replaced by

~/Library/Application Support/com.apple.backgroundtaskmanagementagent/backgrounditems.btm

which is, unless someone here has figured it out, pretty much uneditable by a script.

applebit924
New Contributor

If you base64 -D the NS.data you get the application and path, however I only spent about 5 seconds on it so that looks like garbage to the untrained eye and not something compatible with any type of script at the moment.

G_M__webkfoe_
New Contributor III

Did anyone find a way to achieve that on Catalina..?

shaquir
Contributor III

Hi @webkfoe,

I put together this script for my environment:

#!/bin/bash
#Script to remove Application from macOS Login Items list
###Shaquir Tannis on 7/5/20
#github.com/shaquir/

#App to remove from User's login items
appToRemove="Skype for Business"

#Determine current log in user
currentUser=$(ls -l /dev/console | awk '{print $3}')

#Stop running if there is no user
if [ $currentUser = "root" ] || [ $currentUser = "CompanyAdmin" ] || [ $currentUser = "_mbsetupuser" ] || [ $currentUser = "" ]; then
    echo "ERROR: No User Logged in, aborting"
    exit 1
fi

#Grab User ID
uid=$(id -u "$currentUser")

#List login items - Unformatted
rawLoginItems=$( sudo launchctl asuser $uid /usr/bin/osascript <<-EOD
tell application "System Events" to get the name of every login item
    EOD
)

#Format rawLoginItems list - Remove space after the comma
loginItems=$(echo $rawLoginItems | sed -e 's/, /,/g')

IFS=","

#Initial check to determine if array contains appToRemove
if [[ " ${loginItems[*]} " == *"$appToRemove"* ]]; then

    #Loop through to delete appToRemove where found (Takes care of multiple instances of the same app)
    for item in $loginItems
        do
            if [ "$item" == "$appToRemove" ]; then
                echo "$appToRemove was found in "$currentUser"'s login items"

                #Remove $appToRemove from login items
                sudo launchctl asuser $uid /usr/bin/osascript <<-EOD
                    tell application "System Events" to delete login item "$appToRemove"
                    EOD

                echo "$appToRemove has been removed."
            fi
    done
else
    echo "$appToRemove was NOT found in "$currentUser"'s login items."
fi

#List updated login items
echo "Updated login items:"
sudo launchctl asuser $uid /usr/bin/osascript <<-EOD
tell application "System Events" to get the name of every login item
    EOD

I paired it with a EA:

#!/bin/bash
#Extension Attribute to report on what login items current user has
#Shaquir Tannis 7-5-20

#Determine current log in user
currentUser=$(ls -l /dev/console | awk '{print $3}')

#Stop running if there is no user
if [ $currentUser = "root" ] || [ $currentUser = "CompanyAdmin" ] || [ $currentUser = "_mbsetupuser" ] || [ $currentUser = "" ]; then
    #echo "ERROR: No User Logged in, aborting"
    exit 1
fi

uid=$(id -u "$currentUser")

#List login items - Unformatted
rawLoginItems=$( sudo launchctl asuser $uid /usr/bin/osascript <<-EOD
tell application "System Events" to get the name of every login item
    EOD
)

#Format rawLoginItems list - Remove space after the comma
#loginItems=$(echo $rawLoginItems | sed -e 's/, /,/g')

if [ -z "$rawLoginItems" ]; then
    echo "<result>None</result>"
    else
    echo "<result>$rawLoginItems</result>"
fi

exit 0

Be sure to Whitelist Jamf's Applescript use.