I've redone my deployment for Zoom and wanted to share it with the community.
1. First we need to make the powershell script.
Save as Install-Zoom.ps1 or something similar.
# Install-Zoom.ps1
$ErrorActionPreference = 'Stop'
$Url = "https://zoom.us/client/latest/ZoomInstallerFull.msi?archType=x64"
# AU2 policies via zConfig
$zConfig = @"
IntegrateZoomWithOutlook=1;
AU2_EnableAutoUpdate=true;
AU2_SetUpdateChannel=0;
AU2_InstallAtIdleTime=true;
AU2_SafeUpgradePeriod=0000-0600;
AU2_EnableShowZoomUpdates=false;
AU2_EnableUpdateAvailableBanner=false;
AU2_EnableManualUpdate=false;
"@ -replace "(\r|\n)","" -replace "\s+",""
# Save location requested
$DownloadDir = "C:\temp"
New-Item -ItemType Directory -Path $DownloadDir -Force | Out-Null
$MsiPath = Join-Path $DownloadDir "ZoomInstallerFull_x64.msi"
$LogDir = Join-Path $env:ProgramData "ZoomDeploy"
$LogPath = Join-Path $LogDir "ZoomInstall.log"
New-Item -ItemType Directory -Path $LogDir -Force | Out-Null
# TLS 1.2
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
function Get-ActiveSessionIds {
# Returns an array of active session IDs (best-effort)
$ids = @()
try {
$lines = (quser 2>$null)
foreach ($line in $lines) {
# Look for "Active" lines; session id is typically the 3rd token (varies slightly)
if ($line -match '\s+Active\s+') {
$tokens = ($line -replace '^\s+','') -split '\s+'
# Common formats:
# USERNAME SESSIONNAME ID STATE ...
# USERNAME ID STATE ...
foreach ($t in $tokens) {
if ($t -match '^\d+$') { $ids += [int]$t; break }
}
}
}
} catch {}
return ($ids | Select-Object -Unique)
}
function Send-UserMessage {
param([Parameter(Mandatory=$true)][string]$Message)
try {
& msg.exe * /TIME:60 $Message | Out-Null
} catch {}
}
function Stop-ZoomIfRunning {
# Try graceful close first, then force
$procs = Get-Process -Name "Zoom" -ErrorAction SilentlyContinue
if (-not $procs) { return $false }
Send-UserMessage "Zoom is updating. Please save your work. Zoom will close briefly."
foreach ($p in $procs) {
try {
if ($p.MainWindowHandle -ne 0) {
[void]$p.CloseMainWindow()
}
} catch {}
}
Start-Sleep -Seconds 10
# Re-check and force kill any remaining
$still = Get-Process -Name "Zoom" -ErrorAction SilentlyContinue
if ($still) {
foreach ($p in $still) {
try { Stop-Process -Id $p.Id -Force -ErrorAction SilentlyContinue } catch {}
}
# Extra belt-and-suspenders (kills child processes too)
try { & taskkill.exe /IM zoom.exe /T /F | Out-Null } catch {}
}
return $true
}
function Get-MsiProperty {
param(
[Parameter(Mandatory=$true)][string]$Path,
[Parameter(Mandatory=$true)][string]$Property
)
$installer = New-Object -ComObject WindowsInstaller.Installer
$db = $installer.OpenDatabase($Path, 0)
$view = $db.OpenView("SELECT Value FROM Property WHERE Property='$Property'")
$view.Execute()
$record = $view.Fetch()
$value = $record.StringData(1)
$view.Close()
return $value
}
function Normalize-Version {
param([string]$v)
if ([string]::IsNullOrWhiteSpace($v)) { return $null }
$parts = ($v -replace '[^\d\.]','').Split('.', [System.StringSplitOptions]::RemoveEmptyEntries)
if ($parts.Count -eq 0) { return $null }
$parts = $parts | Select-Object -First 4
while ($parts.Count -lt 4) { $parts += '0' }
try { return [version]($parts -join '.') } catch { return $null }
}
function Get-InstalledZoomVersion {
$paths = @(
"HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*",
"HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*"
)
$candidates = foreach ($p in $paths) {
Get-ItemProperty $p -ErrorAction SilentlyContinue |
Where-Object {
$_.DisplayName -and
(
$_.DisplayName -match '^Zoom( Workplace)?( \(64-bit\))?$' -or
$_.DisplayName -match 'Zoom Workplace' -or
$_.DisplayName -match 'Zoom Meetings'
)
} |
Select-Object DisplayName, DisplayVersion, Publisher
}
if (-not $candidates) { return $null }
$best = $candidates |
Sort-Object @{
Expression = { if ($_.Publisher -match 'Zoom') { 0 } else { 1 } }
}, @{
Expression = { Normalize-Version $_.DisplayVersion }
Descending = $true
} |
Select-Object -First 1
return $best.DisplayVersion
}
Write-Host "Downloading Zoom x64 MSI (latest) to $MsiPath ..."
try {
Start-BitsTransfer -Source $Url -Destination $MsiPath -ErrorAction Stop
}
catch {
Invoke-WebRequest -Uri $Url -OutFile $MsiPath -UseBasicParsing
}
# Verify signature
$sig = Get-AuthenticodeSignature -FilePath $MsiPath
if ($sig.Status -ne 'Valid') {
throw "Downloaded MSI signature not valid. Status=$($sig.Status)"
}
$availableVersionRaw = Get-MsiProperty -Path $MsiPath -Property "ProductVersion"
$availableVersion = Normalize-Version $availableVersionRaw
if (-not $availableVersion) { throw "Could not parse downloaded MSI ProductVersion: '$availableVersionRaw'" }
$installedVersionRaw = Get-InstalledZoomVersion
$installedVersion = Normalize-Version $installedVersionRaw
Write-Host "Installed Zoom version : $installedVersionRaw"
Write-Host "Available Zoom version : $availableVersionRaw"
if ($installedVersion -and ($installedVersion -eq $availableVersion)) {
Write-Host "Zoom already current. Skipping install."
Remove-Item $MsiPath -Force -ErrorAction SilentlyContinue
exit 0
}
# Close Zoom if running (with user notification)
[void](Stop-ZoomIfRunning)
Write-Host "Installing/Updating Zoom..."
$args = @(
"/i", "`"$MsiPath`"",
"/qn", "/norestart",
"/log", "`"$LogPath`"",
"zConfig=`"$zConfig`""
)
$proc = Start-Process -FilePath "msiexec.exe" -ArgumentList $args -Wait -PassThru
if ($proc.ExitCode -ne 0 -and $proc.ExitCode -ne 3010) {
Write-Host "Install failed; leaving MSI in place for troubleshooting: $MsiPath"
throw "Zoom install failed. ExitCode=$($proc.ExitCode). Log=$LogPath"
}
Write-Host "Zoom install complete. ExitCode=$($proc.ExitCode)."
# Success message
Send-UserMessage "Zoom update complete. Please open Zoom from the desktop shortcut."
# Cleanup after success
Remove-Item $MsiPath -Force -ErrorAction SilentlyContinue
Write-Host "Removed installer: $MsiPath"
exit $proc.ExitCode
2. Save the script in your packageshare\Scripts folder.
3. Modify the zconfig items as needed for your scenario. View zooms resources for the AU2 policies.
4. Update/create your deployment.
This will be a Command action for the deployment:
powershell.exe -NoProfile -ExecutionPolicy Bypass -File "{PackageShare}\Scripts\Install-Zoom.ps1"
Note: Change servername to the name of your server or use
What does the script do?
Downloads the latest 64-bit Zoom MSI from Zoom to C:\temp\ZoomInstallerFull_x64.msi (forces TLS 1.2, uses BITS and falls back to Invoke-WebRequest).
Verifies the download is legitimately signed (Authenticode signature must be Valid) and reads the MSI’s ProductVersion.
Detects the currently installed Zoom version by checking uninstall registry entries, normalizes versions, and skips installation if it’s already up to date.
If an update is needed, it warns the user, then closes Zoom if it’s running (tries graceful close, then force-kills as needed).
Installs/updates Zoom silently via msiexec /qn and writes an install log to C:\ProgramData\ZoomDeploy\ZoomInstall.log, applying several Zoom auto-update/policy settings via zConfig (enable auto-update, hide update UI/banners, disable manual update, set safe upgrade period, integrate with Outlook, etc.).
On success, it notifies the user that the update is complete and cleans up the MSI; on failure, it leaves the MSI for troubleshooting and throws an error.