Installing the Teams Outlook Add-In on Systems with WDAC and/or AppLocker
Deploying the Teams Outlook Add-In on systems where WDAC or AppLocker block installation
As a follow up to my series on implementing WDAC (see: https://www.mrgtech.net/implementing-wdac-and-applocker/), I thought I would share a solution to a problem my organisation was experiencing with the Teams Outlook add-in.
The way Microsoft deploys this add-in is... well... weird. When Teams starts, it attempts to install an MSI containing the add-in as the currently logged in user.
Allowing users to install MSI's (even signed ones) is generally frowned upon from a security point of view.
While this MSI could be installed via InTune as system (it would need then need to be registered using regsvr32.exe
so that Outlook sees it), it does mean that you'll have to manage the installer on every release. Another issue we found is that not everyone runs the same version of Teams or the add-in, so the version you push may not be right for the currently installed version of Teams.
Using the WDAC policies I described, the MSI will actually fail to run as the user as it's using a component that's not signed and trusted by the WDAC policies.
The solution we came up with is to run a script on user logon, which does the following:
- Hash the MSI (located at
C:\Program Files\WindowsApps\MSTeams_24231.512.3106.6573_x64__8wekyb3d8bbwe\MicrosoftTeamsMeetingAddinInstaller.msi
) - if it matches a stored hash, then do nothing. Otherwise... - Get the MSI version number
- Extract the MSI using the /a switch - this extracts rather than executes the MSI
- Record if Outlook is open, and close any Outlook processes if running
- Copy the extracted MSI content to
C:\Users\<username>\AppData\Local\Microsoft\TeamsMeetingAdd-in\<versionnumber>
- Run
regsvr32.exe
to register the library as the user - Make some registry changes to ensure Outlook is enabling the add-in
- Start Outlook if it was running before.
Here's the script:
script.ps1
$TeamsPath = $(Get-AppxPackage -Name "MSTeams").InstallLocation
$AddinPath = "$TeamsPath\MicrosoftTeamsMeetingAddinInstaller.msi"
if (-not (Test-Path -Path $AddinPath)) {
throw "Add-in not found at $AddinPath"
}
else {
Write-Host "Add-in found at $AddinPath"
}
$TargetRootDirectory = "$env:LocalAppData\Microsoft\TeamsMeetingAdd-in"
# get the msi version
$AddinFileInformation = Get-AppLockerFileInformation -Path $AddinPath
# the version number is the last element of the publisher attribute
$AddinVersion = $AddinFileInformation.Publisher.ToString().Split(',')[-1]
# test that $addInVersion is a valid version number by parsing with [version]
if (-not ([version]::TryParse($AddinVersion, [ref]$null))) {
throw "Invalid version number: $AddinVersion"
}
[version]$AddinVersion_Parsed = [version]::Parse($AddinVersion)
Write-Host "Add-in version: $AddinVersion"
$TargetDirectory = "$TargetRootDirectory\$($AddinVersion_Parsed.Major).$($AddinVersion_Parsed.Minor).$($AddinVersion_Parsed.Build)"
$LoaderDll = "$TargetDirectory\x64\Microsoft.Teams.AddinLoader.dll"
# msi
$Executable = "msiexec.exe"
$Arguments = @(
"/qn",
"/a ""$AddinPath""",
"TARGETDIR=""$TargetDirectory"""
)
# get the msi hash to use as a flag to know when to apply an update
$AddinHash = Get-FileHash -Path $AddinPath -Algorithm SHA256
$FlagFile = "$env:ProgramData\Scripts\TeamsMeetingAddinInstalled.$env:USERNAME"
# check if content of the flag file is the same as the hash of the msi
$ProceedWithInstall = $false
if (-not (Test-Path -Path $FlagFile)) {
$ProceedWithInstall = $true
}
else {
$FlagHash = Get-Content -Path $FlagFile
if ($FlagHash -ne $AddinHash.Hash) {
$ProceedWithInstall = $true
}
}
# stop installation if the add-in is already installed
if (-not $ProceedWithInstall) {
Write-Host "Add-in is already installed. Skipping installation."
Exit 0
}
# check if Outlook is running
$OutlookProcess = Get-Process -Name "OUTLOOK" -ErrorAction SilentlyContinue
$OutlookWasRunning = $false
$OutlookProcessPath = ""
if ($OutlookProcess) {
# warn the user about what is about to happen
$Message = "The Microsoft Teams Meeting Add-in for Microsoft Office will be installed. Outlook will be closed during the installation. Please save your work and close Outlook before proceeding. Click OK to continue."
$Title = "Microsoft Teams Meeting Add-in for Microsoft Office"
Add-Type -AssemblyName PresentationCore, PresentationFramework
$Result = [System.Windows.MessageBox]::Show($Message, $Title, [System.Windows.MessageBoxButton]::OK, [System.Windows.MessageBoxImage]::Warning)
$OutlookWasRunning = $true
$OutlookProcessPath = $OutlookProcess.Path
Write-Host "Outlook is running. Stopping Outlook..."
# stop Outlook
Stop-Process $OutlookProcess -Force
# loop for up to 10 seconds to wait for Outlook to close
$OutlookProcess = Get-Process -Name "OUTLOOK" -ErrorAction SilentlyContinue
$i = 0
while ($OutlookProcess -and $i -lt 10) {
Start-Sleep -Seconds 1
$OutlookProcess = Get-Process -Name "OUTLOOK" -ErrorAction SilentlyContinue
$i++
}
if ($OutlookProcess) {
throw "Outlook did not close. Exiting."
}
Write-Host "Outlook was running. Stopped Outlook."
Write-Host "Outlook path: $OutlookProcessPath"
}
# delete the existing add-in
if (Test-Path -Path $TargetRootDirectory) {
Remove-Item -Path $TargetRootDirectory -Recurse -Force
}
# install the add-in
Start-Process -FilePath $Executable -ArgumentList $Arguments -Wait
# register new add-in
Start-Process -FilePath "C:\Windows\System32\regsvr32.exe" -ArgumentList "/s /n /i:user '$LoaderDll'" -Wait
$RegistryKey = "HKCU:\Software\Microsoft\Office\Outlook\Addins\TeamsAddin.FastConnect"
if (-not (Test-Path -Path $RegistryKey)) {
New-Item -Path $RegistryKey -Force
}
if (-not (Get-Item $RegistryKey -EA Ignore).Property -contains "LoadBehavior") {
New-ItemProperty -Path $RegistryKey -Name "LoadBehavior" -PropertyType Dword -Value 3
}
else {
Set-ItemProperty -Path $RegistryKey -Name "LoadBehavior" -Value 3
}
if (-not (Get-Item $RegistryKey -EA Ignore).Property -contains "Description") {
New-ItemProperty -Path $RegistryKey -Name "Description" -PropertyType String -Value "Microsoft Teams Meeting Add-in for Microsoft Office"
}
else {
Set-ItemProperty -Path $RegistryKey -Name "Description" -Value "Microsoft Teams Meeting Add-in for Microsoft Office"
}
if (-not (Get-Item $RegistryKey -EA Ignore).Property -contains "FriendlyName") {
New-ItemProperty -Path $RegistryKey -Name "FriendlyName" -PropertyType String -Value "Microsoft Teams Meeting Add-in for Microsoft Office"
}
else {
Set-ItemProperty -Path $RegistryKey -Name "FriendlyName" -Value "Microsoft Teams Meeting Add-in for Microsoft Office"
}
# create the flag file
$AddinHash.Hash | Set-Content -Path $FlagFile
# start Outlook if it was running
if ($OutlookWasRunning) {
Write-Host "Starting Outlook..."
Write-Host "Outlook path: $OutlookProcessPath"
Start-Process "$OutlookProcessPath"
}
To deploy this script, I've created installation and uninstallation scripts:
install.ps1
$InstallationDirectory = "$env:ProgramData\Scripts\TeamsMeetingAddin"
$ScriptRevision = 1
# copy all files to the installation directory - deleting any existing directory
Write-Host "Copying files to $InstallationDirectory"
if (Test-Path -Path $InstallationDirectory) {
Remove-Item -Path $InstallationDirectory -Recurse -Force
}
Copy-Item -Path $PSScriptRoot -Destination $InstallationDirectory -Recurse
# create scheduled task to run the script
Write-Host "Creating scheduled task to run the script"
$taskAction = New-ScheduledTaskAction -Execute "powershell.exe" -Argument "-ExecutionPolicy Bypass -WindowStyle Hidden -File ""$InstallationDirectory\script.ps1"""
$taskTrigger = @(
$(New-ScheduledTaskTrigger `
-AtLogOn
)
)
$taskSettings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -RunOnlyIfNetworkAvailable
$taskPrincipal = New-ScheduledTaskPrincipal -GroupId 'BUILTIN\Users'
Register-ScheduledTask `
-TaskName "TeamsMeetingAddinInstall" `
-Action $taskAction `
-Trigger $taskTrigger `
-Settings $taskSettings `
-Principal $TaskPrincipal
# create a flag file to indicate that the add-in has been installed
$FlagFile = "$InstallationDirectory\$ScriptRevision"
$ScriptRevision | Set-Content -Path $FlagFile
# unregister existing add-in
# check if C:\Program Files (x86)\Microsoft\TeamsMeetingAddin exists, and then loop each sub-directory, unregistering each add-in inside
$AddinDirectory = "C:\Program Files (x86)\Microsoft\TeamsMeetingAddin"
if (Test-Path -Path $AddinDirectory) {
$AddinDirectories = Get-ChildItem -Path $AddinDirectory -Directory
foreach ($Addin in $AddinDirectories) {
Write-Host "Unregistering add-in in $AddinDirectory\$Addin"
if (Test-Path -Path "$AddinDirectory\$Addin\AddinInstaller.dll") {
$LoaderDll = "$AddinDirectory\$Addin\AddinInstaller.dll"
Write-Host "Unregistering $LoaderDll"
Start-Process -FilePath "C:\Windows\System32\regsvr32.exe" -ArgumentList "/s /u:computer '$LoaderDll'" -Wait
Start-Process -FilePath "C:\Windows\System32\regsvr32.exe" -ArgumentList "/s /u:user '$LoaderDll'" -Wait
}
if (Test-Path -Path "$AddinDirectory\$Addin\x86\Microsoft.Teams.AddinLoader.dll") {
$LoaderDll = "$AddinDirectory\$Addin\x86\Microsoft.Teams.AddinLoader.dll"
Write-Host "Unregistering $LoaderDll"
Start-Process -FilePath "C:\Windows\System32\regsvr32.exe" -ArgumentList "/s /u:computer '$LoaderDll'" -Wait
Start-Process -FilePath "C:\Windows\System32\regsvr32.exe" -ArgumentList "/s /u:user '$LoaderDll'" -Wait
}
if (Test-Path -Path "$AddinDirectory\$Addin\x64\Microsoft.Teams.AddinLoader.dll") {
$LoaderDll = "$AddinDirectory\$Addin\x64\Microsoft.Teams.AddinLoader.dll"
Write-Host "Unregistering $LoaderDll"
Start-Process -FilePath "C:\Windows\System32\regsvr32.exe" -ArgumentList "/s /u:computer '$LoaderDll'" -Wait
Start-Process -FilePath "C:\Windows\System32\regsvr32.exe" -ArgumentList "/s /u:user '$LoaderDll'" -Wait
}
}
}
# uninstall the add-in
Write-Host "Uninstalling Teams Meeting Add-in"
Start-Process -FilePath "msiexec.exe" -ArgumentList '/x "{A7AB73A3-CB10-4AA5-9D38-6AEFFBDE4C91}" /qn"' -Wait
# remove the add-in program directory
if (Test-Path -Path $AddinDirectory) {
Remove-Item -Path $AddinDirectory -Recurse -Force
}
# remove any flags
$FlagFile = "$env:ProgramData\Scripts\TeamsMeetingAddinInstalled.*"
Remove-Item -Path $FlagFile -Force
uninstall.ps1
$InstallationDirectory = "$env:ProgramData\Scripts\TeamsMeetingAddin"
# delete the existing add-in
if (Test-Path -Path $InstallationDirectory) {
Remove-Item -Path $InstallationDirectory -Recurse -Force
}
# remove the scheduled task
$TaskName = "TeamsMeetingAddinInstall"
if (Get-ScheduledTask -TaskName $TaskName -ErrorAction SilentlyContinue) {
Unregister-ScheduledTask -TaskName $TaskName -Confirm:$false
}
# remove flags
$FlagFile = "$env:ProgramData\Scripts\TeamsMeetingAddinInstalled.*"
Remove-Item -Path $FlagFile -Force
Package these three scripts up into a *.intunewin file and deploy from Intune.
Once deployed, on the next user logon after the Teams client has updated, the add-in will automatically update to the most recently supplied version.