From 82c4a312d42351e8a16eb7499d6c84d84c53bc94 Mon Sep 17 00:00:00 2001 From: Donna Ryan <100233767+DonnaRyanMicrosoft@users.noreply.github.com> Date: Tue, 25 Feb 2025 20:01:27 -0500 Subject: [PATCH 01/26] Update Get-AVDW365Gateways.ps1 Changed URL to GCCH JSON Download --- Windows 365 Gateway IP Lookup/Get-AVDW365Gateways.ps1 | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Windows 365 Gateway IP Lookup/Get-AVDW365Gateways.ps1 b/Windows 365 Gateway IP Lookup/Get-AVDW365Gateways.ps1 index c403b5c..e52af1a 100644 --- a/Windows 365 Gateway IP Lookup/Get-AVDW365Gateways.ps1 +++ b/Windows 365 Gateway IP Lookup/Get-AVDW365Gateways.ps1 @@ -21,7 +21,8 @@ Param( #JSON sources if using Web $commercial = 'https://www.microsoft.com/en-us/download/details.aspx?id=56519' -$GCCH = "https://www.microsoft.com/en-us/download/confirmation.aspx?id=57063" +#old link $GCCH = "https://www.microsoft.com/en-us/download/confirmation.aspx?id=57063" +$GCCH = "https://www.microsoft.com/en-us/download/details.aspx?id=57063" #output CSV file $CSVFile = "$PSSCriptRoot\W365-Gateways.CSV" From 49a3c2442e75015b0bf3d2b51192654bd8884cf0 Mon Sep 17 00:00:00 2001 From: Donna Ryan <100233767+DonnaRyanMicrosoft@users.noreply.github.com> Date: Mon, 23 Jun 2025 16:03:22 -0400 Subject: [PATCH 02/26] Squashed commit of the following: commit 33f3d0bae1606db5982867d277dfe6987b0d0673 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 --- .../MMR Detection.ps1 | 105 +++++++ .../MMR Remediation.ps1 | 265 ++++++++++++++++++ Multimedia Redirector Updater/readme.md | 53 ++++ 3 files changed, 423 insertions(+) create mode 100644 Multimedia Redirector Updater/MMR Detection.ps1 create mode 100644 Multimedia Redirector Updater/MMR Remediation.ps1 create mode 100644 Multimedia Redirector Updater/readme.md diff --git a/Multimedia Redirector Updater/MMR Detection.ps1 b/Multimedia Redirector Updater/MMR Detection.ps1 new file mode 100644 index 0000000..775c2e4 --- /dev/null +++ b/Multimedia Redirector Updater/MMR Detection.ps1 @@ -0,0 +1,105 @@ +<# +.COPYRIGHT +Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. +See LICENSE in the project root for license information. +#> + +# Remote Desktop Multimedia Redirection updater - Detection Script +# Version 0.0.1 + +##################################### + +Param( + [parameter(mandatory = $false, HelpMessage = "Log path and file name")] + [string]$logpath = "$env:windir\temp\RDMMR-detect.log" +) + +#Retrieves the version number of the current MMR client +function get-CurrentMMRver { + + $response = (Invoke-WebRequest -Uri "https://aka.ms/avdmmr/msi" -UseBasicParsing) + $versionC = $response.BaseResponse.ResponseUri.AbsolutePath -replace ".*HostInstaller_", "" -replace ".x64.msi*", "" + $string = "The latest available version of the RD MMR client is " + $versionC + update-log -Data $string -Class Information -output both + return $versionC +} + +#Retrieves the version of MMR installed on the Cloud PC +function get-installedMMRver{ + if ((Test-Path -Path 'C:\Program Files\MsRDCMMRHost\MsMmrHost.exe') -eq $true){ + $version = (Get-ItemProperty -Path 'C:\Program Files\MsRDCMMRHost\MsMmrHost.exe') + $string = "The currently installed version of the RD MMR client is " + $version.VersionInfo.ProductVersion + update-log -Data $string -Class Information -output both + return $version.VersionInfo.ProductVersion + } + else + { + update-log -data "It doesn't appear that the MMR client is installed" -Class Warning -output both + return "0"} +} + +#Logging function +function update-log { + Param( + [Parameter( + Mandatory = $true, + ValueFromPipeline = $true, + ValueFromPipelineByPropertyName = $true, + Position = 0 + )] + [string]$Data, + [validateset('Information', 'Warning', 'Error', 'Comment')] + [string]$Class = "Information", + [validateset('Console', 'File', 'Both')] + [string]$Output + ) + + $date = get-date -UFormat "%m/%d/%y %r" + $String = $Class + " " + $date + " " + $data + if ($Output -eq "Console") { Write-Output $string | out-host } + if ($Output -eq "file") { Write-Output $String | out-file -FilePath $logpath -Append } + if ($Output -eq "Both") { + Write-Output $string | out-host + Write-Output $String | out-file -FilePath $logpath -Append + } + +} + +#Writes the header of the log +update-log -Data " " -Class Information -output both +update-log -Data "*** Starting RD MMR agent detection ***" -Class Information -output both +update-log -Data " " -Class Information -output both + + +$MMRCurrent = get-CurrentMMRver + +#Calls the function to get the installed version number +$MMRInstalled = get-installedMMRver -Erroraction SilentlyContinue + +#Handles the error code if Web RTC client is not installed. +if ($MMRInstalled -eq $null) { + + update-log -Data "RD MMR client was not detected. Returning Non-compliant" -Class Warning -output both + Exit 1 +} + +#Handles the error code if the Web RTC client is out of date. +if ($MMRInstalled -lt $MMRCurrent) { + + update-log -Data "RD MMR was detected to be out of date. Returning Non-compliant" -Class Warning -output both + Exit 1 +} + +#Handles the error code if the installed agent is newer than the latest available client. (shouldn't happen) +if ($MMRInstalled -gt $MMRCurrent) { + + update-log -data "The installed version is newer than what is available." -Class Warning -output both + exit 1 +} + +#Handles the error code if the agent is current. +if ($MMRInstalled -eq $MMRCurrent) { + + update-log -Data "The RD MMR client is current. Returning Compliant" -Class Information -output both + Exit 0 +} \ No newline at end of file diff --git a/Multimedia Redirector Updater/MMR Remediation.ps1 b/Multimedia Redirector Updater/MMR Remediation.ps1 new file mode 100644 index 0000000..cdfadf9 --- /dev/null +++ b/Multimedia Redirector Updater/MMR Remediation.ps1 @@ -0,0 +1,265 @@ +<# +.COPYRIGHT +Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. +See LICENSE in the project root for license information. +#> + +# Remote Desktop Multimedia Redirection updater - Remediation Script +# Version 0.0.1 + +##################################### + +Param( + [parameter(mandatory = $false, HelpMessage = "Log path and file name")] + [string]$logpath = "$env:windir\temp\TeamsWebRTC-remediate.log", + [parameter(mandatory = $false, HelpMessage = "time to wait past disconnect")] + [string]$DCNTwait = 600, + [parameter(mandatory = $false, HelpMessage = "time to wait to re-check user state")] + [string]$StateDetWait = 300, + [parameter(mandatory = $false, HelpMessage = "evaluate user state only once")] + [switch]$retry, + [parameter(mandatory = $false, HelpMessage = "time in minutes to timeout")] + [int]$TimeOut = 60 +) + +#function to handle logging +function update-log { + Param( + [Parameter( + Mandatory = $true, + ValueFromPipeline = $true, + ValueFromPipelineByPropertyName = $true, + Position = 0 + )] + [string]$Data, + [validateset('Information', 'Warning', 'Error', 'Comment')] + [string]$Class = "Information", + [validateset('Console', 'File', 'Both')] + [string]$Output + ) + + $date = get-date -UFormat "%m/%d/%y %r" + $String = $Class + " " + $date + " " + $data + if ($Output -eq "Console") { Write-Output $string | out-host } + if ($Output -eq "file") { Write-Output $String | out-file -FilePath $logpath -Append } + if ($Output -eq "Both") { + Write-Output $string | out-host + Write-Output $String | out-file -FilePath $logpath -Append + } +} + +#function to query the user state and convert to variable +function get-userstate { + (((quser) -replace '^>', '') -replace '\s{2,}', ',').Trim() | ForEach-Object { + if ($_.Split(',').Count -eq 5) { + Write-Output ($_ -replace '(^[^,]+)', '$1,') + } + else { + Write-Output $_ + } + } | ConvertFrom-Csv +} + +#function to perform the upgrading +function invoke-remediation { + + $MMRCurrent = get-CurrentMMRver + + $MMRInstalled = get-installedMMRver + $string = "Installed MMR agent version is " + $MMRInstalled + update-log -Data $string -Class Information -Output Both + $string = "Latest version of MMR agent is " + $MMRCurrent + update-log -Data $string -Class Information -Output Both + + try { + + #Create a directory to save download files + $tempCreated = $false + if (!(Test-Path C:\RDMMRtemp)) { + New-Item -Path C:\ -ItemType Directory -Name RDMMRtemp | Out-Null + update-log -data "Temp path created" -output both -Class Information + $tempCreated = $true + } + + #Download MMR + update-log -Data "Downloading RD MMR client" -Class Information -output both + invoke-WebRequest -Uri "https://aka.ms/avdmmr/msi" -OutFile "C:\RDMMRtemp\MMR_Installer.msi" -UseBasicParsing -PassThru + + #Install MMR + update-log -Data "Installing RD MMR client" -Class Information -output both + $msireturn = Start-Process msiexec.exe -ArgumentList '/i C:\RDMMRtemp\MMR_Installer.msi /q /n /l*voicewarmup c:\windows\temp\RDMMRmsi.log' -Wait -PassThru + if ($msireturn.ExitCode -eq '0') { + update-log -data "MSIEXEC returned 0" -Class Information -Output Both + } + else { + $string = "MSIEXEC returned exit code " + $msireturn.ExitCode + update-log -data $string -Class Information -Output Both + exit 1 + } + + if ($tempCreated -eq $true) { + #Remove temp folder + update-log -Data "Removing temp directory" -Class Information -output both + Remove-Item -Path C:\RDMMRtemp\ -Recurse | out-null + } + else { + #Remove downloaded WebRTC file + update-log -Data "Removing RD MMR client installer file" -Class Information -output both + Remove-Item -Path C:\RDMMRtemp\MsRdcWebRTCSvc_HostSetup.msi + } + #Return Success + update-log -Data "Media Optimization Installed" -Class Information -output both + $MMRCurrent = get-CurrentMMRver + $string = "Current installed version is now " + $MMRCurrent + update-log -Data $string -Class Information -Output Both + return "Success" + } + catch { + Write-Error -ErrorRecord $_ + return /b "Fail" + } +} + +#function to handle user state detection logic +function invoke-userdetect { + update-log -data "Detecting user state." -Class Information -output both + $explorerprocesses = @(Get-WmiObject -Query "Select * FROM Win32_Process WHERE Name='explorer.exe'" -ErrorAction SilentlyContinue) + if ($explorerprocesses.Count -eq 0) { + update-log -data "There is not a user logged in. Skipping user state detection." -Class Information -Output both + Return + } + else { + foreach ($i in $explorerprocesses) { + $Username = $i.GetOwner().User + $Domain = $i.GetOwner().Domain + $string = $Domain + "\" + $Username + " logged on since: " + ($i.ConvertToDateTime($i.CreationDate)) + update-log -data $string -Class Information -Output Both + } + update-log -data "There is a logged on user" -Class Information -Output Both + } + + if ($retry -eq $true) { + do { + $session = get-userstate + $text = "Waiting for non-active user state." + update-log -data $text -Class Information -output both + $String = "Session State is " + $session.STATE + update-log -data $String -output both -Class Information + $string = "Idle Time is " + $session.'IDLE TIME' + update-log -data $String -output both -Class Information + + if ($TimeOut -gt 0) { + sleep -Seconds $StateDetWait + $TimeOut = ($TimeOut - $StateDetWait) + } + else { + update-log -Data "Timed out. Returning fail" -Class Error -output both + return 3 + } + } while ($session.state -eq "Active") + + update-log -data "User state is not active." -output both -Class Information + invoke-disctimer + } + + if ($retry -eq $false) { + update-log -Data "Attempting to detect only once." -Class Information -output both + $session = get-userstate + if ($session.state -eq "disc") { + $text = "User state is disconnected" + update-log -data $text -Class Information -output both + } + else { + update-log -Data "User state is not disconnected" -Class Warning -output both + return 2 + } + } +} + +#function to handle wait time between first non-active discovery and upgrade +function invoke-disctimer { + $string = "Waiting " + $DCNTwait + " seconds..." + update-log -Data $string -Class Information -output both + sleep -Seconds $DCNTwait + $Timeout = ($Timeout - $DCNTwait) + + $session = get-userstate + if ($session.STATE -eq "Active") { + update-log -Data "User state has become active again. Waiting for non-active state..." -Class Warning -output both + invoke-userdetect + } + else { + update-log -data "Session state is still non-active. Continuing with remediation..." -Class Information -output both + } +} + +#function to query the latest available version number of RD MMR client +function get-CurrentMMRver { + $response = (Invoke-WebRequest -Uri "https://aka.ms/avdmmr/msi" -UseBasicParsing) + $versionC = $response.BaseResponse.ResponseUri.AbsolutePath -replace ".*HostInstaller_", "" -replace ".x64.msi*", "" + $string = "The latest available version of the RD MMR client is " + $versionC + update-log -Data $string -Class Information -output both + return $versionC +} + +#function to determine what version of RDMMR is installed +function get-installedMMRver{ + if ((Test-Path -Path 'C:\Program Files\MsRDCMMRHost\MsMmrHost.exe') -eq $true){ + $version = (Get-ItemProperty -Path 'C:\Program Files\MsRDCMMRHost\MsMmrHost.exe') + $string = "The currently installed version of the RD MMR client is " + $version.VersionInfo.ProductVersion + update-log -Data $string -Class Information -output both + return $version.VersionInfo.ProductVersion + } + else + { + update-log -data "It doesn't appear that the RD MMR client is installed" -Class Warning -output both + return "0"} +} + +#Opening text of log. +update-log -Data " " -Class Information -output both +update-log -Data "*** Starting RD MMR agent remediation ***" -Class Information -output both +update-log -Data " " -Class Information -output both + +#Display timeout amount in the log - if using retry function +if ($retry -eq $true) { + $String = "Time out set for " + $Timeout + " minutes" + update-log -Data $String -output both -Class Information +} + +#Converts Timeout minutes to seconds +$TimeOut = $TimeOut * 60 + +#Starts the user state detection and handling +$var1 = invoke-userdetect + +# Exit if user is active (default). +#Return code for no retry - user is active +if ($var1 -eq 2) { + update-log -Data "User State is active. Returning fail. Try again" -Class Warning -output both + exit 1 +} + +#Exit if process times out. Used with "-retry" parameter. +#Return code for time out +if ($var1 -eq 3) { + update-log -Data "Timed out. Returning fail. Try again" -Class Warning -output both + exit 1 +} + +#Starts the remediaiton function +$result = $null +$result = invoke-remediation + +#Exit if the remediation was successful +if ($result -eq "Success") { + update-log -Data "Remediation Complete" -Class Information -output both + exit 0 +} + +#Exit if remediation failed +if ($result -ne "Success") { + update-log -Data "An error occured." -Class Error -output both + exit 1 +} + \ No newline at end of file diff --git a/Multimedia Redirector Updater/readme.md b/Multimedia Redirector Updater/readme.md new file mode 100644 index 0000000..9a710fd --- /dev/null +++ b/Multimedia Redirector Updater/readme.md @@ -0,0 +1,53 @@ +# Introduction + +The Remote Desktop Multimedia Redirection (RDMMR or MMR) is a critical tool in optimizing video played in a Cloud PC's web browser, and it needs to be kept up to date. +As of now, the MMR does not automatically update, causing many customers to have older versions of this solution still in production – +and degrading user experience. + +This script solution aims to solve these problems by detecting both the latest and the installed versions of MMR Client. If an upgrade is required, +the script will download the latest version automatically. The script also detects if the user state is active or disconnected, and only installing +the upgrade when the user state is disconnected – avoiding end user impact. + +> This solution will not work in a multi-session environment, nor with user profiles managed by FSLogix. + +## Deploying with Proactive Remediation + +### Detection Script + +The detection script checks for the installed and the latest available version of the MMR client. + +The script will return Compliant if the latest version is installed. + +If the version of the MMR client is out-of-date, or if it isn’t installed, it will return Non-Compliant. + +### Remediation Script + +The detection script verifies if the user is logged on, and if their state is active or inactive. The script will return Non-Compliant if the user +state is Active so as to ensure the user isn’t impacted by the upgrade. If the user state isn’t active, the script will continue to download +and install the latest version of the MMR Client. + +## Remediation Stand Alone + +The remediation script can be deployed without Proactive Remediation. The script has four parameters that can allow the script to run continuously +in the background until a users’ state is not active. + +-retry + +Using this parameter causes the script to not immediately return non-compliant if the users’ state is Active. Instead, the script will pause for a +time, and then re-check the user state. Default behavior doesn’t have this switch enabled. + +-StateDetWait + +This parameter sets the time in seconds that the script waits to re-check the user state. The default value is 300 seconds. + +-DCNTwait + +This parameter controls a secondary pause after the user state has disconnected. This wait’s purpose is to prevent impacting a user if they have accidentally disconnected from their Cloud PC, and are immediately reconnecting. Default value is 600 seconds + +-Timeout + +This is a parameter to specify how long the script should continue to try and install before quitting. The default value is 60 minutes. + +## Logging + +Verbose logging is built into the script. By default, the scripts write their logs to c:\windows\temp. This can be changed by using the parameter -logpath. While Proactive Remediation should be able to help diagnose upgrade issues, looking at the logs can provide further insight into the script behavior. From ab86ceb8d6512cab848d966b838e0732982a57fe Mon Sep 17 00:00:00 2001 From: Donna Ryan <100233767+DonnaRyanMicrosoft@users.noreply.github.com> Date: Wed, 8 Oct 2025 17:32:38 -0400 Subject: [PATCH 03/26] Initial Commit --- .../Windows App Installer.ps1 | 87 +++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 Windows App Installer MultiTool/Windows App Installer.ps1 diff --git a/Windows App Installer MultiTool/Windows App Installer.ps1 b/Windows App Installer MultiTool/Windows App Installer.ps1 new file mode 100644 index 0000000..c18ee5a --- /dev/null +++ b/Windows App Installer MultiTool/Windows App Installer.ps1 @@ -0,0 +1,87 @@ +cls + +$source = "Store" #Store,WinGet,MSIX,All +$UninstallMSRDC = $true #$true,$false + +function uninstall-MSRDC{ + try{ + + $MSRDC = Get-Package -Name "Remote Desktop" -ErrorAction Stop + + if ($MSRDC.name -eq "Remote Desktop"){ + write-host "Remote Desktop Install Found" + write-host "Version: " $MSRDC.Version + Uninstall-Package -Name "Remote Desktop" -force | Out-Null + } + } + catch + { + Write-Host "Remote Desktop not found as package." + write-host "Trying reg key detection." + uninstall-MSRDCreg + + } +} + +function uninstall-MSRDCreg{ + + $MSRCDreg = Get-ItemProperty hklm:\software\microsoft\windows\currentversion\uninstall\* | Where-Object {$_.Displayname -like "*Remote Desktop*"} | Select-Object DisplayName,DisplayVersion,UninstallString,QuietUninstallString + if ($MSRCDreg.DisplayName -eq "Remote Desktop"){ + write-host "MSRDC Installation Found" + write-host "Version " $MSRCDreg.DisplayVersion + $uninstall = $MSRCDreg.uninstallstring -replace "MsiExec.exe /X","" + write-host "uninstalling" + Start-Process -FilePath "msiexec.exe" -ArgumentList "/x $($uninstall) /q /norestart" + } + else + {write-host "MSRDC not installed"} +} + +function install-windowsappstore{ + invoke-command -ScriptBlock { winget install 9N1F85V9T8BN --accept-package-agreements --accept-source-agreements} #MS Store Install + +} + +function install-windowsappwinget{ + invoke-command -ScriptBlock {winget install Microsoft.WindowsApp --accept-package-agreements --accept-source-agreements} #Winget Install + + +} + +function install-windowsappMSIX{ + + Invoke-WebRequest -uri "https://go.microsoft.com/fwlink/?linkid=2262633" -outfile "c:\windows\temp\WindowsApp.MSIX" -UseBasicParsing -PassThru #windowsapp download + Add-AppxPackage -Path C:\windows\temp\WindowsApp.MSIX + } + +function invoke-WAInstallCheck{ + if ((($testWA = get-appxpackage -name MicrosoftCorporationII.Windows365).name) -eq "MicrosoftCorporationII.Windows365" ){ + Write-Host "Windows App Installation found." + Return 0 + } + else + { + write-host "Windows App installation not found." + Return 1 + } +} + + +if ((invoke-WAInstallCheck) -eq 0){ + write-host "Skipping Windows App Installation" + } +else + { + if ($source -eq "Store"){install-windowsappstore} + if ($source -eq "WinGet"){install-windowsappwinget} + if ($source -eq "MSIX"){install-windowsappMSIX} +} + +if ((invoke-WAInstallCheck) -eq 0){ + if ($UninstallMSRDC -eq $true){uninstall-msrdc} + write-host "Installation Complete" + } + else + { + write-host "Windows App does not appear to be installed. Something went wrong" + } \ No newline at end of file From 91594b9a90187d146b64282389d164f9c76f75ba Mon Sep 17 00:00:00 2001 From: Donna Ryan <100233767+DonnaRyanMicrosoft@users.noreply.github.com> Date: Wed, 8 Oct 2025 17:44:07 -0400 Subject: [PATCH 04/26] Changed MSRDC uninstall order Also some minor formatting changes --- .../Windows App Installer.ps1 | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/Windows App Installer MultiTool/Windows App Installer.ps1 b/Windows App Installer MultiTool/Windows App Installer.ps1 index c18ee5a..9b4d006 100644 --- a/Windows App Installer MultiTool/Windows App Installer.ps1 +++ b/Windows App Installer MultiTool/Windows App Installer.ps1 @@ -5,26 +5,24 @@ $UninstallMSRDC = $true #$true,$false function uninstall-MSRDC{ try{ - + write-host "Looking to see if Remote Desktop is an installed package" $MSRDC = Get-Package -Name "Remote Desktop" -ErrorAction Stop if ($MSRDC.name -eq "Remote Desktop"){ write-host "Remote Desktop Install Found" write-host "Version: " $MSRDC.Version + write-host "Uninstalling Remote Desktop" Uninstall-Package -Name "Remote Desktop" -force | Out-Null } } catch { Write-Host "Remote Desktop not found as package." - write-host "Trying reg key detection." - uninstall-MSRDCreg - } } function uninstall-MSRDCreg{ - + $MSRCDreg = Get-ItemProperty hklm:\software\microsoft\windows\currentversion\uninstall\* | Where-Object {$_.Displayname -like "*Remote Desktop*"} | Select-Object DisplayName,DisplayVersion,UninstallString,QuietUninstallString if ($MSRCDreg.DisplayName -eq "Remote Desktop"){ write-host "MSRDC Installation Found" @@ -34,22 +32,25 @@ function uninstall-MSRDCreg{ Start-Process -FilePath "msiexec.exe" -ArgumentList "/x $($uninstall) /q /norestart" } else - {write-host "MSRDC not installed"} + { + write-host "MSRDC not detected via registry. Trying as package" + uninstall-MSRDC + + } } +#function to install Windows App from MS Store function install-windowsappstore{ invoke-command -ScriptBlock { winget install 9N1F85V9T8BN --accept-package-agreements --accept-source-agreements} #MS Store Install - } +#Function to install Windows App from Winget CDN function install-windowsappwinget{ invoke-command -ScriptBlock {winget install Microsoft.WindowsApp --accept-package-agreements --accept-source-agreements} #Winget Install - - } +#Function to install Windows App from MSIX direct download function install-windowsappMSIX{ - Invoke-WebRequest -uri "https://go.microsoft.com/fwlink/?linkid=2262633" -outfile "c:\windows\temp\WindowsApp.MSIX" -UseBasicParsing -PassThru #windowsapp download Add-AppxPackage -Path C:\windows\temp\WindowsApp.MSIX } @@ -66,7 +67,7 @@ function invoke-WAInstallCheck{ } } - +#check if Windows App is installed. If so, skip installation. Else, install if ((invoke-WAInstallCheck) -eq 0){ write-host "Skipping Windows App Installation" } @@ -77,8 +78,9 @@ else if ($source -eq "MSIX"){install-windowsappMSIX} } +#verify if Windows App has now been installed. If so, move to uninstalling MSRDC. Else, fail. if ((invoke-WAInstallCheck) -eq 0){ - if ($UninstallMSRDC -eq $true){uninstall-msrdc} + if ($UninstallMSRDC -eq $true){uninstall-MSRDCreg} write-host "Installation Complete" } else From 73506a173f81820f1cf66ae3885b3fcb8a593f2c Mon Sep 17 00:00:00 2001 From: Donna Ryan <100233767+DonnaRyanMicrosoft@users.noreply.github.com> Date: Wed, 8 Oct 2025 22:46:33 -0400 Subject: [PATCH 05/26] DisableAutoUpdate reg key support created function to allow admins to set the DisableAutoUpdate registry key --- .../Windows App Installer.ps1 | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/Windows App Installer MultiTool/Windows App Installer.ps1 b/Windows App Installer MultiTool/Windows App Installer.ps1 index 9b4d006..064dd39 100644 --- a/Windows App Installer MultiTool/Windows App Installer.ps1 +++ b/Windows App Installer MultiTool/Windows App Installer.ps1 @@ -2,6 +2,7 @@ $source = "Store" #Store,WinGet,MSIX,All $UninstallMSRDC = $true #$true,$false +$DisableAutoUpdate = 0 function uninstall-MSRDC{ try{ @@ -67,6 +68,16 @@ function invoke-WAInstallCheck{ } } +function invoke-disableautoupdate($num){ + $path = "HKLM:\SOFTWARE\Microsoft\WindowsApp" + If (!(Test-Path $path)) { + New-Item -Path $path -Force +} + + New-ItemProperty -Path $path -Name DisableAutomaticUpdates -PropertyType DWORD -Value $num -Force + +} + #check if Windows App is installed. If so, skip installation. Else, install if ((invoke-WAInstallCheck) -eq 0){ write-host "Skipping Windows App Installation" @@ -86,4 +97,11 @@ if ((invoke-WAInstallCheck) -eq 0){ else { write-host "Windows App does not appear to be installed. Something went wrong" - } \ No newline at end of file + } + +if ($DisableAutoUpdate -ne 0){ + if ($DisableAutoUpdate -eq 1){invoke-disableautoupdate -num 1} + if ($DisableAutoUpdate -eq 2){invoke-disableautoupdate -num 2} + if ($DisableAutoUpdate -eq 3){invoke-disableautoupdate -num 3} + +} \ No newline at end of file From 56fb1e169effac936ca170d8d3779292ffe9a151 Mon Sep 17 00:00:00 2001 From: Donna Ryan <100233767+DonnaRyanMicrosoft@users.noreply.github.com> Date: Fri, 10 Oct 2025 00:28:55 -0400 Subject: [PATCH 06/26] Updated outputs minor bug fix --- .../Windows App Installer.ps1 | 43 +++++++++++++------ 1 file changed, 31 insertions(+), 12 deletions(-) diff --git a/Windows App Installer MultiTool/Windows App Installer.ps1 b/Windows App Installer MultiTool/Windows App Installer.ps1 index 064dd39..a24133d 100644 --- a/Windows App Installer MultiTool/Windows App Installer.ps1 +++ b/Windows App Installer MultiTool/Windows App Installer.ps1 @@ -2,7 +2,13 @@ $source = "Store" #Store,WinGet,MSIX,All $UninstallMSRDC = $true #$true,$false -$DisableAutoUpdate = 0 + +$DisableAutoUpdate = 0 +#0: Enables updates (default value) +#1: Disable updates from all locations +#2: Disable updates from the Microsoft Store +#3: Disable updates from the CDN location + function uninstall-MSRDC{ try{ @@ -11,9 +17,10 @@ function uninstall-MSRDC{ if ($MSRDC.name -eq "Remote Desktop"){ write-host "Remote Desktop Install Found" - write-host "Version: " $MSRDC.Version + #write-host "Version: " $MSRDC.Version write-host "Uninstalling Remote Desktop" Uninstall-Package -Name "Remote Desktop" -force | Out-Null + write-host "Remote Desktop uninstalled" } } catch @@ -26,10 +33,10 @@ function uninstall-MSRDCreg{ $MSRCDreg = Get-ItemProperty hklm:\software\microsoft\windows\currentversion\uninstall\* | Where-Object {$_.Displayname -like "*Remote Desktop*"} | Select-Object DisplayName,DisplayVersion,UninstallString,QuietUninstallString if ($MSRCDreg.DisplayName -eq "Remote Desktop"){ - write-host "MSRDC Installation Found" - write-host "Version " $MSRCDreg.DisplayVersion + write-host "Remote Desktop Installation Found" + #write-host "Version " $MSRCDreg.DisplayVersion $uninstall = $MSRCDreg.uninstallstring -replace "MsiExec.exe /X","" - write-host "uninstalling" + write-host "Uninstalling Remote Desktop" Start-Process -FilePath "msiexec.exe" -ArgumentList "/x $($uninstall) /q /norestart" } else @@ -42,12 +49,12 @@ function uninstall-MSRDCreg{ #function to install Windows App from MS Store function install-windowsappstore{ - invoke-command -ScriptBlock { winget install 9N1F85V9T8BN --accept-package-agreements --accept-source-agreements} #MS Store Install + invoke-command -ScriptBlock { winget install 9N1F85V9T8BN --accept-package-agreements --accept-source-agreements} | out-null #MS Store Install } #Function to install Windows App from Winget CDN function install-windowsappwinget{ - invoke-command -ScriptBlock {winget install Microsoft.WindowsApp --accept-package-agreements --accept-source-agreements} #Winget Install + invoke-command -ScriptBlock {winget install Microsoft.WindowsApp --accept-package-agreements --accept-source-agreements} | out-null #Winget Install } #Function to install Windows App from MSIX direct download @@ -69,6 +76,7 @@ function invoke-WAInstallCheck{ } function invoke-disableautoupdate($num){ + write-host "Setting disableautoupdate reg key" $path = "HKLM:\SOFTWARE\Microsoft\WindowsApp" If (!(Test-Path $path)) { New-Item -Path $path -Force @@ -84,15 +92,25 @@ if ((invoke-WAInstallCheck) -eq 0){ } else { - if ($source -eq "Store"){install-windowsappstore} - if ($source -eq "WinGet"){install-windowsappwinget} - if ($source -eq "MSIX"){install-windowsappMSIX} + if ($source -eq "Store"){ + write-host "Starting Windows App installation from Microsoft Store" + install-windowsappstore + } + if ($source -eq "WinGet"){ + write-host "Starting Windows App installation from WinGet" + install-windowsappwinget + } + if ($source -eq "MSIX"){ + write-host "Starting Windows App installation from MSIX download" + install-windowsappMSIX + } } #verify if Windows App has now been installed. If so, move to uninstalling MSRDC. Else, fail. if ((invoke-WAInstallCheck) -eq 0){ + write-host "Validated Windows App Installed" if ($UninstallMSRDC -eq $true){uninstall-MSRDCreg} - write-host "Installation Complete" + #write-host "Installation Complete" } else { @@ -104,4 +122,5 @@ if ($DisableAutoUpdate -ne 0){ if ($DisableAutoUpdate -eq 2){invoke-disableautoupdate -num 2} if ($DisableAutoUpdate -eq 3){invoke-disableautoupdate -num 3} -} \ No newline at end of file +} +write-host "Installation Complete" From bc1c21fab28a47a2730cc2d320681c3456f7b7f7 Mon Sep 17 00:00:00 2001 From: Donna Ryan <100233767+DonnaRyanMicrosoft@users.noreply.github.com> Date: Fri, 10 Oct 2025 00:56:56 -0400 Subject: [PATCH 07/26] Added logging for install processes Store and Winget installation output now logged separately --- .../Windows App Installer.ps1 | 152 +++++++++++++++++- 1 file changed, 149 insertions(+), 3 deletions(-) diff --git a/Windows App Installer MultiTool/Windows App Installer.ps1 b/Windows App Installer MultiTool/Windows App Installer.ps1 index a24133d..20252f2 100644 --- a/Windows App Installer MultiTool/Windows App Installer.ps1 +++ b/Windows App Installer MultiTool/Windows App Installer.ps1 @@ -41,7 +41,7 @@ function uninstall-MSRDCreg{ } else { - write-host "MSRDC not detected via registry. Trying as package" + write-host "Remote Desktop not detected via registry. Trying as package" uninstall-MSRDC } @@ -49,12 +49,12 @@ function uninstall-MSRDCreg{ #function to install Windows App from MS Store function install-windowsappstore{ - invoke-command -ScriptBlock { winget install 9N1F85V9T8BN --accept-package-agreements --accept-source-agreements} | out-null #MS Store Install + invoke-command -ScriptBlock { winget install 9N1F85V9T8BN --accept-package-agreements --accept-source-agreements} | Out-File -FilePath c:\windows\temp\WindowsAppStoreInstall.log -Append #MS Store Install } #Function to install Windows App from Winget CDN function install-windowsappwinget{ - invoke-command -ScriptBlock {winget install Microsoft.WindowsApp --accept-package-agreements --accept-source-agreements} | out-null #Winget Install + invoke-command -ScriptBlock {winget install Microsoft.WindowsApp --accept-package-agreements --accept-source-agreements} | Out-File -FilePath c:\windows\temp\WindowsAppWinGetInstall.log -Append #Winget Install } #Function to install Windows App from MSIX direct download @@ -124,3 +124,149 @@ if ($DisableAutoUpdate -ne 0){ } write-host "Installation Complete" +________________________________________ +From: Donna Ryan +Sent: Friday, October 10, 2025 12:36 AM +To: Donna Ryan +Subject: RE: script + +| Out-File -FilePath c:\windows\temp\WindowsAppStoreInstall.log -Append + + +Donna Ryan +Senior Program Manager +Windows 365 +WCX - CAT +ryandonna@microsoft.com + +From: Donna Ryan +Sent: Friday, October 10, 2025 12:25 AM +To: Donna Ryan +Subject: [EXTERNAL] script + +cls + +$source = "WinGet" #Store,WinGet,MSIX,All +$UninstallMSRDC = $true #$true,$false + +$DisableAutoUpdate = 0 +#0: Enables updates (default value) +#1: Disable updates from all locations +#2: Disable updates from the Microsoft Store +#3: Disable updates from the CDN location + + +function uninstall-MSRDC{ + try{ + write-host "Looking to see if Remote Desktop is an installed package" + $MSRDC = Get-Package -Name "Remote Desktop" -ErrorAction Stop + + if ($MSRDC.name -eq "Remote Desktop"){ + write-host "Remote Desktop Install Found" + #write-host "Version: " $MSRDC.Version + write-host "Uninstalling Remote Desktop" + Uninstall-Package -Name "Remote Desktop" -force | Out-Null + write-host "Remote Desktop uninstalled" + } + } + catch + { + Write-Host "Remote Desktop not found as package." + } +} + +function uninstall-MSRDCreg{ + + $MSRCDreg = Get-ItemProperty hklm:\software\microsoft\windows\currentversion\uninstall\* | Where-Object {$_.Displayname -like "*Remote Desktop*"} | Select-Object DisplayName,DisplayVersion,UninstallString,QuietUninstallString + if ($MSRCDreg.DisplayName -eq "Remote Desktop"){ + write-host "Remote Desktop Installation Found" + #write-host "Version " $MSRCDreg.DisplayVersion + $uninstall = $MSRCDreg.uninstallstring -replace "MsiExec.exe /X","" + write-host "Uninstalling Remote Desktop" + Start-Process -FilePath "msiexec.exe" -ArgumentList "/x $($uninstall) /q /norestart" + } + else + { + write-host "MSRDC not detected via registry. Trying as package" + uninstall-MSRDC + + } +} + +#function to install Windows App from MS Store +function install-windowsappstore{ + invoke-command -ScriptBlock { winget install 9N1F85V9T8BN --accept-package-agreements --accept-source-agreements} | out-null #MS Store Install +} + +#Function to install Windows App from Winget CDN +function install-windowsappwinget{ + invoke-command -ScriptBlock {winget install Microsoft.WindowsApp --accept-package-agreements --accept-source-agreements} | out-null #Winget Install +} + +#Function to install Windows App from MSIX direct download +function install-windowsappMSIX{ + Invoke-WebRequest -uri "https://go.microsoft.com/fwlink/?linkid=2262633" -outfile "c:\windows\temp\WindowsApp.MSIX" -UseBasicParsing -PassThru #windowsapp download + Add-AppxPackage -Path C:\windows\temp\WindowsApp.MSIX + } + +function invoke-WAInstallCheck{ + if ((($testWA = get-appxpackage -name MicrosoftCorporationII.Windows365).name) -eq "MicrosoftCorporationII.Windows365" ){ + Write-Host "Windows App Installation found." + Return 0 + } + else + { + write-host "Windows App installation not found." + Return 1 + } +} + +function invoke-disableautoupdate($num){ + write-host "Setting disableautoupdate reg key" + $path = "HKLM:\SOFTWARE\Microsoft\WindowsApp" + If (!(Test-Path $path)) { + New-Item -Path $path -Force +} + + New-ItemProperty -Path $path -Name DisableAutomaticUpdates -PropertyType DWORD -Value $num -Force + +} + +#check if Windows App is installed. If so, skip installation. Else, install +if ((invoke-WAInstallCheck) -eq 0){ + write-host "Skipping Windows App Installation" + } +else + { + if ($source -eq "Store"){ + write-host "Starting Windows App installation from Microsoft Store" + install-windowsappstore + } + if ($source -eq "WinGet"){ + write-host "Starting Windows App installation from WinGet" + install-windowsappwinget + } + if ($source -eq "MSIX"){ + write-host "Starting Windows App installation from MSIX download" + install-windowsappMSIX + } +} + +#verify if Windows App has now been installed. If so, move to uninstalling MSRDC. Else, fail. +if ((invoke-WAInstallCheck) -eq 0){ + write-host "Validated Windows App Installed" + if ($UninstallMSRDC -eq $true){uninstall-MSRDCreg} + #write-host "Installation Complete" + } + else + { + write-host "Windows App does not appear to be installed. Something went wrong" + } + +if ($DisableAutoUpdate -ne 0){ + if ($DisableAutoUpdate -eq 1){invoke-disableautoupdate -num 1} + if ($DisableAutoUpdate -eq 2){invoke-disableautoupdate -num 2} + if ($DisableAutoUpdate -eq 3){invoke-disableautoupdate -num 3} + +} +write-host "Installation Complete" From 50193be9391b586d62ca6c4531604614b121a18a Mon Sep 17 00:00:00 2001 From: Donna Ryan <100233767+DonnaRyanMicrosoft@users.noreply.github.com> Date: Fri, 10 Oct 2025 00:59:17 -0400 Subject: [PATCH 08/26] Fixed ID10T error deleted extra stuff that shouldn't have been pasted --- .../Windows App Installer.ps1 | 146 ------------------ 1 file changed, 146 deletions(-) diff --git a/Windows App Installer MultiTool/Windows App Installer.ps1 b/Windows App Installer MultiTool/Windows App Installer.ps1 index 20252f2..615d64c 100644 --- a/Windows App Installer MultiTool/Windows App Installer.ps1 +++ b/Windows App Installer MultiTool/Windows App Installer.ps1 @@ -124,149 +124,3 @@ if ($DisableAutoUpdate -ne 0){ } write-host "Installation Complete" -________________________________________ -From: Donna Ryan -Sent: Friday, October 10, 2025 12:36 AM -To: Donna Ryan -Subject: RE: script - -| Out-File -FilePath c:\windows\temp\WindowsAppStoreInstall.log -Append - - -Donna Ryan -Senior Program Manager -Windows 365 -WCX - CAT -ryandonna@microsoft.com - -From: Donna Ryan -Sent: Friday, October 10, 2025 12:25 AM -To: Donna Ryan -Subject: [EXTERNAL] script - -cls - -$source = "WinGet" #Store,WinGet,MSIX,All -$UninstallMSRDC = $true #$true,$false - -$DisableAutoUpdate = 0 -#0: Enables updates (default value) -#1: Disable updates from all locations -#2: Disable updates from the Microsoft Store -#3: Disable updates from the CDN location - - -function uninstall-MSRDC{ - try{ - write-host "Looking to see if Remote Desktop is an installed package" - $MSRDC = Get-Package -Name "Remote Desktop" -ErrorAction Stop - - if ($MSRDC.name -eq "Remote Desktop"){ - write-host "Remote Desktop Install Found" - #write-host "Version: " $MSRDC.Version - write-host "Uninstalling Remote Desktop" - Uninstall-Package -Name "Remote Desktop" -force | Out-Null - write-host "Remote Desktop uninstalled" - } - } - catch - { - Write-Host "Remote Desktop not found as package." - } -} - -function uninstall-MSRDCreg{ - - $MSRCDreg = Get-ItemProperty hklm:\software\microsoft\windows\currentversion\uninstall\* | Where-Object {$_.Displayname -like "*Remote Desktop*"} | Select-Object DisplayName,DisplayVersion,UninstallString,QuietUninstallString - if ($MSRCDreg.DisplayName -eq "Remote Desktop"){ - write-host "Remote Desktop Installation Found" - #write-host "Version " $MSRCDreg.DisplayVersion - $uninstall = $MSRCDreg.uninstallstring -replace "MsiExec.exe /X","" - write-host "Uninstalling Remote Desktop" - Start-Process -FilePath "msiexec.exe" -ArgumentList "/x $($uninstall) /q /norestart" - } - else - { - write-host "MSRDC not detected via registry. Trying as package" - uninstall-MSRDC - - } -} - -#function to install Windows App from MS Store -function install-windowsappstore{ - invoke-command -ScriptBlock { winget install 9N1F85V9T8BN --accept-package-agreements --accept-source-agreements} | out-null #MS Store Install -} - -#Function to install Windows App from Winget CDN -function install-windowsappwinget{ - invoke-command -ScriptBlock {winget install Microsoft.WindowsApp --accept-package-agreements --accept-source-agreements} | out-null #Winget Install -} - -#Function to install Windows App from MSIX direct download -function install-windowsappMSIX{ - Invoke-WebRequest -uri "https://go.microsoft.com/fwlink/?linkid=2262633" -outfile "c:\windows\temp\WindowsApp.MSIX" -UseBasicParsing -PassThru #windowsapp download - Add-AppxPackage -Path C:\windows\temp\WindowsApp.MSIX - } - -function invoke-WAInstallCheck{ - if ((($testWA = get-appxpackage -name MicrosoftCorporationII.Windows365).name) -eq "MicrosoftCorporationII.Windows365" ){ - Write-Host "Windows App Installation found." - Return 0 - } - else - { - write-host "Windows App installation not found." - Return 1 - } -} - -function invoke-disableautoupdate($num){ - write-host "Setting disableautoupdate reg key" - $path = "HKLM:\SOFTWARE\Microsoft\WindowsApp" - If (!(Test-Path $path)) { - New-Item -Path $path -Force -} - - New-ItemProperty -Path $path -Name DisableAutomaticUpdates -PropertyType DWORD -Value $num -Force - -} - -#check if Windows App is installed. If so, skip installation. Else, install -if ((invoke-WAInstallCheck) -eq 0){ - write-host "Skipping Windows App Installation" - } -else - { - if ($source -eq "Store"){ - write-host "Starting Windows App installation from Microsoft Store" - install-windowsappstore - } - if ($source -eq "WinGet"){ - write-host "Starting Windows App installation from WinGet" - install-windowsappwinget - } - if ($source -eq "MSIX"){ - write-host "Starting Windows App installation from MSIX download" - install-windowsappMSIX - } -} - -#verify if Windows App has now been installed. If so, move to uninstalling MSRDC. Else, fail. -if ((invoke-WAInstallCheck) -eq 0){ - write-host "Validated Windows App Installed" - if ($UninstallMSRDC -eq $true){uninstall-MSRDCreg} - #write-host "Installation Complete" - } - else - { - write-host "Windows App does not appear to be installed. Something went wrong" - } - -if ($DisableAutoUpdate -ne 0){ - if ($DisableAutoUpdate -eq 1){invoke-disableautoupdate -num 1} - if ($DisableAutoUpdate -eq 2){invoke-disableautoupdate -num 2} - if ($DisableAutoUpdate -eq 3){invoke-disableautoupdate -num 3} - -} -write-host "Installation Complete" From 9a3a4b18e3c2f262c764c2d8dfa26efbd6d5439e Mon Sep 17 00:00:00 2001 From: Donna Ryan <100233767+DonnaRyanMicrosoft@users.noreply.github.com> Date: Fri, 10 Oct 2025 01:06:43 -0400 Subject: [PATCH 09/26] added comments --- .../Windows App Installer.ps1 | 44 ++++++++++--------- 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/Windows App Installer MultiTool/Windows App Installer.ps1 b/Windows App Installer MultiTool/Windows App Installer.ps1 index 615d64c..6cd85db 100644 --- a/Windows App Installer MultiTool/Windows App Installer.ps1 +++ b/Windows App Installer MultiTool/Windows App Installer.ps1 @@ -9,7 +9,26 @@ $DisableAutoUpdate = 0 #2: Disable updates from the Microsoft Store #3: Disable updates from the CDN location +#function to uninstall MSRDC by pulling MSIEXEC.EXE GUID from the registy - primary method +function uninstall-MSRDCreg{ + + $MSRCDreg = Get-ItemProperty hklm:\software\microsoft\windows\currentversion\uninstall\* | Where-Object {$_.Displayname -like "*Remote Desktop*"} | Select-Object DisplayName,DisplayVersion,UninstallString,QuietUninstallString + if ($MSRCDreg.DisplayName -eq "Remote Desktop"){ + write-host "Remote Desktop Installation Found" + #write-host "Version " $MSRCDreg.DisplayVersion + $uninstall = $MSRCDreg.uninstallstring -replace "MsiExec.exe /X","" + write-host "Uninstalling Remote Desktop" + Start-Process -FilePath "msiexec.exe" -ArgumentList "/x $($uninstall) /q /norestart" + } + else + { + write-host "Remote Desktop not detected via registry. Trying as package" + uninstall-MSRDC + + } +} +#Function to uninstall MSRDC via uninstall-package as a secondary method function uninstall-MSRDC{ try{ write-host "Looking to see if Remote Desktop is an installed package" @@ -29,30 +48,12 @@ function uninstall-MSRDC{ } } -function uninstall-MSRDCreg{ - - $MSRCDreg = Get-ItemProperty hklm:\software\microsoft\windows\currentversion\uninstall\* | Where-Object {$_.Displayname -like "*Remote Desktop*"} | Select-Object DisplayName,DisplayVersion,UninstallString,QuietUninstallString - if ($MSRCDreg.DisplayName -eq "Remote Desktop"){ - write-host "Remote Desktop Installation Found" - #write-host "Version " $MSRCDreg.DisplayVersion - $uninstall = $MSRCDreg.uninstallstring -replace "MsiExec.exe /X","" - write-host "Uninstalling Remote Desktop" - Start-Process -FilePath "msiexec.exe" -ArgumentList "/x $($uninstall) /q /norestart" - } - else - { - write-host "Remote Desktop not detected via registry. Trying as package" - uninstall-MSRDC - - } -} - -#function to install Windows App from MS Store +#function to install Windows App from MS Store - write install process log to $env:windir\temp\WindowsAppStoreInstall.log function install-windowsappstore{ invoke-command -ScriptBlock { winget install 9N1F85V9T8BN --accept-package-agreements --accept-source-agreements} | Out-File -FilePath c:\windows\temp\WindowsAppStoreInstall.log -Append #MS Store Install } -#Function to install Windows App from Winget CDN +#Function to install Windows App from Winget CDN - write install process log to $env:windir\temp\WindowsAppWinGetInstall.log function install-windowsappwinget{ invoke-command -ScriptBlock {winget install Microsoft.WindowsApp --accept-package-agreements --accept-source-agreements} | Out-File -FilePath c:\windows\temp\WindowsAppWinGetInstall.log -Append #Winget Install } @@ -63,6 +64,7 @@ function install-windowsappMSIX{ Add-AppxPackage -Path C:\windows\temp\WindowsApp.MSIX } +#Function to check if Windows App is installed function invoke-WAInstallCheck{ if ((($testWA = get-appxpackage -name MicrosoftCorporationII.Windows365).name) -eq "MicrosoftCorporationII.Windows365" ){ Write-Host "Windows App Installation found." @@ -75,6 +77,7 @@ function invoke-WAInstallCheck{ } } +#function to set the registry key to control auto updates function invoke-disableautoupdate($num){ write-host "Setting disableautoupdate reg key" $path = "HKLM:\SOFTWARE\Microsoft\WindowsApp" @@ -117,6 +120,7 @@ if ((invoke-WAInstallCheck) -eq 0){ write-host "Windows App does not appear to be installed. Something went wrong" } +#Apply auto update registry key if option selected if ($DisableAutoUpdate -ne 0){ if ($DisableAutoUpdate -eq 1){invoke-disableautoupdate -num 1} if ($DisableAutoUpdate -eq 2){invoke-disableautoupdate -num 2} From 49e233671b97f0401219f64a19fb8e114b12f7b3 Mon Sep 17 00:00:00 2001 From: Donna Ryan <100233767+DonnaRyanMicrosoft@users.noreply.github.com> Date: Fri, 10 Oct 2025 01:48:36 -0400 Subject: [PATCH 10/26] Added Param Block commented out old variable assignments --- .../Windows App Installer.ps1 | 33 +++++++++++++++---- 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/Windows App Installer MultiTool/Windows App Installer.ps1 b/Windows App Installer MultiTool/Windows App Installer.ps1 index 6cd85db..7eb7bc6 100644 --- a/Windows App Installer MultiTool/Windows App Installer.ps1 +++ b/Windows App Installer MultiTool/Windows App Installer.ps1 @@ -1,9 +1,30 @@ -cls - -$source = "Store" #Store,WinGet,MSIX,All -$UninstallMSRDC = $true #$true,$false - -$DisableAutoUpdate = 0 +#cls +#Needs: +#try/catch in main functions +#change write-host to proper logging +#Test Param Block +#test with store disabled from policy +#test on Windows 10 +#test on LTSC greater than 1809 +#test when Windows App doesn't install (use pause and delete) + +Param( + [parameter(mandatory = $false, HelpMessage = "Where to source installer payload")] + [ValidateSet('Store','WinGet','MSIX')] + [string]$source = "Store", + [parameter(mandatory = $false, HelpMessage = "Value to set auto update reg key")] + [ValidateSet(0,1,2,3)] + [int]$DisableAutoUpdate = 0, + [parameter(mandatory = $false, HelpMessage = "Value to set auto update reg key")] + [ValidateSet($true,$false)] + [string]$UninstallMSRDC = $true +) + + +#$source = "Store" #Store,WinGet,MSIX,All +#$UninstallMSRDC = $true #$true,$false + +#$DisableAutoUpdate = 0 #0: Enables updates (default value) #1: Disable updates from all locations #2: Disable updates from the Microsoft Store From cf28d46c6e4100ee9da3a43dffb0615791c0193b Mon Sep 17 00:00:00 2001 From: Donna Ryan <100233767+DonnaRyanMicrosoft@users.noreply.github.com> Date: Fri, 10 Oct 2025 14:46:35 -0400 Subject: [PATCH 11/26] Formatting --- Windows App Installer MultiTool/Windows App Installer.ps1 | 3 --- 1 file changed, 3 deletions(-) diff --git a/Windows App Installer MultiTool/Windows App Installer.ps1 b/Windows App Installer MultiTool/Windows App Installer.ps1 index 7eb7bc6..94d432f 100644 --- a/Windows App Installer MultiTool/Windows App Installer.ps1 +++ b/Windows App Installer MultiTool/Windows App Installer.ps1 @@ -45,7 +45,6 @@ function uninstall-MSRDCreg{ { write-host "Remote Desktop not detected via registry. Trying as package" uninstall-MSRDC - } } @@ -105,9 +104,7 @@ function invoke-disableautoupdate($num){ If (!(Test-Path $path)) { New-Item -Path $path -Force } - New-ItemProperty -Path $path -Name DisableAutomaticUpdates -PropertyType DWORD -Value $num -Force - } #check if Windows App is installed. If so, skip installation. Else, install From 5e63bb1d163f55cc1da2996ef1a8146baa2b8825 Mon Sep 17 00:00:00 2001 From: Donna Ryan <100233767+DonnaRyanMicrosoft@users.noreply.github.com> Date: Sun, 12 Oct 2025 20:38:50 -0400 Subject: [PATCH 12/26] Logging enabled + error handling Added logging module, Added error handling Added rename MSIX download and rename to original file name. --- .../Windows App Installer.ps1 | 166 ++++++++++++++---- 1 file changed, 130 insertions(+), 36 deletions(-) diff --git a/Windows App Installer MultiTool/Windows App Installer.ps1 b/Windows App Installer MultiTool/Windows App Installer.ps1 index 94d432f..9879e44 100644 --- a/Windows App Installer MultiTool/Windows App Installer.ps1 +++ b/Windows App Installer MultiTool/Windows App Installer.ps1 @@ -1,8 +1,8 @@ #cls #Needs: #try/catch in main functions -#change write-host to proper logging -#Test Param Block +#complete #change update-log -Data to proper logging +#complete #Test Param Block #test with store disabled from policy #test on Windows 10 #test on LTSC greater than 1809 @@ -17,33 +17,65 @@ Param( [int]$DisableAutoUpdate = 0, [parameter(mandatory = $false, HelpMessage = "Value to set auto update reg key")] [ValidateSet($true,$false)] - [string]$UninstallMSRDC = $true + [string]$UninstallMSRDC = $true, + [parameter(mandatory = $false, HelpMessage = "Log path and file name")] + [string]$logpath = "$env:windir\temp\MultiTool.log" ) - -#$source = "Store" #Store,WinGet,MSIX,All -#$UninstallMSRDC = $true #$true,$false - #$DisableAutoUpdate = 0 #0: Enables updates (default value) #1: Disable updates from all locations #2: Disable updates from the Microsoft Store #3: Disable updates from the CDN location +#logging function +function update-log { + Param( + [Parameter( + Mandatory = $true, + ValueFromPipeline = $true, + ValueFromPipelineByPropertyName = $true, + Position = 0 + )] + [string]$Data, + [validateset('Information', 'Warning', 'Error', 'Comment')] + [string]$Class = "Information", + [validateset('Console', 'File', 'Both')] + [string]$Output + ) + + $date = get-date -UFormat "%m/%d/%y %r" + $String = $Class + " " + $date + " " + $data + if ($Output -eq "Console") { Write-Output $string | out-host } + if ($Output -eq "file") { Write-Output $String | out-file -FilePath $logpath -Append } + if ($Output -eq "Both") { + Write-Output $string | out-host + Write-Output $String | out-file -FilePath $logpath -Append + } +} + #function to uninstall MSRDC by pulling MSIEXEC.EXE GUID from the registy - primary method function uninstall-MSRDCreg{ $MSRCDreg = Get-ItemProperty hklm:\software\microsoft\windows\currentversion\uninstall\* | Where-Object {$_.Displayname -like "*Remote Desktop*"} | Select-Object DisplayName,DisplayVersion,UninstallString,QuietUninstallString if ($MSRCDreg.DisplayName -eq "Remote Desktop"){ - write-host "Remote Desktop Installation Found" - #write-host "Version " $MSRCDreg.DisplayVersion + update-log -Data "Remote Desktop Installation Found" -Class Information -Output Both $uninstall = $MSRCDreg.uninstallstring -replace "MsiExec.exe /X","" - write-host "Uninstalling Remote Desktop" - Start-Process -FilePath "msiexec.exe" -ArgumentList "/x $($uninstall) /q /norestart" + update-log -Data "Uninstalling Remote Desktop" -Class Information -Output Both + + try{ + Start-Process -FilePath "msiexec.exe" -ArgumentList "/x $($uninstall) /q /norestart" -ErrorAction Stop + } + catch + { + update-log -Data "Something went wrong uninstalling Remote Desktop" -Class Error -Output Both + Update-Log -data $_.Exception.Message -Class Error -Output Both + } + } else { - write-host "Remote Desktop not detected via registry. Trying as package" + update-log -Data "Remote Desktop not detected via registry. Trying as package" -Class Information -Output Both uninstall-MSRDC } } @@ -51,91 +83,152 @@ function uninstall-MSRDCreg{ #Function to uninstall MSRDC via uninstall-package as a secondary method function uninstall-MSRDC{ try{ - write-host "Looking to see if Remote Desktop is an installed package" - $MSRDC = Get-Package -Name "Remote Desktop" -ErrorAction Stop + update-log -Data "Looking to see if Remote Desktop is an installed package" -Class Information -Output Both + try{ + $MSRDC = Get-Package -Name "Remote Desktop" -ErrorAction Stop + } + catch + { + Update-Log -data $_.Exception.Message -Class Error -Output Both + } if ($MSRDC.name -eq "Remote Desktop"){ - write-host "Remote Desktop Install Found" - #write-host "Version: " $MSRDC.Version - write-host "Uninstalling Remote Desktop" - Uninstall-Package -Name "Remote Desktop" -force | Out-Null - write-host "Remote Desktop uninstalled" + update-log -Data "Remote Desktop Install Found" -Class Information -Output Both + #update-log -Data "Version: " $MSRDC.Version + update-log -Data "Uninstalling Remote Desktop" -Class Information -Output Both + try{ + Uninstall-Package -Name "Remote Desktop" -force -ErrorAction Stop| Out-Null + } + catch + { + Update-Log -data $_.Exception.Message -Class Error -Output Both + } + + update-log -Data "Remote Desktop uninstalled" -Class Information -Output Both } } catch { - Write-Host "Remote Desktop not found as package." + update-log -Data "Remote Desktop not found as package." -Class Information -Output Both } } #function to install Windows App from MS Store - write install process log to $env:windir\temp\WindowsAppStoreInstall.log function install-windowsappstore{ - invoke-command -ScriptBlock { winget install 9N1F85V9T8BN --accept-package-agreements --accept-source-agreements} | Out-File -FilePath c:\windows\temp\WindowsAppStoreInstall.log -Append #MS Store Install + update-log -Data "Writing install process log to $env:windir\temp\WindowsAppStoreInstall.log" -Class Information -Output Both + try{ + invoke-command -ScriptBlock { winget install 9N1F85V9T8BN --accept-package-agreements --accept-source-agreements} | Out-File -FilePath $env:windir\temp\WindowsAppStoreInstall.log -Append #MS Store Install + } + catch + { + Update-Log -data $_.Exception.Message -Class Error -Output Both + Exit 1 + } + } #Function to install Windows App from Winget CDN - write install process log to $env:windir\temp\WindowsAppWinGetInstall.log function install-windowsappwinget{ - invoke-command -ScriptBlock {winget install Microsoft.WindowsApp --accept-package-agreements --accept-source-agreements} | Out-File -FilePath c:\windows\temp\WindowsAppWinGetInstall.log -Append #Winget Install + update-log -Data "Writing install process log to $env:windir\temp\WindowsAppWinGetInstall.log" -Class Information -Output Both + try{ + invoke-command -ScriptBlock {winget install Microsoft.WindowsApp --accept-package-agreements --accept-source-agreements} | Out-File -FilePath $env:windir\temp\WindowsAppWinGetInstall.log -Append #Winget Install + } + catch + { + Update-Log -data $_.Exception.Message -Class Error -Output Both + Exit 1 + } + } #Function to install Windows App from MSIX direct download function install-windowsappMSIX{ - Invoke-WebRequest -uri "https://go.microsoft.com/fwlink/?linkid=2262633" -outfile "c:\windows\temp\WindowsApp.MSIX" -UseBasicParsing -PassThru #windowsapp download - Add-AppxPackage -Path C:\windows\temp\WindowsApp.MSIX + + try{ + if ((test-path -Path $env:windir\Temp\WindowsApp.msix) -eq $true){Remove-Item -Path $env:windir\Temp\WindowsApp.msix -Force -ErrorAction Stop} + + $Payload = Invoke-WebRequest -uri "https://go.microsoft.com/fwlink/?linkid=2262633" -UseBasicParsing -OutFile $env:windir\Temp\WindowsApp.msix -PassThru -ErrorAction Stop + $filename = ($Payload.BaseResponse.ResponseUri.AbsolutePath -replace ".*/") + + if ((test-path -Path $env:windir\Temp\$filename) -eq $true){Remove-Item -Path $env:windir\Temp\$filename -Force -ErrorAction Stop} + + Rename-Item -Path $env:windir\Temp\WindowsApp.msix -NewName $filename -Force -ErrorAction Stop + update-log -Data "Downloaded $filename to $env:windir\temp" -Class Information -Output Both + } + catch{ + Update-Log -data $_.Exception.Message -Class Error -Output Both + } + + try{ + Add-AppxPackage -Path $env:windir\temp\$filename -ErrorAction Stop + } + catch{ + Update-Log -data $_.Exception.Message -Class Error -Output Both + } + + } #Function to check if Windows App is installed function invoke-WAInstallCheck{ if ((($testWA = get-appxpackage -name MicrosoftCorporationII.Windows365).name) -eq "MicrosoftCorporationII.Windows365" ){ - Write-Host "Windows App Installation found." + update-log -Data "Windows App Installation found." -Class Information -Output Both Return 0 } else { - write-host "Windows App installation not found." + update-log -Data "Windows App installation not found." -Class Information -Output Both Return 1 } } #function to set the registry key to control auto updates function invoke-disableautoupdate($num){ - write-host "Setting disableautoupdate reg key" + update-log -Data "Setting disableautoupdate reg key" -Class Information -Output Both $path = "HKLM:\SOFTWARE\Microsoft\WindowsApp" If (!(Test-Path $path)) { New-Item -Path $path -Force } - New-ItemProperty -Path $path -Name DisableAutomaticUpdates -PropertyType DWORD -Value $num -Force + try{ + New-ItemProperty -Path $path -Name DisableAutomaticUpdates -PropertyType DWORD -Value $num -Force -ErrorAction Stop| Out-Null + } + catch{ + Update-Log -data $_.Exception.Message -Class Error -Output Both + } + + } #check if Windows App is installed. If so, skip installation. Else, install if ((invoke-WAInstallCheck) -eq 0){ - write-host "Skipping Windows App Installation" + update-log -Data "Skipping Windows App Installation" -Class Information -Output Both } else { if ($source -eq "Store"){ - write-host "Starting Windows App installation from Microsoft Store" + update-log -Data "Starting Windows App installation from Microsoft Store" -Class Information -Output Both install-windowsappstore } if ($source -eq "WinGet"){ - write-host "Starting Windows App installation from WinGet" + update-log -Data "Starting Windows App installation from WinGet" -Class Information -Output Both install-windowsappwinget } if ($source -eq "MSIX"){ - write-host "Starting Windows App installation from MSIX download" + update-log -Data "Starting Windows App installation from MSIX download" -Class Information -Output Both install-windowsappMSIX } } #verify if Windows App has now been installed. If so, move to uninstalling MSRDC. Else, fail. if ((invoke-WAInstallCheck) -eq 0){ - write-host "Validated Windows App Installed" + update-log -Data "Validated Windows App Installed" -Class Information -Output Both if ($UninstallMSRDC -eq $true){uninstall-MSRDCreg} - #write-host "Installation Complete" + #update-log -Data "Installation Complete" } else { - write-host "Windows App does not appear to be installed. Something went wrong" + update-log -Data "Windows App does not appear to be installed. Something went wrong" -Class Error -Output Both + exit 1 } #Apply auto update registry key if option selected @@ -145,4 +238,5 @@ if ($DisableAutoUpdate -ne 0){ if ($DisableAutoUpdate -eq 3){invoke-disableautoupdate -num 3} } -write-host "Installation Complete" +update-log -Data "Installation Complete" -Class Information -Output Both +update-log -data "************" -Class Information -Output File From b01247c05af4ced34fc737d3ea2fb5a9c80b9dcc Mon Sep 17 00:00:00 2001 From: Donna Ryan <100233767+DonnaRyanMicrosoft@users.noreply.github.com> Date: Tue, 14 Oct 2025 17:22:21 -0400 Subject: [PATCH 13/26] Added Readme.Md instructions Wrote the basic instructions --- Windows App Installer MultiTool/readme.md | 38 +++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 Windows App Installer MultiTool/readme.md diff --git a/Windows App Installer MultiTool/readme.md b/Windows App Installer MultiTool/readme.md new file mode 100644 index 0000000..818f90a --- /dev/null +++ b/Windows App Installer MultiTool/readme.md @@ -0,0 +1,38 @@ +# Remote Desktop Migration Tool (Windows App Installer) +This tool uninstalls Remote Desktop (MSRDC) utilizing two different detection techniques, installs Windows App from three different sources, and can set auto update behavior. + + +## Installation +Download the script from the repository. It is ready to be run from commandline or deployed as a script/package. + +## Parameters +### Source (Store,WinGet,MSIX) (Default is Store) + +This parameter controls where Windows App will be downloaded from. If your organization blocks complete access to the Microsoft Store, use with WinGet or MSIX. If both the Microsoft Store and the WinGet CDN are blocked, use MSIX to download from a URL. + +If using "Store" option, a separate log will be created ($env:windir\temp\WindowsAppStoreInstall.log) that is the output from the store installer. + +If using the "WinGet" option a separate log will be created ($env:windir\temp\WindowsAppWinGetInstall.log) that is the output from the WinGet installer + +IF using the "MSIX" option, logging will be in the main log for this script. The MSIX payload will be downloaded to $env:windir\temp\ + +### DisableAutoUpdate (0,1,2,3) (Default is 0) +See this link for a full explanation of each option. https://learn.microsoft.com/en-us/windows-app/configure-updates-windows#configure-update-behavior + +### UninstallMSRDC (True/False) (Default is True) +This parameter tells the script to uninstall Remote Desktop if detected. Removal of Remote Desktop will only happen after Windows App has been installed successfully. + +### Logpath (path to log file with name) (default is $env:windir\temp\MultiTool.log ) + +## Example +'.\Windows App Installer.ps1' -source Store -DisableAutoUpdate 0 -UninstallMSRDC True + +## Logs and Payloads + +All logs and payloads are created in $env:windir\temp\ by default. + +MultiTool.log - The log recording the activity of the script. + +WindowsAppStoreInstall.log - A log that is the output from the Microsoft Store Installer as it installed Windows App. + +WindowsAppWinGetInstall.log - A log that is the output from the WinGet Installer as it installed Windows App. From eecec0284c675daaccaeeac97bde821307c614f7 Mon Sep 17 00:00:00 2001 From: Donna Ryan <100233767+DonnaRyanMicrosoft@users.noreply.github.com> Date: Mon, 20 Oct 2025 22:18:11 -0400 Subject: [PATCH 14/26] 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. --- .../Windows App Installer.ps1 | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/Windows App Installer MultiTool/Windows App Installer.ps1 b/Windows App Installer MultiTool/Windows App Installer.ps1 index 9879e44..8dd34f3 100644 --- a/Windows App Installer MultiTool/Windows App Installer.ps1 +++ b/Windows App Installer MultiTool/Windows App Installer.ps1 @@ -15,9 +15,11 @@ Param( [parameter(mandatory = $false, HelpMessage = "Value to set auto update reg key")] [ValidateSet(0,1,2,3)] [int]$DisableAutoUpdate = 0, - [parameter(mandatory = $false, HelpMessage = "Value to set auto update reg key")] - [ValidateSet($true,$false)] - [string]$UninstallMSRDC = $true, + #[parameter(mandatory = $false, HelpMessage = "Uninstall Remote Desktop if found")] + #[ValidateSet($true,$false)] + #[string]$UninstallMSRDC = $true, + [parameter(mandatory = $false, HelpMessage = "Do not uninstall Remote Desktop if found")] + [switch]$SkipRemoteDesktopUninstall , [parameter(mandatory = $false, HelpMessage = "Log path and file name")] [string]$logpath = "$env:windir\temp\MultiTool.log" ) @@ -222,7 +224,8 @@ else #verify if Windows App has now been installed. If so, move to uninstalling MSRDC. Else, fail. if ((invoke-WAInstallCheck) -eq 0){ update-log -Data "Validated Windows App Installed" -Class Information -Output Both - if ($UninstallMSRDC -eq $true){uninstall-MSRDCreg} + if ($SkipRemoteDesktopUninstall -eq $False){uninstall-MSRDCreg} + #$SkipRemoteDesktopUninstall #update-log -Data "Installation Complete" } else From 9a138d997f150005600c23e918d293a047d459eb Mon Sep 17 00:00:00 2001 From: Donna Ryan <100233767+DonnaRyanMicrosoft@users.noreply.github.com> Date: Mon, 20 Oct 2025 22:32:48 -0400 Subject: [PATCH 15/26] Updated Readme.md Removed notes from head of script. Updated Readme.md --- .../Windows App Installer.ps1 | 12 +- Windows App Installer MultiTool/readme.md | 139 +++++++++++++----- 2 files changed, 104 insertions(+), 47 deletions(-) diff --git a/Windows App Installer MultiTool/Windows App Installer.ps1 b/Windows App Installer MultiTool/Windows App Installer.ps1 index 8dd34f3..e8872d5 100644 --- a/Windows App Installer MultiTool/Windows App Installer.ps1 +++ b/Windows App Installer MultiTool/Windows App Installer.ps1 @@ -1,14 +1,4 @@ -#cls -#Needs: -#try/catch in main functions -#complete #change update-log -Data to proper logging -#complete #Test Param Block -#test with store disabled from policy -#test on Windows 10 -#test on LTSC greater than 1809 -#test when Windows App doesn't install (use pause and delete) - -Param( +Param( [parameter(mandatory = $false, HelpMessage = "Where to source installer payload")] [ValidateSet('Store','WinGet','MSIX')] [string]$source = "Store", diff --git a/Windows App Installer MultiTool/readme.md b/Windows App Installer MultiTool/readme.md index 818f90a..4572e51 100644 --- a/Windows App Installer MultiTool/readme.md +++ b/Windows App Installer MultiTool/readme.md @@ -1,38 +1,105 @@ -# Remote Desktop Migration Tool (Windows App Installer) -This tool uninstalls Remote Desktop (MSRDC) utilizing two different detection techniques, installs Windows App from three different sources, and can set auto update behavior. - - -## Installation -Download the script from the repository. It is ready to be run from commandline or deployed as a script/package. +# Windows App Installer MultiTool + +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. + +## What it does +- Detects whether the Windows App (`MicrosoftCorporationII.Windows365`) is installed. +- Installs the Windows App from one of three sources: + - Microsoft Store (via winget id `9N1F85V9T8BN`) — `Store` (default) + - WinGet CDN package (`Microsoft.WindowsApp`) — `WinGet` + - Direct MSIX download (FWLINK) and `Add-AppxPackage` — `MSIX` +- Optionally writes `HKLM:\SOFTWARE\Microsoft\WindowsApp\DisableAutomaticUpdates` to control auto updates. +- Optionally uninstalls legacy "Remote Desktop" via registry/MSI or package uninstall methods. +- Writes logs to console and to the configured log file. + +## Files +- `Windows App Installer.ps1` — main script. +- Runtime logs: + - Default log: `%windir%\Temp\MultiTool.log` (can be changed with `-logpath`) + - Store install trace: `%windir%\Temp\WindowsAppStoreInstall.log` + - WinGet install trace: `%windir%\Temp\WindowsAppWinGetInstall.log` + +## Requirements +- Windows (modern Windows 10/11 recommended). +- PowerShell (script compatible with Windows PowerShell 5.1 and later). +- Administrative privileges to install packages and write to HKLM. +- Internet access for Store/WinGet/MSIX downloads. +- Winget and/or Microsoft Store available for corresponding install methods (use `MSIX` when Store is blocked). ## Parameters -### Source (Store,WinGet,MSIX) (Default is Store) - -This parameter controls where Windows App will be downloaded from. If your organization blocks complete access to the Microsoft Store, use with WinGet or MSIX. If both the Microsoft Store and the WinGet CDN are blocked, use MSIX to download from a URL. - -If using "Store" option, a separate log will be created ($env:windir\temp\WindowsAppStoreInstall.log) that is the output from the store installer. - -If using the "WinGet" option a separate log will be created ($env:windir\temp\WindowsAppWinGetInstall.log) that is the output from the WinGet installer - -IF using the "MSIX" option, logging will be in the main log for this script. The MSIX payload will be downloaded to $env:windir\temp\ - -### DisableAutoUpdate (0,1,2,3) (Default is 0) -See this link for a full explanation of each option. https://learn.microsoft.com/en-us/windows-app/configure-updates-windows#configure-update-behavior - -### UninstallMSRDC (True/False) (Default is True) -This parameter tells the script to uninstall Remote Desktop if detected. Removal of Remote Desktop will only happen after Windows App has been installed successfully. - -### Logpath (path to log file with name) (default is $env:windir\temp\MultiTool.log ) - -## Example -'.\Windows App Installer.ps1' -source Store -DisableAutoUpdate 0 -UninstallMSRDC True - -## Logs and Payloads - -All logs and payloads are created in $env:windir\temp\ by default. - -MultiTool.log - The log recording the activity of the script. - -WindowsAppStoreInstall.log - A log that is the output from the Microsoft Store Installer as it installed Windows App. - -WindowsAppWinGetInstall.log - A log that is the output from the WinGet Installer as it installed Windows App. +- `-source` (string) + Where to source the installer payload. Allowed values: `Store` (default), `WinGet`, `MSIX`. +- `-DisableAutoUpdate` (int) + Sets the registry DWORD `HKLM:\SOFTWARE\Microsoft\WindowsApp\DisableAutomaticUpdates`. Allowed values: + - `0` — Enable updates (default) + - `1` — Disable updates from all locations + - `2` — Disable updates from Microsoft Store + - `3` — Disable updates from the CDN +- `-SkipRemoteDesktopUninstall` (switch) + If present, skip attempting to remove legacy Remote Desktop. +- `-logpath` (string) + Path to the primary log file (default: `$env:windir\temp\MultiTool.log`). + +## High-level functions (what they do) +- `update-log` — logging helper (console / file / both). +- `invoke-WAInstallCheck` — returns 0 if Windows App is present, 1 otherwise. +- `install-windowsappstore` — triggers Store install (uses winget id `9N1F85V9T8BN`) and logs to temp file. +- `install-windowsappwinget` — triggers WinGet install for `Microsoft.WindowsApp` and logs to temp file. +- `install-windowsappMSIX` — downloads MSIX via FWLINK and installs with `Add-AppxPackage`. +- `uninstall-MSRDCreg` — primary uninstall of legacy Remote Desktop via registry MSI uninstall string. +- `uninstall-MSRDC` — secondary uninstall via `Get-Package` / `Uninstall-Package`. +- `invoke-disableautoupdate` — creates/sets `DisableAutomaticUpdates` DWORD. + +## Usage examples (run elevated) +Default (Store) install: +```powershell +PowerShell -ExecutionPolicy Bypass -File ".\Windows App Installer.ps1" +``` + +Install via WinGet and disable Store updates (set key = 2): +```powershell +PowerShell -ExecutionPolicy Bypass -File ".\Windows App Installer.ps1" -source WinGet -DisableAutoUpdate 2 +``` + +Install via MSIX and skip removing legacy Remote Desktop: +```powershell +PowerShell -ExecutionPolicy Bypass -File ".\Windows App Installer.ps1" -source MSIX -SkipRemoteDesktopUninstall +``` + +Specify an alternate log file: +```powershell +PowerShell -ExecutionPolicy Bypass -File ".\Windows App Installer.ps1" -logpath "C:\Temp\WinAppInstall.log" +``` + +## Exit behavior & logs +- On fatal install failures the script calls `exit 1`. +- Normal success writes completion messages to logs and returns normally. +- Check logs for details: + - Primary: `%windir%\Temp\MultiTool.log` (or the `-logpath` you supplied) + - Install traces: `%windir%\Temp\WindowsAppStoreInstall.log`, `%windir%\Temp\WindowsAppWinGetInstall.log` + +## Troubleshooting +- Permission / access denied: + - Run PowerShell as Administrator. +- Winget not found or Microsoft Store disabled: + - Use `-source MSIX` to attempt a direct MSIX install. +- `Add-AppxPackage` errors: + - Ensure sideloading is allowed or that the package signature and system policy permit the install. +- Remote Desktop uninstall fails: + - The script tries registry/MSI first then package uninstall. Manual removal may be required if uninstall strings are missing. +- For diagnostics: + - Inspect the three logs listed above for error messages and stack traces. + +## Known limitations +- Script assumes availability of `winget` for Store/WinGet flows; environments with Store disabled may fail unless `MSIX` chosen. +- Error handling is present but could be more granular (some functions catch and log but do not standardize exit codes). +- Not explicitly tested on Windows LTSC or older Windows 10 branches — behavior may vary. + +## Recommended next steps +- Add a `-WhatIf`/dry-run mode. +- Add explicit checks for `winget`/Store presence before choosing install path. +- Convert `update-log` to structured logging (timestamped JSON) for automation. +- Add more granular exit codes to represent specific failure types. + +## License +No license is specified. Add a `LICENSE` file if you intend to publish or share. From f1fd3c2363f6bb5b41bfac5d3f38596172caf023 Mon Sep 17 00:00:00 2001 From: Donna Ryan <100233767+DonnaRyanMicrosoft@users.noreply.github.com> Date: Tue, 28 Oct 2025 22:44:13 -0400 Subject: [PATCH 16/26] Formatting and Copyright Minor formatting changes with copyright, name, and version added --- .../Windows App Installer.ps1 | 33 +++++++++---------- 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/Windows App Installer MultiTool/Windows App Installer.ps1 b/Windows App Installer MultiTool/Windows App Installer.ps1 index e8872d5..48d6dd6 100644 --- a/Windows App Installer MultiTool/Windows App Installer.ps1 +++ b/Windows App Installer MultiTool/Windows App Installer.ps1 @@ -1,20 +1,27 @@ -Param( +<# +.COPYRIGHT +Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. +See LICENSE in the project root for license information. +#> + +#Microsoft Remote Desktop Client Migration Script +#Version 1.0 +#For more info, visit: https://github.com/microsoft/Windows365-PSScripts + +Param( [parameter(mandatory = $false, HelpMessage = "Where to source installer payload")] [ValidateSet('Store','WinGet','MSIX')] [string]$source = "Store", [parameter(mandatory = $false, HelpMessage = "Value to set auto update reg key")] [ValidateSet(0,1,2,3)] [int]$DisableAutoUpdate = 0, - #[parameter(mandatory = $false, HelpMessage = "Uninstall Remote Desktop if found")] - #[ValidateSet($true,$false)] - #[string]$UninstallMSRDC = $true, [parameter(mandatory = $false, HelpMessage = "Do not uninstall Remote Desktop if found")] [switch]$SkipRemoteDesktopUninstall , [parameter(mandatory = $false, HelpMessage = "Log path and file name")] [string]$logpath = "$env:windir\temp\MultiTool.log" ) -#$DisableAutoUpdate = 0 +#$DisableAutoUpdate values: #0: Enables updates (default value) #1: Disable updates from all locations #2: Disable updates from the Microsoft Store @@ -63,7 +70,6 @@ function uninstall-MSRDCreg{ update-log -Data "Something went wrong uninstalling Remote Desktop" -Class Error -Output Both Update-Log -data $_.Exception.Message -Class Error -Output Both } - } else { @@ -116,7 +122,6 @@ function install-windowsappstore{ Update-Log -data $_.Exception.Message -Class Error -Output Both Exit 1 } - } #Function to install Windows App from Winget CDN - write install process log to $env:windir\temp\WindowsAppWinGetInstall.log @@ -130,7 +135,6 @@ function install-windowsappwinget{ Update-Log -data $_.Exception.Message -Class Error -Output Both Exit 1 } - } #Function to install Windows App from MSIX direct download @@ -150,16 +154,13 @@ function install-windowsappMSIX{ catch{ Update-Log -data $_.Exception.Message -Class Error -Output Both } - try{ Add-AppxPackage -Path $env:windir\temp\$filename -ErrorAction Stop } catch{ Update-Log -data $_.Exception.Message -Class Error -Output Both } - - - } +} #Function to check if Windows App is installed function invoke-WAInstallCheck{ @@ -180,15 +181,13 @@ function invoke-disableautoupdate($num){ $path = "HKLM:\SOFTWARE\Microsoft\WindowsApp" If (!(Test-Path $path)) { New-Item -Path $path -Force -} + } try{ New-ItemProperty -Path $path -Name DisableAutomaticUpdates -PropertyType DWORD -Value $num -Force -ErrorAction Stop| Out-Null } catch{ Update-Log -data $_.Exception.Message -Class Error -Output Both } - - } #check if Windows App is installed. If so, skip installation. Else, install @@ -215,8 +214,6 @@ else if ((invoke-WAInstallCheck) -eq 0){ update-log -Data "Validated Windows App Installed" -Class Information -Output Both if ($SkipRemoteDesktopUninstall -eq $False){uninstall-MSRDCreg} - #$SkipRemoteDesktopUninstall - #update-log -Data "Installation Complete" } else { @@ -229,7 +226,7 @@ if ($DisableAutoUpdate -ne 0){ if ($DisableAutoUpdate -eq 1){invoke-disableautoupdate -num 1} if ($DisableAutoUpdate -eq 2){invoke-disableautoupdate -num 2} if ($DisableAutoUpdate -eq 3){invoke-disableautoupdate -num 3} - } + update-log -Data "Installation Complete" -Class Information -Output Both update-log -data "************" -Class Information -Output File From f49ec5f3f3491e7a68a541636f021d321c512eb4 Mon Sep 17 00:00:00 2001 From: Donna Ryan <100233767+DonnaRyanMicrosoft@users.noreply.github.com> Date: Wed, 29 Oct 2025 14:40:43 -0400 Subject: [PATCH 17/26] Changed name of file to new name renamed script --- ...p Installer.ps1 => Remote Desktop Client Migration Script.ps1} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename Windows App Installer MultiTool/{Windows App Installer.ps1 => Remote Desktop Client Migration Script.ps1} (100%) diff --git a/Windows App Installer MultiTool/Windows App Installer.ps1 b/Windows App Installer MultiTool/Remote Desktop Client Migration Script.ps1 similarity index 100% rename from Windows App Installer MultiTool/Windows App Installer.ps1 rename to Windows App Installer MultiTool/Remote Desktop Client Migration Script.ps1 From 01a86c0f4812f0f4494392ad7d8f337a2cba6cba Mon Sep 17 00:00:00 2001 From: Donna Ryan <100233767+DonnaRyanMicrosoft@users.noreply.github.com> Date: Wed, 29 Oct 2025 14:41:47 -0400 Subject: [PATCH 18/26] Changed Folder Name Renamed Folder --- .../Remote Desktop Client Migration Script.ps1 | 0 .../readme.md | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename {Windows App Installer MultiTool => Remote Desktop Client Migration Script}/Remote Desktop Client Migration Script.ps1 (100%) rename {Windows App Installer MultiTool => Remote Desktop Client Migration Script}/readme.md (100%) diff --git a/Windows App Installer MultiTool/Remote Desktop Client Migration Script.ps1 b/Remote Desktop Client Migration Script/Remote Desktop Client Migration Script.ps1 similarity index 100% rename from Windows App Installer MultiTool/Remote Desktop Client Migration Script.ps1 rename to Remote Desktop Client Migration Script/Remote Desktop Client Migration Script.ps1 diff --git a/Windows App Installer MultiTool/readme.md b/Remote Desktop Client Migration Script/readme.md similarity index 100% rename from Windows App Installer MultiTool/readme.md rename to Remote Desktop Client Migration Script/readme.md From ce814f47f9cea61de13255fb2da4aa0a7f8f9a03 Mon Sep 17 00:00:00 2001 From: Donna Ryan <100233767+DonnaRyanMicrosoft@users.noreply.github.com> Date: Mon, 3 Nov 2025 10:29:50 -0500 Subject: [PATCH 19/26] Updated Documentation Also changed log name to reflect new script name --- ...Remote Desktop Client Migration Script.ps1 | 2 +- .../readme.md | 288 +++++++++++++----- 2 files changed, 209 insertions(+), 81 deletions(-) diff --git a/Remote Desktop Client Migration Script/Remote Desktop Client Migration Script.ps1 b/Remote Desktop Client Migration Script/Remote Desktop Client Migration Script.ps1 index 48d6dd6..bf6e5fe 100644 --- a/Remote Desktop Client Migration Script/Remote Desktop Client Migration Script.ps1 +++ b/Remote Desktop Client Migration Script/Remote Desktop Client Migration Script.ps1 @@ -18,7 +18,7 @@ Param( [parameter(mandatory = $false, HelpMessage = "Do not uninstall Remote Desktop if found")] [switch]$SkipRemoteDesktopUninstall , [parameter(mandatory = $false, HelpMessage = "Log path and file name")] - [string]$logpath = "$env:windir\temp\MultiTool.log" + [string]$logpath = "$env:windir\temp\RDC-Migration.log" ) #$DisableAutoUpdate values: diff --git a/Remote Desktop Client Migration Script/readme.md b/Remote Desktop Client Migration Script/readme.md index 4572e51..925f471 100644 --- a/Remote Desktop Client Migration Script/readme.md +++ b/Remote Desktop Client Migration Script/readme.md @@ -1,105 +1,233 @@ -# Windows App Installer MultiTool +# Remote Desktop Client Migration Script -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. +## Overview +This PowerShell script automates the migration from the legacy Remote Desktop client to the new **Windows App** (formerly Windows 365). It handles the installation of Windows App from multiple sources and optionally uninstalls the old Remote Desktop client. + +**Version:** 1.0 +**Copyright:** Microsoft Corporation. All rights reserved. Licensed under the MIT license. ## What it does -- Detects whether the Windows App (`MicrosoftCorporationII.Windows365`) is installed. +- Detects whether the Windows App (`MicrosoftCorporationII.Windows365`) is already installed - Installs the Windows App from one of three sources: - Microsoft Store (via winget id `9N1F85V9T8BN`) — `Store` (default) - WinGet CDN package (`Microsoft.WindowsApp`) — `WinGet` - Direct MSIX download (FWLINK) and `Add-AppxPackage` — `MSIX` -- Optionally writes `HKLM:\SOFTWARE\Microsoft\WindowsApp\DisableAutomaticUpdates` to control auto updates. -- Optionally uninstalls legacy "Remote Desktop" via registry/MSI or package uninstall methods. -- Writes logs to console and to the configured log file. - -## Files -- `Windows App Installer.ps1` — main script. -- Runtime logs: - - Default log: `%windir%\Temp\MultiTool.log` (can be changed with `-logpath`) - - Store install trace: `%windir%\Temp\WindowsAppStoreInstall.log` - - WinGet install trace: `%windir%\Temp\WindowsAppWinGetInstall.log` - -## Requirements -- Windows (modern Windows 10/11 recommended). -- PowerShell (script compatible with Windows PowerShell 5.1 and later). -- Administrative privileges to install packages and write to HKLM. -- Internet access for Store/WinGet/MSIX downloads. -- Winget and/or Microsoft Store available for corresponding install methods (use `MSIX` when Store is blocked). +- Validates successful Windows App installation +- Optionally uninstalls legacy "Remote Desktop" client using two fallback methods: + - Primary: Registry-based MSI uninstall + - Fallback: Package-based uninstall +- Optionally configures automatic update behavior via registry key +- Comprehensive logging to console and file + +## Prerequisites +- Windows 10/11 operating system +- PowerShell 5.1 or later +- Administrator privileges (required for package installation and registry modifications) +- Internet connectivity (for downloading Windows App) +- WinGet (if using WinGet installation method) +- Microsoft Store access (if using Store installation method, or use `MSIX` when Store is blocked) ## Parameters -- `-source` (string) - Where to source the installer payload. Allowed values: `Store` (default), `WinGet`, `MSIX`. -- `-DisableAutoUpdate` (int) - Sets the registry DWORD `HKLM:\SOFTWARE\Microsoft\WindowsApp\DisableAutomaticUpdates`. Allowed values: - - `0` — Enable updates (default) - - `1` — Disable updates from all locations - - `2` — Disable updates from Microsoft Store - - `3` — Disable updates from the CDN -- `-SkipRemoteDesktopUninstall` (switch) - If present, skip attempting to remove legacy Remote Desktop. -- `-logpath` (string) - Path to the primary log file (default: `$env:windir\temp\MultiTool.log`). - -## High-level functions (what they do) -- `update-log` — logging helper (console / file / both). -- `invoke-WAInstallCheck` — returns 0 if Windows App is present, 1 otherwise. -- `install-windowsappstore` — triggers Store install (uses winget id `9N1F85V9T8BN`) and logs to temp file. -- `install-windowsappwinget` — triggers WinGet install for `Microsoft.WindowsApp` and logs to temp file. -- `install-windowsappMSIX` — downloads MSIX via FWLINK and installs with `Add-AppxPackage`. -- `uninstall-MSRDCreg` — primary uninstall of legacy Remote Desktop via registry MSI uninstall string. -- `uninstall-MSRDC` — secondary uninstall via `Get-Package` / `Uninstall-Package`. -- `invoke-disableautoupdate` — creates/sets `DisableAutomaticUpdates` DWORD. - -## Usage examples (run elevated) -Default (Store) install: + +### `-source` +Specifies where to source the Windows App installer payload. + +**Type:** String +**Valid Values:** `Store`, `WinGet`, `MSIX` +**Default:** `Store` +**Required:** No + +- `Store`: Installs from Microsoft Store (ID: 9N1F85V9T8BN) +- `WinGet`: Installs from WinGet CDN using package ID `Microsoft.WindowsApp` +- `MSIX`: Downloads and installs directly from MSIX package (use when Store is blocked) + +### `-DisableAutoUpdate` +Controls the automatic update behavior for Windows App by setting the registry key `HKLM:\SOFTWARE\Microsoft\WindowsApp\DisableAutomaticUpdates`. + +**Type:** Integer +**Valid Values:** `0`, `1`, `2`, `3` +**Default:** `0` +**Required:** No + +- `0`: Enables updates (default) +- `1`: Disables updates from all locations +- `2`: Disables updates from Microsoft Store only +- `3`: Disables updates from CDN location only + +### `-SkipRemoteDesktopUninstall` +Prevents the script from uninstalling the Remote Desktop client. + +**Type:** Switch +**Default:** `$false` +**Required:** No + +### `-logpath` +Specifies the location and filename for the script log. + +**Type:** String +**Default:** `$env:windir\temp\RDC-Migration.log` +**Required:** No + +## Usage Examples + +### Basic Usage (Microsoft Store - Default) +```powershell +.\Remote Desktop Client Migration Script.ps1 +``` + +### Install from WinGet +```powershell +.\Remote Desktop Client Migration Script.ps1 -source WinGet +``` + +### Install from MSIX (Direct Download) +```powershell +.\Remote Desktop Client Migration Script.ps1 -source MSIX +``` + +### Disable All Auto-Updates +```powershell +.\Remote Desktop Client Migration Script.ps1 -DisableAutoUpdate 1 +``` + +### Disable Store Updates Only +```powershell +.\Remote Desktop Client Migration Script.ps1 -source WinGet -DisableAutoUpdate 2 +``` + +### Keep Remote Desktop Client Installed ```powershell -PowerShell -ExecutionPolicy Bypass -File ".\Windows App Installer.ps1" +.\Remote Desktop Client Migration Script.ps1 -SkipRemoteDesktopUninstall ``` -Install via WinGet and disable Store updates (set key = 2): +### Custom Log Location ```powershell -PowerShell -ExecutionPolicy Bypass -File ".\Windows App Installer.ps1" -source WinGet -DisableAutoUpdate 2 +.\Remote Desktop Client Migration Script.ps1 -logpath "C:\Logs\RDC-Migration.log" ``` -Install via MSIX and skip removing legacy Remote Desktop: +### Full Example with All Parameters ```powershell -PowerShell -ExecutionPolicy Bypass -File ".\Windows App Installer.ps1" -source MSIX -SkipRemoteDesktopUninstall +.\Remote Desktop Client Migration Script.ps1 -source Store -DisableAutoUpdate 2 -logpath "C:\Logs\migration.log" ``` -Specify an alternate log file: +### Run with Execution Policy Bypass ```powershell -PowerShell -ExecutionPolicy Bypass -File ".\Windows App Installer.ps1" -logpath "C:\Temp\WinAppInstall.log" +PowerShell -ExecutionPolicy Bypass -File ".\Remote Desktop Client Migration Script.ps1" -source MSIX -SkipRemoteDesktopUninstall ``` -## Exit behavior & logs -- On fatal install failures the script calls `exit 1`. -- Normal success writes completion messages to logs and returns normally. -- Check logs for details: - - Primary: `%windir%\Temp\MultiTool.log` (or the `-logpath` you supplied) - - Install traces: `%windir%\Temp\WindowsAppStoreInstall.log`, `%windir%\Temp\WindowsAppWinGetInstall.log` +## How It Works + +The script follows this workflow: + +1. **Pre-Installation Check**: Verifies if Windows App is already installed + - If found, skips installation + - If not found, proceeds to installation + +2. **Installation**: Installs Windows App from the specified source + - **Store**: Uses WinGet with Store ID `9N1F85V9T8BN` + - **WinGet**: Uses WinGet CDN package `Microsoft.WindowsApp` + - **MSIX**: Downloads from `https://go.microsoft.com/fwlink/?linkid=2262633` and installs via `Add-AppxPackage` + +3. **Validation**: Confirms successful Windows App installation + - Checks for AppX package `MicrosoftCorporationII.Windows365` + - Exits with error code 1 if not found + +4. **Uninstallation** (unless `-SkipRemoteDesktopUninstall` is used): + - **Primary Method**: Registry-based MSI uninstall using `MsiExec.exe /x` + - **Fallback Method**: Package-based uninstall using `Uninstall-Package` + +5. **Configuration**: Applies auto-update registry settings if specified + +6. **Completion**: Logs final status and exits + +## Log Files + +### Main Script Log +- **Default Location:** `%windir%\temp\RDC-Migration.log` +- **Configurable via:** `-logpath` parameter +- **Contains:** Detailed information about script execution with timestamps, actions, warnings, and errors + +### Installation Process Logs +- **Store Installation:** `%windir%\temp\WindowsAppStoreInstall.log` +- **WinGet Installation:** `%windir%\temp\WindowsAppWinGetInstall.log` +- **MSIX Download:** Payload downloaded to `%windir%\temp\` + +### Log Format +Each log entry includes: +- Log level (Information, Warning, Error, Comment) +- Timestamp in format: `MM/DD/YY HH:MM:SS AM/PM` +- Descriptive message + +## Exit Codes +- **0**: Success (normal completion) +- **1**: Failure (Windows App installation failed or not detected after installation) + +## Script Functions + +The script includes the following key functions: + +- **`update-log`**: Logging helper that writes to console and/or file with timestamps +- **`invoke-WAInstallCheck`**: Checks if Windows App is installed (returns 0 if present, 1 if not) +- **`install-windowsappstore`**: Installs Windows App from Microsoft Store via WinGet +- **`install-windowsappwinget`**: Installs Windows App from WinGet CDN +- **`install-windowsappMSIX`**: Downloads and installs Windows App from direct MSIX download +- **`uninstall-MSRDCreg`**: Primary method to uninstall Remote Desktop via registry MSI uninstall string +- **`uninstall-MSRDC`**: Fallback method to uninstall Remote Desktop via `Get-Package`/`Uninstall-Package` +- **`invoke-disableautoupdate`**: Creates/sets the `DisableAutomaticUpdates` registry key + +## Registry Configuration + +When using `-DisableAutoUpdate`, the script creates/modifies: + +``` +Registry Key: HKLM:\SOFTWARE\Microsoft\WindowsApp +Value Name: DisableAutomaticUpdates +Value Type: DWORD +``` ## Troubleshooting -- Permission / access denied: - - Run PowerShell as Administrator. -- Winget not found or Microsoft Store disabled: - - Use `-source MSIX` to attempt a direct MSIX install. -- `Add-AppxPackage` errors: - - Ensure sideloading is allowed or that the package signature and system policy permit the install. -- Remote Desktop uninstall fails: - - The script tries registry/MSI first then package uninstall. Manual removal may be required if uninstall strings are missing. -- For diagnostics: - - Inspect the three logs listed above for error messages and stack traces. - -## Known limitations -- Script assumes availability of `winget` for Store/WinGet flows; environments with Store disabled may fail unless `MSIX` chosen. -- Error handling is present but could be more granular (some functions catch and log but do not standardize exit codes). -- Not explicitly tested on Windows LTSC or older Windows 10 branches — behavior may vary. - -## Recommended next steps -- Add a `-WhatIf`/dry-run mode. -- Add explicit checks for `winget`/Store presence before choosing install path. -- Convert `update-log` to structured logging (timestamped JSON) for automation. -- Add more granular exit codes to represent specific failure types. + +### Permission or Access Denied Errors +- Ensure PowerShell is running as Administrator +- Verify write permissions to log directory and registry + +### Windows App Installation Fails +- **Check Internet Connectivity**: Ensure the device can reach Microsoft services +- **WinGet Not Found**: If using Store or WinGet source, verify WinGet is installed +- **Microsoft Store Disabled**: Use `-source MSIX` for direct MSIX installation +- **Review Logs**: Check installation logs in `%windir%\temp\` for specific errors + +### AppX Package Installation Errors +- Ensure sideloading is allowed in Windows settings +- Verify package signature and system policy permit installation +- Check if Developer Mode is required + +### Remote Desktop Uninstall Issues +- The script automatically tries two methods (registry-based MSI and package-based) +- Check the main log file for specific error messages +- Use `-SkipRemoteDesktopUninstall` to bypass uninstallation if problematic +- Manual removal may be required if uninstall strings are missing + +### Script Exits with Error Code 1 +- Windows App installation failed or was not detected after installation +- Review all log files for error details +- Try an alternative installation source + +### WinGet Command Not Recognized +- Install App Installer from Microsoft Store +- Or use `-source MSIX` to bypass WinGet requirement + +## Known Limitations +- Script assumes WinGet availability for Store/WinGet installation methods +- Error handling logs exceptions but may not provide granular exit codes for all failure types +- Not extensively tested on Windows LTSC or older Windows 10 branches +- Requires internet connectivity for all installation sources + +## Additional Information + +For more information about this script and other Windows 365 PowerShell scripts, visit: +**https://github.com/microsoft/Windows365-PSScripts** ## License -No license is specified. Add a `LICENSE` file if you intend to publish or share. +Copyright (c) Microsoft Corporation. All rights reserved. +Licensed under the MIT license. See LICENSE in the project root for license information. From 40da2a6a8de4f07a31895e898bd4210c43cb3ae4 Mon Sep 17 00:00:00 2001 From: Donna Ryan <100233767+DonnaRyanMicrosoft@users.noreply.github.com> Date: Wed, 3 Dec 2025 13:57:47 -0500 Subject: [PATCH 20/26] Added WAIT to msiexec command, fixed RD query Added -WAIT to the msiexec uninstall command string. Also changed RD client query from "-like" to "-eq" --- .../Remote Desktop Client Migration Script.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Remote Desktop Client Migration Script/Remote Desktop Client Migration Script.ps1 b/Remote Desktop Client Migration Script/Remote Desktop Client Migration Script.ps1 index bf6e5fe..b0b8e8b 100644 --- a/Remote Desktop Client Migration Script/Remote Desktop Client Migration Script.ps1 +++ b/Remote Desktop Client Migration Script/Remote Desktop Client Migration Script.ps1 @@ -56,14 +56,14 @@ function update-log { #function to uninstall MSRDC by pulling MSIEXEC.EXE GUID from the registy - primary method function uninstall-MSRDCreg{ - $MSRCDreg = Get-ItemProperty hklm:\software\microsoft\windows\currentversion\uninstall\* | Where-Object {$_.Displayname -like "*Remote Desktop*"} | Select-Object DisplayName,DisplayVersion,UninstallString,QuietUninstallString + $MSRCDreg = Get-ItemProperty hklm:\software\microsoft\windows\currentversion\uninstall\* | Where-Object {$_.Displayname -eq "Remote Desktop"} | Select-Object DisplayName,DisplayVersion,UninstallString,QuietUninstallString if ($MSRCDreg.DisplayName -eq "Remote Desktop"){ update-log -Data "Remote Desktop Installation Found" -Class Information -Output Both $uninstall = $MSRCDreg.uninstallstring -replace "MsiExec.exe /X","" update-log -Data "Uninstalling Remote Desktop" -Class Information -Output Both try{ - Start-Process -FilePath "msiexec.exe" -ArgumentList "/x $($uninstall) /q /norestart" -ErrorAction Stop + Start-Process -FilePath "msiexec.exe" -ArgumentList "/x $($uninstall) /q /norestart" -Wait -ErrorAction Stop } catch { From 97714f0807a7fdaa354cfbd32879e8bb3b89eb46 Mon Sep 17 00:00:00 2001 From: Donna Ryan <100233767+DonnaRyanMicrosoft@users.noreply.github.com> Date: Wed, 10 Dec 2025 14:33:25 -0500 Subject: [PATCH 21/26] New Version - 1.1 Changed script to be run in System Context (deploy from Intune). Updated Readme to reflect changes. --- ...Remote Desktop Client Migration Script.ps1 | 61 ++--- .../readme.md | 240 ++---------------- 2 files changed, 44 insertions(+), 257 deletions(-) diff --git a/Remote Desktop Client Migration Script/Remote Desktop Client Migration Script.ps1 b/Remote Desktop Client Migration Script/Remote Desktop Client Migration Script.ps1 index b0b8e8b..e01209f 100644 --- a/Remote Desktop Client Migration Script/Remote Desktop Client Migration Script.ps1 +++ b/Remote Desktop Client Migration Script/Remote Desktop Client Migration Script.ps1 @@ -5,13 +5,10 @@ See LICENSE in the project root for license information. #> #Microsoft Remote Desktop Client Migration Script -#Version 1.0 +#Version 1.1 #For more info, visit: https://github.com/microsoft/Windows365-PSScripts Param( - [parameter(mandatory = $false, HelpMessage = "Where to source installer payload")] - [ValidateSet('Store','WinGet','MSIX')] - [string]$source = "Store", [parameter(mandatory = $false, HelpMessage = "Value to set auto update reg key")] [ValidateSet(0,1,2,3)] [int]$DisableAutoUpdate = 0, @@ -111,36 +108,11 @@ function uninstall-MSRDC{ } } -#function to install Windows App from MS Store - write install process log to $env:windir\temp\WindowsAppStoreInstall.log -function install-windowsappstore{ - update-log -Data "Writing install process log to $env:windir\temp\WindowsAppStoreInstall.log" -Class Information -Output Both - try{ - invoke-command -ScriptBlock { winget install 9N1F85V9T8BN --accept-package-agreements --accept-source-agreements} | Out-File -FilePath $env:windir\temp\WindowsAppStoreInstall.log -Append #MS Store Install - } - catch - { - Update-Log -data $_.Exception.Message -Class Error -Output Both - Exit 1 - } -} - -#Function to install Windows App from Winget CDN - write install process log to $env:windir\temp\WindowsAppWinGetInstall.log -function install-windowsappwinget{ - update-log -Data "Writing install process log to $env:windir\temp\WindowsAppWinGetInstall.log" -Class Information -Output Both - try{ - invoke-command -ScriptBlock {winget install Microsoft.WindowsApp --accept-package-agreements --accept-source-agreements} | Out-File -FilePath $env:windir\temp\WindowsAppWinGetInstall.log -Append #Winget Install - } - catch - { - Update-Log -data $_.Exception.Message -Class Error -Output Both - Exit 1 - } -} - #Function to install Windows App from MSIX direct download function install-windowsappMSIX{ try{ + update-log -data "Downloading payload" -Class Information -Output Both if ((test-path -Path $env:windir\Temp\WindowsApp.msix) -eq $true){Remove-Item -Path $env:windir\Temp\WindowsApp.msix -Force -ErrorAction Stop} $Payload = Invoke-WebRequest -uri "https://go.microsoft.com/fwlink/?linkid=2262633" -UseBasicParsing -OutFile $env:windir\Temp\WindowsApp.msix -PassThru -ErrorAction Stop @@ -150,12 +122,15 @@ function install-windowsappMSIX{ Rename-Item -Path $env:windir\Temp\WindowsApp.msix -NewName $filename -Force -ErrorAction Stop update-log -Data "Downloaded $filename to $env:windir\temp" -Class Information -Output Both + } catch{ Update-Log -data $_.Exception.Message -Class Error -Output Both } try{ - Add-AppxPackage -Path $env:windir\temp\$filename -ErrorAction Stop + #Add-AppxPackage -Path $env:windir\temp\$filename -ErrorAction Stop + update-log -data "Installing Windows App MSIX package..." -Class Information -Output Both + add-appxprovisionedpackage -PackagePath $env:windir\temp\$filename -online -SkipLicense -ErrorAction Stop | Out-Null } catch{ Update-Log -data $_.Exception.Message -Class Error -Output Both @@ -164,13 +139,18 @@ function install-windowsappMSIX{ #Function to check if Windows App is installed function invoke-WAInstallCheck{ - if ((($testWA = get-appxpackage -name MicrosoftCorporationII.Windows365).name) -eq "MicrosoftCorporationII.Windows365" ){ - update-log -Data "Windows App Installation found." -Class Information -Output Both + + $WAappx = (Get-AppxProvisionedPackage -online | Where-Object {$_.DisplayName -eq "MicrosoftCorporationII.Windows365"}) + + if ($WAappx.DisplayName -eq "MicrosoftCorporationII.Windows365"){ + update-log -Data "Windows App Provisioning Package installation found." -Class Information -Output Both + update-log -data $WAappx.displayname -Class Information -Output Both + update-log -data $WAappx.version -Class Information -Output Both Return 0 } else { - update-log -Data "Windows App installation not found." -Class Information -Output Both + update-log -Data "Windows App Provisioning Package installation not found." -Class Information -Output Both Return 1 } } @@ -196,18 +176,11 @@ if ((invoke-WAInstallCheck) -eq 0){ } else { - if ($source -eq "Store"){ - update-log -Data "Starting Windows App installation from Microsoft Store" -Class Information -Output Both - install-windowsappstore - } - if ($source -eq "WinGet"){ - update-log -Data "Starting Windows App installation from WinGet" -Class Information -Output Both - install-windowsappwinget - } - if ($source -eq "MSIX"){ + + #if ($source -eq "MSIX"){ update-log -Data "Starting Windows App installation from MSIX download" -Class Information -Output Both install-windowsappMSIX - } + # } } #verify if Windows App has now been installed. If so, move to uninstalling MSRDC. Else, fail. diff --git a/Remote Desktop Client Migration Script/readme.md b/Remote Desktop Client Migration Script/readme.md index 925f471..3e75989 100644 --- a/Remote Desktop Client Migration Script/readme.md +++ b/Remote Desktop Client Migration Script/readme.md @@ -1,233 +1,47 @@ # Remote Desktop Client Migration Script ## Overview -This PowerShell script automates the migration from the legacy Remote Desktop client to the new **Windows App** (formerly Windows 365). It handles the installation of Windows App from multiple sources and optionally uninstalls the old Remote Desktop client. +This PowerShell script automates the migration from the legacy Microsoft Remote Desktop client to the new Windows App for Windows 365. It works by installing Windows App from an MSIX as a provisioned package, which means that every new and existing user of a given computer will get Windows App installed. -**Version:** 1.0 -**Copyright:** Microsoft Corporation. All rights reserved. Licensed under the MIT license. + It performs the following actions: +- Installs the Windows App via MSIX package download +- Optionally uninstalls the legacy Remote Desktop client +- Sets registry keys to control auto-update behavior +- Logs all actions to a specified log file -## What it does -- Detects whether the Windows App (`MicrosoftCorporationII.Windows365`) is already installed -- Installs the Windows App from one of three sources: - - Microsoft Store (via winget id `9N1F85V9T8BN`) — `Store` (default) - - WinGet CDN package (`Microsoft.WindowsApp`) — `WinGet` - - Direct MSIX download (FWLINK) and `Add-AppxPackage` — `MSIX` -- Validates successful Windows App installation -- Optionally uninstalls legacy "Remote Desktop" client using two fallback methods: - - Primary: Registry-based MSI uninstall - - Fallback: Package-based uninstall -- Optionally configures automatic update behavior via registry key -- Comprehensive logging to console and file - -## Prerequisites -- Windows 10/11 operating system -- PowerShell 5.1 or later -- Administrator privileges (required for package installation and registry modifications) -- Internet connectivity (for downloading Windows App) -- WinGet (if using WinGet installation method) -- Microsoft Store access (if using Store installation method, or use `MSIX` when Store is blocked) +## Features +- **Automated Installation:** Downloads and installs the latest Windows App MSIX package. +- **Legacy Client Removal:** Uninstalls the old Remote Desktop client using registry and package methods (unless skipped). +- **Auto-Update Control:** Sets registry keys to enable/disable automatic updates for the Windows App. +- **Comprehensive Logging:** Logs all operations to a file and/or console for troubleshooting. ## Parameters +| Parameter | Description | Default Value | +|--------------------------|------------------------------------------------------------------|------------------------------------| +| `DisableAutoUpdate` | Controls auto-update registry key (0=Enable, 1-3=Disable modes) | `0` | +| `SkipRemoteDesktopUninstall` | If set, skips uninstalling the legacy Remote Desktop client | Not set (uninstall performed) | +| `logpath` | Path to log file | `%windir%\temp\RDC-Migration.log` | -### `-source` -Specifies where to source the Windows App installer payload. - -**Type:** String -**Valid Values:** `Store`, `WinGet`, `MSIX` -**Default:** `Store` -**Required:** No - -- `Store`: Installs from Microsoft Store (ID: 9N1F85V9T8BN) -- `WinGet`: Installs from WinGet CDN using package ID `Microsoft.WindowsApp` -- `MSIX`: Downloads and installs directly from MSIX package (use when Store is blocked) - -### `-DisableAutoUpdate` -Controls the automatic update behavior for Windows App by setting the registry key `HKLM:\SOFTWARE\Microsoft\WindowsApp\DisableAutomaticUpdates`. - -**Type:** Integer -**Valid Values:** `0`, `1`, `2`, `3` -**Default:** `0` -**Required:** No - +### `DisableAutoUpdate` Values - `0`: Enables updates (default) - `1`: Disables updates from all locations -- `2`: Disables updates from Microsoft Store only -- `3`: Disables updates from CDN location only - -### `-SkipRemoteDesktopUninstall` -Prevents the script from uninstalling the Remote Desktop client. +- `2`: Disables updates from Microsoft Store +- `3`: Disables updates from CDN location -**Type:** Switch -**Default:** `$false` -**Required:** No - -### `-logpath` -Specifies the location and filename for the script log. +## Known Limitations -**Type:** String -**Default:** `$env:windir\temp\RDC-Migration.log` -**Required:** No +- Script will not uninstall Remote Desktop if it has been installed in the User context. +- Script must be run with the System account. It is intended to be deployed from Intune or other systems management platforms. PSEXEC can be used for validation purposes. -## Usage Examples +## Usage +Run the script using the System account. It is intended for mass deployment through Intune or other systems management platforms: -### Basic Usage (Microsoft Store - Default) ```powershell +# Example: Enable updates and uninstall legacy client .\Remote Desktop Client Migration Script.ps1 -``` - -### Install from WinGet -```powershell -.\Remote Desktop Client Migration Script.ps1 -source WinGet -``` - -### Install from MSIX (Direct Download) -```powershell -.\Remote Desktop Client Migration Script.ps1 -source MSIX -``` - -### Disable All Auto-Updates -```powershell -.\Remote Desktop Client Migration Script.ps1 -DisableAutoUpdate 1 -``` - -### Disable Store Updates Only -```powershell -.\Remote Desktop Client Migration Script.ps1 -source WinGet -DisableAutoUpdate 2 -``` - -### Keep Remote Desktop Client Installed -```powershell -.\Remote Desktop Client Migration Script.ps1 -SkipRemoteDesktopUninstall -``` - -### Custom Log Location -```powershell -.\Remote Desktop Client Migration Script.ps1 -logpath "C:\Logs\RDC-Migration.log" -``` - -### Full Example with All Parameters -```powershell -.\Remote Desktop Client Migration Script.ps1 -source Store -DisableAutoUpdate 2 -logpath "C:\Logs\migration.log" -``` - -### Run with Execution Policy Bypass -```powershell -PowerShell -ExecutionPolicy Bypass -File ".\Remote Desktop Client Migration Script.ps1" -source MSIX -SkipRemoteDesktopUninstall -``` - -## How It Works -The script follows this workflow: - -1. **Pre-Installation Check**: Verifies if Windows App is already installed - - If found, skips installation - - If not found, proceeds to installation - -2. **Installation**: Installs Windows App from the specified source - - **Store**: Uses WinGet with Store ID `9N1F85V9T8BN` - - **WinGet**: Uses WinGet CDN package `Microsoft.WindowsApp` - - **MSIX**: Downloads from `https://go.microsoft.com/fwlink/?linkid=2262633` and installs via `Add-AppxPackage` - -3. **Validation**: Confirms successful Windows App installation - - Checks for AppX package `MicrosoftCorporationII.Windows365` - - Exits with error code 1 if not found - -4. **Uninstallation** (unless `-SkipRemoteDesktopUninstall` is used): - - **Primary Method**: Registry-based MSI uninstall using `MsiExec.exe /x` - - **Fallback Method**: Package-based uninstall using `Uninstall-Package` - -5. **Configuration**: Applies auto-update registry settings if specified - -6. **Completion**: Logs final status and exits - -## Log Files - -### Main Script Log -- **Default Location:** `%windir%\temp\RDC-Migration.log` -- **Configurable via:** `-logpath` parameter -- **Contains:** Detailed information about script execution with timestamps, actions, warnings, and errors - -### Installation Process Logs -- **Store Installation:** `%windir%\temp\WindowsAppStoreInstall.log` -- **WinGet Installation:** `%windir%\temp\WindowsAppWinGetInstall.log` -- **MSIX Download:** Payload downloaded to `%windir%\temp\` - -### Log Format -Each log entry includes: -- Log level (Information, Warning, Error, Comment) -- Timestamp in format: `MM/DD/YY HH:MM:SS AM/PM` -- Descriptive message - -## Exit Codes -- **0**: Success (normal completion) -- **1**: Failure (Windows App installation failed or not detected after installation) - -## Script Functions - -The script includes the following key functions: - -- **`update-log`**: Logging helper that writes to console and/or file with timestamps -- **`invoke-WAInstallCheck`**: Checks if Windows App is installed (returns 0 if present, 1 if not) -- **`install-windowsappstore`**: Installs Windows App from Microsoft Store via WinGet -- **`install-windowsappwinget`**: Installs Windows App from WinGet CDN -- **`install-windowsappMSIX`**: Downloads and installs Windows App from direct MSIX download -- **`uninstall-MSRDCreg`**: Primary method to uninstall Remote Desktop via registry MSI uninstall string -- **`uninstall-MSRDC`**: Fallback method to uninstall Remote Desktop via `Get-Package`/`Uninstall-Package` -- **`invoke-disableautoupdate`**: Creates/sets the `DisableAutomaticUpdates` registry key - -## Registry Configuration - -When using `-DisableAutoUpdate`, the script creates/modifies: - -``` -Registry Key: HKLM:\SOFTWARE\Microsoft\WindowsApp -Value Name: DisableAutomaticUpdates -Value Type: DWORD -``` - -## Troubleshooting - -### Permission or Access Denied Errors -- Ensure PowerShell is running as Administrator -- Verify write permissions to log directory and registry - -### Windows App Installation Fails -- **Check Internet Connectivity**: Ensure the device can reach Microsoft services -- **WinGet Not Found**: If using Store or WinGet source, verify WinGet is installed -- **Microsoft Store Disabled**: Use `-source MSIX` for direct MSIX installation -- **Review Logs**: Check installation logs in `%windir%\temp\` for specific errors - -### AppX Package Installation Errors -- Ensure sideloading is allowed in Windows settings -- Verify package signature and system policy permit installation -- Check if Developer Mode is required - -### Remote Desktop Uninstall Issues -- The script automatically tries two methods (registry-based MSI and package-based) -- Check the main log file for specific error messages -- Use `-SkipRemoteDesktopUninstall` to bypass uninstallation if problematic -- Manual removal may be required if uninstall strings are missing - -### Script Exits with Error Code 1 -- Windows App installation failed or was not detected after installation -- Review all log files for error details -- Try an alternative installation source - -### WinGet Command Not Recognized -- Install App Installer from Microsoft Store -- Or use `-source MSIX` to bypass WinGet requirement - -## Known Limitations -- Script assumes WinGet availability for Store/WinGet installation methods -- Error handling logs exceptions but may not provide granular exit codes for all failure types -- Not extensively tested on Windows LTSC or older Windows 10 branches -- Requires internet connectivity for all installation sources +# Example: Disable all updates and skip uninstall +.\Remote Desktop Client Migration Script.ps1 -DisableAutoUpdate 1 -SkipRemoteDesktopUninstall -## Additional Information -For more information about this script and other Windows 365 PowerShell scripts, visit: -**https://github.com/microsoft/Windows365-PSScripts** -## License -Copyright (c) Microsoft Corporation. All rights reserved. -Licensed under the MIT license. See LICENSE in the project root for license information. From facf8d47c55459775ff38a12c92e957d39e2c6b1 Mon Sep 17 00:00:00 2001 From: Donna Ryan <100233767+DonnaRyanMicrosoft@users.noreply.github.com> Date: Wed, 10 Dec 2025 14:36:47 -0500 Subject: [PATCH 22/26] update readme Added Troubleshooting section --- Remote Desktop Client Migration Script/readme.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Remote Desktop Client Migration Script/readme.md b/Remote Desktop Client Migration Script/readme.md index 3e75989..9dcca11 100644 --- a/Remote Desktop Client Migration Script/readme.md +++ b/Remote Desktop Client Migration Script/readme.md @@ -42,6 +42,7 @@ Run the script using the System account. It is intended for mass deployment thro # Example: Disable all updates and skip uninstall .\Remote Desktop Client Migration Script.ps1 -DisableAutoUpdate 1 -SkipRemoteDesktopUninstall +``` +## Troubleshooting - - +Steps coming soon. \ No newline at end of file From 49456be902750c0b72a0bdeb9cd856b13bebf7e8 Mon Sep 17 00:00:00 2001 From: Donna Ryan <100233767+DonnaRyanMicrosoft@users.noreply.github.com> Date: Thu, 11 Dec 2025 02:12:43 -0500 Subject: [PATCH 23/26] GUID Added GUID to temp folder --- ...Remote Desktop Client Migration Script.ps1 | 30 ++++++++++++++----- .../readme.md | 6 +++- 2 files changed, 27 insertions(+), 9 deletions(-) diff --git a/Remote Desktop Client Migration Script/Remote Desktop Client Migration Script.ps1 b/Remote Desktop Client Migration Script/Remote Desktop Client Migration Script.ps1 index e01209f..e758080 100644 --- a/Remote Desktop Client Migration Script/Remote Desktop Client Migration Script.ps1 +++ b/Remote Desktop Client Migration Script/Remote Desktop Client Migration Script.ps1 @@ -5,7 +5,7 @@ See LICENSE in the project root for license information. #> #Microsoft Remote Desktop Client Migration Script -#Version 1.1 +#Version 1.2 #For more info, visit: https://github.com/microsoft/Windows365-PSScripts Param( @@ -110,18 +110,21 @@ function uninstall-MSRDC{ #Function to install Windows App from MSIX direct download function install-windowsappMSIX{ - + $guid = New-Guid try{ update-log -data "Downloading payload" -Class Information -Output Both - if ((test-path -Path $env:windir\Temp\WindowsApp.msix) -eq $true){Remove-Item -Path $env:windir\Temp\WindowsApp.msix -Force -ErrorAction Stop} + #if ((test-path -Path $env:windir\Temp\WindowsApp.msix) -eq $true){Remove-Item -Path $env:windir\Temp\WindowsApp.msix -Force -ErrorAction Stop} - $Payload = Invoke-WebRequest -uri "https://go.microsoft.com/fwlink/?linkid=2262633" -UseBasicParsing -OutFile $env:windir\Temp\WindowsApp.msix -PassThru -ErrorAction Stop + new-item -Path $env:windir\temp -Name $guid.guid -ItemType Directory -Force -ErrorAction Stop | Out-Null + $path = $env:windir + "\temp\" + $guid.guid + + $Payload = Invoke-WebRequest -uri "https://go.microsoft.com/fwlink/?linkid=2262633" -UseBasicParsing -OutFile $path\WindowsApp.msix -PassThru -ErrorAction Stop $filename = ($Payload.BaseResponse.ResponseUri.AbsolutePath -replace ".*/") - if ((test-path -Path $env:windir\Temp\$filename) -eq $true){Remove-Item -Path $env:windir\Temp\$filename -Force -ErrorAction Stop} + #if ((test-path -Path $env:windir\Temp\$filename) -eq $true){Remove-Item -Path $env:windir\Temp\$filename -Force -ErrorAction Stop} - Rename-Item -Path $env:windir\Temp\WindowsApp.msix -NewName $filename -Force -ErrorAction Stop - update-log -Data "Downloaded $filename to $env:windir\temp" -Class Information -Output Both + Rename-Item -Path $path\WindowsApp.msix -NewName $filename -Force -ErrorAction Stop + update-log -Data "Downloaded $filename to $path" -Class Information -Output Both } catch{ @@ -130,7 +133,16 @@ function install-windowsappMSIX{ try{ #Add-AppxPackage -Path $env:windir\temp\$filename -ErrorAction Stop update-log -data "Installing Windows App MSIX package..." -Class Information -Output Both - add-appxprovisionedpackage -PackagePath $env:windir\temp\$filename -online -SkipLicense -ErrorAction Stop | Out-Null + add-appxprovisionedpackage -PackagePath $path\$filename -online -SkipLicense -ErrorAction Stop | Out-Null + } + catch{ + Update-Log -data $_.Exception.Message -Class Error -Output Both + } + + try{ + update-log -data "Cleaning up temp folder and files..." -Class Information -Output Both + remove-item -Path $path -Recurse -Force -ErrorAction Stop | Out-Null + } catch{ Update-Log -data $_.Exception.Message -Class Error -Output Both @@ -170,6 +182,8 @@ function invoke-disableautoupdate($num){ } } + + #check if Windows App is installed. If so, skip installation. Else, install if ((invoke-WAInstallCheck) -eq 0){ update-log -Data "Skipping Windows App Installation" -Class Information -Output Both diff --git a/Remote Desktop Client Migration Script/readme.md b/Remote Desktop Client Migration Script/readme.md index 9dcca11..6045c02 100644 --- a/Remote Desktop Client Migration Script/readme.md +++ b/Remote Desktop Client Migration Script/readme.md @@ -45,4 +45,8 @@ Run the script using the System account. It is intended for mass deployment thro ``` ## Troubleshooting -Steps coming soon. \ No newline at end of file +Steps coming soon. + +## How to deploy script with Intune + +Steps coming soon \ No newline at end of file From 859aca46701b5f46f896029c3369dfd74191cf06 Mon Sep 17 00:00:00 2001 From: Donna Ryan <100233767+DonnaRyanMicrosoft@users.noreply.github.com> Date: Mon, 15 Dec 2025 13:56:47 -0500 Subject: [PATCH 24/26] Updated Readme.md Now includes Intune deployment instructions and troubleshooting --- .../readme.md | 180 +++++++++++++++++- 1 file changed, 177 insertions(+), 3 deletions(-) diff --git a/Remote Desktop Client Migration Script/readme.md b/Remote Desktop Client Migration Script/readme.md index 6045c02..4a42157 100644 --- a/Remote Desktop Client Migration Script/readme.md +++ b/Remote Desktop Client Migration Script/readme.md @@ -43,10 +43,184 @@ Run the script using the System account. It is intended for mass deployment thro # Example: Disable all updates and skip uninstall .\Remote Desktop Client Migration Script.ps1 -DisableAutoUpdate 1 -SkipRemoteDesktopUninstall ``` +## How to deploy script with Intune + + +1. **Sign in** to [Microsoft Intune admin center](https://intune.microsoft.com) +2. **Navigate to**: **Devices** → **Scripts and remediations** → **Windows 10 and later** +3. **Click**: **Add** → **Windows 10 and later** + + *Alternative path: Devices → Scripts → Platform scripts* + +#### Configure Basics + +On the **Basics** page: + +| Field | Value | Notes | +|-------|-------|-------| +| **Name** | `Windows App Migration - Automated` | Clear, descriptive name | +| **Description** | `Automates migration from legacy Remote Desktop client to Windows App. Installs Windows App as provisioned package, removes legacy client, and configures update settings.` | Detailed description for tracking | + +Click **Next** + +#### Configure Script Settings + +On the **Script settings** page: + +| Setting | Recommended Value | Explanation | +|---------|-------------------|-------------| +| **Script location** | Upload `Remote Desktop Client Migration Script.ps1` | Click folder icon to browse and select | +| **Run this script using the logged on credentials** | **No** | Must run as SYSTEM for provisioned package installation | +| **Enforce script signature check** | **No** | Unless you've code-signed the script | +| **Run script in 64 bit PowerShell Host** | **Yes** | Required for AppX cmdlets to work properly | + +**Important Notes:** + +⚠️ **Run as SYSTEM is critical** - The script must run with SYSTEM privileges to: +- Install provisioned packages (`Add-AppxProvisionedPackage`) +- Modify HKLM registry keys +- Uninstall system-level applications + +⚠️ **64-bit PowerShell is required** - AppX cmdlets may not function correctly in 32-bit PowerShell + +**Script settings explanation:** + +``` +┌─────────────────────────────────────────────────────────┐ +│ Run as SYSTEM (not logged-on user) │ +│ ✓ Access to all user profiles │ +│ ✓ Can install provisioned packages │ +│ ✓ Can modify system registry │ +│ ✓ Can uninstall system-level apps │ +└─────────────────────────────────────────────────────────┘ +``` + + ## Troubleshooting -Steps coming soon. +### Problem: Script Reports Success but Windows App Not Available -## How to deploy script with Intune +**Symptoms:** +- Log shows "Installation Complete" +- Detection rule passes +- Users don't see Windows App in Start menu + +**Diagnosis:** +```powershell +# Check provisioned packages +Get-AppxProvisionedPackage -Online | Where-Object {$_.DisplayName -like "*Windows365*"} + +# Check user installation +Get-AppxPackage -Name *Windows365* -AllUsers +``` + +**Solutions:** +1. **User hasn't logged out/in:** Provisioned apps register at next logon + - Solution: Have user sign out and back in +2. **Profile corruption:** Provisioned package installed but user profile damaged + - Solution: Recreate user profile or manually install: `Add-AppxPackage -Register -DisableDevelopmentMode` + +### Problem: Remote Desktop Uninstall Fails + +**Symptoms:** +- Log shows "Something went wrong uninstalling Remote Desktop" +- Legacy client remains installed +- Error in log file + +**Common causes:** + +**Cause 1: User-context installation** +- Remote Desktop was installed per-user, not system-wide +- Script running as SYSTEM cannot access user-installed apps + +**Solution:** +```powershell +# Deploy user-context remediation script +# Run as logged-on user +$MSRDC = Get-Package -Name "Remote Desktop" -ProviderName msi +if ($MSRDC) { + $MSRDC | Uninstall-Package -Force +} +``` + +**Cause 2: Application in use** +- Remote Desktop client is currently running +- MSIEXEC cannot remove while processes are active + +**Solution:** +```powershell +# Add to script before uninstall +Get-Process -Name "msrdcw" -ErrorAction SilentlyContinue | Stop-Process -Force +Start-Sleep -Seconds 3 +# Then proceed with uninstall +``` + +**Cause 3: Corrupted installation** +- Registry entry exists but installation files are damaged + +**Solution:** +```powershell +# Manual cleanup required +Remove-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*" | + Where-Object {$_.DisplayName -eq "Remote Desktop"} +``` + +### Problem: Download Fails + +**Symptoms:** +- Log shows error during "Downloading payload" +- Network timeout or access denied errors + +**Diagnosis:** +```powershell +# Test connectivity +Test-NetConnection -ComputerName "download.microsoft.com" -Port 443 + +# Test download manually +Invoke-WebRequest -Uri "https://go.microsoft.com/fwlink/?linkid=2262633" -UseBasicParsing +``` + +**Solutions:** + +1. **Proxy authentication required:** + ```powershell + # Modify script to use default credentials + $WebClient = New-Object System.Net.WebClient + $WebClient.Proxy.Credentials = [System.Net.CredentialCache]::DefaultNetworkCredentials + ``` + +2. **Firewall blocking:** + - Allow outbound HTTPS to *.microsoft.com + - Whitelist specific CDN endpoints + +3. **Insufficient temp space:** + - MSIX package is ~200MB + - Ensure C:\Windows\temp has 500MB+ free + +### Problem: Add-AppxProvisionedPackage Fails + +**Symptoms:** +- "Deployment failed with HRESULT: 0x80073CF3" +- "The package could not be installed" + +**Common error codes:** + +| Error Code | Meaning | Solution | +|------------|---------|----------| +| 0x80073CF3 | Package failed update, higher version exists | Uninstall existing version first | +| 0x80073D02 | The requested state of the package conflicts | Remove conflicting package | +| 0x80073CF9 | Package install prerequisites not met | Check OS version compatibility | +| 0x80070002 | File not found | Verify download completed successfully | + +**Generic solution:** +```powershell +# Reset app package state +Get-AppxPackage *Windows365* -AllUsers | Remove-AppxPackage -AllUsers +Get-AppxProvisionedPackage -Online | Where-Object {$_.DisplayName -like "*Windows365*"} | Remove-AppxProvisionedPackage -Online + +# Retry installation +.\Remote Desktop Client Migration Script.ps1 +``` + +--- -Steps coming soon \ No newline at end of file From b19ad5a8945e456051ea30583c8f553da278ddf4 Mon Sep 17 00:00:00 2001 From: Donna Ryan <100233767+DonnaRyanMicrosoft@users.noreply.github.com> Date: Mon, 15 Dec 2025 14:11:07 -0500 Subject: [PATCH 25/26] update to package uninstall method changed error message flag to information as the detection of the package returns an error if the package is not installed. --- .../Remote Desktop Client Migration Script.ps1 | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Remote Desktop Client Migration Script/Remote Desktop Client Migration Script.ps1 b/Remote Desktop Client Migration Script/Remote Desktop Client Migration Script.ps1 index e758080..ab683c0 100644 --- a/Remote Desktop Client Migration Script/Remote Desktop Client Migration Script.ps1 +++ b/Remote Desktop Client Migration Script/Remote Desktop Client Migration Script.ps1 @@ -84,12 +84,11 @@ function uninstall-MSRDC{ } catch { - Update-Log -data $_.Exception.Message -Class Error -Output Both + Update-Log -data $_.Exception.Message -Class Information -Output Both } if ($MSRDC.name -eq "Remote Desktop"){ update-log -Data "Remote Desktop Install Found" -Class Information -Output Both - #update-log -Data "Version: " $MSRDC.Version update-log -Data "Uninstalling Remote Desktop" -Class Information -Output Both try{ Uninstall-Package -Name "Remote Desktop" -force -ErrorAction Stop| Out-Null From dc23ab384a3c5b04e78a2c18019a5b2345730225 Mon Sep 17 00:00:00 2001 From: Donna Ryan <100233767+DonnaRyanMicrosoft@users.noreply.github.com> Date: Fri, 19 Dec 2025 14:07:21 -0500 Subject: [PATCH 26/26] Updated deployment instructions Updated deployment instructions --- Remote Desktop Client Migration Script/readme.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Remote Desktop Client Migration Script/readme.md b/Remote Desktop Client Migration Script/readme.md index 4a42157..b3badb0 100644 --- a/Remote Desktop Client Migration Script/readme.md +++ b/Remote Desktop Client Migration Script/readme.md @@ -47,12 +47,10 @@ Run the script using the System account. It is intended for mass deployment thro 1. **Sign in** to [Microsoft Intune admin center](https://intune.microsoft.com) -2. **Navigate to**: **Devices** → **Scripts and remediations** → **Windows 10 and later** +2. **Navigate to**: **Devices** → **Scripts and remediations** → **Platform Scripts** 3. **Click**: **Add** → **Windows 10 and later** - *Alternative path: Devices → Scripts → Platform scripts* - -#### Configure Basics + #### Configure Basics On the **Basics** page: