winget-cli

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

ARPHelper.cpp (25473B)


      1 // Copyright (c) Microsoft Corporation.
      2 // Licensed under the MIT License.
      3 #include "pch.h"
      4 #include "ARPHelper.h"
      5 #include "winget/PortableARPEntry.h"
      6 
      7 namespace AppInstaller::Repository::Microsoft
      8 {
      9     using namespace AppInstaller::Registry::Portable;
     10 
     11     namespace
     12     {
     13         // "Unpacks" a GUID in the format used by the UpgradesCode registry key into the usual format.
     14         // Returns empty if it is not a valid GUID
     15         std::optional<std::string> TryUnpackUpgradeCodeGuid(std::string_view packed)
     16         {
     17             // A GUID is made up of 4 parts:
     18             //   - Part 1 is made up of one 4 byte block
     19             //   - Parts 2 and 3 are made up of one 2 byte block
     20             //   - Part 4 is made up of eight 1 byte blocks
     21             //
     22             // The GUID strings we have in the manifests represent all of this in hex in order,
     23             // with dashes between each part, and after the second byte of Part 4.
     24             // The "packed" GUIDs in the registry place the blocks in the same order,
     25             // without dashes and with opposite endian-ness.
     26             //
     27             // For example
     28             //   ARP:          {FECAFEB5-8D0E-4AE4-8FA0-745BAA835C35}
     29             //                FECAFEB5 8D0E 4AE4 8F A0 74 5B AA 83 5C 35
     30             //                 Part 1   P2   P3  <------ Part 4 ------->
     31             //                5BEFACEF E0D8 4EA4 F8 0A 47 B5 AA 38 C5 53
     32             //   UpgradeCode:     5BEFACEFE0D84EA4F80A47B5AA38C553
     33             //
     34             // The conversion can be done by mapping each location in the packed string
     35             // to the appropriate location in the unpacked string.
     36             constexpr size_t PackedLength = 32;
     37             if (packed.length() != PackedLength || !std::all_of(packed.begin(), packed.end(), isxdigit))
     38             {
     39                 return {};
     40             }
     41 
     42             // PositionMapping[i] is the position to which the i-th char is mapped
     43             // I.e., unpacked[ PositionMapping[i] ] = packed[i]
     44             constexpr size_t PositionMapping[PackedLength] =
     45             {
     46                 8,7,6,5,4,3,2,1,
     47                 13,12,11,10,
     48                 18,17,16,15,
     49                 21,20, 23,22,
     50                 26,25, 28,27, 30,29, 32,31, 34,33, 36,35,
     51             };
     52 
     53             std::string unpacked("{00000000-0000-0000-0000-000000000000}");
     54             for (size_t i = 0; i < PackedLength; ++i)
     55             {
     56                 unpacked[PositionMapping[i]] = packed[i];
     57             }
     58 
     59             return unpacked;
     60         }
     61 
     62         // Gets a mapping from ProductCode to UpgradeCode for MSI packages.
     63         std::map<std::string, std::string> GetUpgradeCodes()
     64         {
     65             // The UpgradeCode is not stored in the ARP registry keys, so we have to get it separately.
     66             // We could use MsiGetProductProperty or MsiGetProperty from the MSI API to query it,
     67             // but it is very slow.
     68             //
     69             // The UpgradeCode is also stored in the registry under
     70             //   HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UpgradeCodes
     71             // (Note that this key is not documented, so it is possible that it will change but very unlikely...)
     72             //
     73             // Under 'UpgradeCodes' there is one key for each upgrade code, and each upgrade code key
     74             // contains the product code as a value. All the upgrade codes and product codes are GUIDs,
     75             // but represented in an unusual way - see TryUnpackUpgradeCodeGuid()
     76 
     77             AICLI_LOG(Repo, Info, << "Reading MSI UpgradeCodes");
     78             std::map<std::string, std::string> upgradeCodes;
     79 
     80             try
     81             {
     82                 // There is no UpgradeCodes key on the x86 view of the registry
     83                 Registry::Key upgradeCodesKey = Registry::Key::OpenIfExists(HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Installer\\UpgradeCodes", 0, KEY_READ | KEY_WOW64_64KEY);
     84 
     85                 if (upgradeCodesKey)
     86                 {
     87                     for (const auto& upgradeCodeKeyRef : upgradeCodesKey)
     88                     {
     89                         std::string keyName;
     90 
     91                         try
     92                         {
     93                             keyName = upgradeCodeKeyRef.Name();
     94                             auto upgradeCode = TryUnpackUpgradeCodeGuid(keyName);
     95                             if (upgradeCode)
     96                             {
     97                                 auto upgradeCodeKey = upgradeCodeKeyRef.Open();
     98                                 for (const auto& productCodeValue : upgradeCodeKey.Values())
     99                                 {
    100                                     auto productCode = TryUnpackUpgradeCodeGuid(productCodeValue.Name());
    101                                     if (productCode)
    102                                     {
    103                                         upgradeCodes[*productCode] = *upgradeCode;
    104                                     }
    105                                 }
    106                             }
    107                         }
    108                         CATCH_LOG_MSG("Failed to read upgrade code: %hs", keyName.c_str());
    109                     }
    110                 }
    111             }
    112             CATCH_LOG_MSG("Failed to read upgrade codes.");
    113 
    114             return upgradeCodes;
    115         }
    116     }
    117 
    118 #ifndef AICLI_DISABLE_TEST_HOOKS
    119     using GetARPKeyFunc = std::function<Registry::Key(Manifest::ScopeEnum, Utility::Architecture)>;
    120     static GetARPKeyFunc s_GetARPKey_Override;
    121 
    122     void SetGetARPKeyOverride(GetARPKeyFunc value)
    123     {
    124         s_GetARPKey_Override = value;
    125     }
    126 #endif
    127 
    128     Registry::Key ARPHelper::GetARPKey(Manifest::ScopeEnum scope, Utility::Architecture architecture) const
    129     {
    130 #ifndef AICLI_DISABLE_TEST_HOOKS
    131         if (s_GetARPKey_Override)
    132         {
    133             return s_GetARPKey_Override(scope, architecture);
    134         }
    135 #endif
    136 
    137         HKEY rootKey = NULL;
    138 
    139         switch (scope)
    140         {
    141         case Manifest::ScopeEnum::User:
    142             rootKey = HKEY_CURRENT_USER;
    143             break;
    144         case Manifest::ScopeEnum::Machine:
    145             rootKey = HKEY_LOCAL_MACHINE;
    146             break;
    147         default:
    148             THROW_HR(E_UNEXPECTED);
    149         }
    150 
    151         bool isValid = false;
    152         REGSAM access = KEY_READ;
    153 
    154         switch (Utility::GetSystemArchitecture())
    155         {
    156         case Utility::Architecture::X86:
    157             switch (architecture)
    158             {
    159             case Utility::Architecture::X86:
    160                 isValid = true;
    161                 break;
    162             }
    163             break;
    164         case Utility::Architecture::X64:
    165             switch (architecture)
    166             {
    167             case Utility::Architecture::X86:
    168                 if (scope == Manifest::ScopeEnum::Machine)
    169                 {
    170                     access |= KEY_WOW64_32KEY;
    171                     isValid = true;
    172                 }
    173                 break;
    174             case Utility::Architecture::X64:
    175                 access |= KEY_WOW64_64KEY;
    176                 isValid = true;
    177                 break;
    178             }
    179             break;
    180         case Utility::Architecture::Arm:
    181             switch (architecture)
    182             {
    183             case Utility::Architecture::Arm:
    184                 isValid = true;
    185                 break;
    186             }
    187             break;
    188         case Utility::Architecture::Arm64:
    189             switch (architecture)
    190             {
    191             case Utility::Architecture::X86:
    192                 if (scope == Manifest::ScopeEnum::Machine)
    193                 {
    194 #ifdef _ARM_
    195                     // Not accessible if this is an ARM process
    196                     AICLI_LOG(Repo, Warning, << "Cannot enumerate x86 machine ARP entries when current process is ARM");
    197 #else
    198                     access |= KEY_WOW64_32KEY;
    199                     isValid = true;
    200 #endif
    201                 }
    202                 break;
    203             case Utility::Architecture::Arm64:
    204                 access |= KEY_WOW64_64KEY;
    205                 isValid = true;
    206                 break;
    207             }
    208             break;
    209         }
    210 
    211         if (isValid)
    212         {
    213             return Registry::Key::OpenIfExists(rootKey, SubKeyPath, 0, access);
    214         }
    215         else
    216         {
    217             return {};
    218         }
    219     }
    220 
    221     Registry::Key ARPHelper::FindARPEntry(const std::string& productCode, Manifest::ScopeEnum scope) const
    222     {
    223         if (productCode.empty())
    224         {
    225             return {};
    226         }
    227 
    228         std::vector<Manifest::ScopeEnum> scopesToSearch;
    229         if (scope == Manifest::ScopeEnum::Unknown)
    230         {
    231             scopesToSearch = { Manifest::ScopeEnum::User, Manifest::ScopeEnum::Machine };
    232         }
    233         else
    234         {
    235             scopesToSearch = { scope };
    236         }
    237 
    238         for (auto scopeToSearch : scopesToSearch)
    239         {
    240             for (auto architecture : Utility::GetApplicableArchitectures())
    241             {
    242                 Registry::Key arpRootKey = GetARPKey(scopeToSearch, architecture);
    243                 if (arpRootKey)
    244                 {
    245                     for (const auto& entry : arpRootKey)
    246                     {
    247                         if (Utility::CaseInsensitiveEquals(productCode, entry.Name()))
    248                         {
    249                             return entry.Open();
    250                         }
    251                     }
    252                 }
    253             }
    254         }
    255 
    256         return {};
    257     }
    258 
    259     bool ARPHelper::GetBoolValue(const Registry::Key& arpKey, const std::wstring& name)
    260     {
    261         auto value = arpKey[name];
    262         return (value && value->GetType() == Registry::Value::Type::DWord && value->GetValue<Registry::Value::Type::DWord>());
    263     }
    264 
    265     std::string ARPHelper::GetStringValue(const Registry::Key& arpKey, const std::wstring& name)
    266     {
    267         auto value = arpKey[name];
    268         if (value && value->GetType() == Registry::Value::Type::String)
    269         {
    270             return value->GetValue<Registry::Value::Type::String>();
    271         }
    272 
    273         return {};
    274     }
    275 
    276     std::string ARPHelper::DetermineVersion(const Registry::Key& arpKey) const
    277     {
    278         // First check DisplayVersion for a complete version string
    279         auto displayVersion = arpKey[DisplayVersion];
    280         if (displayVersion && displayVersion->GetType() == Registry::Value::Type::String)
    281         {
    282             std::string result = displayVersion->GetValue<Registry::Value::Type::String>();
    283             if (!result.empty())
    284             {
    285                 return result;
    286             }
    287         }
    288 
    289         // Next attempt VersionMajor.VersionMinor, then MajorVersion.MinorVersion
    290         for (const auto& names : { std::make_pair(std::ref(VersionMajor), std::ref(VersionMinor)), std::make_pair(std::ref(MajorVersion), std::ref(MinorVersion)) })
    291         {
    292             auto majorVersion = arpKey[names.first];
    293             auto minorVersion = arpKey[names.second];
    294             if (majorVersion || minorVersion)
    295             {
    296                 uint32_t majorVersionInt = 0;
    297                 uint32_t minorVersionInt = 0;
    298 
    299                 if (majorVersion && majorVersion->GetType() == Registry::Value::Type::DWord)
    300                 {
    301                     majorVersionInt = majorVersion->GetValue<Registry::Value::Type::DWord>();
    302                 }
    303 
    304                 if (minorVersion && minorVersion->GetType() == Registry::Value::Type::DWord)
    305                 {
    306                     minorVersionInt = minorVersion->GetValue<Registry::Value::Type::DWord>();
    307                 }
    308 
    309                 if (majorVersionInt || minorVersionInt)
    310                 {
    311                     std::ostringstream strstr;
    312                     strstr << majorVersionInt << '.' << minorVersionInt;
    313                     return strstr.str();
    314                 }
    315             }
    316         }
    317 
    318         // Finally attempt to turn the Version DWORD into a version string
    319         auto version = arpKey[Version];
    320         if (version && version->GetType() == Registry::Value::Type::DWord)
    321         {
    322             uint32_t versionInt = version->GetValue<Registry::Value::Type::DWord>();
    323             if (versionInt)
    324             {
    325                 std::ostringstream strstr;
    326                 strstr << ((versionInt & 0xFF000000) >> 24) << '.' << ((versionInt & 0x00FF0000) >> 16) << '.' << (versionInt & 0x0000FFFF);
    327                 return strstr.str();
    328             }
    329         }
    330 
    331         return Utility::Version::CreateUnknown().ToString();
    332     }
    333 
    334     void ARPHelper::AddMetadataIfPresent(const Registry::Key& key, const std::wstring& name, SQLiteIndex& index, SQLiteIndex::IdType manifestId, PackageVersionMetadata metadata) const
    335     {
    336         auto value = key[name];
    337         if (value)
    338         {
    339             std::string valueString;
    340 
    341             if (value->GetType() == Registry::Value::Type::String)
    342             {
    343                 valueString = value->GetValue<Registry::Value::Type::String>();
    344             }
    345             else if (value->GetType() == Registry::Value::Type::ExpandString)
    346             {
    347                 valueString = value->GetValue<Registry::Value::Type::ExpandString>();
    348             }
    349             else if (value->GetType() == Registry::Value::Type::DWord)
    350             {
    351                 DWORD dwordValue = value->GetValue<Registry::Value::Type::DWord>();
    352                 if (name == Language)
    353                 {
    354                     valueString = Locale::LocaleIdToBcp47Tag(dwordValue);
    355                 }
    356                 else
    357                 {
    358                     std::ostringstream strstr;
    359                     strstr << dwordValue;
    360                     valueString = strstr.str();
    361                 }
    362             }
    363 
    364             if (!valueString.empty())
    365             {
    366                 index.SetMetadataByManifestId(manifestId, metadata, valueString);
    367             }
    368         }
    369     }
    370 
    371     void ARPHelper::PopulateIndexFromARP(SQLiteIndex& index, Manifest::ScopeEnum scope) const
    372     {
    373         auto upgradeCodes = GetUpgradeCodes();
    374 
    375         for (auto architecture : Utility::GetApplicableArchitectures())
    376         {
    377             Registry::Key arpRootKey = GetARPKey(scope, architecture);
    378 
    379             if (arpRootKey)
    380             {
    381                 PopulateIndexFromKey(index, arpRootKey, Manifest::ScopeToString(scope), Utility::ToString(architecture), upgradeCodes);
    382             }
    383         }
    384     }
    385 
    386     void ARPHelper::PopulateIndexFromKey(SQLiteIndex& index, const Registry::Key& key, std::string_view scope, std::string_view architecture, const std::map<std::string, std::string>& upgradeCodes) const
    387     {
    388         AICLI_LOG(Repo, Verbose, << "Examining ARP entries for " << scope << " | " << architecture);
    389 
    390         for (const auto& arpEntry : key)
    391         {
    392             std::string productCode;
    393 
    394             try
    395             {
    396                 productCode = arpEntry.Name();
    397 
    398                 Manifest::Manifest manifest;
    399                 manifest.DefaultLocalization.Add<Manifest::Localization::Tags>({ "ARP" });
    400 
    401                 // Construct a unique name for this entry
    402                 const char separator = '\\';
    403 
    404                 std::ostringstream stream;
    405                 stream << "ARP" << separator << scope << separator << architecture << separator << productCode;
    406 
    407                 manifest.Id = stream.str();
    408 
    409                 manifest.Installers.emplace_back();
    410                 // TODO: This likely needs some cleanup applied, as it looks like INNO tends to append an "_is#"
    411                 //       that might vary across machines/installs. There may be other things we want to clean up as well,
    412                 //       like trimming spaces at the ends, or removing the version string from the product code
    413                 //       if it is present.
    414                 manifest.Installers[0].ProductCode = productCode;
    415 
    416                 Registry::Key arpKey = arpEntry.Open();
    417 
    418                 // Ignore entries that are listed as SystemComponent
    419                 if (GetBoolValue(arpKey, SystemComponent))
    420                 {
    421                     AICLI_LOG(Repo, Verbose, << "Skipping " << productCode << " because it is a SystemComponent");
    422                     continue;
    423                 }
    424 
    425                 // If no name is provided, ignore this entry
    426                 auto displayName = arpKey[DisplayName];
    427                 if (!displayName || displayName->GetType() != Registry::Value::Type::String)
    428                 {
    429                     AICLI_LOG(Repo, Verbose, << "Skipping " << productCode << " because DisplayName is not a REG_SZ value");
    430                     continue;
    431                 }
    432                 auto displayNameValue = displayName->GetValue<Registry::Value::Type::String>();
    433                 if (displayNameValue.empty())
    434                 {
    435                     AICLI_LOG(Repo, Verbose, << "Skipping " << productCode << " because DisplayName is empty");
    436                     continue;
    437                 }
    438 
    439                 manifest.DefaultLocalization.Add<Manifest::Localization::PackageName>(displayNameValue);
    440                 // Add DisplayName to ARP entries too
    441                 // This is to help normalized publisher and name correlation where ARP DisplayName matching
    442                 // will be getting improved in future iterations.
    443                 manifest.Installers[0].AppsAndFeaturesEntries.emplace_back();
    444                 manifest.Installers[0].AppsAndFeaturesEntries[0].DisplayName = displayNameValue;
    445 
    446                 // If no version can be determined, ignore this entry
    447                 manifest.Version = DetermineVersion(arpKey);
    448                 if (manifest.Version.empty())
    449                 {
    450                     AICLI_LOG(Repo, Verbose, << "Skipping " << productCode << " because a version could not be determined");
    451                     continue;
    452                 }
    453 
    454                 auto publisher = arpKey[Publisher];
    455                 if (publisher && publisher->GetType() == Registry::Value::Type::String)
    456                 {
    457                     manifest.DefaultLocalization.Add<Manifest::Localization::Publisher>(publisher->GetValue<Registry::Value::Type::String>());
    458 
    459                     // If Publisher is set, change the Id using name normalization
    460                     // TODO: Figure out how to actually make this work since there are often instances of the same
    461                     // data in x64 and x86 entries that will collide.
    462                     //auto normalizedName = index.NormalizeName(
    463                     //    manifest.DefaultLocalization.Get<Manifest::Localization::PackageName>(),
    464                     //    manifest.DefaultLocalization.Get<Manifest::Localization::Publisher>());
    465                     //manifest.Id = normalizedName.Publisher() + '.' + normalizedName.Name();
    466                 }
    467 
    468                 // Pick up WindowsInstaller to determine if this is an MSI install.
    469                 // TODO: Could also determine Inno (and maybe other types) through detecting other keys here.
    470                 auto installedType = Manifest::InstallerTypeEnum::Exe;
    471 
    472                 if (GetBoolValue(arpKey, WindowsInstaller))
    473                 {
    474                     installedType = Manifest::InstallerTypeEnum::Msi;
    475 
    476                     // If this is an MSI, look up the UpgradeCode
    477                     auto upgradeCodeItr = upgradeCodes.find(productCode);
    478                     if (upgradeCodeItr != upgradeCodes.end())
    479                     {
    480                         manifest.Installers[0].AppsAndFeaturesEntries[0].UpgradeCode = upgradeCodeItr->second;
    481                     }
    482                 }
    483 
    484                 // TODO: If we want to keep the constructed manifest around to allow for `show` type commands
    485                 //       against installed packages, we should use URLInfoAbout/HelpLink for the Homepage.
    486 
    487                 // TODO: Determine the best way to handle duplicates; sometimes the same package will be listed under
    488                 //       both x64 and x86 locations for ARP.
    489                 //       For now, we will attempt to insert and catch.
    490                 std::optional<SQLiteIndex::IdType> manifestIdOpt;
    491 
    492                 try
    493                 {
    494                     // Use the ProductCode as a unique key for the path
    495                     manifestIdOpt = index.AddManifest(manifest);
    496                 }
    497                 catch (...)
    498                 {
    499                     // Ignore errors if they occur, they are most likely a duplicate value
    500                 }
    501 
    502                 if (!manifestIdOpt)
    503                 {
    504                     AICLI_LOG(Repo, Warning,
    505                         << "Ignoring duplicate ARP entry " << scope << '|' << architecture << '|' << productCode << " [" << manifest.DefaultLocalization.Get<Manifest::Localization::PackageName>() << "]");
    506                     continue;
    507                 }
    508 
    509                 SQLiteIndex::IdType manifestId = manifestIdOpt.value();
    510 
    511                 // Pass scope along to metadata.
    512                 index.SetMetadataByManifestId(manifestId, PackageVersionMetadata::InstalledScope, scope);
    513 
    514                 // TODO: Pass along architecture, although there are cases where it is not clear what architecture the package
    515                 //       is from it's ARP location, despite it very clearly being a specific architecture. And note that user
    516                 //       scope does not have separate ARP locations, so every architecture would appear as native.
    517 
    518                 // Publisher is needed for certain scenarios but we don't store it from the manifest
    519                 if (manifest.DefaultLocalization.Contains(Manifest::Localization::Publisher))
    520                 {
    521                     index.SetMetadataByManifestId(
    522                         manifestId, PackageVersionMetadata::Publisher,
    523                         manifest.DefaultLocalization.Get<Manifest::Localization::Publisher>());
    524                 }
    525 
    526                 // Pick up InstallLocation when upgrade supports remove/install to enable this location
    527                 // to survive across the removal.
    528                 AddMetadataIfPresent(arpKey, InstallLocation, index, manifestId, PackageVersionMetadata::InstalledLocation);
    529 
    530                 // Pick up UninstallString and QuietUninstallString for uninstall.
    531                 AddMetadataIfPresent(arpKey, UninstallString, index, manifestId, PackageVersionMetadata::StandardUninstallCommand);
    532                 AddMetadataIfPresent(arpKey, QuietUninstallString, index, manifestId, PackageVersionMetadata::SilentUninstallCommand);
    533 
    534                 // Pick up ModifyPath for repair.
    535                 AddMetadataIfPresent(arpKey, ModifyPath, index, manifestId, PackageVersionMetadata::StandardModifyCommand);
    536                 AddMetadataIfPresent(arpKey, NoModify, index, manifestId, PackageVersionMetadata::NoModify);
    537                 AddMetadataIfPresent(arpKey, NoRepair, index, manifestId, PackageVersionMetadata::NoRepair);
    538 
    539                 // Pick up Language to enable proper selection of language for upgrade.
    540                 AddMetadataIfPresent(arpKey, Language, index, manifestId, PackageVersionMetadata::InstalledLocale);
    541 
    542                 if (Manifest::ConvertToInstallerTypeEnum(GetStringValue(arpKey, std::wstring{ ToString(PortableValueName::WinGetInstallerType) })) == Manifest::InstallerTypeEnum::Portable)
    543                 {
    544                     // Portable uninstall requires the installed architecture for locating the entry in the registry.
    545                     index.SetMetadataByManifestId(manifestId, PackageVersionMetadata::InstalledArchitecture, architecture);
    546                     installedType = Manifest::InstallerTypeEnum::Portable;
    547                 }
    548 
    549                 index.SetMetadataByManifestId(manifestId, PackageVersionMetadata::InstalledType, Manifest::InstallerTypeToString(installedType));
    550             }
    551             catch (...)
    552             {
    553                 AICLI_LOG(Repo, Warning, << "Failed to read ARP entry, ignoring it: " << scope << '|' << architecture << '|' << productCode);
    554                 LOG_CAUGHT_EXCEPTION();
    555             }
    556         }
    557     }
    558 
    559     std::vector<wil::unique_registry_watcher> ARPHelper::CreateRegistryWatchers(Manifest::ScopeEnum scope, std::function<void(Manifest::ScopeEnum, Utility::Architecture, wil::RegistryChangeKind)> callback)
    560     {
    561         std::vector<wil::unique_registry_watcher> result;
    562 
    563         auto addToResult = [&](Manifest::ScopeEnum scopeToUse)
    564             {
    565                 for (auto architecture : Utility::GetApplicableArchitectures())
    566                 {
    567                     Registry::Key arpRootKey = GetARPKey(scopeToUse, architecture);
    568 
    569                     if (arpRootKey)
    570                     {
    571                         result.emplace_back(wil::make_registry_watcher(arpRootKey, L"", true, [scopeToUse, architecture, callback](wil::RegistryChangeKind change) { callback(scopeToUse, architecture, change); }));
    572                     }
    573                 }
    574             };
    575 
    576         if (scope == Manifest::ScopeEnum::Unknown)
    577         {
    578             addToResult(Manifest::ScopeEnum::User);
    579             addToResult(Manifest::ScopeEnum::Machine);
    580         }
    581         else
    582         {
    583             addToResult(scope);
    584         }
    585 
    586         return result;
    587     }
    588 }