22// The .NET Foundation licenses this file to you under the MIT license. 
33
44using  System . Runtime . InteropServices ; 
5+ using  Microsoft . Win32 ; 
56
67namespace  Microsoft . DotNet . Cli ; 
78
@@ -12,45 +13,73 @@ namespace Microsoft.DotNet.Cli;
1213internal  static class  MixedInstallationDetector 
1314{ 
1415    /// <summary> 
15-     /// Gets the known  global installation root paths  for the current platform. 
16+     /// Gets the global installation root path  for the current platform. 
1617    /// Based on https://github.com/dotnet/designs/blob/main/accepted/2020/install-locations.md 
1718    /// and https://github.com/dotnet/designs/blob/main/accepted/2021/install-location-per-architecture.md 
1819    /// </summary> 
19-     private  static readonly  string [ ]  GlobalInstallRoots  =  GetGlobalInstallRoots ( ) ; 
20- 
21-     private  static string [ ]  GetGlobalInstallRoots ( ) 
20+     private  static string ?  GetGlobalInstallRoot ( ) 
2221    { 
2322        if  ( RuntimeInformation . IsOSPlatform ( OSPlatform . Windows ) ) 
2423        { 
25-             // Windows global install locations 
26-             return  new [ ] 
27-             { 
28-                 Path . Combine ( Environment . GetFolderPath ( Environment . SpecialFolder . ProgramFiles ) ,  "dotnet" ) , 
29-                 Path . Combine ( Environment . GetFolderPath ( Environment . SpecialFolder . ProgramFilesX86 ) ,  "dotnet" ) 
30-             } ; 
31-         } 
32-         else  if  ( RuntimeInformation . IsOSPlatform ( OSPlatform . OSX ) ) 
33-         { 
34-             // macOS global install locations 
35-             return  new [ ] 
24+             // Windows: Read from registry HKLM\SOFTWARE\dotnet\Setup\InstalledVersions\<arch>\InstallLocation 
25+             // Use 32-bit registry view as specified in the spec 
26+             try 
3627            { 
37-                 "/usr/local/share/dotnet" 
38-             } ; 
39-         } 
40-         else  if  ( RuntimeInformation . IsOSPlatform ( OSPlatform . Linux ) ) 
41-         { 
42-             // Linux global install locations (various distros use different paths) 
43-             return  new [ ] 
28+                 string  arch  =  RuntimeInformation . ProcessArchitecture . ToString ( ) . ToLowerInvariant ( ) ; 
29+                 using  ( var  hklm  =  RegistryKey . OpenBaseKey ( RegistryHive . LocalMachine ,  RegistryView . Registry32 ) ) 
30+                 using  ( var  key  =  hklm . OpenSubKey ( $@ "SOFTWARE\dotnet\Setup\InstalledVersions\{ arch } ") ) 
31+                 { 
32+                     if  ( key  !=  null ) 
33+                     { 
34+                         var  installLocation  =  key . GetValue ( "InstallLocation" )  as  string ; 
35+                         if  ( ! string . IsNullOrEmpty ( installLocation ) ) 
36+                         { 
37+                             return  installLocation ; 
38+                         } 
39+                     } 
40+                 } 
41+             } 
42+             catch 
4443            { 
45-                 "/usr/share/dotnet" , 
46-                 "/usr/lib64/dotnet" , 
47-                 "/usr/lib/dotnet" 
48-             } ; 
44+                 // If registry reading fails, return null 
45+             } 
4946        } 
5047        else 
5148        { 
52-             return  Array . Empty < string > ( ) ; 
49+             // Linux/macOS: Read from /etc/dotnet/install_location or /etc/dotnet/install_location_<arch> 
50+             try 
51+             { 
52+                 string  arch  =  RuntimeInformation . ProcessArchitecture . ToString ( ) . ToLowerInvariant ( ) ; 
53+                 string  archSpecificPath  =  $ "/etc/dotnet/install_location_{ arch } "; 
54+                 string  defaultPath  =  "/etc/dotnet/install_location" ; 
55+ 
56+                 // Try arch-specific location first 
57+                 if  ( File . Exists ( archSpecificPath ) ) 
58+                 { 
59+                     string  location  =  File . ReadAllText ( archSpecificPath ) . Trim ( ) ; 
60+                     if  ( ! string . IsNullOrEmpty ( location ) ) 
61+                     { 
62+                         return  location ; 
63+                     } 
64+                 } 
65+ 
66+                 // Fall back to default location 
67+                 if  ( File . Exists ( defaultPath ) ) 
68+                 { 
69+                     string  location  =  File . ReadAllText ( defaultPath ) . Trim ( ) ; 
70+                     if  ( ! string . IsNullOrEmpty ( location ) ) 
71+                     { 
72+                         return  location ; 
73+                     } 
74+                 } 
75+             } 
76+             catch 
77+             { 
78+                 // If file reading fails, return null 
79+             } 
5380        } 
81+ 
82+         return  null ; 
5483    } 
5584
5685    /// <summary> 
@@ -66,32 +95,28 @@ public static bool IsMixedInstallation(string muxerPath, string? dotnetRoot)
6695            return  false ; 
6796        } 
6897
98+         // Get the registered global install location 
99+         string ?  globalInstallRoot  =  GetGlobalInstallRoot ( ) ; 
100+         if  ( string . IsNullOrEmpty ( globalInstallRoot ) ) 
101+         { 
102+             // No global install registered, cannot detect mixed installation 
103+             return  false ; 
104+         } 
105+ 
69106        // Normalize paths for comparison 
70107        string  normalizedMuxerPath  =  Path . GetFullPath ( muxerPath ) ; 
71108        string  normalizedDotnetRoot  =  Path . GetFullPath ( dotnetRoot ) ; 
109+         string  normalizedGlobalRoot  =  Path . GetFullPath ( globalInstallRoot ) ; 
72110
73-         // Check if the muxer is in a global install root 
74-         bool  isInGlobalRoot  =  false ; 
75-         string ?  muxerRoot  =  null ; 
76- 
77-         foreach  ( var  globalRoot  in  GlobalInstallRoots ) 
78-         { 
79-             if  ( normalizedMuxerPath . StartsWith ( globalRoot ,  GetStringComparison ( ) ) ) 
80-             { 
81-                 isInGlobalRoot  =  true ; 
82-                 muxerRoot  =  globalRoot ; 
83-                 break ; 
84-             } 
85-         } 
86- 
87-         if  ( ! isInGlobalRoot ) 
111+         // Check if the muxer is in the global install root 
112+         if  ( ! normalizedMuxerPath . StartsWith ( normalizedGlobalRoot ,  GetStringComparison ( ) ) ) 
88113        { 
89-             // Muxer is not in a  global install root, no mixed installation 
114+             // Muxer is not in the  global install root, no mixed installation 
90115            return  false ; 
91116        } 
92117
93-         // Check if DOTNET_ROOT points to a different location than the muxer's  root 
94-         bool  isDifferentRoot  =  ! normalizedDotnetRoot . StartsWith ( muxerRoot ! ,  GetStringComparison ( ) ) ; 
118+         // Check if DOTNET_ROOT points to a different location than the global install  root 
119+         bool  isDifferentRoot  =  ! normalizedDotnetRoot . StartsWith ( normalizedGlobalRoot ,  GetStringComparison ( ) ) ; 
95120
96121        return  isDifferentRoot ; 
97122    } 
0 commit comments