winget-cli

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

Runtime.cpp (24063B)


      1 // Copyright (c) Microsoft Corporation.
      2 // Licensed under the MIT License.
      3 #include "pch.h"
      4 #include <binver/version.h>
      5 #include "Public/AppInstallerLogging.h"
      6 #include "Public/AppInstallerRuntime.h"
      7 #include "Public/AppInstallerStrings.h"
      8 #include "Public/winget/UserSettings.h"
      9 #include "Public/winget/Registry.h"
     10 #include <winget/Filesystem.h>
     11 
     12 
     13 #define WINGET_DEFAULT_LOG_DIRECTORY "DiagOutputDir"
     14 
     15 namespace AppInstaller::Runtime
     16 {
     17     using namespace Utility;
     18     using namespace Settings;
     19     using namespace Filesystem;
     20 
     21     namespace
     22     {
     23         using namespace std::string_view_literals;
     24         constexpr std::string_view s_DefaultTempDirectory = "WinGet"sv;
     25         constexpr std::string_view s_SecureSettings_Base = "Microsoft\\WinGet"sv;
     26         constexpr std::string_view s_SecureSettings_UserRelative = "settings"sv;
     27         constexpr std::string_view s_SecureSettings_Relative_Unpackaged = "win"sv;
     28         constexpr std::string_view s_PortablePackageUserRoot_Base = "Microsoft"sv;
     29         constexpr std::string_view s_PortablePackageRoot = "WinGet"sv;
     30         constexpr std::string_view s_PortablePackagesDirectory = "Packages"sv;
     31         constexpr std::string_view s_LinksDirectory = "Links"sv;
     32         constexpr std::string_view s_FontsInstallDirectory = "Microsoft\\Windows\\Fonts"sv;
     33         constexpr std::string_view s_ConfigurationModulesDirectory = "Configuration\\Modules"sv;
     34 // Use production CLSIDs as a surrogate for repository location.
     35 #if USE_PROD_CLSIDS
     36         constexpr std::string_view s_ImageAssetsDirectoryRelative = "Assets\\WinGet"sv;
     37 #else
     38         constexpr std::string_view s_ImageAssetsDirectoryRelative = "Images"sv;
     39 #endif
     40         constexpr std::string_view s_CheckpointsDirectory = "Checkpoints"sv;
     41         constexpr std::string_view s_DevModeSubkey = "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\AppModelUnlock"sv;
     42         constexpr std::string_view s_AllowDevelopmentWithoutDevLicense = "AllowDevelopmentWithoutDevLicense"sv;
     43 #ifndef WINGET_DISABLE_FOR_FUZZING
     44         constexpr std::string_view s_SecureSettings_Relative_Packaged = "pkg"sv;
     45 #endif
     46         constexpr std::string_view s_RuntimePath_Unpackaged_DefaultState = "defaultState"sv;
     47 
     48         constexpr std::string_view s_UserProfileEnvironmentVariable = "%USERPROFILE%";
     49         constexpr std::string_view s_LocalAppDataEnvironmentVariable = "%LOCALAPPDATA%";
     50         constexpr std::string_view s_WindowsApps_Base = "Microsoft\\WindowsApps"sv;
     51         constexpr std::string_view s_WinGetDev_Exe = "wingetdev.exe";
     52         constexpr std::string_view s_WinGet_Exe = "winget.exe";
     53         constexpr std::string_view s_WinGetMCPDev_Exe = "WindowsPackageManagerMCPServerDev.exe";
     54         constexpr std::string_view s_WinGetMCP_Exe = "WindowsPackageManagerMCPServer.exe";
     55 
     56         static std::optional<std::string> s_runtimePathStateName;
     57         static wil::srwlock s_runtimePathStateNameLock;
     58 
     59         // Gets the path to the root of the package containing the current process.
     60         std::filesystem::path GetPackagePath()
     61         {
     62             wchar_t packageFullName[PACKAGE_FULL_NAME_MAX_LENGTH + 1];
     63             UINT32 nameLength = ARRAYSIZE(packageFullName);
     64             THROW_IF_WIN32_ERROR(GetPackageFullName(GetCurrentProcess(), &nameLength, packageFullName));
     65 
     66             UINT32 pathLength = 0;
     67             LONG result = GetPackagePathByFullName(packageFullName, &pathLength, nullptr);
     68             THROW_HR_IF(HRESULT_FROM_WIN32(result), result != ERROR_INSUFFICIENT_BUFFER);
     69 
     70             std::unique_ptr<wchar_t[]> buffer = std::make_unique<wchar_t[]>(pathLength);
     71             THROW_IF_WIN32_ERROR(GetPackagePathByFullName(packageFullName, &pathLength, buffer.get()));
     72 
     73             return { buffer.get() };
     74         }
     75 
     76         // Gets the path to the directory containing the currently executing binary file.
     77         std::filesystem::path GetBinaryDirectoryPath()
     78         {
     79             HMODULE moduleHandle = NULL;
     80             THROW_IF_WIN32_BOOL_FALSE(GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
     81                 (LPCWSTR)&GetBinaryDirectoryPath, &moduleHandle));
     82 
     83             // Get the path for this module.
     84             wil::unique_process_heap_string binaryPath;
     85             THROW_IF_FAILED(wil::GetModuleFileNameW(moduleHandle, binaryPath));
     86 
     87             std::filesystem::path resultFilePath{ binaryPath.get() };
     88             return resultFilePath.parent_path();
     89         }
     90 
     91         std::unique_ptr<byte[]> GetPACKAGE_ID()
     92         {
     93             UINT32 bufferLength = 0;
     94             LONG gcpiResult = GetCurrentPackageId(&bufferLength, nullptr);
     95             THROW_HR_IF(E_UNEXPECTED, gcpiResult != ERROR_INSUFFICIENT_BUFFER);
     96 
     97             std::unique_ptr<byte[]> buffer = std::make_unique<byte[]>(bufferLength);
     98 
     99             gcpiResult = GetCurrentPackageId(&bufferLength, buffer.get());
    100             if (FAILED_WIN32_LOG(gcpiResult))
    101             {
    102                 return {};
    103             }
    104 
    105             return buffer;
    106         }
    107 
    108         // Gets the package name; only succeeds if running in a packaged context.
    109         std::string GetPackageName()
    110         {
    111             std::unique_ptr<byte[]> buffer = GetPACKAGE_ID();
    112             if (!buffer)
    113             {
    114                 return {};
    115             }
    116 
    117             PACKAGE_ID* packageId = reinterpret_cast<PACKAGE_ID*>(buffer.get());
    118             return Utility::ConvertToUTF8(packageId->name);
    119         }
    120 
    121 #ifndef AICLI_DISABLE_TEST_HOOKS
    122         static std::map<PathName, PathDetails> s_Path_TestHook_Overrides;
    123 #endif
    124 
    125         // Gets the user's temp path
    126         std::filesystem::path GetPathToUserTemp(bool forDisplay)
    127         {
    128             if (forDisplay && Settings::User().Get<Setting::AnonymizePathForDisplay>())
    129             {
    130                 return "%TEMP%";
    131             }
    132             else
    133             {
    134                 wchar_t tempPath[MAX_PATH + 1];
    135                 DWORD tempChars = GetTempPathW(ARRAYSIZE(tempPath), tempPath);
    136                 THROW_LAST_ERROR_IF(!tempChars);
    137                 THROW_HR_IF(E_UNEXPECTED, tempChars > ARRAYSIZE(tempPath));
    138                 return { std::wstring_view{ tempPath, static_cast<size_t>(tempChars) } };
    139             }
    140         }
    141 
    142         // Gets the current user's SID for use in paths.
    143         std::filesystem::path GetUserSID()
    144         {
    145             auto userToken = wil::get_token_information<TOKEN_USER>();
    146 
    147             wil::unique_hlocal_string sidString;
    148             THROW_IF_WIN32_BOOL_FALSE(ConvertSidToStringSidW(userToken->User.Sid, &sidString));
    149             return { sidString.get() };
    150         }
    151 
    152         std::string GetRuntimePathStateName()
    153         {
    154             std::string result;
    155             auto lock = s_runtimePathStateNameLock.lock_shared();
    156 
    157             if (s_runtimePathStateName.has_value())
    158             {
    159                 result = s_runtimePathStateName.value();
    160             }
    161 
    162             if (Utility::IsEmptyOrWhitespace(result))
    163             {
    164                 result = s_RuntimePath_Unpackaged_DefaultState;
    165             }
    166 
    167             return result;
    168         }
    169     }
    170 
    171     void SetRuntimePathStateName(std::string name)
    172     {
    173         auto suitablePathPart = MakeSuitablePathPart(name);
    174         auto lock = s_runtimePathStateNameLock.lock_exclusive();
    175         s_runtimePathStateName.emplace(std::move(suitablePathPart));
    176     }
    177 
    178     // Contains all of the paths that are common between the runtime contexts.
    179     PathDetails GetPathDetailsCommon(PathName path, bool forDisplay)
    180     {
    181         PathDetails result;
    182         // We should not create directories by default when they are retrieved for display purposes.
    183         result.Create = !forDisplay;
    184 
    185         bool mayBeInProfilePath = false;
    186 
    187         switch (path)
    188         {
    189         case PathName::UserProfile:
    190             result.Path = (forDisplay && Settings::User().Get<Setting::AnonymizePathForDisplay>()) ? s_UserProfileEnvironmentVariable : GetKnownFolderPath(FOLDERID_Profile);
    191             result.Create = false;
    192             break;
    193         case PathName::PortablePackageUserRoot:
    194             result.Path = Settings::User().Get<Setting::PortablePackageUserRoot>();
    195             if (result.Path.empty())
    196             {
    197                 result.Path = GetKnownFolderPath(FOLDERID_LocalAppData);
    198                 result.Path /= s_PortablePackageUserRoot_Base;
    199                 result.Path /= s_PortablePackageRoot;
    200                 result.Path /= s_PortablePackagesDirectory;
    201             }
    202             mayBeInProfilePath = true;
    203             break;
    204         case PathName::PortablePackageMachineRoot:
    205             result.Path = Settings::User().Get<Setting::PortablePackageMachineRoot>();
    206             if (result.Path.empty())
    207             {
    208                 result.Path = GetKnownFolderPath(FOLDERID_ProgramFiles);
    209                 result.Path /= s_PortablePackageRoot;
    210                 result.Path /= s_PortablePackagesDirectory;
    211             }
    212             break;
    213         case PathName::PortablePackageMachineRootX86:
    214             result.Path = Settings::User().Get<Setting::PortablePackageMachineRoot>();
    215             if (result.Path.empty())
    216             {
    217                 result.Path = GetKnownFolderPath(FOLDERID_ProgramFilesX86);
    218                 result.Path /= s_PortablePackageRoot;
    219                 result.Path /= s_PortablePackagesDirectory;
    220             }
    221             break;
    222         case PathName::PortableLinksUserLocation:
    223             result.Path = GetKnownFolderPath(FOLDERID_LocalAppData);
    224             result.Path /= s_PortablePackageUserRoot_Base;
    225             result.Path /= s_PortablePackageRoot;
    226             result.Path /= s_LinksDirectory;
    227             mayBeInProfilePath = true;
    228             break;
    229         case PathName::PortableLinksMachineLocation:
    230             result.Path = GetKnownFolderPath(FOLDERID_ProgramFiles);
    231             result.Path /= s_PortablePackageRoot;
    232             result.Path /= s_LinksDirectory;
    233             break;
    234         case PathName::UserProfileDownloads:
    235             result.Path = GetKnownFolderPath(FOLDERID_Downloads);
    236             mayBeInProfilePath = true;
    237             break;
    238         case PathName::FontsUserInstallLocation:
    239             result.Path = GetKnownFolderPath(FOLDERID_LocalAppData);
    240             result.Path /= s_FontsInstallDirectory;
    241             mayBeInProfilePath = true;
    242             break;
    243         case PathName::FontsMachineInstallLocation:
    244             result.Path = GetKnownFolderPath(FOLDERID_Fonts);
    245             break;
    246         case PathName::ConfigurationModules:
    247             result.Path = Settings::User().Get<Setting::ConfigureDefaultModuleRoot>();
    248             if (result.Path.empty())
    249             {
    250                 result.Path = GetKnownFolderPath(FOLDERID_LocalAppData);
    251                 result.Path /= s_SecureSettings_Base;
    252                 result.Path /= s_ConfigurationModulesDirectory;
    253             }
    254             mayBeInProfilePath = true;
    255             break;
    256         default:
    257             THROW_HR(E_UNEXPECTED);
    258         }
    259 
    260         if (mayBeInProfilePath && forDisplay && Settings::User().Get<Setting::AnonymizePathForDisplay>())
    261         {
    262             ReplaceProfilePathsWithEnvironmentVariable(result.Path);
    263         }
    264 
    265         return result;
    266     }
    267 
    268 #ifndef WINGET_DISABLE_FOR_FUZZING
    269     PathDetails GetPathDetailsForPackagedContext(PathName path, bool forDisplay)
    270     {
    271         PathDetails result;
    272         // We should not create directories by default when they are retrieved for display purposes.
    273         result.Create = !forDisplay;
    274 
    275         auto appStorage = winrt::Windows::Storage::ApplicationData::Current();
    276         bool mayBeInProfilePath = false;
    277 
    278         switch (path)
    279         {
    280         case PathName::Temp:
    281             result.Path = GetPathToUserTemp(forDisplay) / s_DefaultTempDirectory;
    282             result.SetOwner(ACEPrincipal::CurrentUser);
    283             result.ACL[ACEPrincipal::System] = ACEPermissions::All;
    284             result.ACL[ACEPrincipal::Admins] = ACEPermissions::All;
    285             break;
    286         case PathName::LocalState:
    287         case PathName::UserFileSettings:
    288             result.Path.assign(appStorage.LocalFolder().Path().c_str());
    289             mayBeInProfilePath = true;
    290             break;
    291         case PathName::DefaultLogLocation:
    292             // To enable UIF collection through Feedback hub, we must put our logs here.
    293             result.Path.assign(appStorage.LocalFolder().Path().c_str());
    294             result.Path /= WINGET_DEFAULT_LOG_DIRECTORY;
    295             mayBeInProfilePath = true;
    296             break;
    297         case PathName::StandardSettings:
    298             result.Create = false;
    299             break;
    300         case PathName::SecureSettingsForRead:
    301         case PathName::SecureSettingsForWrite:
    302             result.Path = GetKnownFolderPath(FOLDERID_ProgramData);
    303             result.Path /= s_SecureSettings_Base;
    304             result.Path /= GetUserSID();
    305             result.Path /= s_SecureSettings_UserRelative;
    306             result.Path /= s_SecureSettings_Relative_Packaged;
    307             result.Path /= GetPackageName();
    308             if (path == PathName::SecureSettingsForWrite)
    309             {
    310                 result.SetOwner(ACEPrincipal::Admins);
    311                 // When running as system, we do not set current user permissions to avoid permission conflicts.
    312                 if (!IsRunningAsSystem())
    313                 {
    314                     result.ACL[ACEPrincipal::CurrentUser] = ACEPermissions::ReadExecute;
    315                 }
    316                 result.ACL[ACEPrincipal::System] = ACEPermissions::All;
    317             }
    318             else
    319             {
    320                 result.Create = false;
    321             }
    322             break;
    323         case PathName::UserProfile:
    324         case PathName::PortablePackageMachineRoot:
    325         case PathName::PortablePackageMachineRootX86:
    326         case PathName::PortableLinksMachineLocation:
    327         case PathName::PortableLinksUserLocation:
    328         case PathName::PortablePackageUserRoot:
    329         case PathName::UserProfileDownloads:
    330         case PathName::FontsUserInstallLocation:
    331         case PathName::FontsMachineInstallLocation:
    332         case PathName::ConfigurationModules:
    333             result = GetPathDetailsCommon(path, forDisplay);
    334             break;
    335         case PathName::SelfPackageRoot:
    336         case PathName::ImageAssets:
    337             result.Path = GetPackagePath();
    338             result.Create = false;
    339             if (path == PathName::ImageAssets)
    340             {
    341                 result.Path /= s_ImageAssetsDirectoryRelative;
    342             }
    343             break;
    344         case PathName::CheckpointsLocation:
    345             result = GetPathDetailsForPackagedContext(PathName::LocalState, forDisplay);
    346             result.Path /= s_CheckpointsDirectory;
    347             break;
    348         case PathName::CLIExecutable:
    349         case PathName::MCPExecutable:
    350             result.Path = GetKnownFolderPath(FOLDERID_LocalAppData);
    351             result.Path /= s_WindowsApps_Base;
    352             result.Path /= GetPackageFamilyName();
    353 
    354             if (path == PathName::CLIExecutable)
    355             {
    356 #if USE_PROD_CLSIDS
    357                 result.Path /= s_WinGet_Exe;
    358 #else
    359                 result.Path /= s_WinGetDev_Exe;
    360 #endif
    361             }
    362             else if (path == PathName::MCPExecutable)
    363             {
    364 #if USE_PROD_CLSIDS
    365                 result.Path /= s_WinGetMCP_Exe;
    366 #else
    367                 result.Path /= s_WinGetMCPDev_Exe;
    368 #endif
    369             }
    370 
    371             result.Create = false;
    372             mayBeInProfilePath = true;
    373             break;
    374         default:
    375             THROW_HR(E_UNEXPECTED);
    376         }
    377 
    378         if (mayBeInProfilePath && forDisplay && Settings::User().Get<Setting::AnonymizePathForDisplay>())
    379         {
    380             ReplaceProfilePathsWithEnvironmentVariable(result.Path);
    381         }
    382 
    383         return result;
    384     }
    385 #endif
    386 
    387     PathDetails GetPathDetailsForUnpackagedContext(PathName path, bool forDisplay)
    388     {
    389         PathDetails result;
    390         // We should not create directories by default when they are retrieved for display purposes.
    391         result.Create = !forDisplay;
    392         bool anonymize = forDisplay && Settings::User().Get<Setting::AnonymizePathForDisplay>();
    393 
    394         switch (path)
    395         {
    396         case PathName::Temp:
    397         case PathName::DefaultLogLocation:
    398         {
    399             result.Path = GetPathToUserTemp(forDisplay);
    400             result.Path /= s_DefaultTempDirectory;
    401             result.Path /= GetRuntimePathStateName();
    402             if (path == PathName::Temp)
    403             {
    404                 result.SetOwner(ACEPrincipal::CurrentUser);
    405                 result.ACL[ACEPrincipal::System] = ACEPermissions::All;
    406                 result.ACL[ACEPrincipal::Admins] = ACEPermissions::All;
    407             }
    408         }
    409         break;
    410         case PathName::LocalState:
    411             result = Filesystem::GetPathDetailsFor(Filesystem::PathName::UnpackagedLocalStateRoot, anonymize);
    412             result.Create = !forDisplay;
    413             result.Path /= GetRuntimePathStateName();
    414             break;
    415         case PathName::StandardSettings:
    416         case PathName::UserFileSettings:
    417             result = Filesystem::GetPathDetailsFor(Filesystem::PathName::UnpackagedSettingsRoot, anonymize);
    418             result.Create = !forDisplay;
    419             result.Path /= GetRuntimePathStateName();
    420             break;
    421         case PathName::SecureSettingsForRead:
    422         case PathName::SecureSettingsForWrite:
    423             result.Path = GetKnownFolderPath(FOLDERID_ProgramData);
    424             result.Path /= s_SecureSettings_Base;
    425             result.Path /= GetUserSID();
    426             result.Path /= s_SecureSettings_UserRelative;
    427             result.Path /= s_SecureSettings_Relative_Unpackaged;
    428             result.Path /= GetRuntimePathStateName();
    429             if (path == PathName::SecureSettingsForWrite)
    430             {
    431                 result.SetOwner(ACEPrincipal::Admins);
    432                 // When running as system, we do not set current user permissions to avoid permission conflicts.
    433                 if (!IsRunningAsSystem())
    434                 {
    435                     result.ACL[ACEPrincipal::CurrentUser] = ACEPermissions::ReadExecute;
    436                 }
    437                 result.ACL[ACEPrincipal::System] = ACEPermissions::All;
    438             }
    439             else
    440             {
    441                 result.Create = false;
    442             }
    443             break;
    444         case PathName::UserProfile:
    445         case PathName::PortablePackageMachineRoot:
    446         case PathName::PortablePackageMachineRootX86:
    447         case PathName::PortableLinksMachineLocation:
    448         case PathName::PortableLinksUserLocation:
    449         case PathName::PortablePackageUserRoot:
    450         case PathName::UserProfileDownloads:
    451         case PathName::FontsUserInstallLocation:
    452         case PathName::FontsMachineInstallLocation:
    453         case PathName::ConfigurationModules:
    454             result = GetPathDetailsCommon(path, forDisplay);
    455             break;
    456         case PathName::SelfPackageRoot:
    457         case PathName::CLIExecutable:
    458         case PathName::MCPExecutable:
    459         case PathName::ImageAssets:
    460             result.Path = GetBinaryDirectoryPath();
    461             result.Create = false;
    462             if (path == PathName::CLIExecutable)
    463             {
    464                 result.Path /= s_WinGet_Exe;
    465             }
    466             else if (path == PathName::MCPExecutable)
    467             {
    468                 result.Path /= s_WinGetMCP_Exe;
    469             }
    470             else if (path == PathName::ImageAssets)
    471             {
    472                 result.Path /= s_ImageAssetsDirectoryRelative;
    473                 if (!std::filesystem::is_directory(result.Path))
    474                 {
    475                     result.Path.clear();
    476                 }
    477             }
    478             break;
    479         case PathName::CheckpointsLocation:
    480             result = GetPathDetailsForUnpackagedContext(PathName::LocalState, forDisplay);
    481             result.Path /= s_CheckpointsDirectory;
    482             break;
    483         default:
    484             THROW_HR(E_UNEXPECTED);
    485         }
    486 
    487         return result;
    488     }
    489 
    490     PathDetails GetPathDetailsFor(PathName path, bool forDisplay)
    491     {
    492         PathDetails result;
    493 
    494 #ifndef WINGET_DISABLE_FOR_FUZZING
    495         if (IsRunningInPackagedContext())
    496         {
    497             result = GetPathDetailsForPackagedContext(path, forDisplay);
    498         }
    499         else
    500 #endif
    501         {
    502             result = GetPathDetailsForUnpackagedContext(path, forDisplay);
    503         }
    504 
    505 #ifndef AICLI_DISABLE_TEST_HOOKS
    506         // Override the value after letting the normal code path run
    507         auto itr = s_Path_TestHook_Overrides.find(path);
    508         if (itr != s_Path_TestHook_Overrides.end())
    509         {
    510             result = itr->second;
    511         }
    512 #endif
    513 
    514         return result;
    515     }
    516 
    517     // Try to replace LOCALAPPDATA first as it is the likely location, fall back to trying USERPROFILE.
    518     void ReplaceProfilePathsWithEnvironmentVariable(std::filesystem::path& path)
    519     {
    520         if (!ReplaceCommonPathPrefix(path, GetKnownFolderPath(FOLDERID_LocalAppData), s_LocalAppDataEnvironmentVariable))
    521         {
    522             ReplaceCommonPathPrefix(path, GetKnownFolderPath(FOLDERID_Profile), s_UserProfileEnvironmentVariable);
    523         }
    524     }
    525 
    526     std::filesystem::path GetNewTempFilePath()
    527     {
    528         GUID guid;
    529         THROW_IF_FAILED(CoCreateGuid(&guid));
    530         WCHAR tempFileName[256];
    531         THROW_HR_IF(E_UNEXPECTED, StringFromGUID2(guid, tempFileName, ARRAYSIZE(tempFileName)) == 0);
    532         auto tempFilePath = Runtime::GetPathTo(Runtime::PathName::Temp);
    533         tempFilePath /= tempFileName;
    534 
    535         return tempFilePath;
    536     }
    537 
    538     // Determines whether developer mode is enabled.
    539     // Does not account for the group policy value which takes precedence over this registry value.
    540     bool IsDevModeEnabled()
    541     {
    542         const auto& devModeSubKey = Registry::Key::OpenIfExists(HKEY_LOCAL_MACHINE, s_DevModeSubkey, 0, KEY_READ|KEY_WOW64_64KEY);
    543         const auto& devModeEnabled = devModeSubKey[s_AllowDevelopmentWithoutDevLicense];
    544         if (devModeEnabled.has_value())
    545         {
    546             return devModeEnabled->GetValue<Registry::Value::Type::DWord>() == 1;
    547         }
    548         else
    549         {
    550             return false;
    551         }
    552     }
    553 
    554     // Using "standard" user agent format
    555     // Keeping `winget-cli` for historical reasons
    556     Utility::LocIndString GetDefaultUserAgent()
    557     {
    558         std::ostringstream strstr;
    559         strstr <<
    560             "winget-cli" <<
    561             " WindowsPackageManager/" << GetClientVersion() <<
    562             " DesktopAppInstaller/" << GetPackageVersion();
    563         return Utility::LocIndString{ strstr.str() };
    564     }
    565 
    566     Utility::LocIndString GetUserAgent(std::string_view caller)
    567     {
    568         std::ostringstream strstr;
    569         strstr <<
    570             caller <<
    571             " WindowsPackageManager/" << GetClientVersion() <<
    572             " DesktopAppInstaller/" << GetPackageVersion();
    573         return Utility::LocIndString{ strstr.str() };
    574     }
    575 
    576 #ifndef AICLI_DISABLE_TEST_HOOKS
    577     void TestHook_SetPathOverride(PathName target, const std::filesystem::path& path)
    578     {
    579         if (s_Path_TestHook_Overrides.count(target))
    580         {
    581             s_Path_TestHook_Overrides[target].Path = path;
    582         }
    583         else
    584         {
    585             PathDetails details = GetPathDetailsFor(target);
    586             details.Path = path;
    587             s_Path_TestHook_Overrides[target] = std::move(details);
    588         }
    589     }
    590 
    591     void TestHook_SetPathOverride(PathName target, const PathDetails& details)
    592     {
    593         s_Path_TestHook_Overrides[target] = details;
    594     }
    595 
    596     void TestHook_ClearPathOverrides()
    597     {
    598         s_Path_TestHook_Overrides.clear();
    599     }
    600 #endif
    601 }