Posted on 01-31-2024 09:13 AM
In the computer lab where I work, we use a generic student user account for all students on our Mac computers, and for privacy reasons, all web browsers are set to run in private "incognito" mode. Recently, with the introduction of the new Microsoft Teams (Work or School) app, I observed a couple of issues related to user logins.
First, users could automatically be logged in by simply clicking their names on the splash screen when Teams loaded. Second, sometimes a Teams user would remain logged in even when the macOS user had logged out. To address these issues, I conducted some research and found that Microsoft's recommended solution is to delete Teams login information from the keychain. However, they do not specify which keychain entries need to be removed.
After spending some time investigating, I identified the keychain entries related to Teams. It's worth noting that some of these entries are stored in the "Login" keychain, while others are stored in the "Local Items" keychain. The "Login" keychain can be targeted using the security command, but the "Local Items" keychain cannot. To solve this problem, I developed a script with the following logic:
This script seems to work on the computers I have tested. I suppose my question would be if there is a reason not to delete the local items keychain? The system remakes it and I have not noticed any issues. Any suggestions to make it better are welcome.
#!/bin/zsh
# Readability styling
line=$(echo ${(l:75::-:)''})
stars=$(echo ${(l:75::*:)''})
# Check if the current user is root
loggedInUser=$(stat -f%Su /dev/console)
if [[ "$loggedInUser" == "root" ]]; then
echo "The logged-in user is root."
else
echo "The logged-in user is not root. Exiting."
exit 0
fi
# Get student user.
student=$(dscl /Local/Default -list /Users UniqueID | awk '$2 == 504 { print $1 }')
# Path to the log file where the script's last run time is stored
logFilePath="/Users/${student}/Library/Logs/TeamsCheck.log"
#echo "1970-01-01 00:00:00" >"$logFilePath"
# Path to the application (Microsoft Teams in this case)
appPath="/Applications/Microsoft Teams (work or school).app"
# Function to get the last opened time of an application
get_last_opened_time() {
teams_run_time=$(mdls -name kMDItemLastUsedDate "$appPath" | awk -F'= ' '{print $2}' | sed 's/ \+0000//')
}
# Function to update the script's last run timestamp in the log file
update_script_last_run_time() {
date -u '+%Y-%m-%d %H:%M:%S' >"$logFilePath"
}
# Function to get the script's last run time from the log file
get_script_last_run_time() {
if [[ -f "$logFilePath" ]]; then
last_run_time=$(cat "$logFilePath")
else
# Default to the Unix epoch if the log file doesn't exist
touch "$logFilePath"
echo "1970-01-01 00:00:00" >"$logFilePath"
fi
}
get_script_last_run_time
get_last_opened_time
echo "This script was run: $last_run_time"
echo "Teams was opened: $teams_run_time"
# Function to split date and time and compare
compare_date_time() {
local dateTime1=$1
local dateTime2=$2
# Split the date and time into parts
local y1 m1 d1 H1 M1 S1 y2 m2 d2 H2 M2 S2
IFS=' :-' read y1 m1 d1 H1 M1 S1 <<<"$dateTime1"
IFS=' :-' read y2 m2 d2 H2 M2 S2 <<<"$dateTime2"
# Compare each part
if [[ "$y1" -gt "$y2" ]]; then
echo "$dateTime1 is more recent."
elif [[ "$y1" -lt "$y2" ]]; then
echo "$dateTime2 is more recent."
elif [[ "$m1" -gt "$m2" ]]; then
echo "$dateTime1 is more recent."
elif [[ "$m1" -lt "$m2" ]]; then
echo "$dateTime2 is more recent."
elif [[ "$d1" -gt "$d2" ]]; then
echo "$dateTime1 is more recent."
elif [[ "$d1" -lt "$d2" ]]; then
echo "$dateTime2 is more recent."
elif [[ "$H1" -gt "$H2" ]]; then
echo "$dateTime1 is more recent."
elif [[ "$H1" -lt "$H2" ]]; then
echo "$dateTime2 is more recent."
elif [[ "$M1" -gt "$M2" ]]; then
echo "$dateTime1 is more recent."
elif [[ "$M1" -lt "$M2" ]]; then
echo "$dateTime2 is more recent."
elif [[ "$S1" -gt "$S2" ]]; then
echo "$dateTime1 is more recent."
elif [[ "$S1" -lt "$S2" ]]; then
echo "$dateTime2 is more recent."
else
echo "Both dates are the same."
fi
}
# Function to compare date and time and decide which script to run
compare_and_run() {
compare_result=$(compare_date_time "$last_run_time" "$teams_run_time")
echo $compare_result
if [[ $compare_result == *"$teams_run_time is more recent."* ]]; then
echo "Running the Teams cleanup script because Teams was opened since last cleanup."
# Check if the current user is root
if [[ "$loggedInUser" == "root" ]]; then
echo "The logged-in user is root."
# Kill Teams
/usr/bin/pkill -9 'Microsoft Teams'
/usr/bin/pkill -9 'Microsoft Teams Helper'
update_script_last_run_time
icloudKeychainCheck=$(ls /Users/${student}/Library/Keychains | grep ........-....-....-....-............)
if [[ $icloudKeychainCheck != "" ]]; then
localItemsKeychain=/Users/${student}/Library/Keychains/$icloudKeychainCheck
echo "$localItemsKeychain"
echo "Removing Local Items Keychain."
rm -rf /Users/${student}/Library/Keychains/$icloudKeychainCheck
# Define the target directory
#target_directory="/Users/${student}/Library/Keychains"
# Check if the directory exists
#if [[ -d "$target_directory" ]]; then
# Find and safely remove files matching the pattern
#find "$target_directory" -name 'metadata.keychain-db*' -exec rm -f {} +
#else
#echo "Target directory does not exist."
#fi
fi
# Delete Login Keychain Microsoft Teams items.
echo "Removing Teams passwords from Login Keychain."
loginKeychain=/Users/${student}/Library/Keychains/login.keychain-db
passwords=("Microsoft Teams (work or school)" "Microsoft Teams Identities Cache" "teams" "Teams" "https://teams.microsoft.com" "adalcache")
for password in "${passwords[@]}"; do
while security find-generic-password -a "$password" "$loginKeychain"; do
echo "$stars"
echo "Deleting $password entry from keychain..."
echo "$line"
security delete-generic-password -a "$password" "$loginKeychain"
done
done
# Loop to delete the password until it no longer exists
while security find-generic-password -s OneAuthAccount "$loginKeychain" &>/dev/null; do
echo "$stars"
echo "Deleting OneAuthAccount entry from keychain..."
echo "$line"
security delete-generic-password -s OneAuthAccount "$loginKeychain"
done
echo "All OneAuthAccount entries have been removed."
else
echo "The logged-in user is not root. Exiting."
exit 0
fi
else
echo "Teams has not been opened since last check."
fi
}
# Call the compare and run function
compare_and_run
Solved! Go to Solution.
Posted on 03-08-2024 12:01 PM
Here is the final script, which works well for out lab. It runs on recurring check-in.
#!/bin/zsh
# Readability styling
line=$(printf '%.0s-' {1..75})
stars=$(printf '%.0s*' {1..75})
# Check if the current user is root
loggedInUser=$(stat -f%Su /dev/console)
if [[ "$loggedInUser" == "root" ]]; then
echo "The logged-in user is root."
else
echo "The logged-in user is not root. Exiting."
exit 0
fi
# Get student user.
student=$(dscl /Local/Default -list /Users UniqueID | awk '$2 == 504 { print $1 }')
# Path to the log file where the script's last run time is stored
logFilePath="/Users/${student}/Library/Logs/TeamsCheck.log"
# Path to the application (Microsoft Teams in this case)
appPath="/Applications/Microsoft Teams (work or school).app"
# Function to get the last opened time of an application
get_last_opened_time() {
teams_run_time=$(mdls -name kMDItemLastUsedDate "$appPath" | awk -F'= ' '{print $2}' | sed 's/ \+0000//')
}
# Function to update the script's last run timestamp in the log file
update_script_last_run_time() {
date -u '+%Y-%m-%d %H:%M:%S' >"$logFilePath"
}
# Function to get the script's last run time from the log file
get_script_last_run_time() {
if [[ -f "$logFilePath" ]]; then
last_run_time=$(cat "$logFilePath")
else
touch "$logFilePath"
echo "1970-01-01 00:00:00" >"$logFilePath"
fi
}
get_script_last_run_time
get_last_opened_time
echo "This script was last run: $last_run_time"
echo "Teams was last opened: $teams_run_time"
# Function to split date and time and compare
compare_date_time() {
local dateTime1=$1
local dateTime2=$2
# Split the date and time into parts
local y1 m1 d1 H1 M1 S1 y2 m2 d2 H2 M2 S2
IFS=' :-' read y1 m1 d1 H1 M1 S1 <<<"$dateTime1"
IFS=' :-' read y2 m2 d2 H2 M2 S2 <<<"$dateTime2"
# Compare each part
if [[ "$y1" -gt "$y2" ]]; then
echo "$dateTime1 is more recent."
elif [[ "$y1" -lt "$y2" ]]; then
echo "$dateTime2 is more recent."
elif [[ "$m1" -gt "$m2" ]]; then
echo "$dateTime1 is more recent."
elif [[ "$m1" -lt "$m2" ]]; then
echo "$dateTime2 is more recent."
elif [[ "$d1" -gt "$d2" ]]; then
echo "$dateTime1 is more recent."
elif [[ "$d1" -lt "$d2" ]]; then
echo "$dateTime2 is more recent."
elif [[ "$H1" -gt "$H2" ]]; then
echo "$dateTime1 is more recent."
elif [[ "$H1" -lt "$H2" ]]; then
echo "$dateTime2 is more recent."
elif [[ "$M1" -gt "$M2" ]]; then
echo "$dateTime1 is more recent."
elif [[ "$M1" -lt "$M2" ]]; then
echo "$dateTime2 is more recent."
elif [[ "$S1" -gt "$S2" ]]; then
echo "$dateTime1 is more recent."
elif [[ "$S1" -lt "$S2" ]]; then
echo "$dateTime2 is more recent."
else
echo "Both dates are the same."
fi
}
# Function to compare date and time and decide which script to run
compare_and_run() {
compare_result=$(compare_date_time "$last_run_time" "$teams_run_time")
echo $compare_result
if [[ $compare_result == *"$teams_run_time is more recent."* ]]; then
echo "Running the Teams cleanup script because Teams was opened since last cleanup."
# Kill Teams
/usr/bin/pkill -9 'Microsoft Teams'
/usr/bin/pkill -9 'Microsoft Teams Helper'
update_script_last_run_time
# Capture and remove iCloud Keychain items
icloudKeychainChecks=($(ls /Users/${student}/Library/Keychains | grep -E '([[:xdigit:]]{8}-([[:xdigit:]]{4}-){3}[[:xdigit:]]{12}|metadata.keychain-db)'))
if [[ ${#icloudKeychainChecks[@]} -gt 0 ]]; then
echo "Keychain items to be removed:"
for icloudKeychainCheck in "${icloudKeychainChecks[@]}"; do
localItemsKeychain="/Users/${student}/Library/Keychains/${icloudKeychainCheck}"
echo "$localItemsKeychain"
rm -rf "$localItemsKeychain"
done
echo "Removed Local Items Keychain."
else
echo "No iCloud Keychain items found to remove."
fi
# Delete Login Keychain Microsoft Teams items.
echo "Removing Teams passwords from Login Keychain."
loginKeychain=/Users/${student}/Library/Keychains/login.keychain-db
passwords=("Microsoft Teams (work or school)" "Microsoft Teams Identities Cache" "teams" "Teams" "https://teams.microsoft.com" "adalcache")
for password in "${passwords[@]}"; do
while security find-generic-password -a "$password" "$loginKeychain"; do
echo "$stars"
echo "Deleting $password entry from keychain..."
echo "$line"
security delete-generic-password -a "$password" "$loginKeychain"
done
done
# Loop to delete the password until it no longer exists
while security find-generic-password -s OneAuthAccount "$loginKeychain" &>/dev/null; do
echo "$stars"
echo "Deleting OneAuthAccount entry from keychain..."
echo "$line"
security delete-generic-password -s OneAuthAccount "$loginKeychain"
done
echo "All OneAuthAccount entries have been removed."
else
echo "Teams has not been opened since last check."
fi
}
# Call the compare and run function
compare_and_run
Posted on 01-31-2024 10:32 AM
maybe reading this wrong... but why not use a guest account? no password and everything is nuked on logout?
Posted on 01-31-2024 10:43 AM
Thanks for your comment. You mean a guest account for Teams? Our students need access to their particular Teams account for classes and documents etc. Or do you mean for MacOS? I don't think that would be viable with the way we have things set up. We have Adobe account and other accounts that need to be logged in for software to work. I honestly don't know much about guest accounts. We may switch to using Office 365 accounts for our MacOS accounts this summer via Jamf Connect which will solve the privacy issues we are concerned about.
Posted on 01-31-2024 10:54 AM
with a guest Mac account. once logged into the guest account they can then log into any apps as required.. Teams, Adobe etc.. just when you log out, the account and all the data in that session is deleted.
Posted on 01-31-2024 11:00 AM
Thanks for the info. I will keep that in mind as a possible solution next time I template the lab.
Posted on 03-08-2024 12:01 PM
Here is the final script, which works well for out lab. It runs on recurring check-in.
#!/bin/zsh
# Readability styling
line=$(printf '%.0s-' {1..75})
stars=$(printf '%.0s*' {1..75})
# Check if the current user is root
loggedInUser=$(stat -f%Su /dev/console)
if [[ "$loggedInUser" == "root" ]]; then
echo "The logged-in user is root."
else
echo "The logged-in user is not root. Exiting."
exit 0
fi
# Get student user.
student=$(dscl /Local/Default -list /Users UniqueID | awk '$2 == 504 { print $1 }')
# Path to the log file where the script's last run time is stored
logFilePath="/Users/${student}/Library/Logs/TeamsCheck.log"
# Path to the application (Microsoft Teams in this case)
appPath="/Applications/Microsoft Teams (work or school).app"
# Function to get the last opened time of an application
get_last_opened_time() {
teams_run_time=$(mdls -name kMDItemLastUsedDate "$appPath" | awk -F'= ' '{print $2}' | sed 's/ \+0000//')
}
# Function to update the script's last run timestamp in the log file
update_script_last_run_time() {
date -u '+%Y-%m-%d %H:%M:%S' >"$logFilePath"
}
# Function to get the script's last run time from the log file
get_script_last_run_time() {
if [[ -f "$logFilePath" ]]; then
last_run_time=$(cat "$logFilePath")
else
touch "$logFilePath"
echo "1970-01-01 00:00:00" >"$logFilePath"
fi
}
get_script_last_run_time
get_last_opened_time
echo "This script was last run: $last_run_time"
echo "Teams was last opened: $teams_run_time"
# Function to split date and time and compare
compare_date_time() {
local dateTime1=$1
local dateTime2=$2
# Split the date and time into parts
local y1 m1 d1 H1 M1 S1 y2 m2 d2 H2 M2 S2
IFS=' :-' read y1 m1 d1 H1 M1 S1 <<<"$dateTime1"
IFS=' :-' read y2 m2 d2 H2 M2 S2 <<<"$dateTime2"
# Compare each part
if [[ "$y1" -gt "$y2" ]]; then
echo "$dateTime1 is more recent."
elif [[ "$y1" -lt "$y2" ]]; then
echo "$dateTime2 is more recent."
elif [[ "$m1" -gt "$m2" ]]; then
echo "$dateTime1 is more recent."
elif [[ "$m1" -lt "$m2" ]]; then
echo "$dateTime2 is more recent."
elif [[ "$d1" -gt "$d2" ]]; then
echo "$dateTime1 is more recent."
elif [[ "$d1" -lt "$d2" ]]; then
echo "$dateTime2 is more recent."
elif [[ "$H1" -gt "$H2" ]]; then
echo "$dateTime1 is more recent."
elif [[ "$H1" -lt "$H2" ]]; then
echo "$dateTime2 is more recent."
elif [[ "$M1" -gt "$M2" ]]; then
echo "$dateTime1 is more recent."
elif [[ "$M1" -lt "$M2" ]]; then
echo "$dateTime2 is more recent."
elif [[ "$S1" -gt "$S2" ]]; then
echo "$dateTime1 is more recent."
elif [[ "$S1" -lt "$S2" ]]; then
echo "$dateTime2 is more recent."
else
echo "Both dates are the same."
fi
}
# Function to compare date and time and decide which script to run
compare_and_run() {
compare_result=$(compare_date_time "$last_run_time" "$teams_run_time")
echo $compare_result
if [[ $compare_result == *"$teams_run_time is more recent."* ]]; then
echo "Running the Teams cleanup script because Teams was opened since last cleanup."
# Kill Teams
/usr/bin/pkill -9 'Microsoft Teams'
/usr/bin/pkill -9 'Microsoft Teams Helper'
update_script_last_run_time
# Capture and remove iCloud Keychain items
icloudKeychainChecks=($(ls /Users/${student}/Library/Keychains | grep -E '([[:xdigit:]]{8}-([[:xdigit:]]{4}-){3}[[:xdigit:]]{12}|metadata.keychain-db)'))
if [[ ${#icloudKeychainChecks[@]} -gt 0 ]]; then
echo "Keychain items to be removed:"
for icloudKeychainCheck in "${icloudKeychainChecks[@]}"; do
localItemsKeychain="/Users/${student}/Library/Keychains/${icloudKeychainCheck}"
echo "$localItemsKeychain"
rm -rf "$localItemsKeychain"
done
echo "Removed Local Items Keychain."
else
echo "No iCloud Keychain items found to remove."
fi
# Delete Login Keychain Microsoft Teams items.
echo "Removing Teams passwords from Login Keychain."
loginKeychain=/Users/${student}/Library/Keychains/login.keychain-db
passwords=("Microsoft Teams (work or school)" "Microsoft Teams Identities Cache" "teams" "Teams" "https://teams.microsoft.com" "adalcache")
for password in "${passwords[@]}"; do
while security find-generic-password -a "$password" "$loginKeychain"; do
echo "$stars"
echo "Deleting $password entry from keychain..."
echo "$line"
security delete-generic-password -a "$password" "$loginKeychain"
done
done
# Loop to delete the password until it no longer exists
while security find-generic-password -s OneAuthAccount "$loginKeychain" &>/dev/null; do
echo "$stars"
echo "Deleting OneAuthAccount entry from keychain..."
echo "$line"
security delete-generic-password -s OneAuthAccount "$loginKeychain"
done
echo "All OneAuthAccount entries have been removed."
else
echo "Teams has not been opened since last check."
fi
}
# Call the compare and run function
compare_and_run