winget-cli

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

PackageInstalledStatus.cpp (13032B)


      1 // Copyright (c) Microsoft Corporation.
      2 // Licensed under the MIT License.
      3 #include "pch.h"
      4 #include "Public/winget/InstalledStatus.h"
      5 #include "Public/winget/PackageVersionSelection.h"
      6 #include <winget/Filesystem.h>
      7 
      8 using namespace AppInstaller::Settings;
      9 using namespace std::chrono_literals;
     10 
     11 namespace AppInstaller::Repository
     12 {
     13     namespace
     14     {
     15         HRESULT CheckInstalledLocationStatus(const std::filesystem::path& installedLocation)
     16         {
     17             HRESULT installLocationStatus = WINGET_INSTALLED_STATUS_INSTALL_LOCATION_NOT_APPLICABLE;
     18             if (!installedLocation.empty())
     19             {
     20                 // Use the none throw version, if the directory cannot be reached, it's treated as not found and later file checks are not performed.
     21                 std::error_code error;
     22                 installLocationStatus =
     23                     std::filesystem::exists(installedLocation, error) && std::filesystem::is_directory(installedLocation, error) ?
     24                     WINGET_INSTALLED_STATUS_INSTALL_LOCATION_FOUND :
     25                     WINGET_INSTALLED_STATUS_INSTALL_LOCATION_NOT_FOUND;
     26             }
     27 
     28             return installLocationStatus;
     29         }
     30 
     31         // Map to cache already calculated file hashes.
     32         struct FilePathComparator
     33         {
     34             bool operator()(const std::filesystem::path& a, const std::filesystem::path& b) const
     35             {
     36                 if (std::filesystem::equivalent(a, b))
     37                 {
     38                     return false;
     39                 }
     40 
     41                 return a < b;
     42             }
     43         };
     44         using FileHashMap = std::map<std::filesystem::path, Utility::SHA256::HashBuffer, FilePathComparator>;
     45 
     46         HRESULT CheckInstalledFileStatus(
     47             const std::filesystem::path& filePath,
     48             const Utility::SHA256::HashBuffer& expectedHash,
     49             FileHashMap& fileHashes)
     50         {
     51             HRESULT fileStatus = WINGET_INSTALLED_STATUS_FILE_NOT_FOUND;
     52             try
     53             {
     54                 if (std::filesystem::exists(filePath) && std::filesystem::is_regular_file(filePath))
     55                 {
     56                     fileStatus = WINGET_INSTALLED_STATUS_FILE_FOUND_WITHOUT_HASH_CHECK;
     57                     if (!expectedHash.empty())
     58                     {
     59                         auto itr = fileHashes.find(filePath);
     60                         if (itr == fileHashes.end())
     61                         {
     62                             // If not found in cache, compute the hash.
     63                             std::ifstream in{ filePath, std::ifstream::binary };
     64                             itr = fileHashes.emplace(filePath, Utility::SHA256::ComputeHash(in)).first;
     65                         }
     66 
     67                         fileStatus = Utility::SHA256::AreEqual(expectedHash, itr->second) ?
     68                             WINGET_INSTALLED_STATUS_FILE_HASH_MATCH : WINGET_INSTALLED_STATUS_FILE_HASH_MISMATCH;
     69                     }
     70                 }
     71             }
     72             catch (...)
     73             {
     74                 fileStatus = WINGET_INSTALLED_STATUS_FILE_ACCESS_ERROR;
     75             }
     76 
     77             return fileStatus;
     78         }
     79 
     80         std::vector<InstallerInstalledStatus> CheckInstalledStatusInternal(
     81             const std::shared_ptr<ICompositePackage>& package,
     82             InstalledStatusType checkTypes)
     83         {
     84             using namespace AppInstaller::Manifest;
     85 
     86             std::vector<InstallerInstalledStatus> result;
     87             bool checkFileHash = false;
     88             std::shared_ptr<IPackageVersion> installedVersion = GetInstalledVersion(package);
     89             std::shared_ptr<IPackageVersion> availableVersion;
     90             FileHashMap fileHashes;
     91 
     92             // Variables for metadata from installed version.
     93             InstallerTypeEnum installedType = InstallerTypeEnum::Unknown;
     94             ScopeEnum installedScope = ScopeEnum::Unknown;
     95             std::filesystem::path installedLocation;
     96             std::string installedLocale;
     97             Utility::Architecture installedArchitecture = Utility::Architecture::Unknown;
     98             HRESULT installedLocationStatus = WINGET_INSTALLED_STATUS_INSTALL_LOCATION_NOT_APPLICABLE;
     99 
    100             std::shared_ptr<IPackageVersionCollection> availableVersions = GetAvailableVersionsForInstalledVersion(package);
    101 
    102             // Prepare installed metadata from installed version.
    103             // Determine the available package version to be used for installed status checking.
    104             // Only perform file hash check if we find an available version that matches installed version.
    105             if (installedVersion)
    106             {
    107                 // Installed metadata.
    108                 auto installedMetadata = installedVersion->GetMetadata();
    109                 installedType = ConvertToInstallerTypeEnum(installedMetadata[PackageVersionMetadata::InstalledType]);
    110                 installedScope = ConvertToScopeEnum(installedMetadata[PackageVersionMetadata::InstalledScope]);
    111                 installedLocation = Filesystem::GetExpandedPath(installedMetadata[PackageVersionMetadata::InstalledLocation]);
    112                 installedLocale = installedMetadata[PackageVersionMetadata::InstalledLocale];
    113                 installedArchitecture = Utility::ConvertToArchitectureEnum(installedMetadata[PackageVersionMetadata::InstalledArchitecture]);
    114                 installedLocationStatus = CheckInstalledLocationStatus(installedLocation);
    115 
    116                 // Determine available version.
    117                 Utility::Version installedVersionAsVersion{ installedVersion->GetProperty(PackageVersionProperty::Version) };
    118                 auto installedChannel = installedVersion->GetProperty(PackageVersionProperty::Channel);
    119                 PackageVersionKey versionKey;
    120                 versionKey.Channel = installedChannel.get();
    121 
    122                 if (installedVersionAsVersion.IsApproximate())
    123                 {
    124                     // Use the base version as available version if installed version is mapped to be an approximate.
    125                     versionKey.Version = installedVersionAsVersion.GetBaseVersion().ToString();
    126                     availableVersion = availableVersions->GetVersion(versionKey);
    127                     // It's unexpected if the installed version is already mapped to some version.
    128                     THROW_HR_IF(E_UNEXPECTED, !availableVersion);
    129                 }
    130                 else
    131                 {
    132                     versionKey.Version = installedVersionAsVersion.ToString();
    133                     availableVersion = availableVersions->GetVersion(versionKey);
    134                     if (availableVersion)
    135                     {
    136                         checkFileHash = true;
    137                     }
    138                 }
    139             }
    140 
    141             if (!availableVersion)
    142             {
    143                 // No installed version, or installed version not found in available versions,
    144                 // then attempt to check installed status using latest version.
    145                 availableVersion = availableVersions->GetLatestVersion();
    146                 THROW_HR_IF(E_UNEXPECTED, !availableVersion);
    147             }
    148 
    149             auto manifest = availableVersion->GetManifest();
    150             for (auto const& installer : manifest.Installers)
    151             {
    152                 InstallerInstalledStatus installerStatus;
    153                 installerStatus.Installer = installer;
    154 
    155                 // ARP related checks
    156                 if (WI_IsAnyFlagSet(checkTypes, InstalledStatusType::AllAppsAndFeaturesEntryChecks))
    157                 {
    158                     bool isMatchingInstaller =
    159                         installedVersion &&
    160                         IsInstallerTypeCompatible(installedType, installer.EffectiveInstallerType()) &&
    161                         (installedScope == ScopeEnum::Unknown || installer.Scope == ScopeEnum::Unknown || installedScope == installer.Scope) &&  // Treat unknown scope as compatible
    162                         (installedArchitecture == Utility::Architecture::Unknown || installer.Arch == Utility::Architecture::Neutral || installedArchitecture == installer.Arch) &&  // Treat unknown installed architecture as compatible
    163                         (installedLocale.empty() || installer.Locale.empty() || !Locale::IsWellFormedBcp47Tag(installedLocale) || Locale::GetDistanceOfLanguage(installedLocale, installer.Locale) >= Locale::MinimumDistanceScoreAsCompatibleMatch);  // Treat invalid locale as compatible
    164 
    165                     // ARP entry status
    166                     if (WI_IsFlagSet(checkTypes, InstalledStatusType::AppsAndFeaturesEntry))
    167                     {
    168                         installerStatus.Status.emplace_back(
    169                             InstalledStatusType::AppsAndFeaturesEntry,
    170                             "",
    171                             isMatchingInstaller ? WINGET_INSTALLED_STATUS_ARP_ENTRY_FOUND : WINGET_INSTALLED_STATUS_ARP_ENTRY_NOT_FOUND);
    172                     }
    173 
    174                     // ARP install location status
    175                     if (isMatchingInstaller && WI_IsFlagSet(checkTypes, InstalledStatusType::AppsAndFeaturesEntryInstallLocation))
    176                     {
    177                         installerStatus.Status.emplace_back(
    178                             InstalledStatusType::AppsAndFeaturesEntryInstallLocation,
    179                             installedLocation.u8string(),
    180                             installedLocationStatus);
    181                     }
    182 
    183                     // ARP install location files
    184                     if (isMatchingInstaller &&
    185                         installedLocationStatus == WINGET_INSTALLED_STATUS_INSTALL_LOCATION_FOUND &&
    186                         WI_IsFlagSet(checkTypes, InstalledStatusType::AppsAndFeaturesEntryInstallLocationFile))
    187                     {
    188                         for (auto const& file : installer.InstallationMetadata.Files)
    189                         {
    190                             std::filesystem::path filePath = installedLocation / Utility::ConvertToUTF16(file.RelativeFilePath);
    191                             auto fileStatus = CheckInstalledFileStatus(filePath, checkFileHash ? file.FileSha256 : Utility::SHA256::HashBuffer{}, fileHashes);
    192 
    193                             installerStatus.Status.emplace_back(
    194                                 InstalledStatusType::AppsAndFeaturesEntryInstallLocationFile,
    195                                 filePath.u8string(),
    196                                 fileStatus);
    197                         }
    198                     }
    199                 }
    200 
    201                 // Default install location related checks
    202                 if (WI_IsAnyFlagSet(checkTypes, InstalledStatusType::AllDefaultInstallLocationChecks) && installer.InstallationMetadata.HasData())
    203                 {
    204                     auto defaultInstalledLocation = Filesystem::GetExpandedPath(installer.InstallationMetadata.DefaultInstallLocation);
    205                     HRESULT defaultInstalledLocationStatus = CheckInstalledLocationStatus(defaultInstalledLocation);
    206 
    207                     // Default install location status
    208                     if (WI_IsFlagSet(checkTypes, InstalledStatusType::DefaultInstallLocation))
    209                     {
    210                         installerStatus.Status.emplace_back(
    211                             InstalledStatusType::DefaultInstallLocation,
    212                             defaultInstalledLocation.u8string(),
    213                             defaultInstalledLocationStatus);
    214                     }
    215 
    216                     // Default install location files
    217                     if (defaultInstalledLocationStatus == WINGET_INSTALLED_STATUS_INSTALL_LOCATION_FOUND &&
    218                         WI_IsFlagSet(checkTypes, InstalledStatusType::DefaultInstallLocationFile))
    219                     {
    220                         for (auto const& file : installer.InstallationMetadata.Files)
    221                         {
    222                             std::filesystem::path filePath = defaultInstalledLocation / Utility::ConvertToUTF16(file.RelativeFilePath);
    223                             auto fileStatus = CheckInstalledFileStatus(filePath, checkFileHash ? file.FileSha256 : Utility::SHA256::HashBuffer{}, fileHashes);
    224 
    225                             installerStatus.Status.emplace_back(
    226                                 InstalledStatusType::DefaultInstallLocationFile,
    227                                 filePath.u8string(),
    228                                 fileStatus);
    229                         }
    230                     }
    231                 }
    232 
    233                 if (!installerStatus.Status.empty())
    234                 {
    235                     result.emplace_back(std::move(installerStatus));
    236                 }
    237             }
    238 
    239             return result;
    240         }
    241     }
    242 
    243     std::vector<InstallerInstalledStatus> CheckPackageInstalledStatus(const std::shared_ptr<ICompositePackage>& package, InstalledStatusType checkTypes)
    244     {
    245         return CheckInstalledStatusInternal(package, checkTypes);
    246     }
    247 }