Skip to content

Commit bffda68

Browse files
Windows app installer multi tool (#44)
* Update Get-AVDW365Gateways.ps1 Changed URL to GCCH JSON Download * Squashed commit of the following: commit 33f3d0b Author: Donna Ryan <100233767+DonnaRyanMicrosoft@users.noreply.github.com> Date: Wed Mar 26 15:17:12 2025 -0400 Initial Commit Includes both detection and remediation script, plus readme.md files * Initial Commit * Changed MSRDC uninstall order Also some minor formatting changes * DisableAutoUpdate reg key support created function to allow admins to set the DisableAutoUpdate registry key * Updated outputs minor bug fix * Added logging for install processes Store and Winget installation output now logged separately * Fixed ID10T error deleted extra stuff that shouldn't have been pasted * added comments * Added Param Block commented out old variable assignments * Formatting * Logging enabled + error handling Added logging module, Added error handling Added rename MSIX download and rename to original file name. * Added Readme.Md instructions Wrote the basic instructions * Changed MSRDC Uninstall Parameter Changed parameter type to Switch. This makes the parameter true if present, otherwise false. Changed the name of the parameter for clarity. * Updated Readme.md Removed notes from head of script. Updated Readme.md * Formatting and Copyright Minor formatting changes with copyright, name, and version added * Changed name of file to new name renamed script * Changed Folder Name Renamed Folder
1 parent c2b9b6b commit bffda68

2 files changed

Lines changed: 337 additions & 0 deletions

File tree

Lines changed: 232 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,232 @@
1+
<#
2+
.COPYRIGHT
3+
Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
4+
See LICENSE in the project root for license information.
5+
#>
6+
7+
#Microsoft Remote Desktop Client Migration Script
8+
#Version 1.0
9+
#For more info, visit: https://github.com/microsoft/Windows365-PSScripts
10+
11+
Param(
12+
[parameter(mandatory = $false, HelpMessage = "Where to source installer payload")]
13+
[ValidateSet('Store','WinGet','MSIX')]
14+
[string]$source = "Store",
15+
[parameter(mandatory = $false, HelpMessage = "Value to set auto update reg key")]
16+
[ValidateSet(0,1,2,3)]
17+
[int]$DisableAutoUpdate = 0,
18+
[parameter(mandatory = $false, HelpMessage = "Do not uninstall Remote Desktop if found")]
19+
[switch]$SkipRemoteDesktopUninstall ,
20+
[parameter(mandatory = $false, HelpMessage = "Log path and file name")]
21+
[string]$logpath = "$env:windir\temp\MultiTool.log"
22+
)
23+
24+
#$DisableAutoUpdate values:
25+
#0: Enables updates (default value)
26+
#1: Disable updates from all locations
27+
#2: Disable updates from the Microsoft Store
28+
#3: Disable updates from the CDN location
29+
30+
#logging function
31+
function update-log {
32+
Param(
33+
[Parameter(
34+
Mandatory = $true,
35+
ValueFromPipeline = $true,
36+
ValueFromPipelineByPropertyName = $true,
37+
Position = 0
38+
)]
39+
[string]$Data,
40+
[validateset('Information', 'Warning', 'Error', 'Comment')]
41+
[string]$Class = "Information",
42+
[validateset('Console', 'File', 'Both')]
43+
[string]$Output
44+
)
45+
46+
$date = get-date -UFormat "%m/%d/%y %r"
47+
$String = $Class + " " + $date + " " + $data
48+
if ($Output -eq "Console") { Write-Output $string | out-host }
49+
if ($Output -eq "file") { Write-Output $String | out-file -FilePath $logpath -Append }
50+
if ($Output -eq "Both") {
51+
Write-Output $string | out-host
52+
Write-Output $String | out-file -FilePath $logpath -Append
53+
}
54+
}
55+
56+
#function to uninstall MSRDC by pulling MSIEXEC.EXE GUID from the registy - primary method
57+
function uninstall-MSRDCreg{
58+
59+
$MSRCDreg = Get-ItemProperty hklm:\software\microsoft\windows\currentversion\uninstall\* | Where-Object {$_.Displayname -like "*Remote Desktop*"} | Select-Object DisplayName,DisplayVersion,UninstallString,QuietUninstallString
60+
if ($MSRCDreg.DisplayName -eq "Remote Desktop"){
61+
update-log -Data "Remote Desktop Installation Found" -Class Information -Output Both
62+
$uninstall = $MSRCDreg.uninstallstring -replace "MsiExec.exe /X",""
63+
update-log -Data "Uninstalling Remote Desktop" -Class Information -Output Both
64+
65+
try{
66+
Start-Process -FilePath "msiexec.exe" -ArgumentList "/x $($uninstall) /q /norestart" -ErrorAction Stop
67+
}
68+
catch
69+
{
70+
update-log -Data "Something went wrong uninstalling Remote Desktop" -Class Error -Output Both
71+
Update-Log -data $_.Exception.Message -Class Error -Output Both
72+
}
73+
}
74+
else
75+
{
76+
update-log -Data "Remote Desktop not detected via registry. Trying as package" -Class Information -Output Both
77+
uninstall-MSRDC
78+
}
79+
}
80+
81+
#Function to uninstall MSRDC via uninstall-package as a secondary method
82+
function uninstall-MSRDC{
83+
try{
84+
update-log -Data "Looking to see if Remote Desktop is an installed package" -Class Information -Output Both
85+
try{
86+
$MSRDC = Get-Package -Name "Remote Desktop" -ErrorAction Stop
87+
}
88+
catch
89+
{
90+
Update-Log -data $_.Exception.Message -Class Error -Output Both
91+
}
92+
93+
if ($MSRDC.name -eq "Remote Desktop"){
94+
update-log -Data "Remote Desktop Install Found" -Class Information -Output Both
95+
#update-log -Data "Version: " $MSRDC.Version
96+
update-log -Data "Uninstalling Remote Desktop" -Class Information -Output Both
97+
try{
98+
Uninstall-Package -Name "Remote Desktop" -force -ErrorAction Stop| Out-Null
99+
}
100+
catch
101+
{
102+
Update-Log -data $_.Exception.Message -Class Error -Output Both
103+
}
104+
105+
update-log -Data "Remote Desktop uninstalled" -Class Information -Output Both
106+
}
107+
}
108+
catch
109+
{
110+
update-log -Data "Remote Desktop not found as package." -Class Information -Output Both
111+
}
112+
}
113+
114+
#function to install Windows App from MS Store - write install process log to $env:windir\temp\WindowsAppStoreInstall.log
115+
function install-windowsappstore{
116+
update-log -Data "Writing install process log to $env:windir\temp\WindowsAppStoreInstall.log" -Class Information -Output Both
117+
try{
118+
invoke-command -ScriptBlock { winget install 9N1F85V9T8BN --accept-package-agreements --accept-source-agreements} | Out-File -FilePath $env:windir\temp\WindowsAppStoreInstall.log -Append #MS Store Install
119+
}
120+
catch
121+
{
122+
Update-Log -data $_.Exception.Message -Class Error -Output Both
123+
Exit 1
124+
}
125+
}
126+
127+
#Function to install Windows App from Winget CDN - write install process log to $env:windir\temp\WindowsAppWinGetInstall.log
128+
function install-windowsappwinget{
129+
update-log -Data "Writing install process log to $env:windir\temp\WindowsAppWinGetInstall.log" -Class Information -Output Both
130+
try{
131+
invoke-command -ScriptBlock {winget install Microsoft.WindowsApp --accept-package-agreements --accept-source-agreements} | Out-File -FilePath $env:windir\temp\WindowsAppWinGetInstall.log -Append #Winget Install
132+
}
133+
catch
134+
{
135+
Update-Log -data $_.Exception.Message -Class Error -Output Both
136+
Exit 1
137+
}
138+
}
139+
140+
#Function to install Windows App from MSIX direct download
141+
function install-windowsappMSIX{
142+
143+
try{
144+
if ((test-path -Path $env:windir\Temp\WindowsApp.msix) -eq $true){Remove-Item -Path $env:windir\Temp\WindowsApp.msix -Force -ErrorAction Stop}
145+
146+
$Payload = Invoke-WebRequest -uri "https://go.microsoft.com/fwlink/?linkid=2262633" -UseBasicParsing -OutFile $env:windir\Temp\WindowsApp.msix -PassThru -ErrorAction Stop
147+
$filename = ($Payload.BaseResponse.ResponseUri.AbsolutePath -replace ".*/")
148+
149+
if ((test-path -Path $env:windir\Temp\$filename) -eq $true){Remove-Item -Path $env:windir\Temp\$filename -Force -ErrorAction Stop}
150+
151+
Rename-Item -Path $env:windir\Temp\WindowsApp.msix -NewName $filename -Force -ErrorAction Stop
152+
update-log -Data "Downloaded $filename to $env:windir\temp" -Class Information -Output Both
153+
}
154+
catch{
155+
Update-Log -data $_.Exception.Message -Class Error -Output Both
156+
}
157+
try{
158+
Add-AppxPackage -Path $env:windir\temp\$filename -ErrorAction Stop
159+
}
160+
catch{
161+
Update-Log -data $_.Exception.Message -Class Error -Output Both
162+
}
163+
}
164+
165+
#Function to check if Windows App is installed
166+
function invoke-WAInstallCheck{
167+
if ((($testWA = get-appxpackage -name MicrosoftCorporationII.Windows365).name) -eq "MicrosoftCorporationII.Windows365" ){
168+
update-log -Data "Windows App Installation found." -Class Information -Output Both
169+
Return 0
170+
}
171+
else
172+
{
173+
update-log -Data "Windows App installation not found." -Class Information -Output Both
174+
Return 1
175+
}
176+
}
177+
178+
#function to set the registry key to control auto updates
179+
function invoke-disableautoupdate($num){
180+
update-log -Data "Setting disableautoupdate reg key" -Class Information -Output Both
181+
$path = "HKLM:\SOFTWARE\Microsoft\WindowsApp"
182+
If (!(Test-Path $path)) {
183+
New-Item -Path $path -Force
184+
}
185+
try{
186+
New-ItemProperty -Path $path -Name DisableAutomaticUpdates -PropertyType DWORD -Value $num -Force -ErrorAction Stop| Out-Null
187+
}
188+
catch{
189+
Update-Log -data $_.Exception.Message -Class Error -Output Both
190+
}
191+
}
192+
193+
#check if Windows App is installed. If so, skip installation. Else, install
194+
if ((invoke-WAInstallCheck) -eq 0){
195+
update-log -Data "Skipping Windows App Installation" -Class Information -Output Both
196+
}
197+
else
198+
{
199+
if ($source -eq "Store"){
200+
update-log -Data "Starting Windows App installation from Microsoft Store" -Class Information -Output Both
201+
install-windowsappstore
202+
}
203+
if ($source -eq "WinGet"){
204+
update-log -Data "Starting Windows App installation from WinGet" -Class Information -Output Both
205+
install-windowsappwinget
206+
}
207+
if ($source -eq "MSIX"){
208+
update-log -Data "Starting Windows App installation from MSIX download" -Class Information -Output Both
209+
install-windowsappMSIX
210+
}
211+
}
212+
213+
#verify if Windows App has now been installed. If so, move to uninstalling MSRDC. Else, fail.
214+
if ((invoke-WAInstallCheck) -eq 0){
215+
update-log -Data "Validated Windows App Installed" -Class Information -Output Both
216+
if ($SkipRemoteDesktopUninstall -eq $False){uninstall-MSRDCreg}
217+
}
218+
else
219+
{
220+
update-log -Data "Windows App does not appear to be installed. Something went wrong" -Class Error -Output Both
221+
exit 1
222+
}
223+
224+
#Apply auto update registry key if option selected
225+
if ($DisableAutoUpdate -ne 0){
226+
if ($DisableAutoUpdate -eq 1){invoke-disableautoupdate -num 1}
227+
if ($DisableAutoUpdate -eq 2){invoke-disableautoupdate -num 2}
228+
if ($DisableAutoUpdate -eq 3){invoke-disableautoupdate -num 3}
229+
}
230+
231+
update-log -Data "Installation Complete" -Class Information -Output Both
232+
update-log -data "************" -Class Information -Output File
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
# Windows App Installer MultiTool
2+
3+
A PowerShell utility to detect and install the Windows 365 "Windows App" client, optionally set its auto-update registry key, and remove legacy "Remote Desktop" installs when present. This README documents the script behavior, parameters, examples, logs and troubleshooting.
4+
5+
## What it does
6+
- Detects whether the Windows App (`MicrosoftCorporationII.Windows365`) is installed.
7+
- Installs the Windows App from one of three sources:
8+
- Microsoft Store (via winget id `9N1F85V9T8BN`) — `Store` (default)
9+
- WinGet CDN package (`Microsoft.WindowsApp`) — `WinGet`
10+
- Direct MSIX download (FWLINK) and `Add-AppxPackage``MSIX`
11+
- Optionally writes `HKLM:\SOFTWARE\Microsoft\WindowsApp\DisableAutomaticUpdates` to control auto updates.
12+
- Optionally uninstalls legacy "Remote Desktop" via registry/MSI or package uninstall methods.
13+
- Writes logs to console and to the configured log file.
14+
15+
## Files
16+
- `Windows App Installer.ps1` — main script.
17+
- Runtime logs:
18+
- Default log: `%windir%\Temp\MultiTool.log` (can be changed with `-logpath`)
19+
- Store install trace: `%windir%\Temp\WindowsAppStoreInstall.log`
20+
- WinGet install trace: `%windir%\Temp\WindowsAppWinGetInstall.log`
21+
22+
## Requirements
23+
- Windows (modern Windows 10/11 recommended).
24+
- PowerShell (script compatible with Windows PowerShell 5.1 and later).
25+
- Administrative privileges to install packages and write to HKLM.
26+
- Internet access for Store/WinGet/MSIX downloads.
27+
- Winget and/or Microsoft Store available for corresponding install methods (use `MSIX` when Store is blocked).
28+
29+
## Parameters
30+
- `-source` (string)
31+
Where to source the installer payload. Allowed values: `Store` (default), `WinGet`, `MSIX`.
32+
- `-DisableAutoUpdate` (int)
33+
Sets the registry DWORD `HKLM:\SOFTWARE\Microsoft\WindowsApp\DisableAutomaticUpdates`. Allowed values:
34+
- `0` — Enable updates (default)
35+
- `1` — Disable updates from all locations
36+
- `2` — Disable updates from Microsoft Store
37+
- `3` — Disable updates from the CDN
38+
- `-SkipRemoteDesktopUninstall` (switch)
39+
If present, skip attempting to remove legacy Remote Desktop.
40+
- `-logpath` (string)
41+
Path to the primary log file (default: `$env:windir\temp\MultiTool.log`).
42+
43+
## High-level functions (what they do)
44+
- `update-log` — logging helper (console / file / both).
45+
- `invoke-WAInstallCheck` — returns 0 if Windows App is present, 1 otherwise.
46+
- `install-windowsappstore` — triggers Store install (uses winget id `9N1F85V9T8BN`) and logs to temp file.
47+
- `install-windowsappwinget` — triggers WinGet install for `Microsoft.WindowsApp` and logs to temp file.
48+
- `install-windowsappMSIX` — downloads MSIX via FWLINK and installs with `Add-AppxPackage`.
49+
- `uninstall-MSRDCreg` — primary uninstall of legacy Remote Desktop via registry MSI uninstall string.
50+
- `uninstall-MSRDC` — secondary uninstall via `Get-Package` / `Uninstall-Package`.
51+
- `invoke-disableautoupdate` — creates/sets `DisableAutomaticUpdates` DWORD.
52+
53+
## Usage examples (run elevated)
54+
Default (Store) install:
55+
```powershell
56+
PowerShell -ExecutionPolicy Bypass -File ".\Windows App Installer.ps1"
57+
```
58+
59+
Install via WinGet and disable Store updates (set key = 2):
60+
```powershell
61+
PowerShell -ExecutionPolicy Bypass -File ".\Windows App Installer.ps1" -source WinGet -DisableAutoUpdate 2
62+
```
63+
64+
Install via MSIX and skip removing legacy Remote Desktop:
65+
```powershell
66+
PowerShell -ExecutionPolicy Bypass -File ".\Windows App Installer.ps1" -source MSIX -SkipRemoteDesktopUninstall
67+
```
68+
69+
Specify an alternate log file:
70+
```powershell
71+
PowerShell -ExecutionPolicy Bypass -File ".\Windows App Installer.ps1" -logpath "C:\Temp\WinAppInstall.log"
72+
```
73+
74+
## Exit behavior & logs
75+
- On fatal install failures the script calls `exit 1`.
76+
- Normal success writes completion messages to logs and returns normally.
77+
- Check logs for details:
78+
- Primary: `%windir%\Temp\MultiTool.log` (or the `-logpath` you supplied)
79+
- Install traces: `%windir%\Temp\WindowsAppStoreInstall.log`, `%windir%\Temp\WindowsAppWinGetInstall.log`
80+
81+
## Troubleshooting
82+
- Permission / access denied:
83+
- Run PowerShell as Administrator.
84+
- Winget not found or Microsoft Store disabled:
85+
- Use `-source MSIX` to attempt a direct MSIX install.
86+
- `Add-AppxPackage` errors:
87+
- Ensure sideloading is allowed or that the package signature and system policy permit the install.
88+
- Remote Desktop uninstall fails:
89+
- The script tries registry/MSI first then package uninstall. Manual removal may be required if uninstall strings are missing.
90+
- For diagnostics:
91+
- Inspect the three logs listed above for error messages and stack traces.
92+
93+
## Known limitations
94+
- Script assumes availability of `winget` for Store/WinGet flows; environments with Store disabled may fail unless `MSIX` chosen.
95+
- Error handling is present but could be more granular (some functions catch and log but do not standardize exit codes).
96+
- Not explicitly tested on Windows LTSC or older Windows 10 branches — behavior may vary.
97+
98+
## Recommended next steps
99+
- Add a `-WhatIf`/dry-run mode.
100+
- Add explicit checks for `winget`/Store presence before choosing install path.
101+
- Convert `update-log` to structured logging (timestamped JSON) for automation.
102+
- Add more granular exit codes to represent specific failure types.
103+
104+
## License
105+
No license is specified. Add a `LICENSE` file if you intend to publish or share.

0 commit comments

Comments
 (0)