winget-cli

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

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 }