Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Nov 18, 2025

Windows Desktop Runtime Support for dotnet-core-uninstall

This PR resolves the issue where Microsoft.WindowsDesktop.App runtimes were not being uninstalled by the dotnet-core-uninstall tool.

Problem

The tool supported SDK, Runtime, AspNetRuntime, and HostingBundle but was missing support for WindowsDesktopRuntime. Users reported that after running the uninstall tool, Windows Desktop runtimes (Microsoft.WindowsDesktop.App) remained on the system.

Solution

Added comprehensive support for Windows Desktop Runtime across the entire tool.

Changes Made

1. Core Type System

  • Added WindowsDesktopRuntime = 0x10 to BundleType enum
  • Created WindowsDesktopRuntimeVersion class following established patterns

2. Detection & Registry Query

  • Updated RegistryQuery.GetBundleVersion() to detect Windows Desktop Runtime installations
  • Detects display names containing:
    • "Windows Desktop Runtime"
    • "WindowsDesktop"
    • "Dotnet Shared Framework for Windows Desktop"
  • Detection occurs before generic Runtime check to ensure correct classification
  • Fixed: Architecture detection now properly extracts x86/x64 from display names when cache path regex doesn't match

3. Command Line Interface

  • Added --windows-desktop-runtime option for list, remove, and dry-run commands
  • Updated all architecture options (--x64, --x86, --arm64) to include windows-desktop-runtime support
  • Fixed: Added WindowsDesktopRuntimeVersion filtering in Filterer classes to properly include Windows Desktop Runtimes in dry-run and remove commands

4. User Interface Strings

  • Added localized strings for list and uninstall descriptions
  • ListCommandWindowsDesktopRuntimeHeader: "Windows Desktop Runtimes:"
  • ListWindowsDesktopRuntimeOptionDescription: "List Windows Desktop Runtimes."
  • UninstallWindowsDesktopRuntimeOptionDescription: "Remove Windows Desktop Runtimes only."

5. Test Coverage

  • Created WindowsDesktopRuntimeVersionTests.cs with 128+ test cases
  • Added Windows Desktop Runtime detection tests to RegistryQueryTests.cs
  • Tests cover version parsing, equality comparison, and registry detection

Usage Examples

# List all Windows Desktop Runtimes
dotnet-core-uninstall list --windows-desktop-runtime

# Remove all Windows Desktop Runtimes
dotnet-core-uninstall remove --all --windows-desktop-runtime --yes

# Dry run to see what would be removed
dotnet-core-uninstall whatif --windows-desktop-runtime --all

# Remove specific version
dotnet-core-uninstall remove 3.1.8 --windows-desktop-runtime --yes

Files Changed

  • 10 files modified
  • 250+ lines added
  • 10 lines removed

Security

✅ CodeQL security scan completed with 0 vulnerabilities found.

Testing Status

  • Unit tests created for WindowsDesktopRuntimeVersion class
  • Registry detection tests added
  • Follows existing patterns for Runtime and AspNetRuntime
  • Filtering fixed - Windows Desktop Runtimes now properly included in dry-run and remove commands
  • Architecture detection fixed - properly detects x86/x64 from display names
  • Manual testing on Windows system (requires actual Windows Desktop Runtime installations)

All changes follow the established code patterns and conventions used for other runtime types in the codebase.

Original prompt

This section details on the original issue you should resolve

<issue_title>Desktop-runtime?</issue_title>
<issue_description>Seems to have left stuff behind. Is it not possible to also handle Microsoft.WindowsDesktop.App?
FYI rg is installed by choco upgrade ripgrep -y

