Simple WPF App Re-Profile Microsoft Teams

There are many uses for a simple WPF app so here is an example written purely in PowerShell. In a recent post, I explained how to Re-profile Microsoft Teams with a PowerShell Script, so I thought I'd enhance that a little. WPF Applications written in PowerShell can become very advanced when you start to use PowerShell Runspaces and decide to code several menus, tabs or buttons etc. For now, let's just get a good starting point, and below we will step through a couple of methods that are useful to understand from the outset.

A quick google search will help you find many a blog on this subject using varying techniques. I find the methods detailed in this post quick to implement and can generally gain the functionality I require when PowerShell Toolmaking. The tool we will make in this post will be a basic iteration of 'Teams Helper' and a quick glimpse of what it will look like is pictured. Simple but effective I'd like to think.

We will not explore compiling a .ps1 file into an executable file just yet. At this stage, we will be able to open up powershell.exe and run the script which presents us with the application above. Click Me! says the button on the application. In doing so, the Add_Click method will invoke the script and re-profile Microsoft Teams.

Learn WPF Apps Using PowerShell

From a usability perspective, this is almost counterproductive when you compare the process to just running the script with no GUI. This blog, however, is to introduce the basics around making your script into a GUI tool. For that purpose, it works quite well. We will cover some other advanced topics in some upcoming blog posts to help make this tool something that can become enterprise-ready.

When we consider the format of the label in the code block below, we will see parameters configured within the tag. This is great, and a method you will get used to if you were to use visual studio to write the xaml for you.

<Label Name="Label_Heading" Content="Re-Profile Microsoft Teams" Margin="0,25,0,0" FontSize="14" HorizontalContentAlignment="Center"/>

There are other options when writing the WPF app in PowerShell that you may find easier to work with. The below code shows you a button in WPF, but it has a lot less of the configuration held within.

<Button Name="Button_ReProfile">
    <Button.Resources>
        <Style TargetType="Border">
            <Setter Property="CornerRadius" Value="35"/>
        </Style>
    </Button.Resources>
</Button>

What I like about the latter of the methods is that you can use the FindName method to locate the tag within the window. The next code you will read is that method in action.

# Use .FindName() to locate the WPF element prior to styling
$Button_ReProfile = $window.FindName("Button_ReProfile")

$Button_ReProfile.Content = "C L I C K   M E"
$Button_ReProfile.Margin = "40"
$Button_ReProfile.VerticalAlignment = "Bottom"
$Button_ReProfile.Width = "140"
$Button_ReProfile.Height = "140"
$Button_ReProfile.Background = "#7eabfd"
$Button_ReProfile.BorderBrush = "White"
$Button_ReProfile.Foreground = "White"
$Button_ReProfile.Padding = "8"
$Button_ReProfile.ToolTip = "Click to Re-profile Teams"

The key advantage of the latter method comes when you start to become more advance with WPF applications written in PowerShell. Let's just say hypothetically that you were to click a button, and on-click you want to change an element. You could then change $Button_ReProfile.Background = "#7eabfd" to $Button_ReProfile.Background = "#f00". If you were to favour the initial method, you have some hard coded values that become challenging to modify.

Would you like to buy Alan a coffee?

Visit the AlanPs1 Ko-fi page

Even if I google for code inspiration and find some WPF with tag level customisation, I convert them to property based customisations in my PowerShell. Thank me later ;).

Re-Profile Microsoft Teams WPF APP

If you visit AlanPs1 GitHub PowerShell-WPF, you will be able to clone the script or copy it from below. If you were to have a look at Re-profile Microsoft Teams with a PowerShell Script you will get to grips with the task the script carries out. So here is the full piece of code so that you can copy and paste it and give it a run for yourself.

Add-Type -AssemblyName PresentationFramework

[xml]$Xaml = @"
<Window
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Name="Window"
    Title="Teams Helper"
    WindowStartupLocation="CenterScreen"
    Width="300"
    Height="300"
    ShowInTaskbar="True">
    <Grid Name="Grid">

        <Label Name="Label_Heading" Content="Re-Profile Microsoft Teams" Margin="0,25,0,0" FontSize="14" HorizontalContentAlignment="Center"/>

        <Button Name="Button_ReProfile" >
            <Button.Resources>
                <Style TargetType="Border">
                    <Setter Property="CornerRadius" Value="35"/>
                </Style>
            </Button.Resources>
        </Button>

    </Grid>
</Window>
"@
$Reader = (New-Object System.Xml.XmlNodeReader $Xaml)
$Window = [Windows.Markup.XamlReader]::Load($Reader)

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 = "C:\Users\$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
            

        }
        Else {

            # Do something - removed Write-Host

        }

        If ($OutlookProcess) {
    
            # If 'Outlook' process is running, stop it else do nothing
            $OutlookProcess | Stop-Process -Force
            

        }
        Else {

            # Do something - removed Write-Host

        }

        # 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"
        }
        Else {

            # If 'Teams' folder does not exist notify user then break
            # Do something - removed Write-Host
            break
        }

        # Give the folder a couple of seconds to fully rename to avoid 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"'
        }
        Else {

            # Do something - removed Write-Host

        }
    
        # 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
            }
            
            Start-Process -File "$($OutlookExe.Directory)\$($OutlookExe.Name)"

        }
        Else {

            # Do something - removed Write-Host

        }

    }

    END {

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

        If ($NewlyRenamedFolder) { 

            # Do something - removed Write-Host

        }
        Else {

            # Do Something - removed Write-Host

        }

    }

}

# Use .FindName() to locate the WPF element prior to styling
$Button_ReProfile = $window.FindName("Button_ReProfile")

$Button_ReProfile.Content = "C L I C K   M E"
$Button_ReProfile.Margin = "40"
$Button_ReProfile.VerticalAlignment = "Bottom"
$Button_ReProfile.Width = "140"
$Button_ReProfile.Height = "140"
$Button_ReProfile.Background = "#7eabfd"
$Button_ReProfile.BorderBrush = "White"
$Button_ReProfile.Foreground = "White"
$Button_ReProfile.Padding = "8"
$Button_ReProfile.ToolTip = "Click to Re-profile Teams"

$Button_ReProfile.Add_MouseEnter( {
    
    $Window.Cursor = [System.Windows.Input.Cursors]::Hand
    $Button_ReProfile.ForeGround = '#7eabfd'

})

$Button_ReProfile.Add_MouseLeave( {
    
    $Window.Cursor = [System.Windows.Input.Cursors]::Arrow
    $Button_ReProfile.ForeGround = '#ffffff'

})

$Button_ReProfile.Add_Click( {
    
    Invoke-TeamsReprofile
    $Window.Close()

})

$Window.ShowDialog() | Out-Null

Let's Look a Little Closer at the WPF PowerShell Script

On line 1 we load the WPF .NET Assembly into the PowerShell session. Without this step, the required .NET classes wouldn't mean very little to PowerShell and throw an error. A variable that denotes the XML ($Xaml) format is then initiated and the XAML added within @" "@. This is the app itself. We then load the $Reader variable with a new object of the type XmlNodeReader. An object of this type can be called by the Load() method to allow the creation of our window.

Add-Type -AssemblyName PresentationFramework

[xml]$Xaml = @"
<Window
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Name="Window"
    Title="Teams Helper"
    WindowStartupLocation="CenterScreen"
    Width="300"
    Height="300"
    ShowInTaskbar="True">
    <Grid Name="Grid">

        <Label Name="Label_Heading" Content="Re-Profile Microsoft Teams" Margin="0,25,0,0" FontSize="14" HorizontalContentAlignment="Center"/>

        <Button Name="Button_ReProfile" >
            <Button.Resources>
                <Style TargetType="Border">
                    <Setter Property="CornerRadius" Value="35"/>
                </Style>
            </Button.Resources>
        </Button>

    </Grid>
</Window>
"@
$Reader = (New-Object System.Xml.XmlNodeReader $Xaml)
$Window = [Windows.Markup.XamlReader]::Load($Reader)

We have already understood from above that the button named 'Button_ReProfile' can be located using the FindName() method. Now that we have located the button and assigned it to $Button_ReProfile, we can go on to assign values to each parameter to create some styling for the button.

Button Event Handlers

We introduce 3 of the many event handlers you can apply to WPF Buttons. Add_MouseEnter, Add_MouseLeave & Add_Click.

Add_MouseEnterTriggers as the cursor enters the button
Add_MouseLeaveTriggers as the cursor leaves the button
Add_ClickTriggers on click of the button

For Add_MouseEnter & Add_MouseLeave, we are applying some simple styling to change the cursor. The cursor changes from the basic arrow to the hand or pointer. This enhances the user experience in line with many other applications that deploy this behaviour. The light blue is hover effect is the default hover effect in WPF. The tooltip that we have configured above is also on show here as that is standard behaviour.

# Use .FindName() to locate the WPF element prior to styling
$Button_ReProfile = $window.FindName("Button_ReProfile")

$Button_ReProfile.Content = "C L I C K   M E"
$Button_ReProfile.Margin = "40"
$Button_ReProfile.VerticalAlignment = "Bottom"
$Button_ReProfile.Width = "140"
$Button_ReProfile.Height = "140"
$Button_ReProfile.Background = "#7eabfd"
$Button_ReProfile.BorderBrush = "White"
$Button_ReProfile.Foreground = "White"
$Button_ReProfile.Padding = "8"
$Button_ReProfile.ToolTip = "Click to Re-profile Teams"

$Button_ReProfile.Add_MouseEnter( {
    
    $Window.Cursor = [System.Windows.Input.Cursors]::Hand
    $Button_ReProfile.ForeGround = '#7eabfd'

})

$Button_ReProfile.Add_MouseLeave( {
    
    $Window.Cursor = [System.Windows.Input.Cursors]::Arrow
    $Button_ReProfile.ForeGround = '#ffffff'

})

$Button_ReProfile.Add_Click( {
    
    Invoke-TeamsReprofile
    $Window.Close()

})

As you would expect with an app whose only function is a button that clicks and re-profiles Microsoft Teams. The action happens within Add_Click. When we call the Add_Click event we do 2 things. We firstly run the function Invoke-TeamsReprofile and when that completes, we Close() the window. That pretty much wraps it apart from the most important line of code on the page.

$Window.ShowDialog() | Out-Null

$Window.ShowDialog() wraps it all together and actually launches the app. without this nothing would happen. That completes the journey for this simple WPF Application. I hope you will give it a try yourself.

For a better understanding of the all-important function, see Re-profile Microsoft Teams and Clear Cache PowerShell Script.

Thanks for reading,

Alan