Microsoft Teams (Work or School) Lab use

CooperUser
New Contributor II

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: 

  1. The script first checks whether the logged-in user is root, indicating that the Mac is at the login screen. 
  1. It then verifies whether Teams has been opened since the last time the script ran. If Teams hasn't been opened, the script exits. If it has been opened, the script proceeds to clean up the keychain entries. 
  1. To clear the "Local Items" entries, the script deletes the entire keychain since the "Local Items" keychain cannot be targeted specifically. This deletion has not caused any issues in our lab and does not affect Adobe Creative Cloud. I got some of this code from Jamf Nation user nkalister. 
  1. To clear the "Login" keychain entries, the script utilizes the security command and targets specific accounts and entries, including "Microsoft Teams (work or school)," "Microsoft Teams Identities Cache," "teams," "Teams," "https://teams.microsoft.com," and "adalcache." 

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

 

 

1 ACCEPTED SOLUTION

CooperUser
New Contributor II

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

View solution in original post

5 REPLIES 5

jamf-42
Valued Contributor II

maybe reading this wrong... but why not use a guest account? no password and everything is nuked on logout? 

macos-guest-account

CooperUser
New Contributor II

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.

jamf-42
Valued Contributor II

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.

 

CooperUser
New Contributor II

Thanks for the info. I will keep that in mind as a possible solution next time I template the lab. 

CooperUser
New Contributor II

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