├── CODE_OF_CONDUCT.md ├── LICENSE ├── SUPPORT.md ├── Details.md ├── SECURITY.md ├── src ├── PublishLatestVersion.ps1 └── CheckMsiOverride.ps1 └── README.md /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Microsoft Open Source Code of Conduct 2 | 3 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 4 | 5 | Resources: 6 | 7 | - [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/) 8 | - [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) 9 | - Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Microsoft Corporation. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE 22 | -------------------------------------------------------------------------------- /SUPPORT.md: -------------------------------------------------------------------------------- 1 | # TODO: The maintainer of this repo has not yet edited this file 2 | 3 | **REPO OWNER**: Do you want Customer Service & Support (CSS) support for this product/project? 4 | 5 | - **No CSS support:** Fill out this template with information about how to file issues and get help. 6 | - **Yes CSS support:** Fill out an intake form at [aka.ms/spot](https://aka.ms/spot). CSS will work with/help you to determine next steps. More details also available at [aka.ms/onboardsupport](https://aka.ms/onboardsupport). 7 | - **Not sure?** Fill out a SPOT intake as though the answer were "Yes". CSS will help you decide. 8 | 9 | *Then remove this first heading from this SUPPORT.MD file before publishing your repo.* 10 | 11 | # Support 12 | 13 | ## How to file issues and get help 14 | 15 | This project uses GitHub Issues to track bugs and feature requests. Please search the existing 16 | issues before filing new issues to avoid duplicates. For new issues, file your bug or 17 | feature request as a new Issue. 18 | 19 | For help and questions about using this project, please **REPO MAINTAINER: INSERT INSTRUCTIONS HERE 20 | FOR HOW TO ENGAGE REPO OWNERS OR COMMUNITY FOR HELP. COULD BE A STACK OVERFLOW TAG OR OTHER 21 | CHANNEL. WHERE WILL YOU HELP PEOPLE?**. 22 | 23 | ## Microsoft Support Policy 24 | 25 | Support for this **PROJECT or PRODUCT** is limited to the resources listed above. 26 | -------------------------------------------------------------------------------- /Details.md: -------------------------------------------------------------------------------- 1 | ## Teams Machine-Wide Installer 2 | Microsoft Teams supports installation through an MSI installer, referred to as the Teams Machine-Wide Installer. This installer is used by Microsoft Office to install Teams, or may be used by organizations installing Teams through a deployment package. 3 | 4 | When installed through this method, the MSI installer places an EXE installer onto the machine in the Program Files folder, and a [Run key](https://docs.microsoft.com/en-us/windows/win32/setupapi/run-and-runonce-registry-keys) named TeamsMachineInstaller is created in the Local Machine hive. 5 | 6 | When a user logs into the machine, this [Run key](https://docs.microsoft.com/en-us/windows/win32/setupapi/run-and-runonce-registry-keys) will execute, installing Microsoft Teams into the per-user profile location. From that point the per-user instance of Teams should automatically update itself. 7 | 8 | The Teams Machine-Wide Installer does not update itself, so the installer present on a given machine will generally remain at the version first installed. 9 | Since this is just used to initially install Teams, and then the per-user profile instance of Teams will automatically update itself, this is generally not an issue, except in the case of shared computers where new users are logging into them frequently. 10 | 11 | Even if the Teams Machine-Wide Installer is updated, it will normally not affect the per-user instance of Teams installed into a user's profile. 12 | 13 | ## Teams MSI Override 14 | 15 | For Teams MSI Override to work, two things must occur: 16 | 1) The installed Teams Machine-Wide Installer (MSI) must be updated to the target version 17 | 2) A DWORD registry key must be created: 18 | 19 | ```HKLM\Software\Policies\Microsoft\Office\16.0\Teams\AllowMsiOverride = 1``` 20 | 21 | The next time the user signs into Windows and the TeamsMachineInstaller [Run key](https://docs.microsoft.com/en-us/windows/win32/setupapi/run-and-runonce-registry-keys) is executed, with AllowMsiOverride set to 1, it will check if a newer version of Teams is available from the Teams Machine-Wide Installer and install it into the per-user profile instance. -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Security 4 | 5 | Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/). 6 | 7 | If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://docs.microsoft.com/en-us/previous-versions/tn-archive/cc751383(v=technet.10)), please report it to us as described below. 8 | 9 | ## Reporting Security Issues 10 | 11 | **Please do not report security vulnerabilities through public GitHub issues.** 12 | 13 | Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://msrc.microsoft.com/create-report). 14 | 15 | If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://www.microsoft.com/en-us/msrc/pgp-key-msrc). 16 | 17 | You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://www.microsoft.com/msrc). 18 | 19 | Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: 20 | 21 | * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) 22 | * Full paths of source file(s) related to the manifestation of the issue 23 | * The location of the affected source code (tag/branch/commit or direct URL) 24 | * Any special configuration required to reproduce the issue 25 | * Step-by-step instructions to reproduce the issue 26 | * Proof-of-concept or exploit code (if possible) 27 | * Impact of the issue, including how an attacker might exploit the issue 28 | 29 | This information will help us triage your report more quickly. 30 | 31 | If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://microsoft.com/msrc/bounty) page for more details about our active programs. 32 | 33 | ## Preferred Languages 34 | 35 | We prefer all communications to be in English. 36 | 37 | ## Policy 38 | 39 | Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://www.microsoft.com/en-us/msrc/cvd). 40 | 41 | -------------------------------------------------------------------------------- /src/PublishLatestVersion.ps1: -------------------------------------------------------------------------------- 1 | ################################################################################ 2 | # MIT License 3 | # 4 | # Copyright (c) 2021 Microsoft and Contributors 5 | # 6 | # Permission is hereby granted, free of charge, to any person obtaining a copy 7 | # of this software and associated documentation files (the "Software"), to deal 8 | # in the Software without restriction, including without limitation the rights 9 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | # copies of the Software, and to permit persons to whom the Software is 11 | # furnished to do so, subject to the following conditions: 12 | # 13 | # The above copyright notice and this permission notice shall be included in all 14 | # copies or substantial portions of the Software. 15 | # 16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | # SOFTWARE. 23 | # 24 | # Filename: PublishLatestVersion.ps1 25 | # Version: 1.0.0.0 26 | # Description: Script to publish new MSI installers to the MsiOverride share 27 | # Owner: Christopher Tart 28 | ################################################################################# 29 | 30 | Param( 31 | [Parameter(Mandatory=$false)] 32 | [Switch] $Help = $false, 33 | [Parameter(Mandatory=$true)] 34 | [string] $BaseShare = "", 35 | [Parameter(Mandatory=$false)] 36 | [ValidateSet('Preview', 'General', 'GCCGeneral', 'GCCHGeneral', 'DODGeneral')] 37 | [string] $Ring = "General", 38 | [Parameter(Mandatory=$false)] 39 | [string] $OverrideVersion = "" 40 | ) 41 | 42 | $RingNames = @{ 43 | "Preview" = "ring3_6"; 44 | "General" = "general"; 45 | "GCCGeneral" = "general_gcc"; 46 | "GCCHGeneral" = "gcchigh-general"; 47 | "DODGeneral" = "dod-general"; 48 | } 49 | 50 | $RingFQDNs = @{ 51 | "Preview" = "teams.microsoft.com"; 52 | "General" = "teams.microsoft.com"; 53 | "GCCGeneral" = "teams.microsoft.com"; 54 | "GCCHGeneral" = "gov.teams.microsoft.us"; 55 | "DODGeneral" = "dod.teams.microsoft.us"; 56 | } 57 | 58 | $ScriptName = "Microsoft Teams MsiOverride Publisher" 59 | $Version = "1.0.0.0" 60 | 61 | function ShowHelp 62 | { 63 | Write-Output "" 64 | Write-Output "$ScriptName" 65 | Write-Output "Version $Version" 66 | Write-Output "" 67 | Write-Output "Use this script check for and publish a new Teams MSI installer for use with the" 68 | Write-Output "MsiOverride update process" 69 | Write-Output "" 70 | Write-Output "PublishLatestVersion.ps1 [-Help]" 71 | Write-Output "" 72 | Write-Output "PublishLatestVersion.ps1 [-BaseShare ] [-PreviewRing] [-OverrideVersion ]" 73 | Write-Output "" 74 | Write-Output " -Help : Displays this help message." 75 | Write-Output "" 76 | Write-Output " -BaseShare: Provides the share location where the MSIs are published to." 77 | Write-Output "" 78 | Write-Output " -PreviewRing: Retrieves the downloads for preview ring (Ring3), instead of general ring." 79 | Write-Output "" 80 | Write-Output " -OverrideVersion: Downloads the designated version and sets it as the target version" 81 | Write-Output " Must be given in a format which is similar to 1.2.00.34567" 82 | Write-Output "" 83 | Exit 84 | } 85 | 86 | function GetDownloadUrl($version, $bitness, $fileName) 87 | { 88 | $url = $updateCheckUrl -f $version,$bitness,$RingNames[$Ring],$RingFQDNs[$Ring] 89 | 90 | Write-Host "Sending request to $url" 91 | $updateCheckResponse = Invoke-WebRequest -Uri $url -UseBasicParsing 92 | $updateCheckJson = $updateCheckResponse | ConvertFrom-Json 93 | 94 | if($updateCheckJson.isUpdateAvailable) 95 | { 96 | $downloadPath = $updateCheckJson.releasesPath.Replace("RELEASES", $fileName) 97 | Write-Host "Returning $downloadPath" 98 | return $downloadPath 99 | } 100 | 101 | Write-Host "Returning null" 102 | return "" 103 | } 104 | 105 | function CreateFolder($path) 106 | { 107 | New-Item -ItemType Directory -Path $path -ErrorAction Continue | Out-Null 108 | if(-Not (Test-Path $path)) 109 | { 110 | Write-Host "Unable to create $path" -ForegroundColor Red 111 | Exit -1 112 | } 113 | } 114 | 115 | function DeleteFolder($path) 116 | { 117 | if(Test-Path $path) 118 | { 119 | Remove-Item -Path $path -Recurse -Force -ErrorAction Continue | Out-Null 120 | if(Test-Path $path) 121 | { 122 | Write-Host "Unable to delete $path" -ForegroundColor Red 123 | Exit -1 124 | } 125 | } 126 | } 127 | 128 | function DeleteFile($path) 129 | { 130 | if(Test-Path $path) 131 | { 132 | Remove-Item -Path $path -Force -ErrorAction Continue | Out-Null 133 | if(Test-Path $path) 134 | { 135 | Write-Host "Unable to delete $path" -ForegroundColor Red 136 | Exit -1 137 | } 138 | } 139 | } 140 | 141 | # Constants 142 | $updateCheckUrl = "https://{3}/desktopclient/update/{0}/windows/{1}?ring={2}" 143 | $downloadFormat32 = "https://statics.teams.microsoft.com/production-windows/{0}/Teams_windows.msi" 144 | $downloadFormat64 = "https://statics.teams.microsoft.com/production-windows-x64/{0}/Teams_windows_x64.msi" 145 | $versionRegex = "(?\d+\.\d+\.\d+\.\d+)" 146 | $fileName32 = "Teams_windows.msi" 147 | $fileName64 = "Teams_windows_x64.msi" 148 | 149 | # Main Script 150 | 151 | if($Help) { ShowHelp } 152 | 153 | if(-Not (Test-Path $BaseShare)) 154 | { 155 | Write-Host "Specified BaseShare path does not exist. Please create it first and ensure it has the proper permissions." -ForegroundColor Red 156 | Exit -1 157 | } 158 | 159 | if($OverrideVersion -ne "" -and !($OverrideVersion -match $versionRegex)) 160 | { 161 | Write-Host "Invalid version format provided. Please ensure it follows a format similar to 1.2.00.34567" -ForegroundColor Red 162 | Exit -1 163 | } 164 | 165 | # Add TLS 1.2 for older OSs 166 | if (([Net.ServicePointManager]::SecurityProtocol -ne 'SystemDefault') -and 167 | !(([Net.ServicePointManager]::SecurityProtocol -band 'Tls12') -eq 'Tls12')) 168 | { 169 | Write-Host "Adding TLS 1.2 protocol" 170 | [Net.ServicePointManager]::SecurityProtocol += [Net.SecurityProtocolType]::Tls12 171 | } 172 | 173 | Write-Host "Checking for a new Teams version in ring $Ring ..." -ForegroundColor Green 174 | 175 | # Get the current version in use 176 | $currentVersion = "1.3.00.0000" 177 | $versionFile = Join-Path $BaseShare "Version.txt" 178 | 179 | if($OverrideVersion -eq "") 180 | { 181 | $fileVersion = Get-Content $versionFile -ErrorAction SilentlyContinue 182 | if($fileVersion -match $versionRegex) 183 | { 184 | $currentVersion = $Matches.version 185 | } 186 | Write-Host "Current version $currentVersion" 187 | 188 | # Try to get new download URLs for both 32 and 64 bit MSIs, returns null if none is available 189 | $downloadPath32 = GetDownloadUrl $currentVersion "x32" $fileName32 190 | $downloadPath64 = GetDownloadUrl $currentVersion "x64" $fileName64 191 | } 192 | else 193 | { 194 | $downloadPath32 = $downloadFormat32 -f $OverrideVersion 195 | $downloadPath64 = $downloadFormat64 -f $OverrideVersion 196 | } 197 | 198 | 199 | # Extract new version number from URL 200 | $newVersion = "" 201 | if($downloadPath32 -match $versionRegex) 202 | { 203 | $newVersion = $Matches.version 204 | Write-Host "New version $newVersion" 205 | } 206 | 207 | # If we have a new version number and both download paths, proceed to get them 208 | if($newVersion -ne "" -and $downloadPath32 -ne "" -and $downloadPath64 -ne "") 209 | { 210 | # Create a new version folder on the share 211 | $newFolder = Join-Path $BaseShare $newVersion 212 | Write-Host "Creating folder $newFolder" 213 | DeleteFolder($newFolder) 214 | CreateFolder($newFolder) 215 | 216 | $oldProgressPreference = $ProgressPreference 217 | $ProgressPreference = 'SilentlyContinue' 218 | 219 | # Download both MSIs to the new version folder 220 | Write-Host "Downloading $downloadPath32" 221 | $localPath32 = Join-Path $env:TEMP $fileName32 222 | DeleteFile $localPath32 223 | Invoke-WebRequest -Uri $downloadPath32 -OutFile $localPath32 224 | Write-Host "Download complete. Moving it to $newFolder" 225 | Move-Item -Path $localPath32 -Destination $newFolder 226 | 227 | Write-Host "Downloading $downloadPath64" 228 | $localPath64 = Join-Path $env:TEMP $fileName64 229 | DeleteFile $localPath64 230 | Invoke-WebRequest -Uri $downloadPath64 -OutFile $localPath64 231 | Write-Host "Download complete. Moving it to $newFolder" 232 | Move-Item -Path $localPath64 -Destination $newFolder 233 | 234 | $ProgressPreference = $oldProgressPreference 235 | 236 | $remotePath32 = Join-Path $newFolder $fileName32 237 | $remotePath64 = Join-Path $newFolder $fileName64 238 | if((Test-Path $remotePath32) -and (Test-Path $remotePath64)) 239 | { 240 | # Update the current version 241 | Write-Host "Updating Version.txt to $newVersion" 242 | $newVersion | Out-File $versionFile 243 | Write-Host "New version published!" -ForegroundColor Green 244 | } 245 | else 246 | { 247 | Write-Host "One or both MSIs were not found on the base share, something went wrong!" -ForegroundColor Red 248 | } 249 | } 250 | else 251 | { 252 | Write-Host "New version is not available!" -ForegroundColor Green 253 | } 254 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Teams MSI Override 2 | > [!IMPORTANT] 3 | > Microsoft Teams is in the process of transitioning to the New Teams desktop client. The New Teams desktop client uses a completely different installation process which these scripts will not support. 4 | > These scripts will therefore not be needed once the transition to the New Teams desktop client is complete, and this repository will be removed at that time. 5 | 6 | Microsoft Teams supports installation through an MSI installer, referred to as the Teams Machine-Wide Installer. This installer is used by Microsoft Office to install Teams or may be used by organizations installing Teams through a deployment package. 7 | 8 | The Teams Machine-Wide Installer does not normally get updated, and per-user instances of Teams installed into a user's profile will normally not be affected by changes to the Teams-Machine-Wide Installer. 9 | 10 | There may be cases where an organization needs to update the Teams Machine-Wide Installer or forcibly update the per-user instances of Teams, perhaps for a critical security release, or if the normal update process is failing, or because a machine is shared, and new users are getting an outdated Teams installation. 11 | 12 | In the event an organization needs to update the Teams Machine-Wide Installer, they can use a feature named MSI Override to update the MSI installed on a machine and allow per-user instances of Teams to update from the MSI. 13 | 14 | For additional technical details on the Teams Machine-Wide Installer and MSI Override, please see [Details](Details.md) 15 | 16 | To implement MSI Override, you can use the PublishLatestVersion.ps1 and CheckMsiOverride.ps1 scripts. 17 | 18 | [![Version](https://img.shields.io/github/v/release/microsoft/TeamsMsiOverride?label=latest%20version)](https://github.com/microsoft/TeamsMsiOverride/releases/latest/download/TeamsMsiOverride.zip) 19 | [![Downloads](https://img.shields.io/github/downloads/microsoft/TeamsMsiOverride/total)](https://github.com/microsoft/TeamsMsiOverride/releases/latest/download/TeamsMsiOverride.zip) 20 | 21 | [![Pre-Release Version](https://img.shields.io/github/v/release/microsoft/TeamsMsiOverride?include_prereleases&label=latest%20pre-release%20version)](https://github.com/microsoft/TeamsMsiOverride/releases/tag/v1.0.2311.3001) 22 | 23 | # Getting Started 24 | PowerShell **5.0** (or greater) must be installed on the host machine. Click [here](https://github.com/powershell/powershell) for details on how to get the latest version for your computer. 25 | 26 | ## PublishLatestVersion 27 | The PublishLatestVersion.ps1 script will retrieve the most recent Teams MSI installers and store them onto a file share. This share can then be used by the CheckMsiOverride script. 28 | 29 | ### Command 30 | ``` 31 | PublishLatestVersion.ps1 -BaseShare [-PreviewRing] 32 | ``` 33 | -PreviewRing will check for and download the version currently available in the preview ring. If you are currently running a preview version, this can be used to continue to pull the preview version while using this script package. 34 | 35 | ### Usage 36 | 37 | To use the script, follow these steps: 38 | 1) [Download](https://github.com/microsoft/TeamsMsiOverride/releases/latest/download/TeamsMsiOverride.zip) the latest version of the script package. 39 | 2) Create a file share (i.e., \\\\server\TeamsUpdateShare) which is accessible by all users which require the Teams MSI Override deployment. Ensure general users have read access, and only a few users, such as your IT administrators, have write access (these are your MSI Override Administrators). 40 | 3) Copy the PublishLatestVersion.ps1 script to a location available to your MSI Override Administrators. 41 | 4) Run the PublishLatestVersion.ps1 script, providing the path to the file share as follows: 42 | 43 | ```PublishLatestVersion.ps1 -BaseShare \\server\TeamsUpdateShare``` 44 | 45 | At this point the file share should be populated with a new folder containing the latest Teams MSI installers, as well as a Version.txt file which indicates which version is the latest. 46 | Going forward the MSI Override Administrators can run the PublishLatestVersion.ps1 script to retrieve the latest Teams MSI installers at any time. 47 | 48 | ## CheckMsiOverride 49 | The CheckMsiOverride script is intended to run on user machines, and will set the required registry key, and update the Teams Machine-Wide Installer to the most recent version. 50 | 51 | This script supports three methods of checking and retrieving an updated MSI: Share, CDN, and Package. 52 | - The share method uses a share location populated by the PublishLatestVersion script to check for a newer version and to copy the MSI from. 53 | - The CDN method has each client directly query if a new version is available and then downloads the relevant MSI directly from the Microsoft CDN server. Note that this method has each individual user downloading the files, and therefore can cause increased bandwidth usage if multiple users are executing the script at the same time. 54 | - The package method requires the relevant MSI to be placed in the same folder as the script. This method is intended for deployment via package managers to allow all the relevant files to be placed in one location. 55 | 56 | It must be run with Administrative privileges. It may be ideal to have it run from the SYSTEM account. 57 | 58 | The script is signed, so the user executing the script on each machine will require an execution policy of at least RemoteSigned. 59 | 60 | ### Command 61 | 62 | ``` 63 | CheckMsiOverride.ps1 -Type Share -BaseShare [-OverrideVersion ] [-MsiFileName ] [-AllowInstallOverTopExisting] [-OverwritePolicyKey] [-FixRunKey] [-Uninstall32Bit] 64 | 65 | CheckMsiOverride.ps1 -Type CDN [-PreviewRing] [-OverrideVersion ] [-MsiFileName ] [-AllowInstallOverTopExisting] [-OverwritePolicyKey] [-FixRunKey] [-Uninstall32Bit] 66 | 67 | CheckMsiOverride.ps1 -Type Package -OverrideVersion [-MsiFileName ] [-AllowInstallOverTopExisting] [-OverwritePolicyKey] [-FixRunKey] [-Uninstall32Bit] 68 | ``` 69 | - **-Type \<[ Share | CDN | Package ]\>** specifies which type of retrieval is used. Share is the default value if this parameter is not specified, for backwards compatibility. 70 | - **-BaseShare** specifies the share location which has been populated by PublishLatestVersion.ps1. This is only valid with the -Type Share parameter. 71 | - **-OverrideVersion \** specifies target version number for the client to have. It is required with the -Type Package parameter, and is optional in the other cases. 72 | - **-PreviewRing** specifies that the target version should be from the preview ring. It is only valid with the -Type CDN parameter. 73 | - **-MsiFileName \** specifies the name of the MSI file that was previously used to install Teams. This is only required in rare cases where the file name was changed prior to installation, and the script is unable to determine the correct file name based on what is stored in the registry. It is optional for all -Type parameter values. 74 | - **-FixRunKey** specifies that the script should correct missing or incorrect Run key values for the Teams Machine-Wide Installer. It is optional for all -Type parameter values. 75 | - **-Uninstall32Bit** specifies that the script should uninstall a 32-bit installation of Teams so the 64-bit version can be installed on 64-bit Windows. It is preferred to run 64-bit Teams on 64-bit Windows. It is optional for all -Type parameter values. 76 | - **-AllowInstallOverTopExisting** specifies that Teams can be installed instead of upgraded in a specific case. Please see below for further details. It is optional for all -Type parameter values. 77 | - **-OverwritePolicyKey** specifies that the script should overwrite the AllowMsiOverride key. Please see bleow for further details. It is optional for all -Type parameter values. 78 | 79 | #### AllowInstallOvertopExisting 80 | When installing the Teams Machine-Wide Installer originally, it could have been installed in 3 main ways, relative to the current user: 81 | 1) Installed by any user using ALLUSERS=1 parameter 82 | 2) Installed by the current user without the ALLUSERS=1 parameter 83 | 3) Installed by a different user without the ALLUSERS=1 parameter 84 | 85 | For scenarios 1 and 2, the script can perform an in-place upgrade of the MSI. 86 | 87 | For scenario 3 the current user is not "aware" that the MSI has been installed, and so it is not able to do an in-place upgrade. In this case the script will, by default, exit with an error. 88 | 89 | If you pass the -AllowInstallOvertopExisting switch into the script, it will permit the script to instead perform an installation of the MSI for the current user. This will overwrite the existing files, allowing them to be updated to the correct version. 90 | 91 | ```powershell.exe -File \\share\TeamsUpdateShare\CheckMsiOverride.ps1 -BaseShare \\share\TeamsUpdateShare -AllowInstallOvertopExisting``` 92 | 93 | If this occurs, however, two different users will have separate installation entries created. If either user uninstalls the Teams Machine-Wide Installer, the files will be removed, and it will be shown as uninstalled for the user performing the uninstall, but the second user will still show an installation entry present, even though the files have been removed. 94 | 95 | #### OverwritePolicyKey 96 | By default, the script will populate the AllowMsiOverride key only if it does not already exist. Therefore, if your organization wants to push a value of 0 to some users, this value will remain even if the script is ran. 97 | 98 | If you want to forcibly reset the value back to 1 for all users, you can pass the -OverwritePolicyKey switch. 99 | 100 | ```powershell.exe -File \\share\TeamsUpdateShare\CheckMsiOverride.ps1 -BaseShare \\share\TeamsUpdateShare -OverwritePolicyKey``` 101 | 102 | ### Share Type With Scheduled Task 103 | The CheckMsiOverride.ps1 script can be deployed in various ways; we will provide an example here using Scheduled Tasks. 104 | 105 | To deploy this script as a Scheduled Task you can use the following steps: 106 | 1) [Download](https://github.com/microsoft/TeamsMsiOverride/releases/latest/download/TeamsMsiOverride.zip) the latest version of the script package. 107 | 2) Copy the CheckMsiOverride.ps1 script to the file share you created (\\\\server\TeamsUpdateShare\CheckMsiOverrride.ps1), or any other equally accessible location per your organizations policies. 108 | 3) Create a scheduled task which executes the script as follows: 109 | 110 | ```powershell.exe -File \\share\TeamsUpdateShare\CheckMsiOverride.ps1 -Type Share -BaseShare \\share\TeamsUpdateShare``` 111 | 4) Specify a schedule that is appropriate for your organization. If no update is required, the script will make no changes, so there are no issues running it often (such as daily). 112 | 113 | ### Package Type 114 | If you are building a package with this script you can use the following steps: 115 | 1) [Download](https://github.com/microsoft/TeamsMsiOverride/releases/latest/download/TeamsMsiOverride.zip) the latest version of the script package. 116 | 2) Copy the CheckMsiOverride.ps1 script to the package directory. 117 | 3) Download the latest 32-bit and 64-bit Teams MSI, either from the [Teams website](https://docs.microsoft.com/en-us/MicrosoftTeams/msi-deployment) or use the PublishLatestVersion script to retrieve them for you. 118 | 4) Place the Teams_windows.msi and Teams_windows_x64.msi into the folder with the CheckMsiOverride.ps1 script. 119 | 5) Determine the version number for this MSI, either by installing locally or extracting the files and looking at the properties of the Teams.exe file contained inside. If you used the PublishLatestVersion script, the version number is the folder name they are placed into. 120 | 6) Have the package execute the script similar to as follows, using the proper location for the script for your package deployment software: 121 | 122 | ```powershell.exe -File CheckMsiOverride.ps1 -Type Package -OverrideVersion ``` 123 | 124 | ### Diagnostics 125 | CheckMsiOverride.ps1 will save a trace file to %TEMP%\TeamsMsiOverrideTrace.txt 126 | 127 | It will also write to the Application event log with the source "TeamsMsiOverride" for any failures, or if an update completed successfully. 128 | 129 | # Contributing 130 | 131 | This project welcomes contributions and suggestions. Most contributions require you to agree to a 132 | Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us 133 | the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com. 134 | 135 | When you submit a pull request, a CLA bot will automatically determine whether you need to provide 136 | a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions 137 | provided by the bot. You will only need to do this once across all repos using our CLA. 138 | 139 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 140 | For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or 141 | contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. 142 | 143 | # Trademarks 144 | 145 | This project may contain trademarks or logos for projects, products, or services. Authorized use of Microsoft 146 | trademarks or logos is subject to and must follow 147 | [Microsoft's Trademark & Brand Guidelines](https://www.microsoft.com/en-us/legal/intellectualproperty/trademarks/usage/general). 148 | Use of Microsoft trademarks or logos in modified versions of this project must not cause confusion or imply Microsoft sponsorship. 149 | Any use of third-party trademarks or logos are subject to those third-party's policies. 150 | -------------------------------------------------------------------------------- /src/CheckMsiOverride.ps1: -------------------------------------------------------------------------------- 1 | ################################################################################ 2 | # MIT License 3 | # 4 | # Copyright (c) 2021 Microsoft and Contributors 5 | # 6 | # Permission is hereby granted, free of charge, to any person obtaining a copy 7 | # of this software and associated documentation files (the "Software"), to deal 8 | # in the Software without restriction, including without limitation the rights 9 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | # copies of the Software, and to permit persons to whom the Software is 11 | # furnished to do so, subject to the following conditions: 12 | # 13 | # The above copyright notice and this permission notice shall be included in all 14 | # copies or substantial portions of the Software. 15 | # 16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | # SOFTWARE. 23 | # 24 | # Filename: CheckMsiOverride.ps1 25 | # Version: 1.0.0.0 26 | # Description: Script to check for and applies Teams msiOverride updates 27 | # Owner: Christopher Tart 28 | ################################################################################# 29 | 30 | #Requires -RunAsAdministrator 31 | 32 | Param( 33 | [Parameter(Mandatory=$false)] 34 | [ValidateSet('Share','CDN','Package')] 35 | [string] $Type = "Share", 36 | [Parameter(Mandatory=$false)] 37 | [ValidateSet('Preview', 'General', 'GCCGeneral', 'GCCHGeneral', 'DODGeneral')] 38 | [string] $Ring = "General", 39 | [Parameter(Mandatory=$false)] 40 | [string] $BaseShare = "", 41 | [Parameter(Mandatory=$false)] 42 | [string] $OverrideVersion = "", 43 | [Parameter(Mandatory=$false)] 44 | [string] $MsiFileName = "", 45 | [Parameter(Mandatory=$false)] 46 | [Switch] $AllowInstallOvertopExisting = $false, 47 | [Parameter(Mandatory=$false)] 48 | [Switch] $OverwritePolicyKey = $false, 49 | [Parameter(Mandatory=$false)] 50 | [Switch] $FixRunKey = $false, 51 | [Parameter(Mandatory=$false)] 52 | [Switch] $Uninstall32Bit = $false, 53 | [Parameter(Mandatory=$false)] 54 | [Switch] $UninstallAll = $false, 55 | [Parameter(Mandatory=$false)] 56 | [Switch] $UninstallAllIfBothPresent = $false 57 | ) 58 | 59 | $RingNames = @{ 60 | "Preview" = "ring3_6"; 61 | "General" = "general"; 62 | "GCCGeneral" = "general_gcc"; 63 | "GCCHGeneral" = "gcchigh-general"; 64 | "DODGeneral" = "dod-general"; 65 | } 66 | 67 | $RingFQDNs = @{ 68 | "Preview" = "teams.microsoft.com"; 69 | "General" = "teams.microsoft.com"; 70 | "GCCGeneral" = "teams.microsoft.com"; 71 | "GCCHGeneral" = "gov.teams.microsoft.us"; 72 | "DODGeneral" = "dod.teams.microsoft.us"; 73 | } 74 | 75 | $ScriptName = "Microsoft Teams MsiOverride Checker" 76 | $Version = "1.0.0.0" 77 | 78 | # Trace functions 79 | function InitTracing([string]$traceName, [string]$tracePath = $env:TEMP) 80 | { 81 | $script:TracePath = Join-Path $tracePath $traceName 82 | WriteTrace("") 83 | WriteTrace("Start Trace $(Get-Date)") 84 | } 85 | 86 | function WriteTrace([string]$line, [string]$function = "") 87 | { 88 | $output = $line 89 | if($function -ne "") 90 | { 91 | $output = "[$function] " + $output 92 | } 93 | Write-Verbose $output 94 | $output | Out-File $script:TracePath -Append 95 | } 96 | 97 | function WriteInfo([string]$line, [string]$function = "") 98 | { 99 | $output = $line 100 | if($function -ne "") 101 | { 102 | $output = "[$function] " + $output 103 | } 104 | Write-Host $output 105 | $output | Out-File $script:TracePath -Append 106 | } 107 | 108 | function WriteWarning([string]$line) 109 | { 110 | Write-Host $line -ForegroundColor DarkYellow 111 | $line | Out-File $script:TracePath -Append 112 | } 113 | 114 | function WriteError([string]$line) 115 | { 116 | Write-Host $line -ForegroundColor Red 117 | $line | Out-File $script:TracePath -Append 118 | } 119 | 120 | function WriteSuccess([string]$line) 121 | { 122 | Write-Host $line -ForegroundColor Green 123 | $line | Out-File $script:TracePath -Append 124 | } 125 | 126 | # Removes temp folder 127 | function Cleanup 128 | { 129 | WriteTrace "Removing temp folder $TempPath" 130 | Remove-Item $TempPath -Recurse -Force -ErrorAction SilentlyContinue | Out-Null 131 | } 132 | 133 | # Runs cleanup and exits 134 | function CleanExit($code = 0) 135 | { 136 | Cleanup 137 | WriteTrace("End Trace $(Get-Date)") 138 | Exit $code 139 | } 140 | 141 | function ErrorExit($line, $code) 142 | { 143 | WriteError($line) 144 | Write-EventLog -LogName Application -Source $EventLogSource -Category 0 -EntryType Error -EventId ([Math]::Abs($code)) -Message $line 145 | CleanExit($code) 146 | } 147 | 148 | function IsRunningUnderSystem 149 | { 150 | if(($env:COMPUTERNAME + "$") -eq $env:USERNAME) 151 | { 152 | return $true 153 | } 154 | return $false 155 | } 156 | 157 | function GetFileVersionString($Path) 158 | { 159 | if (Test-Path $Path) 160 | { 161 | $item = [System.Diagnostics.FileVersionInfo]::GetVersionInfo($Path) 162 | if ($item) 163 | { 164 | return $item.FileVersion 165 | } 166 | } 167 | return "" 168 | } 169 | 170 | function HasReg($Path, $Name) 171 | { 172 | if (Test-Path $Path) 173 | { 174 | $item = Get-ItemProperty -Path $Path -Name $Name -ErrorAction SilentlyContinue 175 | if ($item -ne $null) 176 | { 177 | return $true 178 | } 179 | } 180 | return $false 181 | } 182 | 183 | function GetReg($Path, $Name, $DefaultValue) 184 | { 185 | if (HasReg -Path $Path -Name $Name) 186 | { 187 | $item = Get-ItemProperty -Path $Path -Name $Name 188 | return $item.$Name 189 | } 190 | return $DefaultValue 191 | } 192 | 193 | function SetDwordReg($Path, $Name, $Value) 194 | { 195 | if (!(Test-Path $Path)) 196 | { 197 | New-Item -Path $Path -Force | Out-Null 198 | } 199 | Set-ItemProperty -Path $Path -Name $Name -Value $Value -Type DWORD 200 | } 201 | 202 | function SetExpandStringReg($Path, $Name, $Value) 203 | { 204 | if (!(Test-Path $Path)) 205 | { 206 | New-Item -Path $Path -Force | Out-Null 207 | } 208 | Set-ItemProperty -Path $Path -Name $Name -Value $Value -Type ExpandString 209 | } 210 | 211 | function GetInstallerVersion 212 | { 213 | return (GetFileVersionString -Path (GetInstallerPath)) 214 | } 215 | 216 | function GetInstallerPath 217 | { 218 | if($([Environment]::Is64BitOperatingSystem)) 219 | { 220 | return (${env:ProgramFiles(x86)} + "\Teams Installer\Teams.exe") 221 | } 222 | else 223 | { 224 | return ($env:ProgramFiles + "\Teams Installer\Teams.exe") 225 | } 226 | } 227 | 228 | function GetTargetVersion 229 | { 230 | $versionFile = Join-Path $BaseShare "Version.txt" 231 | $fileVersion = Get-Content $versionFile -ErrorAction SilentlyContinue 232 | return (VerifyVersion($fileVersion)) 233 | } 234 | 235 | function VerifyVersion($Version) 236 | { 237 | if($Version -match $versionRegex) 238 | { 239 | return $Matches.version 240 | } 241 | return $null 242 | } 243 | 244 | function GetUninstallKey 245 | { 246 | $UninstallReg1 = Get-ChildItem -Path HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall -ErrorAction SilentlyContinue | Get-ItemProperty | Where-Object { $_ -match 'Teams Machine-Wide Installer' } 247 | $UninstallReg2 = Get-ChildItem -Path HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall -ErrorAction SilentlyContinue | Get-ItemProperty | Where-Object { $_ -match 'Teams Machine-Wide Installer' } 248 | 249 | WriteTrace("UninstallReg1: $($UninstallReg1.PSChildName)") 250 | WriteTrace("UninstallReg2: $($UninstallReg2.PSChildName)") 251 | 252 | if($UninstallReg1) { return $UninstallReg1 } 253 | elseif($UninstallReg2) { return $UninstallReg2 } 254 | return $null 255 | } 256 | 257 | function GetProductsKey 258 | { 259 | $ProductsRegLM = Get-ChildItem -Path HKLM:\SOFTWARE\Classes\Installer\Products -ErrorAction SilentlyContinue | Get-ItemProperty | Where-Object { $_ -match 'Teams Machine-Wide Installer' } # ALLUSERS Install 260 | $ProductsRegCU = Get-ChildItem -Path HKCU:\SOFTWARE\Microsoft\Installer\Products -ErrorAction SilentlyContinue | Get-ItemProperty | Where-Object { $_ -match 'Teams Machine-Wide Installer' } # Local User Install 261 | 262 | WriteTrace("ProductsRegLM: $($ProductsRegLM.PSChildName)") 263 | WriteTrace("ProductsRegCU: $($ProductsRegCU.PSChildName)") 264 | 265 | $result = @(); 266 | if($ProductsRegLM) { $result += $ProductsRegLM } 267 | if($ProductsRegCU) { $result += $ProductsRegCU } 268 | return $result 269 | } 270 | 271 | function Has32BitProductKey($productKeys) 272 | { 273 | foreach($key in $productKeys) 274 | { 275 | if($key.PSChildName -eq $MsiProduct32Guid) 276 | { 277 | return $true 278 | } 279 | } 280 | return $false 281 | } 282 | 283 | function Has64BitProductKey($productKeys) 284 | { 285 | foreach($key in $productKeys) 286 | { 287 | if($key.PSChildName -eq $MsiProduct64Guid) 288 | { 289 | return $true 290 | } 291 | } 292 | return $false 293 | } 294 | 295 | function GetPackageKey() 296 | { 297 | [array]$msiKeys = GetProductsKey 298 | if($msiKeys.count -eq 1) 299 | { 300 | $msiKey = $msiKeys[0] 301 | $msiPkgReg = (Get-ChildItem -Path $msiKey.PSPath -Recurse | Get-ItemProperty | Where-Object { $_ -match 'PackageName' }) 302 | 303 | if ($msiPkgReg.PackageName) 304 | { 305 | WriteTrace("PackageName: $($msiPkgReg.PackageName)") 306 | return $msiPkgReg 307 | } 308 | } 309 | return $null 310 | } 311 | 312 | function GetInstallBitnessFromUninstall() 313 | { 314 | $uninstallReg = GetUninstallKey 315 | if($uninstallReg) 316 | { 317 | if ($uninstallReg.PSPath | Select-String -Pattern $MsiPkg64Guid) 318 | { 319 | return "x64" 320 | } 321 | elseif ($uninstallReg.PSPath | Select-String -Pattern $MsiPkg32Guid) 322 | { 323 | return "x86" 324 | } 325 | } 326 | return $null 327 | } 328 | 329 | function GetInstallBitnessFromSource() 330 | { 331 | $msiPkgReg = GetPackageKey 332 | if($msiPkgReg) 333 | { 334 | WriteTrace("LastUsedSource: $($msiPkgReg.LastUsedSource)") 335 | if ($msiPkgReg.LastUsedSource | Select-String -Pattern ${env:ProgramFiles(x86)}) 336 | { 337 | return "x86" 338 | } 339 | elseif ($msiPkgReg.LastUsedSource | Select-String -Pattern $env:ProgramFiles) 340 | { 341 | if($([Environment]::Is64BitOperatingSystem)) 342 | { 343 | return "x64" 344 | } 345 | else 346 | { 347 | return "x86" 348 | } 349 | } 350 | } 351 | return $null 352 | } 353 | 354 | function GetInstallBitnessForOS() 355 | { 356 | if($([Environment]::Is64BitOperatingSystem)) 357 | { 358 | return "x64" 359 | } 360 | else 361 | { 362 | return "x86" 363 | } 364 | } 365 | 366 | function GetInstallBitness([ref]$outMode, [ref]$outFileName) 367 | { 368 | $installBitness = GetInstallBitnessFromUninstall 369 | $packageKey = GetPackageKey 370 | # Determine the install bitness and mode 371 | if($installBitness) 372 | { 373 | # Uninstall key existed and we matched to known GUID 374 | if($packageKey) 375 | { 376 | # Update Scenario, Package key existed (meaning MSI was installed by this user, or as ALLUSERS). 377 | $mode = "update" 378 | } 379 | else 380 | { 381 | # Install Scenario, Package key did not exist (meaning MSI is installed, but not by this user and not as ALLUSERS). 382 | $mode = "installovertop" 383 | } 384 | } 385 | else 386 | { 387 | # Uninstall key did not exist or we did not match a known GUID 388 | if($packageKey) 389 | { 390 | # Update Scenario, we do have a package key, so we must not have matched a known GUID, so try to read LastUsedSource path (Office installation scenario). 391 | $mode = "update" 392 | $installBitness = GetInstallBitnessFromSource 393 | if(-not $installBitness) 394 | { 395 | # Fall back to OS bitness as a last resort. 396 | $installBitness = GetInstallBitnessForOS 397 | } 398 | } 399 | else 400 | { 401 | # Install Scenario, Neither Uninstall key or Package key existed, so it will be a fresh install 402 | $mode = "install" 403 | $installBitness = GetInstallBitnessForOS 404 | } 405 | } 406 | 407 | $outMode.Value = $mode 408 | $outFileName.Value = $packageKey.PackageName 409 | 410 | return $installBitness 411 | } 412 | 413 | function DeleteFile($path) 414 | { 415 | if(Test-Path $path) 416 | { 417 | Remove-Item -Path $path -Force | Out-Null 418 | if(Test-Path $path) 419 | { 420 | Write-Host "Unable to delete $path" -ForegroundColor Red 421 | ErrorExit "Failed to delete existing file $path" -8 422 | } 423 | } 424 | } 425 | 426 | function SetParametersWithCDN([ref]$outVersion, [ref]$outPath) 427 | { 428 | WriteInfo "Using CDN to check for an update and aquire the new MSI..." 429 | $updateCheckUrl = "https://{3}/desktopclient/update/{0}/windows/{1}?ring={2}" 430 | $downloadFormat32 = "https://statics.teams.microsoft.com/production-windows/{0}/Teams_windows.msi" 431 | $downloadFormat64 = "https://statics.teams.microsoft.com/production-windows-x64/{0}/Teams_windows_x64.msi" 432 | $bitness = $installBitness.Replace("x86", "x32") 433 | 434 | # Add TLS 1.2 for older OSs 435 | if (([Net.ServicePointManager]::SecurityProtocol -ne 'SystemDefault') -and 436 | !(([Net.ServicePointManager]::SecurityProtocol -band 'Tls12') -eq 'Tls12')) 437 | { 438 | WriteTrace "Adding TLS 1.2 protocol" 439 | [Net.ServicePointManager]::SecurityProtocol += [Net.SecurityProtocolType]::Tls12 440 | } 441 | 442 | $downloadPath = "" 443 | $fileName = "" 444 | if($bitness -eq "x32") 445 | { 446 | $fileName = $FileName32 447 | } 448 | else 449 | { 450 | $fileName = $FileName64 451 | } 452 | if($outVersion.Value -eq "") 453 | { 454 | $url = $updateCheckUrl -f $currentVersion,$bitness,$RingNames[$Ring],$RingFQDNs[$Ring] 455 | 456 | WriteInfo "Sending request to $url" 457 | $updateCheckResponse = Invoke-WebRequest -Uri $url -UseBasicParsing 458 | $updateCheckJson = $updateCheckResponse | ConvertFrom-Json 459 | 460 | if($updateCheckJson.isUpdateAvailable) 461 | { 462 | $downloadPath = $updateCheckJson.releasesPath.Replace("RELEASES", $fileName) 463 | } 464 | else 465 | { 466 | $outVersion.Value = $currentVersion 467 | return 468 | } 469 | } 470 | else 471 | { 472 | if($bitness -eq "x32") 473 | { 474 | $downloadPath = $downloadFormat32 -f $outVersion.Value 475 | } 476 | else 477 | { 478 | $downloadPath = $downloadFormat64 -f $outVersion.Value 479 | } 480 | } 481 | WriteInfo "Download path: $downloadPath" 482 | 483 | # Extract new version number from URL 484 | $newVersion = "" 485 | if($downloadPath -match $versionRegex) 486 | { 487 | $newVersion = $Matches.version 488 | WriteInfo "New version $newVersion" 489 | } 490 | 491 | # If we have a new version number and the download path, proceed 492 | if($newVersion -ne "" -and $downloadPath -ne "") 493 | { 494 | $localPath = Join-Path $TempPath "CDN" 495 | New-Item -ItemType Directory -Path $localPath | Out-Null 496 | $localPath = Join-Path $localPath $fileName 497 | WriteInfo "Downloading $downloadPath" 498 | DeleteFile $localPath 499 | $oldProgressPreference = $ProgressPreference 500 | $ProgressPreference = 'SilentlyContinue' 501 | Invoke-WebRequest -Uri $downloadPath -OutFile $localPath 502 | $ProgressPreference = $oldProgressPreference 503 | WriteInfo "Download complete." 504 | if(Test-Path $localPath) 505 | { 506 | WriteInfo "Successfully downloaded new installer to $localPath" 507 | $outVersion.Value = $newVersion 508 | $outPath.Value = $localPath 509 | return 510 | } 511 | } 512 | ErrorExit "Failed to check for or retrieve the update from the CDN!" -9 513 | } 514 | 515 | function SetParametersAsPackage([ref]$outVersion, [ref]$outPath) 516 | { 517 | WriteInfo "Using working directory to aquire the new MSI..." 518 | if($outVersion.Value -eq "") 519 | { 520 | ErrorExit "Target version should already be provided by OverrideVersion parameter" 521 | } 522 | 523 | $workingDirectory = Get-Location 524 | 525 | WriteInfo "Working Directory: $workingDirectory" 526 | 527 | # Select MSI based on the bitness 528 | if ($installBitness -eq "x86") 529 | { 530 | WriteInfo "Using 32-bit MSI from working directory" 531 | $fromMsi = Join-Path $workingDirectory $FileName32 # x86 MSI 532 | } 533 | else 534 | { 535 | WriteInfo "Using 64-bit MSI from working directory" 536 | $fromMsi = Join-Path $workingDirectory $FileName64 # x64 MSI 537 | } 538 | $outPath.Value = $fromMsi 539 | } 540 | 541 | function SetParametersWithShare([ref]$outVersion, [ref]$outPath) 542 | { 543 | WriteInfo "Using the BaseShare check for an update and aquire the new MSI..." 544 | if($outVersion.Value -eq "") 545 | { 546 | # Get the target Teams Machine Installer version from the share 547 | $targetVersion = GetTargetVersion 548 | $outVersion.Value = $targetVersion 549 | } 550 | 551 | # Select MSI based on the bitness 552 | if ($installBitness -eq "x86") 553 | { 554 | WriteInfo "Using 32-bit MSI from BaseShare" 555 | $fromMsi = "$BaseShare\$targetVersion\$FileName32" # x86 MSI 556 | } 557 | else 558 | { 559 | WriteInfo "Using 64-bit MSI from BaseShare" 560 | $fromMsi = "$BaseShare\$targetVersion\$FileName64" # x64 MSI 561 | } 562 | $outPath.Value = $fromMsi 563 | } 564 | 565 | function GetMsiExecFlags() 566 | { 567 | $msiExecFlags = "" 568 | # Set msiExec flags based on our mode 569 | if ($mode -eq "install") 570 | { 571 | WriteInfo "This will be an install" 572 | $msiExecFlags = "/i" # new install flag 573 | } 574 | elseif ($mode -eq "update") 575 | { 576 | WriteInfo "This will be an override update" 577 | $msiExecFlags = "/fav" # override flag 578 | } 579 | elseif ($mode -eq "installovertop") 580 | { 581 | if($AllowInstallOvertopExisting) 582 | { 583 | WriteInfo "This will be an install overtop an existing install" 584 | $msiExecFlags = "/i" # new install flag 585 | } 586 | else 587 | { 588 | ErrorExit "ERROR: Existing Teams Machine-Wide Installer is present but it was not installed by the current user or as an ALLUSERS=1 install" -4 589 | } 590 | } 591 | else 592 | { 593 | ErrorExit "UNEXPECTED ERROR! Unknown mode" -5 594 | } 595 | return $msiExecFlags 596 | } 597 | 598 | function CheckPolicyKey() 599 | { 600 | # Set AllowMsiOverride key if needed 601 | $AllowMsiExists = (HasReg -Path $AllowMsiRegPath -Name $AllowMsiRegName) 602 | if ((-not $AllowMsiExists) -or $OverwritePolicyKey) 603 | { 604 | WriteInfo "The policy key AllowMsiOverride is not set, setting $AllowMsiRegPath\$AllowMsiRegName to 1..." 605 | SetDwordReg -Path $AllowMsiRegPath -Name $AllowMsiRegName -Value 1 | Out-Null 606 | } 607 | $AllowMsiValue = !!(GetReg -Path $AllowMsiRegPath -Name $AllowMsiRegName -DefaultValue 0) 608 | WriteInfo "AllowMsiOverride policy is set to $AllowMsiValue" 609 | 610 | if(-not $AllowMsiValue) 611 | { 612 | ErrorExit "ERROR: AllowMsiOverride is not enabled by policy!" -1 613 | } 614 | } 615 | 616 | function CheckParameters() 617 | { 618 | if( $Type -eq "Share" -and $BaseShare -eq "" ) 619 | { 620 | ErrorExit "ERROR: BaseShare must be provided" 621 | } 622 | if( $Type -ne "Share" -and $BaseShare -ne "" ) 623 | { 624 | ErrorExit "ERROR: BaseShare should only be provided with Share type" 625 | } 626 | if( $Type -eq "Package" -and $OverrideVersion -eq "") 627 | { 628 | ErrorExit "ERROR: You must provide an OverrideVersion with Package type" 629 | } 630 | } 631 | 632 | # ----- Constants ----- 633 | 634 | $versionRegex = "(?\d+\.\d+\.\d+\.\d+)" 635 | 636 | $AllowMsiRegPath = "HKLM:\Software\Policies\Microsoft\Office\16.0\Teams" 637 | $AllowMsiRegName = "AllowMsiOverride" 638 | 639 | $RunKeyPath32 = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Run" 640 | $RunKeyPath64 = "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Run" 641 | 642 | $MsiPkg32Guid = "{39AF0813-FA7B-4860-ADBE-93B9B214B914}" 643 | $MsiPkg64Guid = "{731F6BAA-A986-45A4-8936-7C3AAAAA760B}" 644 | 645 | $MsiProduct32Guid = "3180FA93B7AF0684DAEB399B2B419B41" 646 | $MsiProduct64Guid = "AAB6F137689A4A549863C7A3AAAA67B0" 647 | 648 | $FileName32 = "Teams_windows.msi" 649 | $FileName64 = "Teams_windows_x64.msi" 650 | 651 | $TempPath = Join-Path $env:TEMP "TeamsMsiOverrideCheck" 652 | 653 | $EventLogSource = "TeamsMsiOverride" 654 | 655 | #----- Main Script ----- 656 | 657 | # Set the default error action preference 658 | $ErrorActionPreference = "Continue" 659 | 660 | InitTracing("TeamsMsiOverrideTrace.txt") 661 | 662 | WriteTrace("Script Version $Version") 663 | WriteTrace("Parameters Type: $Type, Ring: $Ring, BaseShare: $BaseShare, OverrideVersion: $OverrideVersion, MsiFileName: $MsiFileName, AllowInstallOvertopExisting: $AllowInstallOvertopExisting, OverwritePolicyKey: $OverwritePolicyKey, FixRunKey: $FixRunKey") 664 | WriteTrace("Environment IsSystemAccount: $(IsRunningUnderSystem), IsOS64Bit: $([Environment]::Is64BitOperatingSystem)") 665 | 666 | # Create event log source 667 | try { New-EventLog -LogName Application -Source $EventLogSource -ErrorAction SilentlyContinue } 668 | catch { } 669 | 670 | # Delete the temp directory 671 | Cleanup 672 | 673 | # Validate parameters passed in 674 | CheckParameters 675 | 676 | # Check and set AllowMsiOverride key 677 | CheckPolicyKey 678 | 679 | $Remove32 = $false 680 | $Remove64 = $false 681 | 682 | [array]$productsKeys = GetProductsKey 683 | if($UninstallAll) 684 | { 685 | WriteInfo "Removing all existing versions of the machine-wide installer." 686 | $Remove32 = $true 687 | $Remove64 = $true 688 | } 689 | else 690 | { 691 | # Check if we have both 32 bit and 64 bit versions of the MSI installed. This will be an issue. 692 | if((Has32BitProductKey $productsKeys) -and (Has64BitProductKey $productsKeys)) 693 | { 694 | # If switch is passed, uninstall the 32 bit MSI before we perform the upgrade on 64 bit. 695 | if($Uninstall32Bit) 696 | { 697 | WriteInfo "Removing 32-bit version of the machine-wide installer." 698 | $Remove32 = $true 699 | } 700 | elseif($UninstallAllIfBothPresent) 701 | { 702 | WriteInfo "Removing all existing versions of the machine-wide installer." 703 | $Remove32 = $true 704 | $Remove64 = $true 705 | } 706 | else 707 | { 708 | ErrorExit "It appears you have both 32 and 64 bit versions of the machine-wide installer present. Please uninstall them and reinstall the correct one, or use the Uninstall32Bit switch to attempt to uninstall the 32 bit version." -16 709 | } 710 | } 711 | } 712 | 713 | if($Remove32 -and (Has32BitProductKey $productsKeys)) 714 | { 715 | $msiExecUninstallArgs = "/X $MsiPkg32Guid /quiet /l*v $env:TEMP\msiOverrideCheck_msiexecUninstall32.log" 716 | 717 | WriteInfo "About to uninstall 32-bit MSI using this msiexec command:" 718 | WriteInfo " msiexec.exe $msiExecUninstallArgs" 719 | 720 | $res = Start-Process "msiexec.exe" -ArgumentList $msiExecUninstallArgs -Wait -PassThru -WindowStyle Hidden 721 | if ($res.ExitCode -eq 0) 722 | { 723 | WriteInfo "MsiExec completed successfully." 724 | } 725 | else 726 | { 727 | ErrorExit "ERROR: MsiExec failed with exit code $($res.ExitCode)" $res.ExitCode 728 | } 729 | } 730 | 731 | if($Remove64 -and (Has64BitProductKey $productsKeys)) 732 | { 733 | $msiExecUninstallArgs = "/X $MsiPkg64Guid /quiet /l*v $env:TEMP\msiOverrideCheck_msiexecUninstall64.log" 734 | 735 | WriteInfo "About to uninstall 64-bit MSI using this msiexec command:" 736 | WriteInfo " msiexec.exe $msiExecUninstallArgs" 737 | 738 | $res = Start-Process "msiexec.exe" -ArgumentList $msiExecUninstallArgs -Wait -PassThru -WindowStyle Hidden 739 | if ($res.ExitCode -eq 0) 740 | { 741 | WriteInfo "MsiExec completed successfully." 742 | } 743 | else 744 | { 745 | ErrorExit "ERROR: MsiExec failed with exit code $($res.ExitCode)" $res.ExitCode 746 | } 747 | } 748 | 749 | # Get the existing Teams Machine Installer version 750 | $currentVersion = GetInstallerVersion 751 | if($currentVersion) 752 | { 753 | WriteInfo "Current Teams Machine-Wide Installer version is $currentVersion" 754 | } 755 | else 756 | { 757 | WriteInfo "Teams Machine-Wide Installer was not found." 758 | $currentVersion = "1.3.00.00000" 759 | } 760 | 761 | $fromMsi = "" 762 | $mode = "" 763 | $packageFileName = "" 764 | $installBitness = GetInstallBitness ([ref]$mode) ([ref]$packageFileName) 765 | 766 | if($packageFileName -is [array]) 767 | { 768 | ErrorExit "Two or more package file names were found, indicating the machine-wide installer may be installed multiple times! Unable to continue." -17 769 | } 770 | 771 | $targetVersion = "" 772 | if($OverrideVersion -ne "") 773 | { 774 | $targetVersion = VerifyVersion $OverrideVersion 775 | if($targetVersion -eq $null) 776 | { 777 | ErrorExit "Specified OverrideVersion is not the correct format. Please ensure it follows a format similar to 1.2.00.34567" -10 778 | } 779 | 780 | if($currentVersion -eq $targetVersion) 781 | { 782 | WriteSuccess "Version specified in OverrideVersion is already installed!" 783 | CleanExit 784 | } 785 | } 786 | 787 | # Set the parameters either using CDN or file share 788 | if($Type -eq "CDN") 789 | { 790 | SetParametersWithCDN ([ref]$targetVersion) ([ref]$fromMsi) 791 | } 792 | elseif($Type -eq "Package") 793 | { 794 | SetParametersAsPackage ([ref]$targetVersion) ([ref]$fromMsi) 795 | } 796 | else 797 | { 798 | SetParametersWithShare([ref]$targetVersion) ([ref]$fromMsi) 799 | } 800 | 801 | # Confirm we have the target version 802 | if($targetVersion) 803 | { 804 | WriteInfo "Target Teams Machine-Wide Installer version is $targetVersion" 805 | } 806 | else 807 | { 808 | ErrorExit "ERROR: TargetVersion is invalid!" -2 809 | } 810 | 811 | # Confirm we don't already have the target version installed 812 | if($currentVersion -eq $targetVersion) 813 | { 814 | WriteSuccess "Target version already installed!" 815 | CleanExit 816 | } 817 | 818 | # Get our MSIExec flags 819 | $msiExecFlags = GetMsiExecFlags 820 | 821 | # Check that we can reach the MSI file 822 | if (-not (Test-Path $fromMsi)) 823 | { 824 | ErrorExit "ERROR: Unable to access the MSI at $fromMsi" -6 825 | } 826 | 827 | # Get the new MSI file name (must match the original for an in place repair operation) 828 | if($MsiFileName -ne "") 829 | { 830 | $msiName = $MsiFileName 831 | } 832 | else 833 | { 834 | $msiName = $packageFileName 835 | } 836 | 837 | if (-not $msiName) 838 | { 839 | # If this is a new install, or we don't know the MSI name, use the original MSI name 840 | $msiName = Split-Path $fromMsi -Leaf 841 | } 842 | 843 | # Rename (for CDN based) or copy from the share with the new name (for share based) 844 | if($Type -eq "CDN") 845 | { 846 | WriteInfo "Renaming $fromMsi to $msiName..." 847 | $toMsi = (Rename-Item -Path $fromMsi -NewName $msiName -PassThru).FullName 848 | } 849 | else 850 | { 851 | # Copy MSI to our temp folder 852 | $toMsi = Join-Path $TempPath $msiName 853 | WriteInfo "Copying $fromMsi to $toMsi..." 854 | New-Item -ItemType File -Path $toMsi -Force | Out-Null 855 | Copy-Item -Path $fromMsi -Destination $toMsi | Out-Null 856 | } 857 | 858 | #Construct our full MsiExec arg statement 859 | $msiExecArgs = "$msiExecFlags `"$toMsi`" /quiet ALLUSERS=1 /l*v $env:TEMP\msiOverrideCheck_msiexec.log" 860 | 861 | # Output our action 862 | WriteInfo "About to perform deployment using this msiexec command:" 863 | WriteInfo " msiexec.exe $msiExecArgs" 864 | 865 | # Do the install or upgrade 866 | $res = Start-Process "msiexec.exe" -ArgumentList $msiExecArgs -Wait -PassThru -WindowStyle Hidden 867 | if ($res.ExitCode -eq 0) 868 | { 869 | WriteInfo "MsiExec completed successfully." 870 | } 871 | else 872 | { 873 | ErrorExit "ERROR: MsiExec failed with exit code $($res.ExitCode)" $res.ExitCode 874 | } 875 | 876 | # Fixup the HKLM Run key if option is set 877 | if($FixRunKey) 878 | { 879 | $installer = GetInstallerPath 880 | $keyValue = "`"$installer`" --checkInstall --source=default" 881 | WriteInfo "Rewriting the HKLM Run key with $keyValue" 882 | if($([Environment]::Is64BitOperatingSystem)) 883 | { 884 | SetExpandStringReg $RunKeyPath64 "TeamsMachineInstaller" $keyValue 885 | } 886 | else 887 | { 888 | SetExpandStringReg $RunKeyPath32 "TeamsMachineInstaller" $keyValue 889 | } 890 | } 891 | 892 | # Get final confirmation we actually did update the installer 893 | $currentVersion = GetInstallerVersion 894 | if($currentVersion) 895 | { 896 | WriteInfo "New Teams Machine Installer version is $currentVersion" 897 | } 898 | if($currentVersion -eq $targetVersion) 899 | { 900 | WriteSuccess "Deployment successful, installer is now at target version!" 901 | try { Write-EventLog -LogName Application -Source $EventLogSource -Category 0 -EntryType Information -EventId 0 -Message "Successfully updated Teams Machine-Wide Installer to $targetVersion" } 902 | catch { } 903 | CleanExit 904 | } 905 | else 906 | { 907 | ErrorExit "ERROR: Script completed, however the Teams Machine-Wide Installer is still not at the target version!" -7 908 | } 909 | --------------------------------------------------------------------------------