Quasi un anno è passato dal lancio in anteprima del Nuovo Microsoft Teams e, anche se non proprio tutte le funzionalità sembrano propriamente stabili, Microsoft ha deciso già da tempo di distribuire automaticamente questa nuova versione al posto del caro, vecchio, consolidato, Teams Classic.
Ora, in linea di principio, questo aggiornamento automatico mi vede assolutamente favorevole, stando alle parole di Microsoft:
Gli utenti che hanno installato una versione diversa di Teams avranno la loro versione sostituita con la versione di cui è stato eseguito il provisioning
Ben venga l’aggiornameto automatico quindi? Ni!
L’aggiornamento al Nuovo Teams udite udite…non fa totale piazza pulita delle vecchie installazioni! (fino ad oggi almeno)
Come ben sappiamo, durante il suo ciclo di vita, l’installazione di Teams Classic ha avuto varie e fantasiose incarnazioni: spaziamo infatti dall’installazione su base utente in AppData o anche ProgramData fino al machine-wide installer e di certo vogliamo eliminare ogni residuo di queste vecchie installazioni!
A questo aggiungiamo che se siete abituati a lavorare con GPO e distribuzioni MSI questa volta dovrete farne a meno: New Teams non viene fornito come pacchetto MSI.
Tutto ciò premesso ho pensato quindi fosse utile scrivermi uno script PowerShell per avere maggiore controllo ed automazione sia sul processo di deploy del Nuovo Microsoft Teams, sia sulla rimozione di Teams Classic in tutte le sue varianti.
Disinstalliamo Teams Classic via PowerShell#
Sappiamo che Teams Classic può essere stato installato sia a livello di profilo utente sia machine-wide, dobbiamo quindi tenerne conto per una rimozione completa.
Rimozione per utente#
Controlliamo i registri utente e lanciamo la QuietUninstallString
, questo ci permette di andare a colpo sicuro nel rimuovere Teams Classic senza l’utilizzo di path predefiniti.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
# Specify the registry path
$reg = "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Teams"
# Get Teams uninstall information for each user
$userSids = Get-WmiObject Win32_UserProfile | Where-Object { $_.Special -eq $false } | Select-Object -ExpandProperty SID
foreach ($userSid in $userSids) {
$userReg= "Registry::HKEY_USERS\$userSid\$reg"
$teamsUninstallInfo = Get-ItemProperty -LiteralPath $userReg -ErrorAction SilentlyContinue
# Display the Teams uninstall information for each user
if ($teamsUninstallInfo) {
$sid = New-Object System.Security.Principal.SecurityIdentifier($userSid)
# Use Translate to find user from sid
$objUser = $sid.Translate([System.Security.Principal.NTAccount])
if ($teamsUninstallInfo.QuietUninstallString) {
Start-Process -FilePath "cmd" -ArgumentList "/c", $teamsUninstallInfo.QuietUninstallString -Wait
}
# Cleanup registry
if (Test-Path -path $userReg) {
Remove-Item $userReg -Recurse -Force
}
}
}
|
Rimozione machine-wide#
Utilizziamo i guid
conosciuti di Teams e anche qui andiamo a sfruttare il registry per disinstallare tramite msiexec.
Rispetto a molte altre fonti trovate in rete questo metodo è molto più veloce perchè evita l’utilizzo di Get-WmiObject -Class Win32_Product
per ottenere la lista dei programmi installati.
1
2
3
4
5
6
7
8
9
10
11
12
13
|
# Known Guid
$msiPkg32Guid = "{39AF0813-FA7B-4860-ADBE-93B9B214B914}"
$msiPkg64Guid = "{731F6BAA-A986-45A4-8936-7C3AAAAA760B}"
$uninstallReg64 = Get-Item -Path HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\* -ErrorAction SilentlyContinue | Get-ItemProperty | Where-Object { $_.DisplayName -match 'Teams Machine-Wide Installer' }
$uninstallReg32 = Get-Item -Path HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\* -ErrorAction SilentlyContinue | Get-ItemProperty | Where-Object { $_.DisplayName -match 'Teams Machine-Wide Installer' }
if ($uninstallReg64) {
$msiExecUninstallArgs = "/X $msiPkg64Guid /quiet"
} elseif ($uninstallReg32) {
$msiExecUninstallArgs = "/X $msiPkg32Guid /quiet"
} else {
return
}
$p = Start-Process "msiexec.exe" -ArgumentList $msiExecUninstallArgs -Wait -PassThru -WindowStyle Hidden
|
Installiamo New Teams via PowerShell#
Partiamo col dire che i metodi uffialmente supportati per l’installazione li troviamo qui
https://learn.microsoft.com/en-us/microsoftteams/new-teams-bulk-install-client
e scopriamo che dovremo necessariamente utilizzare teamsbootstrapper.exe per il nostro deploy, quindi per prima cosa scarichiamolo tramite una helper function.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
function DownloadFile {
param(
[Parameter(Mandatory=$true)]
[string]$url,
[Parameter(Mandatory=$true)]
[string]$fileName,
[string]$path = [System.Environment]::GetEnvironmentVariable('TEMP','Machine')
)
# Construct WebClient object
$webClient = New-Object -TypeName System.Net.WebClient
$file = $null
# Create path if it doesn't exist
if (-not(Test-Path -Path $path)) {
New-Item -Path $path -ItemType Directory -Force | Out-Null
}
# Download
try {
$outputPath = Join-Path -Path $path -ChildPath $fileName
$webClient.DownloadFile($url, $outputPath)
$file = $outputPath
}
catch {}
# Dispose of the WebClient object
$webClient.Dispose()
return $file
}
$BootstrapperPath = DownloadFile "https://go.microsoft.com/fwlink/?linkid=2243204&clcid=0x409" "bootstrapper.exe"
|
Poi passiamo all’installazione tramite bootstrapper.exe, possiamo lasciare che scarichi Teams direttamente da internet o fornire un path locale o UNC se preferiamo risparmiare tempo e banda
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
function InstallTeams {
param(
[Parameter(Mandatory=$true)]
[string]$bootstrapperPath,
[string]$teamsPackagePath = ''
)
try {
# Using the teamsbootstrapper.exe -p command always guarantees the latest Teams client is installed.
# Use -o with path to Teams's MSIX package minimizing the amount of bandwidth used for the initial installation.
# The MSIX can exist in a local path or UNC.
if ($teamsPackagePath -ne '') {
$arg = "-o $teamsPackagePath"
}
$r = & $bootstrapperPath -p $arg
$resultObj = try { $r | ConvertFrom-Json } catch { $null }
if ($resultObj -eq $null -or $resultObj.success -eq $false) {
throw ''
}
return $true
}
catch {
return $false
}
}
InstallTeams -BootstrapperPath $BootstrapperPath
|
Teams-Posh, lo script definitivo per rimozione e installazione di Teams#
Non ci resta che mettere insieme tutti i pezzi visti fino ad ora e buttare giù lo script completo e con il necessario logging.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
|
<#
.SYNOPSIS
Teams-Posh installs or uninstalls Microsoft Teams.
.DESCRIPTION
This script allows for the installation or uninstallation of Microsoft Teams.
When installing, it downloads the bootstrapper and Teams package if not provided,
and then installs Microsoft Teams.
When uninstalling, it removes the installed Microsoft Teams application,
this includes Teams Classic uninstallation querying related registry keys thus
avoiding use of very slow call to "Get-WmiObject -Class Win32_Product".
.PARAMETER Action
Specifies the action to perform. Valid values are 'Install' or 'Uninstall'.
.PARAMETER BootstrapperPath
Specifies the path to the bootstrapper executable.
If not provided, it will be downloaded by Microsoft website.
.PARAMETER TeamsPackagePath
Specifies the path to the Microsoft Teams package.
If not provided (required for installation), it will be downloaded.
.EXAMPLE
.\Teams-Posh.ps1 -Action Install
Installs Microsoft Teams.
.EXAMPLE
.\Teams-Posh.ps1 -Action Uninstall
Uninstalls Microsoft Teams.
.NOTES
Author:[lestoilfante](https://github.com/lestoilfante)
#>
param (
[Parameter(Mandatory=$true)]
[ValidateSet('Install', 'Uninstall')]
[string]$Action,
[string]$BootstrapperPath = '',
[string]$TeamsPackagePath = ''
)
function Teams-Posh {
# Check running with elevated privileges
if (!([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")) {
Log "This script requires elevation. Please run as administrator"
exit 1
}
if ($BootstrapperPath -eq '') {
$BootstrapperPath = DownloadFile "https://go.microsoft.com/fwlink/?linkid=2243204&clcid=0x409" "bootstrapper.exe"
if ($BootstrapperPath -eq $null) { exit 1 }
}
if ($Action -eq 'Install') {
$install = InstallTeams -BootstrapperPath $BootstrapperPath -TeamsPackagePath $TeamsPackagePath
if ($install -eq $true) { exit 0 }
exit 1
}
if ($Action -eq 'Uninstall') {
RemoveTeamsClassicWide
RemoveTeamsClassic
RemoveTeams $BootstrapperPath
exit 0
}
}
function InstallTeams {
param(
[Parameter(Mandatory=$true)]
[string]$bootstrapperPath,
[string]$teamsPackagePath = ''
)
try {
# Using the teamsbootstrapper.exe -p command always guarantees the latest Teams client is installed.
# Use -o with path to Teams's MSIX package minimizing the amount of bandwidth used for the initial installation.
# The MSIX can exist in a local path or UNC.
if ($teamsPackagePath -ne '') {
$arg = "-o $teamsPackagePath"
} else { Log 'Downloading Teams' }
$r = & $bootstrapperPath -p $arg
$resultObj = try { $r | ConvertFrom-Json } catch { $null }
if ($resultObj -eq $null -or $resultObj.success -eq $false) {
throw ''
}
Log 'Teams installation done'
return $true
}
catch {
Log 'ERROR: Teams installation failed'
return $false
}
}
function RemoveTeamsClassicWide {
# Known Guid
$msiPkg32Guid = "{39AF0813-FA7B-4860-ADBE-93B9B214B914}"
$msiPkg64Guid = "{731F6BAA-A986-45A4-8936-7C3AAAAA760B}"
$uninstallReg64 = Get-Item -Path HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\* -ErrorAction SilentlyContinue | Get-ItemProperty | Where-Object { $_.DisplayName -match 'Teams Machine-Wide Installer' }
$uninstallReg32 = Get-Item -Path HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\* -ErrorAction SilentlyContinue | Get-ItemProperty | Where-Object { $_.DisplayName -match 'Teams Machine-Wide Installer' }
if ($uninstallReg64) {
$msiExecUninstallArgs = "/X $msiPkg64Guid /quiet"
Log "Teams Classic Machine-Wide Installer x64 found."
} elseif ($uninstallReg32) {
$msiExecUninstallArgs = "/X $msiPkg32Guid /quiet"
Log "Teams Machine-Wide Installer x86 found."
} else {
return
}
$p = Start-Process "msiexec.exe" -ArgumentList $msiExecUninstallArgs -Wait -PassThru -WindowStyle Hidden
if ($p.ExitCode -eq 0) {
Log "Teams Classic Machine-Wide uninstalled."
} else {
Log "ERROR: Teams Classic Machine-Wide uninstall failed with exit code $($p.ExitCode)"
}
}
function RemoveTeamsClassic {
# Specify the registry path
$reg = "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Teams"
# Get Teams uninstall information for each user
$userSids = Get-WmiObject Win32_UserProfile | Where-Object { $_.Special -eq $false } | Select-Object -ExpandProperty SID
foreach ($userSid in $userSids) {
$userReg= "Registry::HKEY_USERS\$userSid\$reg"
$teamsUninstallInfo = Get-ItemProperty -LiteralPath $userReg -ErrorAction SilentlyContinue
# Display the Teams uninstall information for each user
if ($teamsUninstallInfo) {
$sid = New-Object System.Security.Principal.SecurityIdentifier($userSid)
# Use Translate to find user from sid
$objUser = $sid.Translate([System.Security.Principal.NTAccount])
if ($teamsUninstallInfo.QuietUninstallString) {
Start-Process -FilePath "cmd" -ArgumentList "/c", $teamsUninstallInfo.QuietUninstallString -Wait
Log "Teams Classic Removed for user $($objUser.Value)"
}
# Cleanup registry
if (Test-Path -path $userReg) {
Remove-Item $userReg -Recurse -Force
}
}
}
}
function RemoveTeams {
param(
[string]$bootstrapper = ''
)
try{
$appx = Get-AppxPackage -AllUsers | Where-Object { $PSItem.Name -eq "MSTeams" }
if ($appx) {
Log "Teams $($appx.Version) package found"
$appx | Remove-AppxPackage -AllUsers
} else { Log "No Teams package found" }
if($bootstrapper -ne '') {
Log "Deprovisioning Teams using $bootstrapper"
$r = & $bootstrapper -x
$resultObj = try { $r | ConvertFrom-Json } catch { $null }
if ($resultObj -eq $null) {
throw ''
}
Log "Deprovisioning Teams using $bootstrapper done"
}
}
catch {
Log "ERROR: Teams package remove error"
}
}
function DownloadFile {
param(
[Parameter(Mandatory=$true)]
[string]$url,
[Parameter(Mandatory=$true)]
[string]$fileName,
[string]$path = [System.Environment]::GetEnvironmentVariable('TEMP','Machine')
)
# Construct WebClient object
$webClient = New-Object -TypeName System.Net.WebClient
$file = $null
# Create path if it doesn't exist
if (-not(Test-Path -Path $path)) {
New-Item -Path $path -ItemType Directory -Force | Out-Null
}
# Download
try {
Log "Download of $fileName start"
$outputPath = Join-Path -Path $path -ChildPath $fileName
$webClient.DownloadFile($url, $outputPath)
Log "Download of $fileName done"
$file = $outputPath
}
catch {
Log "ERROR: Download of $fileName failed"
}
# Dispose of the WebClient object
$webClient.Dispose()
return $file
}
function Log {
param (
[string]$Text
)
$timestamp = "{0:yyyy-MM-dd HH:mm:ss}" -f [DateTime]::Now
Write-Information -MessageData "$timestamp `- $($Text)" -InformationAction Continue
}
Teams-Posh
|