This Bash script performs a controlled migration and cleanup process...

September 2, 2025 at 11:20 PM

/bin/bash -c # This script is loaded into system domain of lauhchd during postinstall script # and runs as root. Be very careful what you do here as potential for damage is # huge. The script listens for commands on a named pipe and runs migration # when it receives "run" command. DESIRED_BUNDLE_PATH="/Applications/Microsoft Teams.app" PREVIEW_BUNDLE_PATH="/Applications/Microsoft Teams (work preview).app" WORK_OR_SCHOOL_BUNDLE_PATH="/Applications/Microsoft Teams (work or school).app" MIGRATION_TOOL_PATH="$DESIRED_BUNDLE_PATH/Contents/Helpers/com.microsoft.teams2.migrationtool" CONTROL_PIPE="/private/var/run/com.microsoft.teams2.migrationtool.ctl" CONTROL_UNINSTALL_PIPE="/private/var/run/com.microsoft.teams2.migrationtool.t1removal.ctl" CMD_RUN="run" CMD_REMOVET1="removet1" USER_NAME=$(/usr/bin/defaults read /Library/Preferences/com.apple.loginwindow lastUserName) TEAMS_CLASSIC_BUNDLE_ID="com.microsoft.teams" TEAMS_CLASSIC_PATH="/Applications/Microsoft Teams classic.app" log() { echo "$(date +"%Y-%m-%dT%H:%M:%S%z") [$$] $1" } _AS_USER=$(/usr/bin/defaults read /Library/Preferences/com.apple.loginwindow lastUserName) log "UI user: $_AS_USER" run_as_ui_user() { /usr/bin/sudo -u "$_AS_USER" "$@" } stop_mau() { log "Killing MAU processes" /usr/bin/pkill -9 -x "Microsoft AutoUpdate" /usr/bin/pkill -9 -x "Microsoft Update Assistant" /usr/bin/pkill -9 -x "com.microsoft.autoupdate.helper" } migrate_bundle() { if [ ! -d "$1" ]; then log "$1 not found" return fi if [ -d "$DESIRED_BUNDLE_PATH" ]; then if /bin/rm -rf "$1"; then log "Removed $1" else log "Failed to remove $1" fi else if /bin/mv "$1" "$DESIRED_BUNDLE_PATH"; then log "Renamed $1 to $DESIRED_BUNDLE_PATH" else log "Failed to rename $1 to $DESIRED_BUNDLE_PATH" fi fi } remove_file() { if [ ! -d "$1" ]; then log "$1 not found" return fi if [ -d "$1" ]; then if /usr/bin/sudo /bin/rm -rf "$1"; then log "Removed $1" else log "Failed to remove $1" fi fi } remove_classic_teams_traces() { TEAMS_CLASSIC_APP=$1 TEAMS_CLASSIC_CACHE="/Users/$USER_NAME/Library/Application Support/Microsoft/Teams" T1_TEAMS_UPDATER_DAEMON="$TEAMS_CLASSIC_APP/Contents/TeamsUpdaterDaemon.xpc/Contents/MacOS/TeamsUpdaterDaemon" T1_PROCESS_COUNT=$(ps aux | grep -v grep | grep -ci "$TEAMS_CLASSIC_APP") T1_UPDATER_DAEMON_COUNT=$(ps aux | grep -v grep | grep -ci "$T1_TEAMS_UPDATER_DAEMON") if [ "$T1_PROCESS_COUNT" -eq "0" ] || ([ "$T1_PROCESS_COUNT" -eq "1" ] && [ "$T1_UPDATER_DAEMON_COUNT" -eq "1" ]); then remove_file "$TEAMS_CLASSIC_APP" remove_file "$TEAMS_CLASSIC_CACHE" fi } uninstall_classic_teams() { if [ -d "$TEAMS_CLASSIC_PATH" ]; then T1_BUNDLE_ID=$(/usr/bin/defaults read "$TEAMS_CLASSIC_PATH/Contents/Info" CFBundleIdentifier) if [ "$T1_BUNDLE_ID" == "$TEAMS_CLASSIC_BUNDLE_ID" ]; then remove_classic_teams_traces "$TEAMS_CLASSIC_PATH" fi else TEAMS_CLASSIC_PATH=$(mdfind "kMDItemCFBundleIdentifier == '$TEAMS_CLASSIC_BUNDLE_ID'" -onlyin /Applications) TEAMS_CLASSIC_APP_COUNT=$(mdfind "kMDItemCFBundleIdentifier == '$TEAMS_CLASSIC_BUNDLE_ID'" -onlyin /Applications -count) if [ ! -z "$TEAMS_CLASSIC_PATH" ] && [ "$TEAMS_CLASSIC_APP_COUNT" == "1" ]; then remove_classic_teams_traces "$TEAMS_CLASSIC_PATH" else log "Classic Teams not found" fi fi } check_app_exists() { APP_BUNDLE_ID=$1 APP_PATH=$(mdfind "kMDItemCFBundleIdentifier == '$APP_BUNDLE_ID'" -onlyin /Applications) APP_COUNT=$(mdfind "kMDItemCFBundleIdentifier == '$APP_BUNDLE_ID'" -onlyin /Applications -count) if [ ! -z "$APP_PATH" ] && [ "$APP_COUNT" == "1" ]; then return 0 else return 1 fi } run_migration() { log "Runnig migration" if [ -d "$PREVIEW_BUNDLE_PATH" ] && [ -d "$WORK_OR_SCHOOL_BUNDLE_PATH" ]; then log "There are both $PREVIEW_BUNDLE_PATH and $WORK_OR_SCHOOL_BUNDLE_PATH, not sure which one to rename" return fi migrate_bundle "$PREVIEW_BUNDLE_PATH" migrate_bundle "$WORK_OR_SCHOOL_BUNDLE_PATH" } fix_dock_icon() { if [ -x "$MIGRATION_TOOL_PATH" ]; then log "Running migration tool to fix dock icon" run_as_ui_user "$MIGRATION_TOOL_PATH" --fix-dock-icon else log "Migration tool not found at $MIGRATION_TOOL_PATH" fi } # should be in-sync with the code in src/mac/scripts/installer/postinstall MAU_BUNDLE_ID="com.microsoft.autoupdate2" MAU_APP_PATH="/Library/Application Support/Microsoft/MAU2.0/Microsoft AutoUpdate.app" MAU_SYSTEM_PREFS_PATH="/Library/Preferences/${MAU_BUNDLE_ID}" MAU_APPLICATION_ID="TEAMS21" MAU_LCID="1033" MAU_TEAMS_DICT="{ 'Application ID' = '${MAU_APPLICATION_ID}'; LCID = '${MAU_LCID}';}" register_to_mau() { if [ ! -d "${MAU_APP_PATH}" ]; then log "MAU is not installed, skipping registration." return fi local REGISTERED_USER_APPLICATION_ID=$(/usr/libexec/PlistBuddy -c "Print :Applications:'${DESIRED_BUNDLE_PATH}':'Application ID'" ~/Library/Preferences/${MAU_BUNDLE_ID}.plist 2> /dev/null) if [ "${REGISTERED_USER_APPLICATION_ID}" = "${MAU_APPLICATION_ID}" ]; then log "Teams is already registered to MAU (user)" else if run_as_ui_user /usr/bin/defaults write ${MAU_BUNDLE_ID} Applications -dict-add "${DESIRED_BUNDLE_PATH}" "${MAU_TEAMS_DICT}"; then log "Teams has been registered to MAU (user)" else log "Failed to register Teams to MAU (user)" fi fi local REGISTERED_SYSTEM_APPLICATION_ID=$(/usr/libexec/PlistBuddy -c "Print :ApplicationsSystem:'${DESIRED_BUNDLE_PATH}':'Application ID'" ${MAU_SYSTEM_PREFS_PATH}.plist 2> /dev/null) if [ "${REGISTERED_SYSTEM_APPLICATION_ID}" = "${MAU_APPLICATION_ID}" ]; then log "Teams is already registered to MAU (system)" else if /usr/bin/defaults write ${MAU_SYSTEM_PREFS_PATH} ApplicationsSystem -dict-add "${DESIRED_BUNDLE_PATH}" "${MAU_TEAMS_DICT}"; then log "Teams has been registered to MAU (system)" else log "Failed to register Teams to MAU (system)" fi fi } start_app() { log "Starting $DESIRED_BUNDLE_PATH as $_AS_USER" run_as_ui_user /usr/bin/open "$DESIRED_BUNDLE_PATH" } remove_control_pipe() { if [ -e "$1" ]; then log "Removing existing pipe at $1" /bin/rm -f "$1" fi } clean_cache() { log "Cleaning up any stale cache" remove_control_pipe "$CONTROL_PIPE" remove_control_pipe "$CONTROL_UNINSTALL_PIPE" } make_control_pipe() { remove_control_pipe "$1" /usr/bin/mkfifo "$1" if [ -p "$1" ]; then /bin/chmod 0666 "$1" log "Created a pipe at $1" else log "Failed to create a pipe at $1" exit 1 fi } listen_for_command_on_rename_pipe() { if [ -e "$CONTROL_PIPE" ]; then log "Listening for commands on $CONTROL_PIPE" while true; do if read -r RECV_CMD < "$CONTROL_PIPE"; then log "Received command: $RECV_CMD on $CONTROL_PIPE" if [ "$RECV_CMD" = "$CMD_RUN" ]; then stop_mau sleep 1 # for a good measure run_migration fix_dock_icon register_to_mau start_app fi break elif [ $? -ne 0 ]; then log "Interrupted system call on $CONTROL_PIPE, retrying..." fi done fi } listen_for_command_on_uinstall_pipe() { if [ -e "$CONTROL_UNINSTALL_PIPE" ]; then log "Listening for commands on $CONTROL_UNINSTALL_PIPE" while true; do if read -r RECV_CMD < "$CONTROL_UNINSTALL_PIPE"; then log "Received command: $RECV_CMD on $CONTROL_UNINSTALL_PIPE" if [ "$RECV_CMD" = "$CMD_REMOVET1" ]; then uninstall_classic_teams fi break elif [ $? -ne 0 ]; then log "Interrupted system call on $CONTROL_UNINSTALL_PIPE, retrying..." fi done fi } exit_cleanup() { local exit_status=$? if [ $exit_status -eq 0 ]; then log "Successfully completed script." else log "Script exited with non-zero status: $exit_status." fi clean_cache log "Unloading migration tool" /bin/launchctl bootout system/com.microsoft.teams2.migrationtool # We're in exit trap but command above will cause launchd to kill us log "This should not be reached" } trap exit_cleanup EXIT clean_cache # clean up any stale state # create control pipe for rename only if legacy T2.1 app is detected if [ -d "$PREVIEW_BUNDLE_PATH" ] || [ -d "$WORK_OR_SCHOOL_BUNDLE_PATH" ]; then make_control_pipe "$CONTROL_PIPE" listen_for_command_on_rename_pipe fi # create control pipe for T1 removal only if Classic Teams app is detected check_app_exists "$TEAMS_CLASSIC_BUNDLE_ID" T1_EXISTS=$? if [ $T1_EXISTS -eq 0 ]; then make_control_pipe "$CONTROL_UNINSTALL_PIPE" listen_for_command_on_uinstall_pipe fi check_app_exists "$TEAMS_CLASSIC_BUNDLE_ID" T1_EXISTS=$? if [ ! -d "$PREVIEW_BUNDLE_PATH" ] && [ ! -d "$WORK_OR_SCHOOL_BUNDLE_PATH" ] && [ $T1_EXISTS -eq 1 ]; then # If legacy T2.1 app and T1 app are not detected, unload migration tool as it is not needed anymore log "Migration tool is no longer needed. Exiting." exit 0 fi

