Skip to content

Automate Teams on AVD Session Hosts (New Teams + WebRTC)

Technical Article

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.

Categories
Euc Enduser ComputingMicrosoft
Tags
Microsoft TeamsNew TeamsTeamsWebrtcAzure Virtual DesktopAvdWvdPowershellCustom Script Extension
Automate Teams on AVD Session Hosts (New Teams + WebRTC)

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 = 1 so 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.exe and 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 with msiexec /i Teams_windows_x64.msi ALLUSER=1 ALLUSERS=1 /qn /norestart. The ALLUSER=1 switch is the per-machine install flag; ALLUSERS=1 is the standard MSI all-users flag. Both are required on AVD. Note that several download URLs from the original 2020 post (the hardcoded query.prod.cms.rt.microsoft.com/cms/api/am/binary/... links) now return 404; use the aka.ms shortlinks 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 Tls12 before any download and uses aka.ms redirects to vendor-signed installers. Do not substitute http:// 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 in commandToExecute. If you need to authenticate to a private storage account, use the managed identity of the VM or the storage account key via protectedSettings, 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 via Get-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.