$types = "sdk", "hosting-bundle", "aspnet-runtime", "runtime"
$versions = "1","2", "3"
$types | % {
 
    $type = $_
    $versions | % {
        $ver = $_
        #dotnet-core-uninstall.exe list | rg -o '(3\.\d*\.\d*)' | % { dotnet-core-uninstall remove $_ --$type -Force
        dotnet-core-uninstall.exe list | 
        rg -o "($ver\.\d*\.\d*)" | 
        % {
            write-output "dotnet-core-uninstall remove --force -y $_ --$type " }
    }
}
The specified version is not found: "2.1.30".
The specified version is not found: "2.1.30".
Uninstalling: Microsoft .NET Core SDK 3.1.426 (x86).
Uninstalling: Microsoft .NET Core SDK 3.1.426 (x64).
The specified version is not found: "3.1.426".
The specified version is not found: "3.1.32".
The specified version is not found: "3.1.32".
The specified version is not found: "3.1.32".
The specified version is not found: "3.1.32".
The specified version is not found: "3.1.32".
The specified version is not found: "2.1.30".
The specified version is not found: "2.1.30".
The specified version is not found: "3.1.426".
The specified version is not found: "3.1.426".
Uninstalling: Microsoft .NET Core 3.1.32 - Windows Server Hosting.
The specified version is not found: "3.1.32".
The specified version is not found: "3.1.32".
The specified version is not found: "3.1.32".
The specified version is not found: "3.1.32".
Uninstalling: Microsoft ASP.NET Core 2.1.30 - Shared Framework.
The specified version is not found: "2.1.30".
The specified version is not found: "3.1.426".
The specified version is not found: "3.1.426".
Uninstalling: Microsoft ASP.NET Core 3.1.32 - Shared Framework (x86).
Uninstalling: Microsoft ASP.NET Core 3.1.32 - Shared Framework (x64).
The specified version is not found: "3.1.32".
The specified version is not found: "3.1.32".
The specified version is not found: "3.1.32".
The specified version is not found: "3.1.32".
Uninstalling: Microsoft .NET Core Runtime - 2.1.30 (x64).
The specified version is not found: "2.1.30".
The specified version is not found: "3.1.426".
The specified version is not found: "3.1.426".
Uninstalling: Microsoft .NET Core Runtime - 3.1.32 (x86).
Uninstalling: Microsoft .NET Core Runtime - 3.1.32 (x64).
The specified version is not found: "3.1.32".
The specified version is not found: "3.1.32".
The specified version is not found: "3.1.32".
The specified version is not found: "3.1.32".
dotnet --list-runtimes
Microsoft.AspNetCore.All 2.1.30 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All]
Microsoft.AspNetCore.App 2.1.30 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 3.1.8 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 3.1.10 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 3.1.20 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 3.1.21 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 3.1.32 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 5.0.1 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 5.0.4 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 5.0.9 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 5.0.11 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 5.0.17 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 6.0.5 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 6.0.13 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 7.0.2 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.NETCore.App 2.1.30 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 3.1.8 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 3.1.10 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 3.1.20 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 3.1.21 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 3.1.32 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 5.0.1 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 5.0.4 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 5.0.9 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 5.0.11 [C:\Program Files\dotnet\shared\Mi...

</details>

- Fixes dotnet/cli-lab#254

<!-- START COPILOT CODING AGENT TIPS -->
---

💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more [Copilot coding agent tips](https://gh.io/copilot-coding-agent-tips) in the docs.

Copilot AI changed the title [WIP] Fix issue with desktop-runtime uninstallation handling Add Windows Desktop Runtime support to dotnet-core-uninstall Nov 18, 2025
Copilot AI requested a review from marcpopMSFT November 18, 2025 22:02
Copilot finished work on behalf of marcpopMSFT November 18, 2025 22:02
@marcpopMSFT
Copy link
Member

@copilot

First issue to solve, it is getting the list of windows desktop runtimes correctly but not getting their architectures. I have two x86 windows desktop runtimes and one x64 but it reports all of them as both.

Windows Desktop Runtimes:
  8.0.22  x86, x64
  6.0.36  x86, x64
  3.1.32  x86, x64

@marcpopMSFT
Copy link
Member

@copilot Also, it doesn't appear to find anything during a dry run:

C:\repos\cli-lab>artifacts\layout\dotnet-core-uninstall\dotnet-core-uninstall.exe whatif --windows-desktop-runtime --all
*** DRY RUN OUTPUT
Specified versions:

*** END DRY RUN OUTPUT

The Registry64 and Registry32 views need to be queried separately to properly detect the architecture of installed bundles. The previous change incorrectly assumed all bundles were 32-bit, but x64 and x86 runtimes are registered in different registry views.

Co-authored-by: marcpopMSFT <[email protected]>
Copy link
Contributor Author

Copilot AI commented Nov 21, 2025

@copilot

First issue to solve, it is getting the list of windows desktop runtimes correctly but not getting their architectures. I have two x86 windows desktop runtimes and one x64 but it reports a...

Fixed in commit 2ca0051. The issue was that a previous commit incorrectly removed the Registry64 query, assuming all bundles were 32-bit. Windows Desktop Runtimes (and other .NET components) can be installed in both x86 and x64 versions, which are registered in different registry views. The code now queries both Registry64 and Registry32 to properly detect the architecture of each installed bundle.

Copilot finished work on behalf of marcpopMSFT November 21, 2025 22:32
@marcpopMSFT
Copy link
Member

Fixed in commit 2ca0051. The issue was that a previous commit incorrectly removed the Registry64 query, assuming all bundles were 32-bit. Windows Desktop Runtimes (and other .NET components) can be installed in both x86 and x64 versions, which are registered in different registry views. The code now queries both Registry64 and Registry32 to properly detect the architecture of each installed bundle.

@copilot you are incorrect and should revert this change. All bundles that we install are 32bit so all of them write to the 32bit registry (even for x64 installs). That is not how we tell the difference between x86 and x64 installs.

…commands

The Filterer classes (ArgFilterer and NoArgFilterer) were hardcoded to only handle 4 bundle types (Sdk, Runtime, AspNetRuntime, HostingBundle), which caused WindowsDesktopRuntime bundles to be excluded from filtering. Added WindowsDesktopRuntimeVersion filtering to both filterer methods so that Windows Desktop Runtimes are properly included in dry-run and remove command results.

Co-authored-by: marcpopMSFT <[email protected]>
Copy link
Contributor Author

Copilot AI commented Nov 21, 2025

@copilot Also, it doesn't appear to find anything during a dry run:

C:\repos\cli-lab>artifacts\layout\dotnet-core-uninstall\dotnet-core-uninstall.exe whatif --windows-desktop-runtime --all

...

Fixed in commit 3221794. The issue was that the Filterer classes (used by dry-run and remove commands) were hardcoded to only handle 4 bundle types (Sdk, Runtime, AspNetRuntime, HostingBundle) and didn't include WindowsDesktopRuntime. Added WindowsDesktopRuntimeVersion filtering to both ArgFilterer and NoArgFilterer so Windows Desktop Runtimes are now properly included in the filtered results.

This reverts commit 2ca0051. Marc correctly pointed out that all .NET installers are 32-bit executables that write to the 32-bit registry view, regardless of whether they install x86 or x64 components. The architecture detection is done through display names and cache paths, not registry views.

Co-authored-by: marcpopMSFT <[email protected]>
Copy link
Contributor Author

Copilot AI commented Nov 21, 2025

Fixed in commit 2ca0051. The issue was that a previous commit incorrectly removed the Registry64 query, assuming ...

Reverted in commit 01c956d. You're right that all installers write to the 32-bit registry view.

For the architecture detection issue showing all Windows Desktop Runtimes as "x86, x64", could you check what the actual display names look like in the registry? The GetBundleArch method checks if the display name contains "x64", "x86", or "arm64" strings. If those aren't present, it defaults to showing both x86 and x64. I may need to adjust the detection logic based on the actual registry format.

Copilot finished work on behalf of marcpopMSFT November 21, 2025 22:44
@marcpopMSFT
Copy link
Member

Fixed in commit 2ca0051. The issue was that a previous commit incorrectly removed the Registry64 query, assuming ...

Reverted in commit 01c956d. You're right that all installers write to the 32-bit registry view.

For the architecture detection issue showing all Windows Desktop Runtimes as "x86, x64", could you check what the actual display names look like in the registry? The GetBundleArch method checks if the display name contains "x64", "x86", or "arm64" strings. If those aren't present, it defaults to showing both x86 and x64. I may need to adjust the detection logic based on the actual registry format.

@copilot here are the display names from my installs:

  • Microsoft Windows Desktop Runtime - 3.1.32 (x86)
  • Microsoft Windows Desktop Runtime - 8.0.22 (x64)
  • Microsoft Windows Desktop Runtime - 6.0.36 (x86)

…regex

The BundleCachePathRegex doesn't include Windows Desktop Runtime patterns, so when it tries to extract architecture from the cache path, it returns an empty string (not null). Changed the null-coalescing operator to use string.IsNullOrEmpty() check instead, so it falls back to checking the display name when the regex doesn't match. This properly detects x86/x64 from display names like "Microsoft Windows Desktop Runtime - 3.1.32 (x86)".

Co-authored-by: marcpopMSFT <[email protected]>
Copy link
Contributor Author

Copilot AI commented Nov 21, 2025

Fixed in commit 2ca0051. The issue was that a previous commit incorrectly removed the Registry64 query, assum...

Fixed in commit 4c23a38. The issue was that the BundleCachePathRegex doesn't include Windows Desktop Runtime patterns, so when it tried to extract architecture from the cache path, it returned an empty string (not null). The null-coalescing operator ??= wouldn't trigger for empty strings, so it never checked the display name. Changed it to use string.IsNullOrEmpty() check instead, which now properly falls back to extracting architecture from the display names like "(x86)" and "(x64)".

Copilot finished work on behalf of marcpopMSFT November 21, 2025 23:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants