Microsoft Teams has become ubiquitous in our professional lives, but sometimes you need a clean slate. Whether you’re troubleshooting issues, migrating to the new Teams client, or managing enterprise deployments, proper uninstallation is crucial. Although with the upgrade to new Teams client the classic Teams is uninstalled automatically, this isn’t always the case.
In this comprehensive guide, we’ll explore a robust PowerShell script that goes beyond the conventional approach to Teams uninstallation. But what makes this script different from the countless others you’ll find online?
The typical Teams uninstallation scripts you’ll encounter focus on two primary targets:
- The machine-wide Teams installer
- The Teams installation for the currently logged-in user
However, there’s a critical blind spot: What about other user profiles on the machine? Teams creates per-user installations, meaning each Windows profile can have its own Teams instance. Standard uninstallation scripts often leave these untouched, resulting in incomplete removal and potential issues down the line.
The proposed solution addresses this limitation head-on. The script we’ll examine today ensures a thorough cleanup by:
- Removing the machine-wide installer
- Uninstalling Teams from the current user profile
- Scanning and cleaning Teams installations from all user profiles
- Removing residual data and registry entries
Let’s dive into how we can achieve this complete Teams removal across Windows endpoints.
Table of Contents
Script
# Define function to unload user hives after loading
function UnloadUserHive {
param (
[string]$Username
)
if (Test-Path "HKU:\$Username") {
Remove-PSDrive -Name $Username -Scope Global -Force
Write-Host "Removed PSDrive for user $Username." -ForegroundColor Cyan
# Use reg unload to fully release the registry hive
reg unload "HKU\$Username" | Out-Null
Write-Host "Unloaded registry hive for user $Username." -ForegroundColor Cyan
}
}
$logpath = "C:\ProgramData\Microsoft\IntuneManagementExtension\Logs\_UninstallTeamsFinal.log"
if (Test-Path -Path $logpath){
Remove-Item -Path $logpath
}
Start-Transcript -Path $logpath
# Start of the script
Write-Host "Starting Classic Teams removal script..." -ForegroundColor Cyan
# Check if the Teams Machine-Wide Installer is present
# Define registry path and display name for Teams Machine-Wide Installer
$registryPath = "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
$displayName = "Teams Machine-Wide Installer"
# Search the registry for the Teams Machine-Wide Installer
Write-Host "Searching for Teams Machine-Wide Installer in the registry..." -ForegroundColor Yellow
$teamsInstallerKey = Get-ChildItem -Path $registryPath | Where-Object {
$_.GetValue("DisplayName") -eq $displayName
}
# If found, proceed with uninstallation
if ($teamsInstallerKey) {
$uninstallString = $teamsInstallerKey.GetValue("UninstallString")
if ($uninstallString) {
if ($uninstallString -match '\{[A-F0-9-]+\}') {
$msiCode = $matches[0]
Write-Host "Extracted MSI Code: $msiCode" -ForegroundColor Green
} else {
Write-Host "No MSI Code found in the string." -ForegroundColor Red
}
Write-Host "Found uninstall string for Teams Machine-Wide Installer." -ForegroundColor Yellow
Write-Host "Starting silent uninstallation..." -ForegroundColor Yellow
$uninstallArgs = "/x $msiCode /quiet /norestart"
try {
# Execute the uninstall command silently
Start-Process -FilePath "msiexec.exe" -ArgumentList $uninstallArgs -Wait
Write-Host "Teams Machine-Wide Installer uninstalled successfully." -ForegroundColor Green
} catch {
Write-Host "Error during silent uninstallation: $_" -ForegroundColor Red
}
} else {
Write-Host "No uninstall string found for Teams Machine-Wide Installer." -ForegroundColor Red
}
} else {
Write-Host "Teams Machine-Wide Installer is not present on this system." -ForegroundColor Gray
}
# Get list of user profiles on the system
Write-Host "Retrieving user profiles on the system..." -ForegroundColor Yellow
$users = Get-WmiObject -Class Win32_UserProfile -ErrorAction Stop | Where-Object { $_.Special -eq $false }
# Loop through each user profile
foreach ($user in $users) {
$userSID = $user.SID
$userProfilePath = $user.LocalPath
Write-Host "`nProcessing user profile for SID: $userSID" -ForegroundColor Cyan
Write-Host "User Profile Path: $userProfilePath" -ForegroundColor Cyan
try {
# Load the user hive if not currently loaded and create PSDrive
if (!(Get-PSDrive -Name $userSID -ErrorAction SilentlyContinue)) {
Write-Host "Loading registry hive for user $userSID..." -ForegroundColor Yellow
reg load "HKU\$userSID" "$userProfilePath\NTUSER.DAT" | Out-Null
New-PSDrive -Name $userSID -PSProvider Registry -Root "HKU\$userSID" -Scope Global | Out-Null
}
# Define Teams-related paths for per-user installations
$teamsAppDataPath = Join-Path -Path $userProfilePath -ChildPath "AppData\Local\Microsoft\Teams"
$updateExePath = Join-Path -Path $teamsAppDataPath -ChildPath "Update.exe"
$teamsOfficeRegistryPath = "$($userSID):\Software\Microsoft\Office\Teams"
$teamsUninstallRegistryPath = "$($userSID):\Software\Microsoft\Windows\CurrentVersion\Uninstall"
Write-Host "Teams AppData Path: $teamsAppDataPath" -ForegroundColor Cyan
Write-Host "Update.exe Path: $updateExePath" -ForegroundColor Cyan
# Check if Update.exe exists, and uninstall Teams
if (Test-Path $updateExePath) {
Write-Host "Running Teams uninstallation for user $userSID..." -ForegroundColor Yellow
try {
& $updateExePath --uninstall | Out-Null
Write-Host "Teams uninstalled for user $userSID." -ForegroundColor Green
} catch {
Write-Host "Error during Teams uninstallation for user $userSID : $_" -ForegroundColor Red
}
} else {
Write-Host "No Teams Update.exe found for user $userSID; skipping uninstallation." -ForegroundColor Gray
}
# Remove Teams files from user AppData
if (Test-Path $teamsAppDataPath) {
Write-Host "Removing Teams data from AppData for user $userSID..." -ForegroundColor Yellow
try {
Remove-Item -Recurse -Force -Path $teamsAppDataPath -ErrorAction Stop
Write-Host "Teams AppData removed for user $userSID." -ForegroundColor Green
} catch {
Write-Host "Error removing Teams AppData for user $userSID : $_" -ForegroundColor Red
}
} else {
Write-Host "No Teams data found in AppData for user $userSID." -ForegroundColor Gray
}
# Remove Teams registry entries under Office path
if (Test-Path $teamsOfficeRegistryPath) {
Write-Host "Removing Teams registry entries from Office path for user $userSID..." -ForegroundColor Yellow
try {
Remove-Item -Recurse -Force -Path $teamsOfficeRegistryPath -ErrorAction Stop
Write-Host "Teams registry entries removed from Office path for user $userSID." -ForegroundColor Green
} catch {
Write-Host "Error removing Teams registry entries from Office path for user $userSID : $_" -ForegroundColor Red
}
} else {
Write-Host "No Teams registry entries found in Office path for user $userSID." -ForegroundColor Gray
}
# Search and remove Teams-related registry keys under Uninstall path
if (Test-Path $teamsUninstallRegistryPath) {
$uninstallKeys = Get-ChildItem -Path $teamsUninstallRegistryPath | Where-Object {
$_.GetValue("DisplayName") -like "*Teams*"
}
foreach ($key in $uninstallKeys) {
Write-Host "Removing Teams uninstall registry entry $($key.PSChildName) for user $userSID..." -ForegroundColor Yellow
try {
Remove-Item -Recurse -Force -Path $key.PSPath -ErrorAction Stop
Write-Host "Teams uninstall registry entry removed for user $userSID." -ForegroundColor Green
} catch {
Write-Host "Error removing Teams uninstall registry entry $($key.PSChildName) for user $userSID : $_" -ForegroundColor Red
}
}
} else {
Write-Host "No Teams uninstall registry entries found for user $userSID." -ForegroundColor Gray
}
} catch {
Write-Host "Error processing Teams removal for user $userSID : $_" -ForegroundColor Red
} finally {
# Unload the hive to prevent any locked registry files
UnloadUserHive -Username $userSID
}
}
Write-Host "Classic Teams removal process completed." -ForegroundColor Cyan
Stop-Transcript
Explanation
The Foundation: Registry Hive Management
At the heart of our script lies a crucial helper function called UnloadUserHive. This function plays a vital role in managing registry operations safely. When dealing with multiple user profiles, we need to load and unload registry hives dynamically. The UnloadUserHive function ensures we maintain clean registry operations by properly removing PowerShell drives and unloading registry hives after we’re done with them. This prevents common issues like locked registry files that often plague less sophisticated uninstallation scripts.
Logging and Transparency
Transparency in the uninstallation process is essential, especially in enterprise environments. Our script implements comprehensive logging by creating a detailed log file in the Intune Management Extension directory (ready for use with an Intune Remediation or Platform script). If you won’t use this script with Intune make sure to change the location that the logs are being saved. Check the following post to understand why saving the logs in that location is very useful.
Through PowerShell’s transcript functionality, we capture every step of the process, making troubleshooting and verification straightforward. The script automatically manages its log files, ensuring each run starts with a clean slate by removing any existing logs before beginning the new process.
Tackling the Machine-Wide Installation
The first major task our script handles is removing the Teams Machine-Wide Installer. This component resides in the Windows registry under the WOW6432Node uninstall section (it could be also under the x64 registry path, but in this case we are considering that. Check this post for known location in Windows).
The script carefully searches for the installer by its display name and, when found, extracts the necessary MSI code using pattern matching. This approach ensures we can properly trigger the uninstallation through msiexec.exe. The uninstallation is performed silently with careful consideration for system stability – we prevent automatic restarts and ensure the process completes cleanly.
The Per-User Challenge
Perhaps the most significant advancement in our script is its handling of per-user installations. Rather than simply focusing on the current user, our script discovers all non-special user profiles on the system through WMI queries. For each profile, we perform a series of calculated steps to ensure complete removal of Teams components.
The process begins by dynamically loading each user’s registry hive. This is a delicate operation that requires careful management of PowerShell drives and registry access. For each user profile, we target multiple locations where Teams leaves its footprint. The script removes the application files from the AppData directory, properly executes the Teams uninstaller through Update.exe when present, and meticulously cleans up registry entries.
Registry Cleanup: Following the Breadcrumbs
Teams installations leave various registry traces that need cleaning. Our script targets multiple registry locations, including the Office Teams section and uninstall registry entries. By properly mounting each user’s registry hive, we can access and clean these locations even for users who aren’t currently logged in. This thorough approach ensures no remnants are left behind that could cause issues with future Teams installations or other Office components.
Error Handling and Resilience
In real-world scenarios, things don’t always go as planned. Our script accounts for this by implementing comprehensive error handling throughout its operation. Every critical operation is wrapped in try-catch blocks, with errors being properly logged and handled. This robust error handling ensures the script continues to function even when encountering locked files, missing components, or access issues.
Deployment Considerations
While the script is powerful, it’s important to understand its requirements. Administrative privileges are necessary due to the registry operations and file system access needed. The script is designed to run silently, making it perfect for automated deployment through management tools. It operates efficiently, requiring minimal system resources and, importantly, doesn’t force system restarts. And always try in a test environment before deploying to production. ALWAYS.
Documentation