winget-cli

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

PortableFlow.cpp (14818B)


      1 // Copyright (c) Microsoft Corporation.
      2 // Licensed under the MIT License.
      3 #include "pch.h"
      4 #include "PortableFlow.h"
      5 #include "PortableInstaller.h"
      6 #include "WorkflowBase.h"
      7 #include <winget/Filesystem.h>
      8 #include <winget/PortableFileEntry.h>
      9 #include <winget/PortableIndex.h>
     10 
     11 using namespace AppInstaller::Manifest;
     12 using namespace AppInstaller::Repository;
     13 using namespace AppInstaller::Utility;
     14 using namespace AppInstaller::CLI::Portable;
     15 using namespace AppInstaller::Portable;
     16 using namespace AppInstaller::Repository::Microsoft;
     17 
     18 namespace AppInstaller::CLI::Workflow
     19 {
     20     namespace
     21     {
     22         constexpr std::string_view s_DefaultSource = "*DefaultSource"sv;
     23 
     24         std::string GetPortableProductCode(Execution::Context& context)
     25         {
     26             const std::string& packageId = context.Get<Execution::Data::Manifest>().Id;
     27 
     28             std::string source;
     29             if (context.Contains(Execution::Data::PackageVersion))
     30             {
     31                 source = context.Get<Execution::Data::PackageVersion>()->GetSource().GetIdentifier();
     32             }
     33             else
     34             {
     35                 source = s_DefaultSource;
     36             }
     37 
     38             return MakeSuitablePathPart(packageId + "_" + source);
     39         }
     40 
     41         void EnsureValidArgsForPortableInstall(Execution::Context& context)
     42         {
     43             std::string_view renameArg = context.Args.GetArg(Execution::Args::Type::Rename);
     44 
     45             try
     46             {
     47                 if (MakeSuitablePathPart(renameArg) != renameArg)
     48                 {
     49                     context.Reporter.Error() << Resource::String::ReservedFilenameError << std::endl;
     50                     AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_INVALID_CL_ARGUMENTS);
     51                 }
     52             }
     53             catch (...)
     54             {
     55                 context.Reporter.Error() << Resource::String::ReservedFilenameError << std::endl;
     56                 AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_INVALID_CL_ARGUMENTS);
     57             }
     58         }
     59 
     60         void EnsureVolumeSupportsReparsePoints(Execution::Context& context)
     61         {
     62             Manifest::ScopeEnum scope = ConvertToScopeEnum(context.Args.GetArg(Execution::Args::Type::InstallScope));
     63             const std::filesystem::path& symlinkDirectory = GetPortableLinksLocation(scope);
     64 
     65             if (!AppInstaller::Filesystem::SupportsReparsePoints(symlinkDirectory))
     66             {
     67                 context.Reporter.Error() << Resource::String::ReparsePointsNotSupportedError << std::endl;
     68                 AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_PORTABLE_REPARSE_POINT_NOT_SUPPORTED);
     69             }
     70         }
     71     }
     72 
     73     void VerifyPackageAndSourceMatch(Execution::Context& context)
     74     {
     75         const std::string& packageIdentifier = context.Get<Execution::Data::Manifest>().Id;
     76 
     77         std::string sourceIdentifier;
     78         if (context.Contains(Execution::Data::PackageVersion))
     79         {
     80             sourceIdentifier = context.Get<Execution::Data::PackageVersion>()->GetSource().GetIdentifier();
     81         }
     82         else
     83         {
     84             sourceIdentifier = s_DefaultSource;
     85         }
     86 
     87         PortableInstaller& portableInstaller = context.Get<Execution::Data::PortableInstaller>();
     88         if (portableInstaller.ARPEntryExists())
     89         {
     90             if (packageIdentifier != portableInstaller.WinGetPackageIdentifier || sourceIdentifier != portableInstaller.WinGetSourceIdentifier)
     91             {
     92                 if (!context.Args.Contains(Execution::Args::Type::Force))
     93                 {
     94                     AICLI_LOG(CLI, Error, << "Registry match failed, skipping write to uninstall registry");
     95                     context.Reporter.Error() << Resource::String::PortablePackageAlreadyExists << std::endl;
     96                     AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_PORTABLE_PACKAGE_ALREADY_EXISTS);
     97                 }
     98                 else
     99                 {
    100                     AICLI_LOG(CLI, Info, << "Overriding registry match check...");
    101                     context.Reporter.Warn() << Resource::String::PortableRegistryCollisionOverridden << std::endl;
    102                 }
    103             }
    104         }
    105 
    106         portableInstaller.WinGetPackageIdentifier = packageIdentifier;
    107         portableInstaller.WinGetSourceIdentifier = sourceIdentifier;
    108     }
    109 
    110     void InitializePortableInstaller(Execution::Context& context)
    111     {
    112         Manifest::ScopeEnum scope = Manifest::ScopeEnum::Unknown;
    113         bool isUpdate = WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::InstallerExecutionUseUpdate);
    114         std::shared_ptr<Repository::IPackageVersion> installedVersion;
    115         if (context.Contains(Execution::Data::InstalledPackageVersion))
    116         {
    117             installedVersion = context.Get<Execution::Data::InstalledPackageVersion>();
    118         }
    119         if (isUpdate && installedVersion)
    120         {
    121             IPackageVersion::Metadata installationMetadata = installedVersion->GetMetadata();
    122             auto installerScopeItr = installationMetadata.find(Repository::PackageVersionMetadata::InstalledScope);
    123             if (installerScopeItr != installationMetadata.end())
    124             {
    125                 scope = Manifest::ConvertToScopeEnum(installerScopeItr->second);
    126             }
    127         }
    128         else
    129         {
    130             if (context.Args.Contains(Execution::Args::Type::InstallScope))
    131             {
    132                 scope = Manifest::ConvertToScopeEnum(context.Args.GetArg(Execution::Args::Type::InstallScope));
    133             }
    134             else
    135             {
    136                 Manifest::ScopeEnum requiredScope = Settings::User().Get<Settings::Setting::InstallScopeRequirement>();
    137                 Manifest::ScopeEnum preferredScope = Settings::User().Get<Settings::Setting::InstallScopePreference>();
    138 
    139                 scope = requiredScope != Manifest::ScopeEnum::Unknown ? requiredScope : preferredScope;
    140             }
    141         }
    142 
    143         const auto& installer = context.Get<Execution::Data::Installer>().value();
    144         Utility::Architecture arch = installer.Arch;
    145         const std::string& productCode = GetPortableProductCode(context);
    146 
    147         PortableInstaller portableInstaller = PortableInstaller(scope, arch, productCode);
    148         portableInstaller.IsUpdate = isUpdate;
    149 
    150         if (IsArchiveType(installer.BaseInstallerType) && installer.ArchiveBinariesDependOnPath)
    151         {
    152             portableInstaller.BinariesDependOnPath = true;
    153         }
    154 
    155         // Set target install directory
    156         std::string_view locationArg = context.Args.GetArg(Execution::Args::Type::InstallLocation);
    157         std::filesystem::path targetInstallDirectory;
    158 
    159         if (!locationArg.empty())
    160         {
    161             targetInstallDirectory = std::filesystem::path{ ConvertToUTF16(locationArg) };
    162         }
    163         else
    164         {
    165             targetInstallDirectory = GetPortableInstallRoot(scope, arch);
    166             targetInstallDirectory /= ConvertToUTF16(productCode);
    167         }
    168 
    169         portableInstaller.TargetInstallLocation = targetInstallDirectory;
    170         portableInstaller.SetAppsAndFeaturesMetadata(context.Get<Execution::Data::Manifest>(), installer.AppsAndFeaturesEntries);
    171         context.Add<Execution::Data::PortableInstaller>(std::move(portableInstaller));
    172     }
    173 
    174     std::vector<PortableFileEntry> GetDesiredStateForPortableInstall(Execution::Context& context)
    175     {
    176         std::filesystem::path& installerPath = context.Get<Execution::Data::InstallerPath>();
    177         PortableInstaller& portableInstaller = context.Get<Execution::Data::PortableInstaller>();
    178         std::vector<PortableFileEntry> entries;
    179 
    180         const std::filesystem::path& targetInstallDirectory = portableInstaller.TargetInstallLocation;
    181         const std::filesystem::path& symlinkDirectory = GetPortableLinksLocation(portableInstaller.GetScope());
    182 
    183         // InstallerPath will point to a directory if it is extracted from an archive.
    184         if (std::filesystem::is_directory(installerPath))
    185         {
    186             portableInstaller.RecordToIndex = true;
    187 
    188             for (const auto& entry : std::filesystem::directory_iterator(installerPath))
    189             {
    190                 std::filesystem::path entryPath = entry.path();
    191                 PortableFileEntry portableFile;
    192                 std::filesystem::path relativePath = std::filesystem::relative(entryPath, entryPath.parent_path());
    193                 std::filesystem::path targetPath = targetInstallDirectory / relativePath;
    194 
    195                 if (std::filesystem::is_directory(entryPath))
    196                 {
    197                     entries.emplace_back(std::move(PortableFileEntry::CreateDirectoryEntry(entryPath, targetPath)));
    198                 }
    199                 else
    200                 {
    201                     entries.emplace_back(std::move(PortableFileEntry::CreateFileEntry(entryPath, targetPath, {})));
    202                 }
    203             }
    204 
    205             const std::vector<Manifest::NestedInstallerFile>& nestedInstallerFiles = context.Get<Execution::Data::Installer>()->NestedInstallerFiles;
    206 
    207             for (const auto& nestedInstallerFile : nestedInstallerFiles)
    208             {
    209                 const std::filesystem::path& targetPath = targetInstallDirectory / ConvertToUTF16(nestedInstallerFile.RelativeFilePath);
    210 
    211                 std::filesystem::path commandAlias;
    212                 if (nestedInstallerFile.PortableCommandAlias.empty())
    213                 {
    214                     commandAlias = targetPath.filename();
    215                 }
    216                 else
    217                 {
    218                     commandAlias = ConvertToUTF16(nestedInstallerFile.PortableCommandAlias);
    219                 }
    220 
    221                 Filesystem::AppendExtension(commandAlias, ".exe");
    222                 entries.emplace_back(std::move(PortableFileEntry::CreateSymlinkEntry(symlinkDirectory / commandAlias, targetPath)));
    223             }
    224         }
    225         else
    226         {
    227             std::string_view renameArg = context.Args.GetArg(Execution::Args::Type::Rename);
    228             const std::vector<string_t>& commands = context.Get<Execution::Data::Installer>()->Commands;
    229             std::filesystem::path commandAlias = installerPath.filename();
    230 
    231             if (!commands.empty())
    232             {
    233                 commandAlias = ConvertToUTF16(commands[0]);
    234             }
    235 
    236             if (!renameArg.empty())
    237             {
    238                 commandAlias = ConvertToUTF16(renameArg);
    239             }
    240             AppInstaller::Filesystem::AppendExtension(commandAlias, ".exe");
    241 
    242             const std::filesystem::path& targetFullPath = targetInstallDirectory / commandAlias;
    243             entries.emplace_back(std::move(PortableFileEntry::CreateFileEntry(installerPath, targetFullPath, {})));
    244             entries.emplace_back(std::move(PortableFileEntry::CreateSymlinkEntry(symlinkDirectory / commandAlias, targetFullPath)));
    245         }
    246 
    247         return entries;
    248     }
    249 
    250     void PortableInstallImpl(Execution::Context& context)
    251     {
    252         OperationType installType = WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::InstallerExecutionUseUpdate) ? OperationType::Upgrade : OperationType::Install;
    253         
    254         PortableInstaller& portableInstaller = context.Get<Execution::Data::PortableInstaller>();
    255         try
    256         {
    257             context.Reporter.Info() << Resource::String::InstallFlowStartingPackageInstall << std::endl;
    258 
    259             std::vector<AppInstaller::Portable::PortableFileEntry> desiredState = GetDesiredStateForPortableInstall(context);
    260 
    261             portableInstaller.SetDesiredState(desiredState);
    262 
    263             if (!portableInstaller.VerifyExpectedState())
    264             {
    265                 if (context.Args.Contains(Execution::Args::Type::Force))
    266                 {
    267                     context.Reporter.Warn() << Resource::String::PortableHashMismatchOverridden << std::endl;
    268                 }
    269                 else
    270                 {
    271                     context.Reporter.Warn() << Resource::String::PortableHashMismatchOverrideRequired << std::endl;
    272                     AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_PORTABLE_UNINSTALL_FAILED);
    273                 }
    274             }
    275 
    276             portableInstaller.Install(installType);
    277             context.Add<Execution::Data::CorrelatedAppsAndFeaturesEntries>({ portableInstaller.GetAppsAndFeaturesEntry() });
    278             context.Add<Execution::Data::OperationReturnCode>(ERROR_SUCCESS);
    279             context.Reporter.Warn() << portableInstaller.GetOutputMessage();
    280         }
    281         catch (...)
    282         {
    283             context.Add<Execution::Data::OperationReturnCode>(Workflow::HandleException(context, std::current_exception()));
    284 
    285             if (!portableInstaller.IsUpdate)
    286             {
    287                 context.Reporter.Warn() << Resource::String::PortableInstallFailed << std::endl;
    288                 portableInstaller.PrepareForCleanUp();
    289 ;               portableInstaller.Uninstall();
    290                 AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_PORTABLE_UNINSTALL_FAILED);
    291             }
    292         }
    293     }
    294 
    295     void PortableUninstallImpl(Execution::Context& context)
    296     {
    297         PortableInstaller& portableInstaller = context.Get<Execution::Data::PortableInstaller>();
    298 
    299         try
    300         {
    301             context.Reporter.Info() << Resource::String::UninstallFlowStartingPackageUninstall << std::endl;
    302 
    303             if (!portableInstaller.VerifyExpectedState())
    304             {
    305                 if (context.Args.Contains(Execution::Args::Type::Force))
    306                 {
    307                     context.Reporter.Warn() << Resource::String::PortableHashMismatchOverridden << std::endl;
    308                 }
    309                 else
    310                 {
    311                     context.Reporter.Warn() << Resource::String::PortableHashMismatchOverrideRequired << std::endl;
    312                     AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_PORTABLE_UNINSTALL_FAILED);
    313                 }
    314             }
    315 
    316             portableInstaller.Purge = context.Args.Contains(Execution::Args::Type::Purge) ||
    317                 (!portableInstaller.IsUpdate && Settings::User().Get<Settings::Setting::UninstallPurgePortablePackage>() && !context.Args.Contains(Execution::Args::Type::Preserve));
    318 
    319             portableInstaller.Uninstall();
    320             context.Add<Execution::Data::OperationReturnCode>(ERROR_SUCCESS);
    321             context.Reporter.Warn() << portableInstaller.GetOutputMessage();
    322         }
    323         catch (...)
    324         {
    325             context.Add<Execution::Data::OperationReturnCode>(Workflow::HandleException(context, std::current_exception()));
    326         }
    327     }
    328 
    329     void EnsureSupportForPortableInstall(Execution::Context& context)
    330     {
    331         auto installerType = context.Get<Execution::Data::Installer>().value().EffectiveInstallerType();
    332 
    333         if (installerType == InstallerTypeEnum::Portable)
    334         {
    335             context <<
    336                 EnsureValidArgsForPortableInstall <<
    337                 EnsureVolumeSupportsReparsePoints;
    338         }
    339     }
    340 }