Re-profile Microsoft Teams and Clear Cache PowerShell Script

The Microsoft Teams revolution is firmly upon us, but at times the desktop client requires some attention. You may need a Re-profile Microsoft Teams script to repair the desktop client or perhaps clear some of the local caches. In Teams, sometimes you will find profile pictures are not updating when a user has recently changed theirs in Delve. You may find a user's status can become out of sync, reporting as online when in fact they are out of office. These are telltale signs of local issues with the desktop client and quite simply, the easiest remedy is a fix-all approach and to re-create the local user profile.

This post will provide you with a PowerShell script that is made up of a single advanced function that will carry that task out for you. If that is what you are looking for, or even if it's not, read on!

Error 'Folder In Use' When Re-Profiling Microsoft Teams

When the need to re-create the Teams user profile arises, you can navigate to %APPDATA%\Microsoft and delete or rename the folder called 'Teams'. If you attempt to do this with Teams or Outlook open you will get the error pictured called 'Folder In Use'.

This means, that front-line support staff or users experiencing this issue will have to manually close down Teams, then Outlook and once both are closed, the folder will need renaming or deleting. Once these tasks have been carried out, you would then proceed in re-opening each application.

Easy, I hear you say … Well, what if you want to save time or increase accuracy and to carry this task out in one simple process from PowerShell? That's right, you will need a script that furnishes you with a 1 step approach.

The Re-profile Microsoft Teams PowerShell Script

If you vistit AlanPs1 github, you will be able to clone the script or copy it from below. This script carries out some key steps.

  • Create a unique name to rename the 'Teams' folder and store it in a variable
  • Capture the username of the logged-on user
  • Construct the logged-on user's $Home drive location
  • Check the status of Teams process, is it currently running?
  • Check the status of Outlook process, is it currently running?
  • If running, the script will close Microsoft Teams & Outlook
  • Rename the 'Teams' folder thus clearing all local caches etc …
  • Re-open the previously opened programs, Teams or Outlook, or both.

As mentioned above, the script then goes on to re-open applications that were active at the time of script invocation. The version below then provides some feedback to the console to let you know what programs were running and if they required closure at that time. It then rounds off by providing clarity around whether the folder renaming was successful or not. This Microsoft Teams re-profile script or Microsoft Teams clear cache script will re-open Microsoft Teams or Microsoft Outlook if they were open at the time of invoking the script. If only Teams was running per se, only Teams would re-open. If both MS Teams & Outlook were open, it will re-launch both of the applications to allow the user to easily return to their working applications, sign in and get back to their work.

That's enough background for now. So here is the important part of this post … Go on, give it a copy & paste and see the result.

Would you like to buy Alan a coffee?

Visit the AlanPs1 Ko-fi page

