PowerShell script for an Azure Custom Script Extension that prepares an AVD (formerly WVD) session host for Microsoft Teams. Sets the IsWVDEnvironment registry flag, installs the Visual C++ Redistributable, installs the Remote Desktop WebRTC Redirector Service, and provisions New Teams via the bootstrapper. Includes the original Teams Classic MSI path for older base images.

This is the modern replacement for the 2020 WVD Teams deployment script. The original installed Teams Classic per-machine, the WebRTC redirector and a Visual C++ Redistributable as an Azure Custom Script Extension. Teams Classic was retired by Microsoft in July 2024, so the script below provisions New Teams via teamsbootstrapper.exe -p while keeping the Teams Classic path documented for anyone still on an older image.
What the script does
- Sets
HKLM:\SOFTWARE\Microsoft\Teams\IsWVDEnvironment = 1so Teams enables A/V redirection on AVD session hosts. - Downloads and installs the latest x64 Visual C++ Redistributable (a prerequisite for the WebRTC redirector).
- Downloads and installs the Remote Desktop WebRTC Redirector Service MSI.
- Downloads
teamsbootstrapper.exeand provisions New Teams for all users with-p(per-machine provisioning). - Skips any component that is already installed, transcripts everything to
C:\Windows\Temp\AVD-Teams-Setup.log, and (optionally) reboots.
PowerShell
#requires -Version 5.1
#requires -RunAsAdministrator
<#
.SYNOPSIS
Provisions an AVD session host for Microsoft Teams with A/V redirection.
.DESCRIPTION
Designed to run as an Azure Custom Script Extension. Sets the AVD
Teams environment flag, installs the Visual C++ Redistributable, the
Remote Desktop WebRTC Redirector Service, and New Teams via the
bootstrapper. Each step is idempotent.
Teams Classic was retired in July 2024. For pre-2024 base images that
still need the legacy MSI flow, see the "Teams Classic fallback"
block at the bottom of this script.
.PARAMETER InstallVCRedist
Install the x64 Visual C++ Redistributable. Default: $true.
.PARAMETER InstallWebRTC
Install the Remote Desktop WebRTC Redirector Service. Default: $true.
.PARAMETER InstallNewTeams
Provision New Teams for all users via teamsbootstrapper.exe -p. Default: $true.
.PARAMETER NoReboot
Suppress the reboot at the end. Default: reboot.
.PARAMETER LogPath
Transcript path. Default: C:\Windows\Temp\AVD-Teams-Setup.log.
.EXAMPLE
.\Install-AvdTeams.ps1
.EXAMPLE
.\Install-AvdTeams.ps1 -InstallVCRedist:$false -NoReboot
#>
[CmdletBinding()]
param(
[bool] $InstallVCRedist = $true,
[bool] $InstallWebRTC = $true,
[bool] $InstallNewTeams = $true,
[switch] $NoReboot,
[string] $LogPath = 'C:\Windows\Temp\AVD-Teams-Setup.log'
)
$ErrorActionPreference = 'Stop'
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
Start-Transcript -Path $LogPath -Append | Out-Null
Write-Host "AVD Teams setup starting at $(Get-Date -Format s)"
$work = Join-Path $env:TEMP 'avd-teams-setup'
New-Item -ItemType Directory -Path $work -Force | Out-Null
function Invoke-Download {
param([Parameter(Mandatory)] [string] $Uri, [Parameter(Mandatory)] [string] $Destination)
Write-Host "Downloading $Uri"
Invoke-WebRequest -Uri $Uri -OutFile $Destination -UseBasicParsing
if (-not (Test-Path $Destination) -or (Get-Item $Destination).Length -lt 1KB) {
throw "Download failed or returned an empty file: $Uri"
}
}
function Test-PackageInstalled {
param([Parameter(Mandatory)] [string] $NameLike)
# Get-Package is more reliable than parsing the Uninstall registry.
try {
return [bool](Get-Package -ProviderName Programs -Name $NameLike -ErrorAction SilentlyContinue)
}
catch { return $false }
}
try {
# ----------------------------------------------------------------------
# 1. IsWVDEnvironment registry flag.
# ----------------------------------------------------------------------
try {
$teamsKey = 'HKLM:\SOFTWARE\Microsoft\Teams'
if (-not (Test-Path $teamsKey)) { New-Item -Path $teamsKey -Force | Out-Null }
New-ItemProperty -Path $teamsKey -Name 'IsWVDEnvironment' -PropertyType DWord -Value 1 -Force | Out-Null
Write-Host "Set $teamsKey\IsWVDEnvironment = 1"
}
catch {
Write-Warning "Failed to set IsWVDEnvironment: $($_.Exception.Message)"
}
# ----------------------------------------------------------------------
# 2. Visual C++ Redistributable (x64).
# ----------------------------------------------------------------------
if ($InstallVCRedist) {
try {
if (Test-PackageInstalled 'Microsoft Visual C++ 2015-2022 Redistributable (x64)*') {
Write-Host "VC++ x64 redistributable already installed. Skipping."
}
else {
$vc = Join-Path $work 'vc_redist.x64.exe'
Invoke-Download -Uri 'https://aka.ms/vs/17/release/vc_redist.x64.exe' -Destination $vc
Write-Host "Installing VC++ x64 redistributable..."
$p = Start-Process -FilePath $vc -ArgumentList '/install','/quiet','/norestart' -Wait -PassThru
# 0 = success, 3010 = success but reboot required.
if ($p.ExitCode -notin 0, 3010) { throw "vc_redist.x64.exe returned exit code $($p.ExitCode)." }
Write-Host "VC++ install exit code: $($p.ExitCode)"
}
}
catch {
Write-Warning "VC++ install failed: $($_.Exception.Message)"
}
}
# ----------------------------------------------------------------------
# 3. Remote Desktop WebRTC Redirector Service.
# ----------------------------------------------------------------------
if ($InstallWebRTC) {
try {
if (Test-PackageInstalled 'Remote Desktop WebRTC Redirector*') {
Write-Host "WebRTC Redirector already installed. Skipping."
}
else {
$webrtc = Join-Path $work 'MsRdcWebRTCSvc_HostSetup.msi'
Invoke-Download -Uri 'https://aka.ms/msrdcwebrtcsvc/msi' -Destination $webrtc
Write-Host "Installing Remote Desktop WebRTC Redirector Service..."
$msiLog = Join-Path $work 'webrtc-install.log'
$p = Start-Process msiexec.exe -ArgumentList "/i `"$webrtc`" /qn /norestart /l*v `"$msiLog`"" -Wait -PassThru
if ($p.ExitCode -notin 0, 3010) { throw "WebRTC MSI returned exit code $($p.ExitCode). See $msiLog." }
Write-Host "WebRTC install exit code: $($p.ExitCode)"
}
}
catch {
Write-Warning "WebRTC install failed: $($_.Exception.Message)"
}
}
# ----------------------------------------------------------------------
# 4. New Teams via the bootstrapper (-p per-machine provisioning).
# ----------------------------------------------------------------------
if ($InstallNewTeams) {
try {
# Detect New Teams MSIX package for all users.
$pkg = Get-AppxPackage -AllUsers -Name MSTeams -ErrorAction SilentlyContinue
if ($pkg) {
Write-Host "New Teams already provisioned (version $($pkg.Version)). Skipping."
}
else {
$boot = Join-Path $work 'teamsbootstrapper.exe'
Invoke-Download -Uri 'https://go.microsoft.com/fwlink/?linkid=2243204' -Destination $boot
Write-Host "Provisioning New Teams via teamsbootstrapper.exe -p ..."
$p = Start-Process -FilePath $boot -ArgumentList '-p' -Wait -PassThru -NoNewWindow
if ($p.ExitCode -ne 0) { throw "teamsbootstrapper.exe returned exit code $($p.ExitCode)." }
Write-Host "New Teams bootstrap exit code: $($p.ExitCode)"
}
}
catch {
Write-Warning "New Teams provisioning failed: $($_.Exception.Message)"
}
}
Write-Host "AVD Teams setup finished at $(Get-Date -Format s)"
}
finally {
Stop-Transcript | Out-Null
}
# ----------------------------------------------------------------------
# 5. Reboot (so the WebRTC service and New Teams initialise cleanly).
# ----------------------------------------------------------------------
if (-not $NoReboot) {
Write-Host "Restarting in 30 seconds..."
Start-Sleep -Seconds 30
Restart-Computer -Force
}
How to use it
Custom Script Extension via ARM / Bicep
Upload the script to a private storage account, then run it on every session host with the CustomScriptExtension VM extension:
{
"type": "Microsoft.Compute/virtualMachines/extensions",
"apiVersion": "2023-09-01",
"name": "[concat(parameters('sessionHostName'), '/InstallAvdTeams')]",
"location": "[resourceGroup().location]",
"properties": {
"publisher": "Microsoft.Compute",
"type": "CustomScriptExtension",
"typeHandlerVersion": "1.10",
"autoUpgradeMinorVersion": true,
"settings": {
"fileUris": [
"https://contosoavd.blob.core.windows.net/scripts/Install-AvdTeams.ps1"
]
},
"protectedSettings": {
"commandToExecute": "powershell.exe -ExecutionPolicy Bypass -NoProfile -File Install-AvdTeams.ps1 -NoReboot",
"storageAccountName": "contosoavd",
"storageAccountKey": "[parameters('storageKey')]"
}
}
}
Pass -NoReboot from the extension and reboot the VM as a separate, controlled step in your deployment pipeline.
Manual run on a single host (for testing)
# Elevated PowerShell on the session host:
.\Install-AvdTeams.ps1 -InstallVCRedist:$true -InstallWebRTC:$true -InstallNewTeams:$true -NoReboot
notepad C:\Windows\Temp\AVD-Teams-Setup.log
Notes and modern alternatives
- Teams Classic has been retired. Microsoft retired Teams Classic on 1 July 2024 and the legacy per-machine MSI (
https://aka.ms/teams64bitmsi) no longer installs a supported client. Use the New Teams bootstrapper path above on any image newer than mid-2024. - Teams Classic fallback for older images. If you are on a pre-2024 base image that has not yet been migrated, the legacy flow was: download
https://aka.ms/teams64bitmsi, install withmsiexec /i Teams_windows_x64.msi ALLUSER=1 ALLUSERS=1 /qn /norestart. TheALLUSER=1switch is the per-machine install flag;ALLUSERS=1is the standard MSI all-users flag. Both are required on AVD. Note that several download URLs from the original 2020 post (the hardcodedquery.prod.cms.rt.microsoft.com/cms/api/am/binary/...links) now return 404; use theaka.msshortlinks instead. - Image-bake vs runtime. For golden images managed by Azure Image Builder or Packer, fold this script into the image build rather than running it as a Custom Script Extension on every new VM. New Teams provisions during the first user logon either way, but baking saves several minutes per host start.
- FSLogix profile container. New Teams stores its profile under
%LOCALAPPDATA%\Packages\MSTeams_8wekyb3d8bbwe. Make sure your FSLogix profile container includes%LocalAppData%\Packages(it does by default) or Teams will re-initialise at every login.
Security notes
- HTTPS and TLS 1.2 only. The script forces
Tls12before any download and usesaka.msredirects to vendor-signed installers. Do not substitutehttp://URLs. - Verify installers if you mirror them. If you cache the WebRTC MSI, VC++ redist, and Teams bootstrapper in your own storage account, verify the Authenticode signature of each (
Get-AuthenticodeSignature .\file.exe) before publishing. - Run as the VM extension identity, not your own. The Custom Script Extension runs as
SYSTEM. Do not embed personal credentials incommandToExecute. If you need to authenticate to a private storage account, use the managed identity of the VM or the storage account key viaprotectedSettings, which is encrypted at rest. - Idempotent and resumable. Every install step checks before it acts (
Get-Package,Get-AppxPackage). Re-running after a partial failure will only attempt the missing pieces. - Rollback. New Teams:
Get-AppxPackage -AllUsers -Name MSTeams | Remove-AppxPackage -AllUsers. WebRTC redirector: uninstall viaGet-Package -Name 'Remote Desktop WebRTC*' | Uninstall-Package. VC++ redist: leave it; other Microsoft components depend on it.
Original 2020 script
The original post pointed at https://github.com/RMITBLOG/WVD_TeamsDeploy/blob/master/WVD_teamsInstall.ps1, which installed Teams Classic with ALLUSER=1 ALLUSERS=1 and used hard-coded Microsoft CDN URLs that have since gone 404. The 2020 flow has been preserved in the comments inside the script above; use the modern bootstrapper path for any new build.




