Clear Microsoft Teams Cache Script

Posted by : on

powershell   teams   troubleshooting   guides

:bulb: Introduction

Microsoft Teams keeps a local cache of messages, images, icons, thumbnails, web cache, IndexedDB data, GPU cache, local storage, and service worker data. Over time this cache can become corrupted or stale, leading to problems like missing presence, blank screens, stuck loading, broken images, or sign-in loops.

The usual fix is to clear the Teams cache so the client can rebuild it fresh. This post covers a PowerShell script that does exactly that—safely—for both Classic Teams and New Teams on Windows.

The script is compatible with both Windows PowerShell 5.1 and PowerShell 7+ on Windows, and it is fully parameter-driven, so you can control its behavior with switches instead of editing the file.


:arrow_down: How to get it

GitHub Repository - Clear-TeamsCache :arrow_left: Project Repository
or
Clear-TeamsCache.ps1 :arrow_left: Direct Download Link

You can also download and run the script directly with PowerShell:

Invoke-WebRequest -Uri 'https://raw.githubusercontent.com/blakedrumm/Clear-TeamsCache/main/Clear-TeamsCache.ps1' -OutFile "$env:TEMP\Clear-TeamsCache.ps1"

Disclaimer: Always test in a development environment before applying to production.

By default, the script clears Teams cache for the currently signed-in user only.


:mag: What Gets Cleared

The script targets the following cache roots:

Teams Type Cache Root
Classic Teams %AppData%\Microsoft\Teams
New Teams %LocalAppData%\Packages\MSTeams_8wekyb3d8bbwe\LocalCache\Microsoft\MSTeams

:information_source: The script is intentionally scoped to Teams client cache paths. It does not clear Windows credentials, browser credentials, Office identity cache, WAM tokens, or OneAuth tokens.


:gear: How It Works

  1. Verifies it is running on Windows and that the selected options are valid
  2. Stops running Teams processes (graceful close first, then optional force stop), including Teams-related WebView2 processes
  3. Builds the list of profiles to process (current user, or all local profiles)
  4. Runs a safety check on every cache path before touching it
  5. Removes cache items with retry logic, and an optional robocopy mirror fallback for stubborn folders
  6. Optionally restarts Teams and writes a summary

:shield: Safety First

Before any deletion, each path is validated with a safety check that:

  • Rejects wildcard characters in deletion paths
  • Confirms the path ends with a known Classic Teams or New Teams cache suffix
  • Refuses to operate on high-level roots like C:\, the user profile, %AppData%, or %LocalAppData%\Packages

If a path fails this check, the script throws and refuses to continue.


:wrench: Parameters

The script uses safe defaults out of the box—running it with no parameters clears the current user’s cache. Behavior is controlled through the parameters below. Most are switches, so the default behavior is enabled unless you opt out with a -Skip... or -Disable... switch.

Parameter Type Description
-ClearAllUserProfiles Switch Clear cache for all local profiles. Requires running as Administrator.
-SkipCurrentUserProfile Switch Skip cache cleanup for the signed-in user.
-SkipClassicTeams Switch Skip Classic Teams cache cleanup.
-SkipNewTeams Switch Skip New Teams cache cleanup.
-SkipStopTeamsBeforeCleanup Switch Do not stop Teams before clearing cache.
-SkipForceStopTeamsProcesses Switch Do not force stop Teams if graceful close times out.
-StopTeamsGracefulTimeoutSeconds Int (1-300) Seconds to wait after graceful close before force-stopping. Default 15.
-SkipTeamsWebView2Processes Switch Do not stop Teams-related WebView2 processes.
-SkipRestartTeamsAfterCleanup Switch Do not relaunch Teams when cleanup finishes.
-DryRunOnly Switch Log what would be removed without deleting anything.
-CreateBackupBeforeCleanup Switch Copy cache to a backup folder before deletion.
-BackupRoot String Folder where backups are stored. Defaults to a timestamped folder in %TEMP%.
-DisableLogFile Switch Disable writing a log file.
-LogFilePath String Full path to the log file. Defaults to a timestamped file in %TEMP%.
-RetryCount Int (1-20) Retry attempts for file removal and process stop actions. Default 3.
-RetryDelaySeconds Int (1-60) Seconds to wait between retries. Default 2.
-DisableRobocopyFallbackForStubbornFolders Switch Disable the robocopy mirror fallback.
-ResetNewTeamsAppPackage Switch Reset the New Teams AppX package (current user only).

:floppy_disk: How to Use the Script

1. Save the Script Locally

Save the script as a .ps1 file:

Clear-TeamsCache.ps1

For normal use, run the script as the signed-in user who is having Teams problems:

.\Clear-TeamsCache.ps1

The script will stop Teams, clear the cache, and relaunch Teams for that user.

3. (Optional) Clear All User Profiles

To clear cache for every local profile, run PowerShell as Administrator and use the -ClearAllUserProfiles switch:

.\Clear-TeamsCache.ps1 -ClearAllUserProfiles

:question: Examples

Preview changes without deleting (Dry Run)

Use -DryRunOnly to see exactly what would be removed:

.\Clear-TeamsCache.ps1 -DryRunOnly

Every cache item will be logged with a Would remove: entry, but nothing is deleted.

Back up the cache before clearing

Use -CreateBackupBeforeCleanup to copy the cache to %TEMP% before deletion:

.\Clear-TeamsCache.ps1 -CreateBackupBeforeCleanup

If the backup fails, cleanup stops so nothing is removed without a safety copy.

Clear only New Teams cache

Use -SkipClassicTeams so only the New Teams cache is processed:

.\Clear-TeamsCache.ps1 -SkipClassicTeams

Clear cache without restarting Teams

Use -SkipRestartTeamsAfterCleanup to leave Teams closed after cleanup:

.\Clear-TeamsCache.ps1 -SkipRestartTeamsAfterCleanup

Reset the New Teams app package

Use -ResetNewTeamsAppPackage to also reset the New Teams AppX package for the current user:

.\Clear-TeamsCache.ps1 -ResetNewTeamsAppPackage

:bar_chart: Logging and Summary

By default the script writes a timestamped log to %TEMP% (for example, Clear-TeamsCache_COMPUTERNAME_yyyyMMdd_HHmmss.log). Use -DisableLogFile to turn this off, or -LogFilePath to choose a custom location.

At the end of every run, a summary reports:

  • Cache roots checked, found, and fully cleared
  • Cache items discovered, removed, and failed
  • Teams-related processes stopped or skipped
  • The log file and backup paths (when enabled)

:warning: Things to Know

  • After the cache is cleared, Teams may take longer to open the first time while it rebuilds the cache.
  • Clearing all user profiles requires Administrator rights.
  • -ResetNewTeamsAppPackage only applies to the current user and can remove New Teams app data and personalization settings—use with care.
  • Backups only cover cache folders; AppX reset data is not backed up.

:page_facing_up: The Full Script

If you prefer to copy the script directly, the complete contents of Clear-TeamsCache.ps1 are below. This is the same version published in the GitHub repository.

#requires -Version 5.1

<#
.SYNOPSIS
    Clears Microsoft Teams cache for Classic Teams and New Teams on Windows.

.DESCRIPTION
    This script clears Microsoft Teams cache so Teams can rebuild fresh cached data,
    including messages, images, icons, thumbnails, web cache, IndexedDB data, GPU cache,
    local storage, service worker cache, and other Teams client cached content.

    This script is compatible with:
        Windows PowerShell 5.1
        PowerShell 7+ on Windows

    By default, this script clears Teams cache for the currently signed-in user only.

    Supported cache roots cleared by this script:

        Classic Teams:
            %AppData%\Microsoft\Teams

        New Teams:
            %LocalAppData%\Packages\MSTeams_8wekyb3d8bbwe\LocalCache\Microsoft\MSTeams

.PARAMETER ClearAllUserProfiles
    Clears Teams cache for all local user profiles. Requires PowerShell to be run as Administrator.

.PARAMETER SkipCurrentUserProfile
    Skips Teams cache cleanup for the currently signed-in user.

.PARAMETER SkipClassicTeams
    Skips Classic Teams cache cleanup.

.PARAMETER SkipNewTeams
    Skips New Teams cache cleanup.

.PARAMETER SkipStopTeamsBeforeCleanup
    Skips stopping Teams-related processes before cache cleanup.

.PARAMETER SkipForceStopTeamsProcesses
    Skips force-stopping Teams-related processes if graceful close does not work.

.PARAMETER StopTeamsGracefulTimeoutSeconds
    Number of seconds to wait after graceful close before force-stopping Teams-related processes.

.PARAMETER SkipTeamsWebView2Processes
    Skips stopping Teams-related WebView2 processes.

.PARAMETER SkipRestartTeamsAfterCleanup
    Skips restarting Teams after cache cleanup completes.

.PARAMETER DryRunOnly
    Shows what would be removed without deleting anything.

.PARAMETER CreateBackupBeforeCleanup
    Creates a backup of Teams cache contents before removal.

.PARAMETER BackupRoot
    Folder where backups are stored when CreateBackupBeforeCleanup is enabled.

.PARAMETER DisableLogFile
    Disables logging to a file.

.PARAMETER LogFilePath
    Full path to the log file.

.PARAMETER RetryCount
    Number of retry attempts for file removal and process stop actions.

.PARAMETER RetryDelaySeconds
    Number of seconds to wait between retry attempts.

.PARAMETER DisableRobocopyFallbackForStubbornFolders
    Disables robocopy mirror fallback for stubborn cache folders.

.PARAMETER ResetNewTeamsAppPackage
    Optional reset of the New Teams AppX package for the current user.
    Disabled by default because it can remove New Teams app data and personalization settings.

.EXAMPLE
    .\Clear-TeamsCache.ps1

    Clears Teams cache for the current user using safe defaults.

.EXAMPLE
    .\Clear-TeamsCache.ps1 -DryRunOnly

    Shows what would be removed without deleting anything.

.EXAMPLE
    .\Clear-TeamsCache.ps1 -ClearAllUserProfiles

    Clears Teams cache for all local profiles. Requires Administrator.

.EXAMPLE
    .\Clear-TeamsCache.ps1 -CreateBackupBeforeCleanup

    Creates a backup before clearing Teams cache.

.EXAMPLE
    .\Clear-TeamsCache.ps1 -SkipRestartTeamsAfterCleanup

    Clears Teams cache but does not restart Teams afterward.

.EXAMPLE
    .\Clear-TeamsCache.ps1 -ResetNewTeamsAppPackage

    Clears Teams cache and attempts to reset the New Teams AppX package for the current user.

.LINK
    https://github.com/blakedrumm/Clear-TeamsCache

.NOTES
    Author: Blake Drumm ([email protected])
    Creation Date: June 4th, 2026
    Repository: https://github.com/blakedrumm/Clear-TeamsCache

    Recommended normal usage:
        Run this script as the affected signed-in user.

    For all local user profiles:
        Run PowerShell as Administrator and use:
            .\Clear-TeamsCache.ps1 -ClearAllUserProfiles

    This script does not clear Windows credentials, browser credentials, Office identity cache,
    WAM tokens, OneAuth tokens, or Microsoft 365 identity data. It is intentionally scoped to
    Teams client cache paths.

    After Teams cache is cleared, Teams may take longer to open the first time while it rebuilds cache.
#>

[CmdletBinding()]
param (
    [Parameter(Mandatory = $false)]
    [switch]$ClearAllUserProfiles,

    [Parameter(Mandatory = $false)]
    [switch]$SkipCurrentUserProfile,

    [Parameter(Mandatory = $false)]
    [switch]$SkipClassicTeams,

    [Parameter(Mandatory = $false)]
    [switch]$SkipNewTeams,

    [Parameter(Mandatory = $false)]
    [switch]$SkipStopTeamsBeforeCleanup,

    [Parameter(Mandatory = $false)]
    [switch]$SkipForceStopTeamsProcesses,

    [Parameter(Mandatory = $false)]
    [ValidateRange(1, 300)]
    [int]$StopTeamsGracefulTimeoutSeconds = 15,

    [Parameter(Mandatory = $false)]
    [switch]$SkipTeamsWebView2Processes,

    [Parameter(Mandatory = $false)]
    [switch]$SkipRestartTeamsAfterCleanup,

    [Parameter(Mandatory = $false)]
    [switch]$DryRunOnly,

    [Parameter(Mandatory = $false)]
    [switch]$CreateBackupBeforeCleanup,

    [Parameter(Mandatory = $false)]
    [string]$BackupRoot = (Join-Path -Path $env:TEMP -ChildPath ("TeamsCacheBackup_{0}" -f (Get-Date -Format 'yyyyMMdd_HHmmss'))),

    [Parameter(Mandatory = $false)]
    [switch]$DisableLogFile,

    [Parameter(Mandatory = $false)]
    [string]$LogFilePath = (Join-Path -Path $env:TEMP -ChildPath ("Clear-TeamsCache_{0}_{1}.log" -f $env:COMPUTERNAME, (Get-Date -Format 'yyyyMMdd_HHmmss'))),

    [Parameter(Mandatory = $false)]
    [ValidateRange(1, 20)]
    [int]$RetryCount = 3,

    [Parameter(Mandatory = $false)]
    [ValidateRange(1, 60)]
    [int]$RetryDelaySeconds = 2,

    [Parameter(Mandatory = $false)]
    [switch]$DisableRobocopyFallbackForStubbornFolders,

    [Parameter(Mandatory = $false)]
    [switch]$ResetNewTeamsAppPackage,

    [Parameter(Mandatory = $false)]
    [string[]]$ExcludedProfileFolderNames = @(
        'All Users',
        'Default',
        'Default User',
        'Public',
        'desktop.ini'
    ),

    [Parameter(Mandatory = $false)]
    [string[]]$TeamsExecutableProcessNames = @(
        'Teams.exe',
        'ms-teams.exe',
        'MSTeams.exe'
    ),

    [Parameter(Mandatory = $false)]
    [string[]]$TeamsUpdateProcessNames = @(
        'Update.exe',
        'Squirrel.exe'
    ),

    [Parameter(Mandatory = $false)]
    [string[]]$TeamsWebView2ProcessNames = @(
        'msedgewebview2.exe'
    )
)