Function Invoke-TeamsReprofile {

    <#
    .SYNOPSIS
    Invoke-TeamsReprofile is a simple function that will re-profile the MS Teams
    user profile for the Teams desktop client on a windows 10 pc.

    .DESCRIPTION
    This Function will test for MS Teams & MS Outlook running then will close them
    both in advance of re-naming the 'Teams' folder in %APPDATA%. Once the re-naming,
    or re-profiling has occurred it will restart MS Teams & MS Outlook if they were
    previously running

    .EXAMPLE
    PS C:\> Invoke-TeamsReprofile

    .NOTES

    Author:  AlanPs1
    Website: http://AlanPs1.io
    Twitter: @AlanO365

    #>

    [OutputType()]
    [CmdletBinding()]
    Param()

    BEGIN {

        # Create a small guid type value to help avoid folder re-naming clashes
        $Guid = (New-Guid).Guid.Split('-')[4]

        # Populate $Date variable to suit either UK or US format
        If ((Get-Culture).LCID -eq "1033") {

            $Date = (Get-Date).tostring("MM_dd_yy")

        } Else {

            $Date = (Get-Date).tostring("dd_MM_yy")

        }

        # Build the unique name for the folder re-naming
        $NewFolderName = "Teams.Old_$($Date)_$($Guid)"

        # Capture logged on user's username
        $LoggedOnUser = (Get-CimInstance -ClassName Win32_ComputerSystem | Select-Object UserName).UserName.Split('\')[1]

        # Construct the logged on user's equivelant to $Home variable
        $LoggedOnUserHome = "$($Home.Split($($LoggedOnUser))[0])$($LoggedOnUser)"

        # Get MS Teams process. Only using 'SilentlyContinue' as we test this below
        $TeamsProcess = Get-Process Teams -ErrorAction SilentlyContinue

        # Get Outlook process. Only using 'SilentlyContinue' as we test this below
        $OutlookProcess = Get-Process Outlook -ErrorAction SilentlyContinue

        # Get Teams Folder
        $TeamsFolder = Get-Item "$LoggedOnUserHome\AppData\Roaming\Microsoft\Teams" -ErrorAction SilentlyContinue

    }

    PROCESS {

        If ($TeamsProcess) {
    
            # If 'Teams' process is running, stop it else do nothing
            $TeamsProcess | Stop-Process -Force
            Write-Host "MS Teams process was running, so we stopped it" -ForegroundColor Green

        }
        Else {

            Write-Host "MS Teams process was not running, so we ignored it" -ForegroundColor Yellow

        }

        If ($OutlookProcess) {
    
            # If 'Outlook' process is running, stop it else do nothing
            $OutlookProcess | Stop-Process -Force
            Write-Host "Outlook process was running, so we stopped it" -ForegroundColor Green

        }
        Else {

            Write-Host "Outlook process was not running, so we ignored it" -ForegroundColor Yellow

        }

        # Give the processes a little time to completely stop to avoid error
        Start-Sleep -Seconds 10

        If ($TeamsFolder) {
    
            # If 'Teams' folder exists in %APPDATA%\Microsoft\Teams, rename it
            Rename-Item "$LoggedOnUserHome\AppData\Roaming\Microsoft\Teams" "$LoggedOnUserHome\AppData\Roaming\Microsoft\$NewFolderName"
            Write-Host "Great news, 'Teams' folder has been renamed to '$NewFolderName'" -ForegroundColor Cyan
        } Else {

            # If 'Teams' folder does not exist notify user then break
            Write-Host "There is no folder called 'Teams'. Open MS Teams, it will create one then try again" -ForegroundColor Red
            break
        }

        # Give the folder a couple of seconds to fully rename to avoide error
        Start-Sleep -Seconds 2

        # Restart MS Teams desktop client
        If ($TeamsProcess) { 

            Start-Process -File "$LoggedOnUserHome\AppData\Local\Microsoft\Teams\Update.exe" -ArgumentList '--processStart "Teams.exe"'
            Write-Host "MS Teams was initially running, so we restarted it" -ForegroundColor Green
        }
        Else {

            Write-Host "MS Teams wasn't running initially, so no need to restart" -ForegroundColor Yellow

        }
    
        # Restart Outlook
        If ($OutlookProcess) {

            $OutlookExe = Get-ChildItem -Path 'C:\Program Files\Microsoft Office\root\Office16' -Filter Outlook.exe -Recurse -ErrorAction SilentlyContinue -Force | 
                Where-Object { $_.Directory -notlike "*Updates*" } | 
                Select-Object Name, Directory

            If (!$OutlookExe) {

                $OutlookExe = Get-ChildItem -Path 'C:\Program Files (x86)\Microsoft Office\root\Office16' -Filter Outlook.exe -Recurse -ErrorAction SilentlyContinue -Force | 
                Where-Object { $_.Directory -notlike "*Updates*" } | 
                Select-Object Name, Directory
            }

            Write-Host "Outlook was initially running, so we restarted it" -ForegroundColor Green

        }
        Else {

            Write-Host "Outlook wasn't running initially, so no need to restart" -ForegroundColor Yellow

        }

    }

    END {

        # Check for newly renamed folder
        $NewlyRenamedFolder = Get-Item "$LoggedOnUserHome\AppData\Roaming\Microsoft\$NewFolderName" -ErrorAction SilentlyContinue

        If ($NewlyRenamedFolder) { 

            # If newly renamed folder exists, output success message
            Write-Host
            Write-Host "Congratulations, you have successfully recreated the MS Teams profile for user: $($LoggedOnUser)" -ForegroundColor White
            Write-Host

        } Else {

            # If there is no newly renamed folder, output failure message
            Write-Host
            Write-Host "Uht oht, there was an issue re-profiling MS Teams for user: $($LoggedOnUser)" -ForegroundColor Red
            Write-Host

        }

        # Optional - clean up some variables etc ...
        Remove-Variable TeamsProcess
        Remove-Variable OutlookProcess
        Remove-Variable TeamsFolder

    }

}

Invoke-TeamsReprofile

Code of this nature is best supported with some explanations. Let us now go on to break down and further explain some of the code snippets. Following the structure of a PowerShell Advanced Function, I will cover the code within the begin, process, and end blocks in order.

The Begin Block

Initially, I create a guid using New-Guid, which would normally look something like this: 0f9eddde-0181-44f0-b92b-9e881202db89. A guid is 5 sets of alphanumeric characters separated by 4 hyphens. Here we split the entire guid and just use the fifth grouping of alpha-numeric characters. We are only creating this to avoid folder renaming clashes. Clashes could occur should a profile be recreated more than once on any given day. I then go on to use Get-Culture to test for LCID equalling 1033. 1033 would match US date formats so I would load my $Date variable as the US. If no match to 1033, it would load the $Date variable with the UK format.

Now I load a variable called $NewFolderName. Formatted as follows: Teams.Old_$($Date)_$($Guid), meaning a renamed folder would become Teams.Old_28_04_21_9e881202db89 using the example guid above.

# Create a small guid type value to help avoid folder re-naming clashes
$Guid = (New-Guid).Guid.Split('-')[4]

# Populate $Date variable to suit either UK or US format
If ((Get-Culture).LCID -eq "1033") {

    $Date = (Get-Date).tostring("MM_dd_yy")

} Else {

    $Date = (Get-Date).tostring("dd_MM_yy")

}

# Build the unique name for the folder re-naming
$NewFolderName = "Teams.Old_$($Date)_$($Guid)"

This code can be used on any user's device. So we need to capture the logged-on user's home drive location and their username. Assuming all profiles have the same location (likely C:\Users), we split the $Home variable for the administrator. See Get-CimInstance and its many classes to get some additional information and alternative methods to capture this value.

$LoggedOnUser = JohnDoe

$LoggedOnUserHome = C:\Users\JohnDoe

# Capture logged on user's username
$LoggedOnUser = (Get-CimInstance -ClassName Win32_ComputerSystem | Select-Object UserName).UserName.Split('\')[1]

# Construct the logged on user's equivelant to $Home variable
$LoggedOnUserHome = "$($Home.Split($($LoggedOnUser))[0])$($LoggedOnUser)"

NB: I have writen a dedicated blog post on the topic 2 libes of code above. It has many uses so please see: Get Logged-On User's Home Drive Location

It's not often I would promote the use of -ErrorAction SilentlyContinue, and would normally favour Try/Catch methods. For this example, I can't see an issue as I test the contents of the variable later in the code. At this stage, I am loading 3 key variables that will help with the overall process as we move on.

# Get MS Teams process. Only using 'SilentlyContinue' as we test this below
$TeamsProcess = Get-Process Teams -ErrorAction SilentlyContinue

# Get Outlook process. Only using 'SilentlyContinue' as we test this below
$OutlookProcess = Get-Process Outlook -ErrorAction SilentlyContinue

# Get Teams Folder
$TeamsFolder = Get-Item "$LoggedOnUserHome\AppData\Roaming\Microsoft\Teams" -ErrorAction SilentlyContinue

The Process Block

This is the section of the advanced function where the main action occurs.

As listed in the comments above each command, I am testing to see if the process variables contain a value. Next, we go on to kill the process if they are running. I also output data to the console stating the actions taken if there were actions taken. In order, we target MS Teams followed by MS Outlook. Rounding things off by delaying for ten seconds to give each process time to fully stop. What are ten seconds afterall to avoid error?

If ($TeamsProcess) {
    
    # If 'Teams' process is running, stop it else do nothing
    $TeamsProcess | Stop-Process -Force
    Write-Host "MS Teams process was running, so we stopped it" -ForegroundColor Green

}
Else {

    Write-Host "MS Teams process was not running, so we ignored it" -ForegroundColor Yellow

}

If ($OutlookProcess) {

    # If 'Outlook' process is running, stop it else do nothing
    $OutlookProcess | Stop-Process -Force
    Write-Host "Outlook process was running, so we stopped it" -ForegroundColor Green

}
Else {

    Write-Host "Outlook process was not running, so we ignored it" -ForegroundColor Yellow

}

# Give the processes a little time to completely stop to avoid error
Start-Sleep -Seconds 10

At this stage, we are ready to rename our 'Teams' folder as our applications will have stopped running. You will have seen the applications visually close before your eyes if you were actually running the script. I first test to make sure the folder exists then rename it using Rename-Item. You will see I am calling the data from previously loaded variables to target the folder path. If the folder weren't to exist at that location, that could mean that it has been deleted already. The script would break at that stage with no further action. It then rounds off with a small two-second delay just to allow time for the folder renaming. The renaming should be instant, but again, what is two seconds to wait?

If ($TeamsFolder) {
    
    # If 'Teams' folder exists in %APPDATA%\Microsoft\Teams, rename it
    Rename-Item "$LoggedOnUserHome\AppData\Roaming\Microsoft\Teams" "$LoggedOnUserHome\AppData\Roaming\Microsoft\$NewFolderName"
    Write-Host "Great news, 'Teams' folder has been renamed to '$NewFolderName'" -ForegroundColor Cyan
} Else {

    # If 'Teams' folder does not exist notify user then break
    Write-Host "There is no folder called 'Teams'. Open MS Teams, it will create one then try again" -ForegroundColor Red
    break
}

# Give the folder a couple of seconds to fully rename to avoide error
Start-Sleep -Seconds 2

Now for a little nice to have feature. Having gone to the length of writing the code, we don't need our front-line support staff re-opening their applications. Once again, we use the previously loaded process variables, and if populated we re-launch the application. We then output to the console to provide feedback for the user.

# Restart MS Teams desktop client
If ($TeamsProcess) { 

    Start-Process -File "$LoggedOnUserHome\AppData\Local\Microsoft\Teams\Update.exe" -ArgumentList '--processStart "Teams.exe"'
    Write-Host "MS Teams was initially running, so we restarted it" -ForegroundColor Green
}
Else {

    Write-Host "MS Teams wasn't running initially, so no need to restart" -ForegroundColor Yellow

}

# Restart Outlook
If ($OutlookProcess) {

    $OutlookExe = Get-ChildItem -Path 'C:\Program Files\Microsoft Office\root\Office16' -Filter Outlook.exe -Recurse -ErrorAction SilentlyContinue -Force | 
        Where-Object { $_.Directory -notlike "*Updates*" } | 
        Select-Object Name, Directory

    If (!$OutlookExe) {

        $OutlookExe = Get-ChildItem -Path 'C:\Program Files (x86)\Microsoft Office\root\Office16' -Filter Outlook.exe -Recurse -ErrorAction SilentlyContinue -Force | 
        Where-Object { $_.Directory -notlike "*Updates*" } | 
        Select-Object Name, Directory

    }

    Write-Host "Outlook was initially running, so we restarted it" -ForegroundColor Green

}
Else {

    Write-Host "Outlook wasn't running initially, so no need to restart" -ForegroundColor Yellow

}

The End Block

Our journey now takes us to the end block. Let's test the success of the process and do some cleanup.

Firstly, we check for the existence of the newly renamed folder by loading the variable $NewlyRenamedFolder. If the variable is not $null, we have success and can provide a success message using Write-Host to the console. If there is no sign of the newly renamed folder, there has been an issue. The script may have had to break, so we inform the user of its failure to rename the folder.

We then move on to use Remove-Variable to clean up some variables. This step is not required as the scope of the variable is at the function level. That means they will not be able to be interrogated from the console. I left them in for food for thought, however. It's possible someone may expand on this solution with other functions. You may see this and want to create a module or a WPF application that carries out multiple tasks per se.

# Check for newly renamed folder
$NewlyRenamedFolder = Get-Item "$LoggedOnUserHome\AppData\Roaming\Microsoft\$NewFolderName" -ErrorAction SilentlyContinue

If ($NewlyRenamedFolder) { 

    # If newly renamed folder exists, output success message
    Write-Host
    Write-Host "Congratulations, you have successfully recreated the MS Teams profile for user: $($LoggedOnUser)" -ForegroundColor White
    Write-Host

} Else {

    # If there is no newly renamed folder, output failure message
    Write-Host
    Write-Host "Uht oht, there was an issue re-profiling MS Teams for user: $($LoggedOnUser)" -ForegroundColor Red
    Write-Host

}

# Optional - clean up some variables etc ...
Remove-Variable TeamsProcess
Remove-Variable OutlookProcess
Remove-Variable TeamsFolder

I now look forward to you giving this a try yourself. By all means, take my code and use it to build a simple PowerShell WPF app that can invoke the function. Feel free to remove the Write-Host lines that prints output to the screen. These are just here for demonstration. They could help anyone who may use this script get a clearer understanding of the tasks it has carried out.

Thanks for reading,

Alan