WinGetIntegrity.cs (8099B)
1 // ----------------------------------------------------------------------------- 2 // <copyright file="WinGetIntegrity.cs" company="Microsoft Corporation"> 3 // Copyright (c) Microsoft Corporation. Licensed under the MIT License. 4 // </copyright> 5 // ----------------------------------------------------------------------------- 6 7 namespace Microsoft.WinGet.Client.Engine.Common 8 { 9 using System; 10 using System.ComponentModel; 11 using System.IO; 12 using System.Management.Automation; 13 using Microsoft.WinGet.Client.Engine.Exceptions; 14 using Microsoft.WinGet.Client.Engine.Helpers; 15 using Microsoft.WinGet.Common.Command; 16 using Microsoft.WinGet.Resources; 17 18 /// <summary> 19 /// Validates winget runs correctly. 20 /// </summary> 21 internal static class WinGetIntegrity 22 { 23 /// <summary> 24 /// Verifies winget runs correctly. If it doesn't, tries to find the reason why it failed. 25 /// </summary> 26 /// <param name="pwshCmdlet">The calling cmdlet.</param> 27 /// <param name="expectedVersion">Expected version.</param> 28 public static void AssertWinGet(PowerShellCmdlet pwshCmdlet, string expectedVersion) 29 { 30 // In-proc shouldn't have other dependencies and thus should be ok. 31 if (Utilities.UsesInProcWinget) 32 { 33 // Only check the OS version support for in-proc 34 if (!IsSupportedOSVersion()) 35 { 36 throw new WinGetIntegrityException(IntegrityCategory.OsNotSupported); 37 } 38 39 return; 40 } 41 42 try 43 { 44 // Start by calling winget without its WindowsApp PFN path. 45 // If it succeeds and the exit code is 0 then we are good. 46 var wingetCliWrapper = new WingetCLIWrapper(false); 47 var result = wingetCliWrapper.RunCommand(pwshCmdlet, "--version"); 48 result.VerifyExitCode(); 49 } 50 catch (Win32Exception e) 51 { 52 pwshCmdlet.Write(StreamType.Verbose, $"'winget.exe' Win32Exception {e.Message}"); 53 throw new WinGetIntegrityException(GetReason(pwshCmdlet)); 54 } 55 catch (Exception e) when (e is WinGetCLIException || e is WinGetCLITimeoutException) 56 { 57 pwshCmdlet.Write(StreamType.Verbose, $"'winget.exe' WinGetCLIException {e.Message}"); 58 throw new WinGetIntegrityException(IntegrityCategory.Failure, e); 59 } 60 catch (Exception e) 61 { 62 pwshCmdlet.Write(StreamType.Verbose, $"'winget.exe' Exception {e.Message}"); 63 throw new WinGetIntegrityException(IntegrityCategory.Unknown, e); 64 } 65 66 // WinGet is installed. Verify version if needed. 67 if (!string.IsNullOrEmpty(expectedVersion)) 68 { 69 // This assumes caller knows that the version exist. 70 WinGetVersion expectedWinGetVersion = new WinGetVersion(expectedVersion); 71 var installedVersion = WinGetVersion.InstalledWinGetVersion(pwshCmdlet); 72 if (expectedWinGetVersion.CompareTo(installedVersion) != 0) 73 { 74 throw new WinGetIntegrityException( 75 IntegrityCategory.UnexpectedVersion, 76 string.Format( 77 Resources.IntegrityUnexpectedVersionMessage, 78 installedVersion.TagVersion, 79 expectedVersion)); 80 } 81 } 82 } 83 84 private static IntegrityCategory GetReason(PowerShellCmdlet pwshCmdlet) 85 { 86 // Ok, so you are here because calling winget --version failed. Lets try to figure out why. 87 var category = IntegrityCategory.Unknown; 88 pwshCmdlet.ExecuteInPowerShellThread(() => 89 { 90 // When running winget.exe on PowerShell the message of the Win32Exception will distinguish between 91 // 'The system cannot find the file specified' and 'No applicable app licenses found' but of course 92 // the HRESULT is the same (E_FAIL). 93 // To not compare strings let Powershell handle it. If calling winget throws an 94 // ApplicationFailedException then is most likely that the license is not there. 95 try 96 { 97 var ps = PowerShell.Create(RunspaceMode.CurrentRunspace); 98 ps.AddCommand("winget").Invoke(); 99 } 100 catch (ApplicationFailedException e) 101 { 102 pwshCmdlet.Write(StreamType.Verbose, e.Message); 103 category = IntegrityCategory.AppInstallerNoLicense; 104 } 105 catch (Exception) 106 { 107 } 108 }); 109 110 if (category != IntegrityCategory.Unknown) 111 { 112 return category; 113 } 114 115 // First lets check if the file is there, which means it is installed or someone is taking our place. 116 if (File.Exists(WingetCLIWrapper.WinGetFullPath)) 117 { 118 // The file exists, but we couldn't call it... Well maybe winget's app execution alias is not enabled. 119 // The trick is knowing that a magical file appears under WindowsApp when its enabled. 120 string wingetAliasPath = Path.Combine(Utilities.LocalDataWindowsAppPath, Constants.WinGetExe); 121 if (File.Exists(wingetAliasPath)) 122 { 123 // App execution alias is enabled. Then maybe the path? 124 string? envPath = Environment.GetEnvironmentVariable(Constants.PathEnvVar, EnvironmentVariableTarget.User); 125 if (string.IsNullOrEmpty(envPath) || 126 !envPath.EndsWith(Utilities.LocalDataWindowsAppPath) || 127 !envPath.Contains($"{Utilities.LocalDataWindowsAppPath};")) 128 { 129 return IntegrityCategory.NotInPath; 130 } 131 } 132 else 133 { 134 return IntegrityCategory.AppExecutionAliasDisabled; 135 } 136 } 137 138 // Not under %LOCALAPPDATA%\\Microsoft\\WindowsApps\PFN\ 139 140 // Check OS version 141 if (!IsSupportedOSVersion()) 142 { 143 return IntegrityCategory.OsNotSupported; 144 } 145 146 // It could be that AppInstaller package is old or the package is not 147 // registered at this point. To know that, call Get-AppxPackage. 148 var appxModule = new AppxModuleHelper(pwshCmdlet); 149 string? version = appxModule.GetAppInstallerPropertyValue("Version"); 150 if (version is null) 151 { 152 // This can happen in Windows Sandbox. 153 return IntegrityCategory.AppInstallerNotInstalled; 154 } 155 156 // Now AppInstaller version has to be greater than 1.11.11451 157 var minAppInstallerVersion = new Version(1, 11, 11451); 158 var appInstallerVersion = new Version(version); 159 if (appInstallerVersion.CompareTo(minAppInstallerVersion) < 0) 160 { 161 return IntegrityCategory.AppInstallerNotSupported; 162 } 163 164 // If we get here, we know the package is in the machine but not registered for the user. 165 return IntegrityCategory.AppInstallerNotRegistered; 166 } 167 168 private static bool IsSupportedOSVersion() 169 { 170 // Windows version has to be equal or newer than 10.0.17763.0 171 var minWindowsVersion = new Version(10, 0, 17763, 0); 172 var osVersion = Environment.OSVersion.Version; 173 return osVersion.CompareTo(minWindowsVersion) >= 0; 174 } 175 } 176 }