This Bash script performs a controlled migration and cleanup process for Microsoft Teams applications on macOS. It is run as the root user after installation and is heavily focused on managing different versions of Microsoft Teams apps and performing migration-related tasks. Below is a breakdown of what the script does:

1. Definition of Key Paths and Variables

  • Defines paths for different Microsoft Teams application bundles (e.g., classic and preview versions).
  • Defines control mechanisms using named pipes (CONTROL_PIPE and CONTROL_UNINSTALL_PIPE) to listen for specific commands (run and removet1).
  • Identifies the currently logged-in user using macOS preferences (/Library/Preferences/com.apple.loginwindow lastUserName).

2. Log Helper Function

  • Logs timestamped messages (log function) to track the flow and state of operations.

3. Helper Functions

  • run_as_ui_user: Executes commands as the logged-in user.
  • stop_mau: Terminates Microsoft AutoUpdate (MAU)-related processes forcibly.
  • migrate_bundle: Renames or removes an existing app bundle for newer versions.
  • remove_file & remove_classic_teams_traces: Removes specific app traces and caches, such as Classic Teams files.
  • uninstall_classic_teams: Checks and removes the "classic" Teams version, including its traces.
  • check_app_exists: Checks for the presence of a specific application bundle by identifier.
  • run_migration: Handles the migration of legacy Teams bundles to the desired bundle path.
  • fix_dock_icon: Runs a migration tool to fix the app's dock icon after migration.
  • register_to_mau: Registers the migrated Teams app with Microsoft AutoUpdate (MAU).
  • start_app: Opens the newly migrated Teams app for the user.
  • remove_control_pipe & make_control_pipe: Manages the creation and cleanup of named pipes for inter-process communication.

4. Main Processes

  • Cleanup on Start:
    • Removes any stale state (e.g., existing named pipes).
  • Migration Logic:
    • If legacy Teams "work preview" or "work or school" bundles are detected, sets up a named pipe (CONTROL_PIPE) to listen for the run command and initiates migration.
  • Uninstallation Logic:
    • If the classic version of Teams is detected, sets up another named pipe (CONTROL_UNINSTALL_PIPE) to listen for the removet1 command and removes the classic app and its traces.
  • Exit Clean-Up:
    • Performs cleanup upon script exit, such as removing named pipes and unloading the migration tool.

5. Listening for Commands

  • listen_for_command_on_rename_pipe:
    • Waits for the run command on the CONTROL_PIPE, triggering Teams migration, fixing the dock icon, registering the app with MAU, and starting the app.
  • listen_for_command_on_uninstall_pipe:
    • Waits for the removet1 command on the CONTROL_UNINSTALL_PIPE, triggering the removal of the Classic Teams app.

6. Exit Behavior

  • If no legacy or classic Teams apps are present, the script unloads the migration tool and exits because it's no longer needed.

In Summary

The script aims to:

  1. Migrate users from older versions of Microsoft Teams (Work Preview / Classic) to the new desired bundle app.
  2. Optionally remove legacy traces of the Classic Teams app.
  3. Ensure the migrated Teams app is properly registered with Microsoft AutoUpdate and functions as expected.
  4. Manage communication with the migration tool using named pipes and react to specific commands (run and removet1).
  5. Safeguard the system by cleaning up stale state and unloading the migration tool when no longer needed.

The script operates cautiously because it is executed as root and modifies system-critical application paths, which carries a high potential for unintended consequences if misused or containing errors.

Generate your own explanations
Download our vscode extension
Read other generated explanations

Built by @thebuilderjr
Sponsored by beam analytics
Read our terms and privacy policy
Forked from openai-quickstart-node