$ErrorActionPreference = 'Stop'

$ShouldClearCurrentUserProfile = -not $SkipCurrentUserProfile.IsPresent
$ShouldClearAllUserProfiles = $ClearAllUserProfiles.IsPresent
$ShouldIncludeClassicTeams = -not $SkipClassicTeams.IsPresent
$ShouldIncludeNewTeams = -not $SkipNewTeams.IsPresent
$ShouldStopTeamsBeforeCleanup = -not $SkipStopTeamsBeforeCleanup.IsPresent
$ShouldForceStopTeamsProcesses = -not $SkipForceStopTeamsProcesses.IsPresent
$ShouldStopTeamsWebView2Processes = -not $SkipTeamsWebView2Processes.IsPresent
$ShouldRestartTeamsAfterCleanup = -not $SkipRestartTeamsAfterCleanup.IsPresent
$ShouldDryRunOnly = $DryRunOnly.IsPresent
$ShouldCreateBackupBeforeCleanup = $CreateBackupBeforeCleanup.IsPresent
$ShouldEnableLogFile = -not $DisableLogFile.IsPresent
$ShouldUseRobocopyFallbackForStubbornFolders = -not $DisableRobocopyFallbackForStubbornFolders.IsPresent
$ShouldResetNewTeamsAppPackage = $ResetNewTeamsAppPackage.IsPresent

$script:TotalCacheRootsChecked = 0
$script:TotalCacheRootsFound = 0
$script:TotalCacheRootsCleared = 0
$script:TotalItemsDiscovered = 0
$script:TotalItemsRemoved = 0
$script:TotalItemsFailed = 0
$script:TotalProcessesStopped = 0
$script:TotalProcessesSkipped = 0

function Write-Log {
    param (
        [Parameter(Mandatory = $true)]
        [string]$Message,

        [Parameter(Mandatory = $false)]
        [ValidateSet('INFO', 'WARN', 'ERROR', 'SUCCESS')]
        [string]$Level = 'INFO'
    )

    $Line = "{0} - {1} - {2}" -f (Get-Date -Format 'yyyy-MM-dd HH:mm:ss'), $Level, $Message

    Write-Output $Line

    if ($ShouldEnableLogFile) {
        try {
            $LogFolder = Split-Path -Path $LogFilePath -Parent

            if (-not [string]::IsNullOrWhiteSpace($LogFolder) -and -not (Test-Path -LiteralPath $LogFolder)) {
                New-Item -Path $LogFolder -ItemType Directory -Force | Out-Null
            }

            Add-Content -LiteralPath $LogFilePath -Value $Line -Encoding UTF8
        }
        catch {
            Write-Output ("{0} - WARN - Failed to write to log file. Error: {1}" -f (Get-Date -Format 'yyyy-MM-dd HH:mm:ss'), $_.Exception.Message)
        }
    }
}

function Test-IsWindows {
    if ($PSVersionTable.PSVersion.Major -ge 6) {
        return $IsWindows
    }

    return ([System.Environment]::OSVersion.Platform -eq [System.PlatformID]::Win32NT)
}

function Test-IsAdministrator {
    $CurrentIdentity = [Security.Principal.WindowsIdentity]::GetCurrent()
    $Principal = New-Object Security.Principal.WindowsPrincipal($CurrentIdentity)
    return $Principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
}

function Get-PowerShellHostDescription {
    return "PowerShell {0} Edition {1}" -f $PSVersionTable.PSVersion.ToString(), $PSVersionTable.PSEdition
}

function Get-WindowsPowerShellExePath {
    $PowerShellExePath = Join-Path -Path $env:WINDIR -ChildPath 'System32\WindowsPowerShell\v1.0\powershell.exe'

    if (Test-Path -LiteralPath $PowerShellExePath) {
        return $PowerShellExePath
    }

    $FallbackPowerShellExePath = Join-Path -Path $env:SystemRoot -ChildPath 'System32\WindowsPowerShell\v1.0\powershell.exe'

    if (Test-Path -LiteralPath $FallbackPowerShellExePath) {
        return $FallbackPowerShellExePath
    }

    return $null
}

function Invoke-WindowsPowerShellTextCommand {
    param (
        [Parameter(Mandatory = $true)]
        [string]$CommandText
    )

    $PowerShellExePath = Get-WindowsPowerShellExePath

    if ([string]::IsNullOrWhiteSpace($PowerShellExePath)) {
        throw "Windows PowerShell 5.1 executable was not found."
    }

    $Bytes = [System.Text.Encoding]::Unicode.GetBytes($CommandText)
    $EncodedCommand = [Convert]::ToBase64String($Bytes)

    $ProcessStartInfo = New-Object System.Diagnostics.ProcessStartInfo
    $ProcessStartInfo.FileName = $PowerShellExePath
    $ProcessStartInfo.Arguments = "-NoProfile -ExecutionPolicy Bypass -EncodedCommand $EncodedCommand"
    $ProcessStartInfo.RedirectStandardOutput = $true
    $ProcessStartInfo.RedirectStandardError = $true
    $ProcessStartInfo.UseShellExecute = $false
    $ProcessStartInfo.CreateNoWindow = $true

    $Process = New-Object System.Diagnostics.Process
    $Process.StartInfo = $ProcessStartInfo

    [void]$Process.Start()

    $StandardOutput = $Process.StandardOutput.ReadToEnd()
    $StandardError = $Process.StandardError.ReadToEnd()

    $Process.WaitForExit()

    return [PSCustomObject]@{
        ExitCode       = $Process.ExitCode
        StandardOutput = $StandardOutput
        StandardError  = $StandardError
    }
}

function Get-NewTeamsPackageFullName {
    $PackageFullName = $null

    $GetAppxPackageCommand = Get-Command -Name Get-AppxPackage -ErrorAction SilentlyContinue

    if ($GetAppxPackageCommand) {
        try {
            $Package = Get-AppxPackage -Name 'MSTeams' -ErrorAction SilentlyContinue | Select-Object -First 1

            if ($Package -and $Package.PackageFullName) {
                $PackageFullName = $Package.PackageFullName
            }
        }
        catch {
            $PackageFullName = $null
        }
    }

    if ([string]::IsNullOrWhiteSpace($PackageFullName)) {
        try {
            $CommandText = @'
$ErrorActionPreference = 'Stop'
$Package = Get-AppxPackage -Name 'MSTeams' -ErrorAction SilentlyContinue | Select-Object -First 1
if ($Package -and $Package.PackageFullName) {
    Write-Output $Package.PackageFullName
}
'@

            $Result = Invoke-WindowsPowerShellTextCommand -CommandText $CommandText

            if ($Result.ExitCode -eq 0 -and -not [string]::IsNullOrWhiteSpace($Result.StandardOutput)) {
                $PackageFullName = ($Result.StandardOutput.Trim() -split "`r?`n" | Select-Object -First 1)
            }
        }
        catch {
            $PackageFullName = $null
        }
    }

    return $PackageFullName
}

function Invoke-WithRetry {
    param (
        [Parameter(Mandatory = $true)]
        [scriptblock]$Action,

        [Parameter(Mandatory = $true)]
        [string]$Description,

        [Parameter(Mandatory = $false)]
        [int]$MaximumAttempts = $RetryCount,

        [Parameter(Mandatory = $false)]
        [int]$DelaySeconds = $RetryDelaySeconds
    )

    for ($Attempt = 1; $Attempt -le $MaximumAttempts; $Attempt++) {
        try {
            & $Action
            return $true
        }
        catch {
            if ($Attempt -ge $MaximumAttempts) {
                Write-Log -Level 'ERROR' -Message ("Failed after {0} attempt(s): {1}. Error: {2}" -f $Attempt, $Description, $_.Exception.Message)
                return $false
            }

            Write-Log -Level 'WARN' -Message ("Attempt {0} failed: {1}. Retrying in {2} second(s). Error: {3}" -f $Attempt, $Description, $DelaySeconds, $_.Exception.Message)
            Start-Sleep -Seconds $DelaySeconds
        }
    }

    return $false
}

function Get-SafeFullPath {
    param (
        [Parameter(Mandatory = $true)]
        [string]$Path
    )

    if ([string]::IsNullOrWhiteSpace($Path)) {
        throw "Path cannot be empty."
    }

    if ($Path.Contains('*') -or $Path.Contains('?')) {
        throw "Wildcard characters are not allowed in deletion paths: $Path"
    }

    $FullPath = [System.IO.Path]::GetFullPath($Path)
    return $FullPath.TrimEnd('\')
}

function Test-SafeTeamsCacheRoot {
    param (
        [Parameter(Mandatory = $true)]
        [string]$Path
    )

    try {
        $FullPath = Get-SafeFullPath -Path $Path
    }
    catch {
        Write-Log -Level 'ERROR' -Message $_.Exception.Message
        return $false
    }

    $ClassicTeamsSuffix = '\AppData\Roaming\Microsoft\Teams'
    $NewTeamsSuffix = '\AppData\Local\Packages\MSTeams_8wekyb3d8bbwe\LocalCache\Microsoft\MSTeams'

    $IsClassicTeamsRoot = $FullPath.EndsWith($ClassicTeamsSuffix, [System.StringComparison]::OrdinalIgnoreCase)
    $IsNewTeamsRoot = $FullPath.EndsWith($NewTeamsSuffix, [System.StringComparison]::OrdinalIgnoreCase)

    if (-not $IsClassicTeamsRoot -and -not $IsNewTeamsRoot) {
        return $false
    }

    $InvalidRoots = @(
        '',
        '\',
        'C:',
        'C:\',
        $env:SystemDrive,
        "$env:SystemDrive\",
        $env:USERPROFILE,
        $env:APPDATA,
        $env:LOCALAPPDATA,
        "$env:LOCALAPPDATA\Packages",
        "$env:APPDATA\Microsoft"
    ) | Where-Object {
        -not [string]::IsNullOrWhiteSpace($_)
    } | ForEach-Object {
        try {
            Get-SafeFullPath -Path $_
        }
        catch {
            $null
        }
    }

    foreach ($InvalidRoot in $InvalidRoots) {
        if ($FullPath.Equals($InvalidRoot, [System.StringComparison]::OrdinalIgnoreCase)) {
            return $false
        }
    }

    return $true
}

function Get-CurrentUserSid {
    return [Security.Principal.WindowsIdentity]::GetCurrent().User.Value
}

function Get-ProcessOwnerSid {
    param (
        [Parameter(Mandatory = $true)]
        [Microsoft.Management.Infrastructure.CimInstance]$CimProcess
    )

    try {
        $OwnerSid = Invoke-CimMethod -InputObject $CimProcess -MethodName GetOwnerSid -ErrorAction Stop

        if ($OwnerSid -and $OwnerSid.Sid) {
            return $OwnerSid.Sid
        }
    }
    catch {
        return $null
    }

    return $null
}

function Test-IsTeamsRelatedProcess {
    param (
        [Parameter(Mandatory = $true)]
        [Microsoft.Management.Infrastructure.CimInstance]$CimProcess
    )

    $Name = $CimProcess.Name
    $NameLower = $Name.ToLowerInvariant()

    $CommandLine = ''
    $ExecutablePath = ''

    if ($CimProcess.CommandLine) {
        $CommandLine = $CimProcess.CommandLine
    }

    if ($CimProcess.ExecutablePath) {
        $ExecutablePath = $CimProcess.ExecutablePath
    }

    $CombinedText = "{0} {1}" -f $CommandLine, $ExecutablePath

    foreach ($ProcessName in $TeamsExecutableProcessNames) {
        if ($NameLower -eq $ProcessName.ToLowerInvariant()) {
            return $true
        }
    }

    foreach ($ProcessName in $TeamsUpdateProcessNames) {
        if ($NameLower -eq $ProcessName.ToLowerInvariant()) {
            if ($CombinedText -match '(?i)\\Microsoft\\Teams\\|\\AppData\\Local\\Microsoft\\Teams\\|MSTeams_8wekyb3d8bbwe|Microsoft Teams') {
                return $true
            }
        }
    }

    if ($ShouldStopTeamsWebView2Processes) {
        foreach ($ProcessName in $TeamsWebView2ProcessNames) {
            if ($NameLower -eq $ProcessName.ToLowerInvariant()) {
                if ($CombinedText -match '(?i)MSTeams_8wekyb3d8bbwe|\\Microsoft\\Teams\\|ms-teams|msteams|teams\.microsoft\.com') {
                    return $true
                }
            }
        }
    }

    return $false
}

function Stop-TeamsProcesses {
    param (
        [Parameter(Mandatory = $true)]
        [bool]$AllUsers
    )

    Write-Log "Checking for running Microsoft Teams processes."

    $CurrentUserSid = Get-CurrentUserSid

    $CimProcesses = @(Get-CimInstance -ClassName Win32_Process -ErrorAction SilentlyContinue | Where-Object {
        Test-IsTeamsRelatedProcess -CimProcess $_
    })

    if (-not $CimProcesses -or $CimProcesses.Count -eq 0) {
        Write-Log "No running Teams-related processes were found."
        return
    }

    $ProcessTargets = New-Object System.Collections.Generic.List[object]

    foreach ($CimProcess in $CimProcesses) {
        $OwnerSid = Get-ProcessOwnerSid -CimProcess $CimProcess

        if (-not $AllUsers) {
            if ([string]::IsNullOrWhiteSpace($OwnerSid)) {
                Write-Log -Level 'WARN' -Message ("Skipping process because owner SID could not be determined: {0} PID {1}" -f $CimProcess.Name, $CimProcess.ProcessId)
                $script:TotalProcessesSkipped++
                continue
            }

            if (-not $OwnerSid.Equals($CurrentUserSid, [System.StringComparison]::OrdinalIgnoreCase)) {
                Write-Log -Level 'INFO' -Message ("Skipping Teams-related process from another user: {0} PID {1}" -f $CimProcess.Name, $CimProcess.ProcessId)
                $script:TotalProcessesSkipped++
                continue
            }
        }

        $ProcessTargets.Add([PSCustomObject]@{
            ProcessId      = [int]$CimProcess.ProcessId
            Name           = [string]$CimProcess.Name
            OwnerSid       = [string]$OwnerSid
            ExecutablePath = [string]$CimProcess.ExecutablePath
            CommandLine    = [string]$CimProcess.CommandLine
        })
    }

    if ($ProcessTargets.Count -eq 0) {
        Write-Log "No Teams-related processes matched the selected cleanup scope."
        return
    }

    foreach ($Target in $ProcessTargets) {
        try {
            $Process = Get-Process -Id $Target.ProcessId -ErrorAction Stop

            Write-Log ("Attempting graceful close for process: {0} PID {1}" -f $Target.Name, $Target.ProcessId)

            if ($Process.MainWindowHandle -ne 0) {
                [void]$Process.CloseMainWindow()
            }
        }
        catch {
            Write-Log -Level 'WARN' -Message ("Graceful close was not available for process: {0} PID {1}. Error: {2}" -f $Target.Name, $Target.ProcessId, $_.Exception.Message)
        }
    }

    $Stopwatch = [Diagnostics.Stopwatch]::StartNew()

    do {
        Start-Sleep -Seconds 1

        $StillRunning = @()

        foreach ($Target in $ProcessTargets) {
            $ExistingProcess = Get-Process -Id $Target.ProcessId -ErrorAction SilentlyContinue

            if ($ExistingProcess) {
                $StillRunning += $Target
            }
        }
    }
    while ($StillRunning.Count -gt 0 -and $Stopwatch.Elapsed.TotalSeconds -lt $StopTeamsGracefulTimeoutSeconds)

    $Stopwatch.Stop()

    $StillRunning = @()

    foreach ($Target in $ProcessTargets) {
        $ExistingProcess = Get-Process -Id $Target.ProcessId -ErrorAction SilentlyContinue

        if ($ExistingProcess) {
            $StillRunning += $Target
        }
    }

    if ($StillRunning.Count -gt 0 -and $ShouldForceStopTeamsProcesses) {
        foreach ($Target in $StillRunning) {
            $Stopped = Invoke-WithRetry -Description ("Force stop process {0} PID {1}" -f $Target.Name, $Target.ProcessId) -Action {
                Stop-Process -Id $Target.ProcessId -Force -ErrorAction Stop
            }

            if ($Stopped) {
                Write-Log ("Stopped process: {0} PID {1}" -f $Target.Name, $Target.ProcessId)
                $script:TotalProcessesStopped++
            }
            else {
                $script:TotalProcessesSkipped++
            }
        }
    }
    elseif ($StillRunning.Count -gt 0) {
        throw "One or more Teams-related processes are still running. Close Teams and rerun the script, or rerun without -SkipForceStopTeamsProcesses."
    }

    foreach ($Target in $ProcessTargets) {
        $ExistingProcess = Get-Process -Id $Target.ProcessId -ErrorAction SilentlyContinue

        if (-not $ExistingProcess) {
            if ($script:TotalProcessesStopped -lt $ProcessTargets.Count) {
                $script:TotalProcessesStopped++
            }
        }
    }

    Write-Log "Finished Teams process stop operation."
}

function Get-LocalUserProfiles {
    $Profiles = @()

    try {
        $CimProfiles = @(Get-CimInstance -ClassName Win32_UserProfile -ErrorAction Stop | Where-Object {
            $_.Special -eq $false -and
            -not [string]::IsNullOrWhiteSpace($_.LocalPath) -and
            (Test-Path -LiteralPath $_.LocalPath)
        })

        foreach ($CimProfile in $CimProfiles) {
            $ProfileName = Split-Path -Path $CimProfile.LocalPath -Leaf

            if ($ExcludedProfileFolderNames -contains $ProfileName) {
                continue
            }

            $Profiles += [PSCustomObject]@{
                ProfileName = $ProfileName
                ProfilePath = $CimProfile.LocalPath
                Source      = 'Win32_UserProfile'
            }
        }
    }
    catch {
        Write-Log -Level 'WARN' -Message ("Unable to query Win32_UserProfile. Falling back to C:\Users. Error: {0}" -f $_.Exception.Message)
    }

    if (-not $Profiles -or $Profiles.Count -eq 0) {
        $UsersRoot = Join-Path -Path $env:SystemDrive -ChildPath 'Users'

        if (Test-Path -LiteralPath $UsersRoot) {
            $ProfileFolders = @(Get-ChildItem -LiteralPath $UsersRoot -Directory -Force -ErrorAction Stop | Where-Object {
                $ExcludedProfileFolderNames -notcontains $_.Name
            })

            foreach ($ProfileFolder in $ProfileFolders) {
                $Profiles += [PSCustomObject]@{
                    ProfileName = $ProfileFolder.Name
                    ProfilePath = $ProfileFolder.FullName
                    Source      = 'FolderEnumeration'
                }
            }
        }
    }

    $Profiles = @($Profiles | Sort-Object -Property ProfilePath -Unique)

    return $Profiles
}

function Get-TeamsCacheTargetsForProfile {
    param (
        [Parameter(Mandatory = $true)]
        [string]$ProfilePath,

        [Parameter(Mandatory = $true)]
        [string]$ProfileName
    )

    $Targets = @()

    if ($ShouldIncludeClassicTeams) {
        $Targets += [PSCustomObject]@{
            ProfileName = $ProfileName
            ProfilePath = $ProfilePath
            TeamsType   = 'Classic Teams'
            CacheRoot   = Join-Path -Path $ProfilePath -ChildPath 'AppData\Roaming\Microsoft\Teams'
        }
    }

    if ($ShouldIncludeNewTeams) {
        $Targets += [PSCustomObject]@{
            ProfileName = $ProfileName
            ProfilePath = $ProfilePath
            TeamsType   = 'New Teams'
            CacheRoot   = Join-Path -Path $ProfilePath -ChildPath 'AppData\Local\Packages\MSTeams_8wekyb3d8bbwe\LocalCache\Microsoft\MSTeams'
        }
    }

    return $Targets
}

function Backup-TeamsCacheRoot {
    param (
        [Parameter(Mandatory = $true)]
        [string]$CacheRoot,

        [Parameter(Mandatory = $true)]
        [string]$ProfileName,

        [Parameter(Mandatory = $true)]
        [string]$TeamsType
    )

    if (-not (Test-Path -LiteralPath $CacheRoot)) {
        return $true
    }

    $SafeProfileName = $ProfileName -replace '[\\/:*?"<>|]', '_'
    $SafeTeamsType = $TeamsType -replace '[\\/:*?"<>|]', '_'

    $BackupDestination = Join-Path -Path $BackupRoot -ChildPath (Join-Path -Path $SafeProfileName -ChildPath $SafeTeamsType)

    try {
        if (-not (Test-Path -LiteralPath $BackupDestination)) {
            New-Item -Path $BackupDestination -ItemType Directory -Force | Out-Null
        }

        $Items = @(Get-ChildItem -LiteralPath $CacheRoot -Force -ErrorAction Stop)

        foreach ($Item in $Items) {
            Copy-Item -LiteralPath $Item.FullName -Destination $BackupDestination -Recurse -Force -ErrorAction Stop
        }

        Write-Log ("Backup completed for {0} cache in profile {1}: {2}" -f $TeamsType, $ProfileName, $BackupDestination)
        return $true
    }
    catch {
        Write-Log -Level 'ERROR' -Message ("Backup failed for {0} cache in profile {1}. Error: {2}" -f $TeamsType, $ProfileName, $_.Exception.Message)
        return $false
    }
}

function Clear-FolderUsingRobocopyMirror {
    param (
        [Parameter(Mandatory = $true)]
        [string]$CacheRoot
    )

    if (-not $ShouldUseRobocopyFallbackForStubbornFolders) {
        return $false
    }

    if (-not (Test-SafeTeamsCacheRoot -Path $CacheRoot)) {
        Write-Log -Level 'ERROR' -Message ("Robocopy fallback blocked by safety check: {0}" -f $CacheRoot)
        return $false
    }

    $RobocopyPath = Join-Path -Path $env:SystemRoot -ChildPath 'System32\robocopy.exe'

    if (-not (Test-Path -LiteralPath $RobocopyPath)) {
        Write-Log -Level 'WARN' -Message "Robocopy was not found. Fallback cleanup cannot be used."
        return $false
    }

    $EmptyFolder = Join-Path -Path $env:TEMP -ChildPath ("EmptyTeamsCacheMirror_{0}" -f ([guid]::NewGuid().Guid))

    try {
        New-Item -Path $EmptyFolder -ItemType Directory -Force | Out-Null

        $ArgumentString = '"{0}" "{1}" /MIR /R:{2} /W:{3} /NFL /NDL /NJH /NJS /NC /NS /NP' -f $EmptyFolder, $CacheRoot, $RetryCount, $RetryDelaySeconds

        Write-Log ("Running robocopy fallback cleanup for: {0}" -f $CacheRoot)

        $RobocopyProcess = Start-Process -FilePath $RobocopyPath -ArgumentList $ArgumentString -NoNewWindow -Wait -PassThru

        if ($RobocopyProcess.ExitCode -le 7) {
            Write-Log -Level 'SUCCESS' -Message ("Robocopy fallback completed successfully. Exit code: {0}" -f $RobocopyProcess.ExitCode)
            return $true
        }

        Write-Log -Level 'ERROR' -Message ("Robocopy fallback failed. Exit code: {0}" -f $RobocopyProcess.ExitCode)
        return $false
    }
    catch {
        Write-Log -Level 'ERROR' -Message ("Robocopy fallback failed. Error: {0}" -f $_.Exception.Message)
        return $false
    }
    finally {
        try {
            if (Test-Path -LiteralPath $EmptyFolder) {
                Remove-Item -LiteralPath $EmptyFolder -Recurse -Force -ErrorAction SilentlyContinue
            }
        }
        catch {
            Write-Log -Level 'WARN' -Message ("Failed to remove temporary robocopy mirror folder: {0}" -f $EmptyFolder)
        }
    }
}

function Clear-TeamsCacheRoot {
    param (
        [Parameter(Mandatory = $true)]
        [string]$CacheRoot,

        [Parameter(Mandatory = $true)]
        [string]$ProfileName,

        [Parameter(Mandatory = $true)]
        [string]$TeamsType
    )

    $script:TotalCacheRootsChecked++

    Write-Log ("Checking {0} cache for profile {1}: {2}" -f $TeamsType, $ProfileName, $CacheRoot)

    if (-not (Test-SafeTeamsCacheRoot -Path $CacheRoot)) {
        throw "Safety check failed. Refusing to clear unexpected path: $CacheRoot"
    }

    if (-not (Test-Path -LiteralPath $CacheRoot)) {
        Write-Log ("Cache root was not found. Skipping {0} cache for profile {1}." -f $TeamsType, $ProfileName)
        return
    }

    $script:TotalCacheRootsFound++

    $Items = @(Get-ChildItem -LiteralPath $CacheRoot -Force -ErrorAction SilentlyContinue)

    if (-not $Items -or $Items.Count -eq 0) {
        Write-Log ("Cache root exists but contains no items. Skipping {0} cache for profile {1}." -f $TeamsType, $ProfileName)
        return
    }

    $script:TotalItemsDiscovered += $Items.Count

    Write-Log ("Found {0} cache item(s) in {1} cache for profile {2}." -f $Items.Count, $TeamsType, $ProfileName)

    if ($ShouldDryRunOnly) {
        foreach ($Item in $Items) {
            Write-Log ("Dry run only. Would remove: {0}" -f $Item.FullName)
        }

        return
    }

    if ($ShouldCreateBackupBeforeCleanup) {
        $BackupSuccessful = Backup-TeamsCacheRoot -CacheRoot $CacheRoot -ProfileName $ProfileName -TeamsType $TeamsType

        if (-not $BackupSuccessful) {
            throw "Backup failed. Cleanup stopped because -CreateBackupBeforeCleanup is enabled."
        }
    }

    $AnyFailures = $false

    foreach ($Item in $Items) {
        $RemoveDescription = "Remove cache item $($Item.FullName)"

        $Removed = Invoke-WithRetry -Description $RemoveDescription -Action {
            Remove-Item -LiteralPath $Item.FullName -Recurse -Force -ErrorAction Stop
        }

        if ($Removed) {
            $script:TotalItemsRemoved++
        }
        else {
            $AnyFailures = $true
            $script:TotalItemsFailed++
        }
    }

    $RemainingItems = @(Get-ChildItem -LiteralPath $CacheRoot -Force -ErrorAction SilentlyContinue)

    if ($RemainingItems.Count -gt 0 -and $ShouldUseRobocopyFallbackForStubbornFolders) {
        Write-Log -Level 'WARN' -Message ("{0} item(s) remain after standard deletion. Trying robocopy fallback." -f $RemainingItems.Count)

        $FallbackSucceeded = Clear-FolderUsingRobocopyMirror -CacheRoot $CacheRoot

        if ($FallbackSucceeded) {
            $RemainingAfterFallback = @(Get-ChildItem -LiteralPath $CacheRoot -Force -ErrorAction SilentlyContinue)

            if ($RemainingAfterFallback.Count -eq 0) {
                $script:TotalCacheRootsCleared++
                Write-Log -Level 'SUCCESS' -Message ("Cleared {0} cache for profile {1}: {2}" -f $TeamsType, $ProfileName, $CacheRoot)
                return
            }

            Write-Log -Level 'ERROR' -Message ("Robocopy fallback completed, but {0} item(s) still remain in: {1}" -f $RemainingAfterFallback.Count, $CacheRoot)
            $script:TotalItemsFailed += $RemainingAfterFallback.Count
            return
        }
    }

    $RemainingItems = @(Get-ChildItem -LiteralPath $CacheRoot -Force -ErrorAction SilentlyContinue)

    if ($RemainingItems.Count -eq 0) {
        $script:TotalCacheRootsCleared++
        Write-Log -Level 'SUCCESS' -Message ("Cleared {0} cache for profile {1}: {2}" -f $TeamsType, $ProfileName, $CacheRoot)
    }
    else {
        Write-Log -Level 'ERROR' -Message ("Cleanup completed with remaining items. Remaining item count: {0}. Path: {1}" -f $RemainingItems.Count, $CacheRoot)

        foreach ($RemainingItem in $RemainingItems) {
            Write-Log -Level 'WARN' -Message ("Remaining item: {0}" -f $RemainingItem.FullName)
        }

        if (-not $AnyFailures) {
            $script:TotalItemsFailed += $RemainingItems.Count
        }
    }
}

function Reset-NewTeamsAppPackageForCurrentUser {
    if (-not $ShouldResetNewTeamsAppPackage) {
        return
    }

    if ($ShouldClearAllUserProfiles) {
        Write-Log -Level 'WARN' -Message "New Teams AppX reset is only supported by this script for the current user. Skipping AppX reset because all-user cleanup is enabled."
        return
    }

    Write-Log -Level 'WARN' -Message "ResetNewTeamsAppPackage is enabled. This can remove New Teams app data and personalization settings for the current user."

    $PackageFullName = Get-NewTeamsPackageFullName

    if ([string]::IsNullOrWhiteSpace($PackageFullName)) {
        Write-Log "New Teams AppX package was not found for the current user. Skipping AppX reset."
        return
    }

    if ($ShouldDryRunOnly) {
        Write-Log ("Dry run only. Would reset AppX package: {0}" -f $PackageFullName)
        return
    }

    $ResetAppxPackageCommand = Get-Command -Name Reset-AppxPackage -ErrorAction SilentlyContinue

    if ($ResetAppxPackageCommand) {
        $ResetSucceeded = Invoke-WithRetry -Description ("Reset AppX package {0}" -f $PackageFullName) -Action {
            Reset-AppxPackage -Package $PackageFullName -ErrorAction Stop
        }

        if ($ResetSucceeded) {
            Write-Log -Level 'SUCCESS' -Message ("Reset New Teams AppX package for current user: {0}" -f $PackageFullName)
            return
        }
    }

    try {
        $CommandText = @"
`$ErrorActionPreference = 'Stop'
Reset-AppxPackage -Package '$PackageFullName'
"@

        $Result = Invoke-WindowsPowerShellTextCommand -CommandText $CommandText

        if ($Result.ExitCode -eq 0) {
            Write-Log -Level 'SUCCESS' -Message ("Reset New Teams AppX package for current user by Windows PowerShell fallback: {0}" -f $PackageFullName)
            return
        }

        Write-Log -Level 'ERROR' -Message ("Windows PowerShell fallback failed to reset New Teams AppX package. Exit code: {0}. Error: {1}" -f $Result.ExitCode, $Result.StandardError.Trim())
    }
    catch {
        Write-Log -Level 'ERROR' -Message ("Unable to reset New Teams AppX package. Error: {0}" -f $_.Exception.Message)
    }
}

function Start-MicrosoftTeams {
    if (-not $ShouldRestartTeamsAfterCleanup) {
        Write-Log "Restart after cleanup was skipped. Teams will not be restarted automatically."
        return
    }

    if ($ShouldDryRunOnly) {
        Write-Log "Dry run only. Teams will not be restarted."
        return
    }

    if ($ShouldClearAllUserProfiles -and -not $ShouldClearCurrentUserProfile) {
        Write-Log -Level 'WARN' -Message "All-user cleanup was selected without current-user cleanup. Teams will not be restarted automatically."
        return
    }

    Write-Log "Attempting to restart Microsoft Teams for the current user."

    $StartedTeams = $false

    try {
        Start-Process -FilePath 'explorer.exe' -ArgumentList 'shell:AppsFolder\MSTeams_8wekyb3d8bbwe!MSTeams' -ErrorAction Stop
        $StartedTeams = $true
        Write-Log -Level 'SUCCESS' -Message "New Microsoft Teams restart command was sent by AppsFolder shell command."
    }
    catch {
        Write-Log -Level 'WARN' -Message ("Unable to start New Microsoft Teams by AppsFolder shell command. Error: {0}" -f $_.Exception.Message)
    }

    if (-not $StartedTeams) {
        try {
            $CmdExePath = Join-Path -Path $env:SystemRoot -ChildPath 'System32\cmd.exe'

            if (Test-Path -LiteralPath $CmdExePath) {
                Start-Process -FilePath $CmdExePath -ArgumentList '/c start "" "msteams:"' -WindowStyle Hidden -ErrorAction Stop
                $StartedTeams = $true
                Write-Log -Level 'SUCCESS' -Message "New Microsoft Teams restart command was sent using msteams protocol."
            }
        }
        catch {
            Write-Log -Level 'WARN' -Message ("Unable to start New Microsoft Teams by msteams protocol. Error: {0}" -f $_.Exception.Message)
        }
    }

    if (-not $StartedTeams) {
        $ClassicTeamsUpdateExe = Join-Path -Path $env:LOCALAPPDATA -ChildPath 'Microsoft\Teams\Update.exe'

        if (Test-Path -LiteralPath $ClassicTeamsUpdateExe) {
            try {
                Start-Process -FilePath $ClassicTeamsUpdateExe -ArgumentList '--processStart "Teams.exe"' -ErrorAction Stop
                $StartedTeams = $true
                Write-Log -Level 'SUCCESS' -Message "Classic Microsoft Teams restart command was sent."
            }
            catch {
                Write-Log -Level 'WARN' -Message ("Unable to start Classic Microsoft Teams. Error: {0}" -f $_.Exception.Message)
            }
        }
    }

    if (-not $StartedTeams) {
        Write-Log -Level 'WARN' -Message "Teams was not restarted automatically. Open Microsoft Teams manually."
    }
}

function Write-Summary {
    Write-Log "============================================================"
    Write-Log "Microsoft Teams cache cleanup summary"
    Write-Log "============================================================"
    Write-Log ("PowerShell host: {0}" -f (Get-PowerShellHostDescription))
    Write-Log ("Dry run only: {0}" -f $ShouldDryRunOnly)
    Write-Log ("Current user cleanup: {0}" -f $ShouldClearCurrentUserProfile)
    Write-Log ("All user profile cleanup: {0}" -f $ShouldClearAllUserProfiles)
    Write-Log ("Classic Teams included: {0}" -f $ShouldIncludeClassicTeams)
    Write-Log ("New Teams included: {0}" -f $ShouldIncludeNewTeams)
    Write-Log ("Cache roots checked: {0}" -f $script:TotalCacheRootsChecked)
    Write-Log ("Cache roots found: {0}" -f $script:TotalCacheRootsFound)
    Write-Log ("Cache roots fully cleared: {0}" -f $script:TotalCacheRootsCleared)
    Write-Log ("Cache items discovered: {0}" -f $script:TotalItemsDiscovered)
    Write-Log ("Cache items removed by standard deletion: {0}" -f $script:TotalItemsRemoved)
    Write-Log ("Cache item failures reported: {0}" -f $script:TotalItemsFailed)
    Write-Log ("Teams-related processes stopped or already exited: {0}" -f $script:TotalProcessesStopped)
    Write-Log ("Teams-related processes skipped: {0}" -f $script:TotalProcessesSkipped)

    if ($ShouldEnableLogFile) {
        Write-Log ("Log file: {0}" -f $LogFilePath)
    }

    if ($ShouldCreateBackupBeforeCleanup) {
        Write-Log ("Backup root: {0}" -f $BackupRoot)
    }

    Write-Log "============================================================"
}

try {
    Write-Log "Starting Microsoft Teams cache cleanup."
    Write-Log ("PowerShell host: {0}" -f (Get-PowerShellHostDescription))

    if (-not (Test-IsWindows)) {
        throw "This script is intended for Windows only."
    }

    if (-not $ShouldClearCurrentUserProfile -and -not $ShouldClearAllUserProfiles) {
        throw "Nothing to do. Do not use -SkipCurrentUserProfile unless also using -ClearAllUserProfiles."
    }

    if (-not $ShouldIncludeClassicTeams -and -not $ShouldIncludeNewTeams) {
        throw "Nothing to do. Do not use both -SkipClassicTeams and -SkipNewTeams."
    }

    if ($ShouldClearAllUserProfiles -and -not (Test-IsAdministrator)) {
        throw "Clearing all user profiles requires PowerShell to be run as Administrator."
    }

    if ($ShouldResetNewTeamsAppPackage -and $ShouldCreateBackupBeforeCleanup) {
        Write-Log -Level 'WARN' -Message "Backup only covers cache folders. AppX reset data is not backed up by this script."
    }

    if ($ShouldEnableLogFile) {
        Write-Log ("Log file enabled: {0}" -f $LogFilePath)
    }

    if ($ShouldDryRunOnly) {
        Write-Log -Level 'WARN' -Message "DryRunOnly is enabled. No cache files or folders will be removed."
    }

    if ($ShouldStopTeamsBeforeCleanup) {
        Stop-TeamsProcesses -AllUsers:$ShouldClearAllUserProfiles
    }
    else {
        Write-Log -Level 'WARN' -Message "Stopping Teams before cleanup was skipped. Locked cache files may remain."
    }

    $ProfilesToProcess = @()
    $CurrentUserSid = Get-CurrentUserSid
    $CurrentProfilePath = $env:USERPROFILE
    $CurrentProfileName = Split-Path -Path $CurrentProfilePath -Leaf

    if ($ShouldClearCurrentUserProfile) {
        if ([string]::IsNullOrWhiteSpace($CurrentProfilePath) -or -not (Test-Path -LiteralPath $CurrentProfilePath)) {
            throw "Current user profile path was not found."
        }

        $ProfilesToProcess += [PSCustomObject]@{
            ProfileName = $CurrentProfileName
            ProfilePath = $CurrentProfilePath
            Source      = 'CurrentUser'
        }
    }

    if ($ShouldClearAllUserProfiles) {
        $AllProfiles = @(Get-LocalUserProfiles)

        foreach ($Profile in $AllProfiles) {
            if ($SkipCurrentUserProfile.IsPresent) {
                if ($Profile.ProfilePath.Equals($CurrentProfilePath, [System.StringComparison]::OrdinalIgnoreCase)) {
                    Write-Log ("Skipping current user profile because -SkipCurrentUserProfile was used: {0}" -f $Profile.ProfilePath)
                    continue
                }
            }

            $ProfilesToProcess += $Profile
        }
    }

    $ProfilesToProcess = @($ProfilesToProcess | Sort-Object -Property ProfilePath -Unique)

    if (-not $ProfilesToProcess -or $ProfilesToProcess.Count -eq 0) {
        throw "No user profiles were selected for processing."
    }

    Write-Log ("User profiles selected for processing: {0}" -f $ProfilesToProcess.Count)

    $CacheTargets = @()

    foreach ($Profile in $ProfilesToProcess) {
        Write-Log ("Preparing cache targets for profile {0}: {1}" -f $Profile.ProfileName, $Profile.ProfilePath)

        $CacheTargets += Get-TeamsCacheTargetsForProfile -ProfilePath $Profile.ProfilePath -ProfileName $Profile.ProfileName
    }

    $CacheTargets = @($CacheTargets | Sort-Object -Property CacheRoot -Unique)

    foreach ($Target in $CacheTargets) {
        Clear-TeamsCacheRoot -CacheRoot $Target.CacheRoot -ProfileName $Target.ProfileName -TeamsType $Target.TeamsType
    }

    Reset-NewTeamsAppPackageForCurrentUser

    Start-MicrosoftTeams

    Write-Summary

    if ($script:TotalItemsFailed -gt 0) {
        Write-Log -Level 'WARN' -Message "Cleanup completed, but one or more items could not be removed. Review the log output above."
    }
    else {
        Write-Log -Level 'SUCCESS' -Message "Microsoft Teams cache cleanup completed successfully."
    }
}
catch {
    Write-Log -Level 'ERROR' -Message ("Microsoft Teams cache cleanup failed. Error: {0}" -f $_.Exception.Message)
    Write-Summary
    throw
}

:speech_balloon: Share Your Experience

Have you run into Teams cache issues that this script fixed—or one it didn’t? Let me know how you handle Teams cache cleanup at scale!

Page Views


Share on:
About Blake Drumm
Blake Drumm

I like to collaborate and work on projects. My skills with Powershell allow me to quickly develop automated solutions to suit my customers, and my own needs.

Email :

Website :

About Blake Drumm

My name is Blake Drumm, I am working on the Azure Monitoring Enterprise Team with Microsoft. Currently working to update public documentation for System Center products and write troubleshooting guides to assist with fixing issues that may arise while using the products. I like to blog on Operations Manager and Azure Automation products, keep checking back for new posts. My goal is to post atleast once a month if possible.

Follow @blakedrumm
Useful Links